Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice
  2. We've opened up a space to discuss, share feedback, and showcase everything related to the Unity Shader Graph! Come show us what you've made.
    Dismiss Notice

Simple node editor

Discussion in 'Extensions & OnGUI' started by unimechanic, Jul 5, 2013.

  1. Cognetic

    Cognetic

    Joined:
    Jun 15, 2017
    Posts:
    7
    I have encountered an issue when porting to ios recently that I have a fix for but i'd call it pretty unsatisfactory. I'm sure there is a cleaner fix I just don't have time to go through it.

    The Problem:
    The dynamic port system saves its object list with info such as:
    "Object refID="-750799744" type="System.Collections.Generic.List`1[[System.String, mscorlib, Version=0.0.0.0, Culture=neutral, PublicKeyToken" etc

    This seems to be an issue because I work on windows and obviously have to compile the ios version in xcode on a mac. Its a problem because the info changes for the ios build(possibly because i have to target a different .NET framework) meaning the GetType method fails when reading data from a different compile and the xml doesn't load.

    The Solution
    My simple solution is to manually set the Type rather than using GetType to set it. So, in the XMLImportExport.cs specifically the ImportData method under the //OBJECTS section I insert a manual change for any relevant types.
    eg:

    Code (CSharp):
    1.  
    2.      Type type = typeof(List<string>);
    3.                     //this deal with Mac ios issues of assembly mismatch
    4.                   if (typeName.Contains("System.Collections.Generic.List`1[[System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"))
    5.                     {
    6.                         typeName = "System.Collections.Generic.List`1[System.Collections.Generic.List`1[System.String]]";
    7.                         type = typeof(List<List<string>>);
    8.                     }
    9.                     else if (typeName.Contains("System.Collections.Generic.List`1[[System.String, mscorlib,"))
    10.                     {
    11.                         typeName = "System.Collections.Generic.List`1[System.String]";
    12.                         type = typeof(List<string>);
    13.                     }
    14.                     else if (typeName.Contains("System.Collections.Generic.List`1[[System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"))
    15.                     {
    16.                         typeName = "System.Collections.Generic.List`1[System.Collections.Generic.List`1[System.Int32]]";
    17.                         type = typeof(List<List<Int32>>);
    18.                      
    19.                     }
    20.                     else if(typeName.Contains("System.Collections.Generic.List`1[[System.Int32, mscorlib,") )
    21.                     {
    22.                         typeName = "System.Collections.Generic.List`1[System.Int32]";
    23.                         type = typeof(List<Int32>);
    24.                     }
    25.                     else
    26.                     {
    27.                         type = Type.GetType(typeName, true);
    28.                     }
    29.  
    Anyways, I'm sure there are better solutions or reasons I've missed for why the info is changing, you could also not save that info in the first place.

    I'm just posting this for an FYI and for any suggestions for a better way of approaching the issue.
     
    Seneral likes this.
  2. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    18
    I was making a hotkey to focus a node but I noticed that I cannot just set the panning to the node position but I have to invert the node position to get the panning right and have the node in the center of the canvas. But I don't quite understand why? Why do I need to invert the position of the node? Did I just do a space transformation from ? space to ? space? Because the nodes are already in canvas space right? And panning is in canvas space also?

    Code (CSharp):
    1.         [HotkeyAttribute(KeyCode.F, EventModifiers.Control, EventType.KeyUp)]
    2.         private static void FocusNode(NodeEditorInputInfo inputInfo)
    3.         {
    4.             if (GUIUtility.keyboardControl > 0)
    5.                 return;
    6.             NodeEditorState state = inputInfo.editorState;
    7.             if (state.selectedNode != null)
    8.             {
    9.                 inputInfo.SetAsCurrentEnvironment();
    10.                 state.panOffset = new Vector2(-state.selectedNode.position.x, -state.selectedNode.position.y);
    11.                 inputInfo.inputEvent.Use();
    12.             }
    13.         }
     
    Seneral likes this.
  3. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,160
    Hm it does seem unintuitive I guess, and there's no strong reason for it to be that way anymore. It is basically setting the center of the canvas to the center of the window + panOffset -- NOT the center of the window to the center of the canvas + panOffset, which would be what you expected.
    But yeah, sometimes you need space transformations that can be VERY confusing, even to me - luckily pretty rarely.
    Hope it didn't cause you too much headache!
     
  4. undead_ooze

    undead_ooze

    Joined:
    Apr 24, 2018
    Posts:
    3
    I have been using this for a project adapting the dialogue example, but I'm having an issue with saving and exporting/importing the canvas - I have to use a lot of game object references from a scene, and every time I open the canvas those references are lost. I figured exporting an xml and re importing it would work, but I get an error when I try exporting to xml. Anyone else having this issue or does anyone have any ideas on how to solve the issue with the scene references?
     
  5. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,160
    This is expected, since the canvas - by default - is an asset saved as a file, and these can never reference objects from scenes (only prefabs and such). If you saved the canvas in the scene instead (it basically living on a hidden object in the scene) it will retain the references, but only for that scene obviously. You can find the functionality next in the same dropdown and it will list all canvases saved in the current scene.
    If you need to have the canvas in multiple scenes, with different references each, but want the core structure synchronized, you might want to look into a different possibility: Find a way of tagging objects and adapt the nodes themselves to fetch the references when needed by name/tag.

    Note: I can't remember when I implemented this, quite a while ago, so don't know for sure how it behaves with multiple scenes loaded at once - probably can only access the canvases of one scene, best to test it out before!
    Note 2: You mentioned Importing/Exporting aswell, I assume the XML way? If that's the case, the XML format can't reference ANY external assets, be it in the scene or even another asset, it will attempt to serialize everything as XML and embed it which will only work for some objects, but certainly not game objects...
     
  6. undead_ooze

    undead_ooze

    Joined:
    Apr 24, 2018
    Posts:
    3
    Thank you for your quick response! I had to deal with classes last week, so it took me a while to test this again.

    That was the first thing I thought, and I have tried this, but doesn't seem to work properly unless I'm doing something wrong. I only need it to be saved to individual scenes, which would work fine like this. However the save isn't working as expected - the object shows in there, the canvas saved is set as last session, and when l open the editor with a different canvas open the option to load canvas from scene is greyed out. Also if I open the node editor on a different scene the canvas is cleared out, and going back to the scene where it was saved does not allow me to load it.

    Another issue is that clicking on a selection will sometimes open it, but without actually being selected - it's hard to explain, but say, if there is a dropdown and I click it directly before clicking the node first and making sure it's selected the dropdown will open, but whatever selection I make won't actually stick.
     
  7. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,160
    Hm works for me as far as I tested. Only have 2018.2.16 installed right now.
    Try this in a new scene: Load any canvas (Calc example) and save it to the scene with a new name (this should also give a warning saying it is converted to a scene canvas from an asset).
    An object NodeEditor_SceneSaveHolder will appear in the scene (thought I made it hidden, but whatever, probably decided against it for debugging purposes). It should have two components with your newly saved canvas and a lastSession canvas. Now move the nodes around a bit, and save it in the scene under a new name. Now you should be able to see and load both canvases from the scene (not the lastSession obviously).
    After saving the scene and creating a new one, the window should indeed create a new canvas since the previously opened one cannot be found anymore (as it lives in the previously closed scene).
    Loading the scene again should allow you to load both test canvases again though, since they are stored in the NodeEditor_SceneSaveHolder object.

    If anything is different from what I described, please tell me exactly where and how. Maybe it's the unity version, haven't updated in a while, so will check that too.
     
  8. undead_ooze

    undead_ooze

    Joined:
    Apr 24, 2018
    Posts:
    3
    I managed to get it working - seems the problem is that saving it once doesn't create the 2 components, it only creates the lastSession one, saving it again creates the second one. I also get no warning for the conversion to a scene canvas. In order to load it I have to open it through the lastSession component, and then Load canvas from scene using whatever save name I used - I haven't tested enough times to be certain of where it goes wrong if I don't follow these steps.

    Not sure if this is as intended, but once a canvas is loaded the components sometimes disappear, so technically I can't load a previous save in this case.

    I'm using 2018.3.3f1 right now. One thing that I noticed once I updated to this version is that the UI changed the colors of some elements - the save canvas to scene dialogue box is all light gray, which makes it hard to read the buttons.

    Thank you for your help so far!
     
  9. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,160
    Hm that's definitely unintended behaviour, probably from the new version. Haven't followed new versions for a while now so no clue. The UI color issue seems to happen sometimes due to either version incompabilities or import errors of the textures involved. When you say the dialogue box, you mean the one popping up on the canvas? As far as I remember that is using the same style as a node body, but these are fine?
     
  10. th3flyboy

    th3flyboy

    Joined:
    May 28, 2016
    Posts:
    6
    I can confirm I encountered the same issue on my end as well with it being light grey.
     
  11. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    18
    Hi, I was subscribing the node canvas to events and I noticed that when I add the event the subscribed method gets called multiple times. There is always one instance of the NodeCanvas right, because I logged the instance id of the object that has the method and I get back multile id's. How is this possible? When creating the working copy of the canvas, does the previous canvas get deleted?
     
  12. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,160
    The working copy is only created so that the canvas that is actively worked on is NOT the one in the AssetDatabase, incase something goes wrong. By default there should be no reason the same event for the canvas that is edited is repeated for the canvas that is saved - it is not even referenced.
    I can't check right now whether this happens due to the working copy or for some other reason, but since you mentioned you verified they are two different objects, that seems to be the only solution.
    Can you check if this happens with all events (or if not, for which it does)?
     
  13. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    18
    I created a method OnEditorStartup() in NodeCanvas. I call this on the canvas cache when the editor gets opened. In this method I unsubscribe and the subscribe to all events in NodeEditorCalbacks(made them events so you cannot say Action<Node> = null). I subscribe the method OnNodeSelected() to NodeEditorCalbacks.OnSelectNode(). I know for sure this callback gets called only once. Then in the OnNodeSelected() method I debug the instance ID. And the first time I startup the editor it gets called once. But when I restart the editor it gets called twice with different ID's and this increments. This must mean there are multiple canvases right? I am now looking for the problem because I believe there are multiple canvases.

    // code in NodeEditorWindow

    Code (CSharp):
    1.         [MenuItem("Window/Story Editor")]
    2.         public static NodeEditorWindow OpenNodeEditor()
    3.         {
    4.             editorWindow = GetWindow<NodeEditorWindow>();
    5.             editorWindow.minSize = new Vector2(400, 200);
    6.  
    7.             NodeEditor.ReInit(false);
    8.             editorWindow.canvasCache.nodeCanvas.OnEditorStartup();
    9.             Texture iconTexture = ResourceManager.LoadTexture(EditorGUIUtility.isProSkin ? "Textures/Icon_Dark.png" : "Textures/Icon_Light.png");
    10.             editorWindow.titleContent = new GUIContent("Story Editor", iconTexture);
    11.  
    12.             return editorWindow;
    13.         }
    // code in story canvas

    Code (CSharp):
    1.         public override void OnEditorStartup()
    2.         {
    3.             Unsubscribe();
    4.             Subscribe();
    5.             EffectTypes.FetchEffectTypes();
    6.             SetupGUI();
    7.             base.OnEditorStartup();
    8.         }
    9.  
    10.         protected void Subscribe()
    11.         {
    12.             NodeEditorCallbacks.OnDeleteNode += OnNodeDeleted;
    13.             NodeEditorCallbacks.OnAddNode += OnNodeCreated;
    14.             NodeEditorCallbacks.OnSelectNode += OnNodeSelected;
    15.         }
    16.  
    17.         protected void Unsubscribe()
    18.         {
    19.             NodeEditorCallbacks.OnDeleteNode -= OnNodeDeleted;
    20.             NodeEditorCallbacks.OnAddNode -= OnNodeCreated;
    21.             NodeEditorCallbacks.OnSelectNode -= OnNodeSelected;
    22.         }
     
    Last edited: Mar 27, 2019
  14. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    18
    AddClonedSO creates a new instance of a scriptable object but never deletes the old one am I correct? Could this be the problem? The objects still remain in memory right?
     
  15. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,160
    Just an idea, but have you verified that Subscribe/Unsubscribe are called exactly once each? With events (depending if events or UnityEvents) you should be able to verify the amount of listeners to be 1.
    Long time not worked with events of the like but when you open the editor for the second time, you remove the same methods again but they are from different instances of Canvas so it won't actually remove the previous listener. Not sure though. Try clearing it directly.
    The event system is not really made to handle multiple canvases at the same time so all subscribed methods will receive all events, even if it isn't on the same canvas.

    On the other hand, I dont know why the previously loaded canvas would still be generating events. After it is saved to lastSession it is closed and no code runs, then after the editor opens again it will work on a working copy of that lastSession but the ties should be broken completely.

    No, it's correct. The way the canvas are structured is that the canvas and all nodes and ports are SOs referencing each other. When creating a working copy first the canvas is copied, bit the referencs will still point to the old nodes. Afterwards, all nodes are cloned and recorded in pairs with their uncloned versions (AddClonedSO). Finally all references in the cloned canvas are replaced using these lists as lookup tables to replace all references to SOs with the newly cloned ones.

    That works fine until you add own SOs that should clone alongside the canvas. In that case, there are two methods you need to override to handle this. But even failing to do that should not cause any events to be duplicated.
     
  16. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    18
    Well, I created a little test, and it appears that even if you delete a scriptable object it's method will still be called. So the canvas probably is another instance and the previous one is gone. But it's method will still be called. So I probably should unsub that canvas before saving and then sub the new canvas after saving.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class TestScripableObject : ScriptableObject {
    6.  
    7.     private void Awake()
    8.     {
    9.         TestCallbackReciever.testEvent += OnTest;
    10.     }
    11.  
    12.     private void OnTest()
    13.     {
    14.         Debug.Log(this.GetInstanceID());
    15.     }
    16. }
    17.  
    18.  
    19. using System.Collections;
    20. using System.Collections.Generic;
    21. using UnityEngine;
    22.  
    23. public class Test : MonoBehaviour
    24. {
    25.     public void Start()
    26.     {
    27.         ScriptableObject so = ScriptableObject.CreateInstance(typeof(TestScripableObject));
    28.         TestCallbackReciever.OnIssueTestEvent();
    29.  
    30.         ScriptableObject so2 = ScriptableObject.CreateInstance(typeof(TestScripableObject));
    31.         DestroyImmediate(so);
    32.         TestCallbackReciever.OnIssueTestEvent();
    33.     }
    34. }
    35.  
    36. using System.Collections;
    37. using System.Collections.Generic;
    38. using UnityEngine;
    39. using System;
    40.  
    41. public static class TestCallbackReciever
    42. {
    43.     public static event Action testEvent;
    44.  
    45.     public static void OnIssueTestEvent()
    46.     {
    47.         Action temp = testEvent;
    48.         if (temp != null)
    49.             temp.Invoke();
    50.     }
    51. }
     
  17. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,160
    Ah yes that makes sense! Good luck going forward:)