Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Simple node editor

Discussion in 'Immediate Mode GUI (IMGUI)' started by unimechanic, Jul 5, 2013.

  1. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    617
    Nope, just using the ValueConnectionKnob attributes. This is my ValueConnectionKnob code:

    Code (CSharp):
    1. public override bool AutoLayout { get { return true; } }
    2. public override Vector2 MinSize { get { return new Vector2(250, 10); } }
    3.  
    4. [ValueConnectionKnob("Primary", Direction.In, "string", ConnectionCount.Single, NodeSide.Bottom, 50)]
    5. public ValueConnectionKnob primaryInputKnob;
    Not using any
     
  2. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    No call to it in the NodeGUI? That is weird, vecause that means it should be fixed at 50 from the left, unlike the screen...
     
  3. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    617
    I have a NodeGUI override but I'm not doing anything to the knobs. I just double checked by deleting everything in my Node code and leaving just the ValueConnectionKnob and it's still doing it.

    I've written in a workaround for the time being by using a MaxValue and clamping knobSize to it
     
  4. quyrean

    quyrean

    Joined:
    Nov 29, 2016
    Posts:
    16
    I have been trying to get my stuff to work at runtime and thought I would start with the Dialog System example. As previously stated the dialog system example will not build. I tried to use the #if to wrap all UnityEditor calls,

    example:

    #if UNITY_EDITOR
    using UnityEditor;
    #endif

    but whenever I put this in my code, the window for the node editor in unity goes away. I noticed bjornrun
    had success with the preprocesser commands, so I don't know what I am doing wrong, or even how to debug. I am using unity 2017.1.0f3 on windows. Anyone have any ideas?

    thanks.

    upload_2018-2-3_13-17-29.png
     
  5. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    That example is not maintained by me, so hard for me to tell.
    There might be some usages of UnityEditor you need to find a workaround for, not sure. The base framework should work fine though.
    When you say the window dissappears, does that mean there are further errors in the console?
    Will check it myself when I find time!
     
  6. quyrean

    quyrean

    Joined:
    Nov 29, 2016
    Posts:
    16
    Thanks. What happens is that I can no longer open the node editor window in unity. So the menu "Window/Node Editor" is no longer available. There are still build errors, but I can press play. Or the log fills up with strange gui errors, then I remove the code changes and the node editor window is still missing.

    for example in

    node-runtime-test\Assets\ExampleDialogSystem\Core\Nodes\DialogNode.cs

    all I do is change

    using UnityEditor;

    to

    #if UNITY_EDITOR
    using UnityEditor;
    #endif

    and "Node Editor" disappears from my unity windows menu. I don't see how this can possibly effect anything. I know I havn't fixed all the code, so I don't expect it to build, but why is the Node Editor window missing?

    thanks.
     
  7. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Well that breaks all code in that node that references UnityEditor, obviously. You need to enclose that with the #if, too, and make sure it works.
    When there are errors, it is normal for the menu items to dissappear. So you need to fix all errors right away...
    Hope it works for you. I'll try taking a look myself.
     
  8. quyrean

    quyrean

    Joined:
    Nov 29, 2016
    Posts:
    16
    Doh. That was the trick. I did not realize that I had to fix ALL the build errors or that the build errors would affect the menu items. Once I got the build to complete the menu came back. Thanks for the help.
     
  9. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    617
    Hey @Seneral quick question:

    How do I trigger a Save Cache from my Custom Node?

    I have a button on one of my nodes which turns the Node backgroundColor to green, and then immediately load a scene (Not in runtime, just in editor), the problem is loading a scene triggers a reload, which means the color change never gets saved before the reload and so it doesn't work. So I want to trigger a Save Cache just before loading the scene.

    What's the best way around that?
     
  10. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Yah that's the right approach, unfortunately it is controlled solely by the editor. Only option would be to make a callback just like Repaint with RepaintClients...
    You can directly call it but it's (at least theretical) not necessarily the default node editor window, since you can create your own... But for the editor window, this should work (in #if UNITY_EDITOR): NodeEditorWindow.editor.canvasCache.SaveCache
    Seneral
     
  11. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    617
    So does this mean I need to move NodeEditorWindow out of the Editor assembly (out of the Editor folder)? You can't use classes in the Editor assembly in non editor code, so then I'd also have to wrap the entire NodeEditorWindow code inside #IF UNITY_EDITOR to make it build. Ugh, seems really dirty haha
     
  12. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ahh sorry forgot about that again...
    Then callback is the only and best way:)
    Easy to add, can do that if you want later this day!
     
  13. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    617
    No worries I figured out how to add the callback:

    Added these lines to NodeEditor:
    Code (CSharp):
    1. public static Action SaveCanvasAction;
    2. public static void SaveCanvas() { if (SaveCanvasAction != null) SaveCanvasAction(); }
    NodeEditorWindow in OnEnable():
    Code (CSharp):
    1. NodeEditor.SaveCanvasAction -= canvasCache.SaveCache;
    2. NodeEditor.SaveCanvasAction += canvasCache.SaveCache;
    And I just call
    Code (CSharp):
    1. NodeEditor.SaveCanvas();
    In my custom node code to save the canvas, and that seems to work great.

    Thanks for all your help!
     
  14. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ya exactly:) Might need to unsubscribe in OnDisable, else it will stack everytime you open the window, though.
     
  15. toto007

    toto007

    Joined:
    Jul 18, 2014
    Posts:
    33
    Hi guys,
    I noticed that once I create the build of my application in runtime mode I can not editing e overwrite the preloaded canvas with the script RTNodeEditor.
    I can only save the canvas using the "Save canvas to scene" feature but it won't let me make the canvas Persistent.
    How can I create and load a canvas in runtime mode and store it in the Application.persisentPath folder, so I can find it when I open the app again?
     
  16. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ya that's given by Unity, you always have to get your own hands dirty when it comes to saving information at runtime. Most importantly you have to convert your data to either plain text or bytes yourself, no way to just tell to save a prefab...
    As far as Node Editor goes, a while ago I implemented an XML-export/import option on the develop branch. You can see it used with GUI implementation here (lot's of abstraction through generic callbacks since it's extendable). It is managed in NodeEditorFramework.IO.ImportExportManager.

    If you directly want to call into the XML implementation, here's some untested code (remember to import NodeEditorFramework.IO):
    Code (csharp):
    1. ImportExportFormat xmlFormat = new XMLImportExport ();
    2. // OR
    3. ImportExportFormat xmlFormat = ImportExportManager.ParseFormat ("XML"); // Gets a XML formatter from the pool
    4. // Import and Export from/to 'path'
    5. NodeCanvas canvas = xmlFormat.Import (path);
    6. xmlFormat.Export (canvas, path);
    If you really want, you could also do your own format, but it's rather complex. Just override either ImportExportFormat or StructuredImportExportFormat...
    As always, you'd need to handle path picking yourself. There are some embedded file browsers available or you could just enter a static path in your game folder, e.g. 'Files/NodeGraph.xml'.
    Hope that helps:)
    Seneral
     
  17. MasterMati

    MasterMati

    Joined:
    May 25, 2017
    Posts:
    4
    Hello there! I really like this tool, but I have one problem, and I don't know where to figure it out.

    Basically, I want to create event system using Nodes. For example item, that would magically damage the mob, who attacked you (with damage equal to 1% of attack) would look like on image.

    The problem is that if I want to create new IConnection with type of UnityEvent (or Action, etc.) it has to "be reversed".

    DamageReceiver wants DamageDealer.Trigger() to invoke, not DamageDealer DamageReceiver.OnDamageReceived()

    But when I want to create OnDamageReceiver as Input of DamageReceiver and Trigger as output of DamageDealer, node editor throws problems with recursion.

    What should I do with this? Even though, thank you very much for this tool :) keep it extending, for exmaple you could add tabs with multiple nodes canvases opened.

    PS Sorry for my english :p
     

    Attached Files:

    • help.png
      help.png
      File size:
      191.5 KB
      Views:
      880
  18. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    That is a 'problem' with the default calculation routine - it does exactly what it says, calculating values and passing results down the graph (left to right).
    What you are trying to achieve is a combination of that (calculation of the values below) and a statemachine type of system.
    Recursion error makes sense here, because the default calculation routine wants to go from reveiver to dealer through the bottom connections and then back to the receiver through you event stuff.

    Now, when you have that UnityEvent - I assume that the receiver puts it's UnityEvent for OnDamageReceived into the connection which gets pushed down to the dealer - you could subscribe something to it in the dealer (and make sure to unsubscribe the same first so nothing gets subscribed twice). Then, when your receiver issues your UnityEvent OnDamageReceived, the dealer's function will execute.

    If that's not what you wanted, I need more information, as I said the current 'calculation' routine (which can be replaced and scripted completely from the ground up easily btw) isn't made for action/event handling, just for calculating.
    Hope you can get it to work:)
    Seneral
     
  19. MasterMati

    MasterMati

    Joined:
    May 25, 2017
    Posts:
    4
    Seneral, thank you very much for spending your time on my problem.
    In fact you understand it entirely. After think, I got some ideas (hope they help you in future too):

    1. abstract TriggerableNode : Node that would have public abstract void Trigger( int triggerIndex ) method. Than I could check which Input/Output is trigger and invoke them "backward".
    2. TriggerBridgeEnterNode, TriggerBridgeExitNode.

    I think the first idea may be better, because it doesn't contain so much Nodes.
     
  20. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ok if I got it right, what prevents you from doing what I said in the sencon paragraph? Pass the UnityEvent that is OnDamageReceived to the dealer, dealer can subscribe it's function to it and when the receiver triggers the event, dealer is notified.
     
  21. MasterMati

    MasterMati

    Joined:
    May 25, 2017
    Posts:
    4
    It sounds good, and much less complex, but as I remember I tried it in the past. The problem occured, when multiple "TriggerSources" wanted to trigger multiple "TriggerTargets". I'll see if I just hadn't done it wrongly.
     
  22. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Hm I see, what you need is a connection where knobs on both side can accept multiple connections.
    As this framework initially was made for value calculation (where an input with multiple connections didn't make sense) it has only been added a few months ago and there is no easy way to edit it up until now (current editing would make it impossible to delete connections on multi-multi knobs)...
    Can't recommend any built-in solution for now really, sorry. Will have to revamp the editing first to allow for that:/
    Seneral
     
  23. MasterMati

    MasterMati

    Joined:
    May 25, 2017
    Posts:
    4
    MultiTriggerNode + UnityEvent subscribing + custom Node class (to init, when MonoBehaviour start) = succes

    If you want some mentioning of author of this framework in future, you can write what you want, so send image. Have a nice day!
     

    Attached Files:

    • yesh.png
      yesh.png
      File size:
      335.1 KB
      Views:
      911
    Seneral likes this.
  24. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Good workaround:)
     
  25. jocyf

    jocyf

    Joined:
    Jan 30, 2007
    Posts:
    284
    Hi,
    I've seen a Dynamic Node example (witch is outdated). Does anyone know how to implement an updated DynamicNode?
    I want to ba able to create new connections in the editor.

    Here is the old example:
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections.Generic;
    4. using NodeEditorFramework;
    5. using NodeEditorFramework.Utilities;
    6.  
    7. namespace NodeEditorFramework.Standard
    8. {
    9.     [Node (false, "Example/Dynamic Calculation Node")]
    10.     public class DynamicNode : Node
    11.     {
    12.         public enum CalcType { Add, Substract, Multiply, Divide }
    13.         [Serializable]
    14.         public struct InputData
    15.         {
    16.             public float value;
    17.             public CalcType type;
    18.         }
    19.  
    20.         public const string ID = "dynamicNode";
    21.         public override string GetID { get { return ID; } }
    22.  
    23.         public List<InputData> inputData = new List<InputData> ();
    24.  
    25.         public override Node Create (Vector2 pos)
    26.         {
    27.             DynamicNode node = CreateInstance<DynamicNode> ();
    28.  
    29.             node.rect = new Rect (pos.x, pos.y, 300, 200);
    30.             node.name = "Dynamic Calculation Node";
    31.  
    32.             node.CreateInput ("Base", "Float");
    33.             node.inputData.Add (new InputData ());
    34.             node.CreateOutput ("Output", "Float");
    35.  
    36.             return node;
    37.         }
    38.  
    39.         protected internal override void NodeGUI ()
    40.         {
    41.             if (GUILayout.Button ("Add Input"))
    42.             {
    43.                 NodeInput newInput = CreateInput ("Input " + (Inputs.Count+1), "Float");
    44.                 NodeEditorCallbacks.IssueOnAddNodeKnob (newInput);
    45.                 inputData.Add (new InputData ());
    46.             }
    47.  
    48.             for (int cnt = 0; cnt < Inputs.Count; cnt++)
    49.             {
    50.                 InputData data = inputData[cnt];
    51.                 NodeInput input = Inputs [cnt];
    52.                 GUILayout.BeginHorizontal (GUILayout.MaxWidth (rect.width));
    53.                 if (input.connection != null)
    54.                     GUILayout.Label (input.name + ": " + input.GetValue<float> ());
    55.                 #if UNITY_EDITOR
    56.                 else
    57.                     data.value = UnityEditor.EditorGUILayout.FloatField (input.name, data.value);
    58.                 if (cnt > 0)
    59.                 {
    60.                     data.type = (CalcType)UnityEditor.EditorGUILayout.EnumPopup (data.type);
    61.                     if (GUILayout.Button ("-", GUILayout.Width (20)))
    62.                     {
    63.                         input.Delete ();
    64.                         inputData.RemoveAt (cnt);
    65.                         cnt--;
    66.                         continue;
    67.                     }
    68.                 }
    69.                 #endif
    70.                 GUILayout.EndHorizontal ();
    71.                 InputKnob (cnt);
    72.                 inputData[cnt] = data;
    73.             }
    74.  
    75.             Outputs [0].DisplayLayout ();
    76.  
    77.             if (GUI.changed)
    78.                 NodeEditor.RecalculateFrom (this);
    79.         }
    80.  
    81.         public override bool Calculate ()
    82.         {
    83.             float result = 0;
    84.             for (int cnt = 0; cnt < Inputs.Count; cnt++)
    85.             {
    86.                 InputData data = inputData[cnt];
    87.                 NodeInput input = Inputs [cnt];
    88.                 if (input.connection != null)
    89.                     data.value = input.connection.GetValue<float> ();
    90.                 inputData[cnt] = data;
    91.  
    92.                 if (cnt == 0)
    93.                 {
    94.                     result = data.value;
    95.                     continue;
    96.                 }
    97.  
    98.                 switch (data.type)
    99.                 {
    100.                 case CalcType.Add:
    101.                     result += data.value;
    102.                     break;
    103.                 case CalcType.Substract:
    104.                     result -= data.value;
    105.                     break;
    106.                 case CalcType.Multiply:
    107.                     result *= data.value;
    108.                     break;
    109.                 case CalcType.Divide:
    110.                     result /= data.value;
    111.                     break;
    112.                 }
    113.             }
    114.             Outputs[0].SetValue<float> (result);
    115.  
    116.             return true;
    117.         }
    118.     }
    119. }
     
  26. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Hm can you tell me where you got that example from? That is indeed very outdated, don't know right now where that would be online...
    Since a few months, the develop branch includes a functional ResizingNode, which allows to add dynamic connection ports and the node automatically resizes to fit them.
    Tell me if that's what you needed:)
    Seneral
     
  27. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    617
    @Seneral I'm having trouble doing something I thought was pretty basic :(

    I'm writing a function in my custom Node class that just returns a reference to the next node it's connected to, connected by a ValueConnectionKnob. Like this:

    Code (CSharp):
    1. public LevelNode NextLevel()
    2. {
    3.     if (nextLevelKnob.connected())
    4.     {
    5.         LevelNode l_node = nextLevelKnob.GetValue<LevelNode>();
    6.         return l_node;
    7.     }
    8.     else
    9.     {
    10.         Debug.Log("There is no next level!");
    11.         return null;
    12.     }
    13. }
    The VCKs itself are defined like this:

    Code (CSharp):
    1. [ValueConnectionKnob("Previous", Direction.Out, "LevelNode", ConnectionCount.Single, NodeSide.Left, 210)]
    2. public ValueConnectionKnob previousLevelKnob;
    3.  
    4. [ValueConnectionKnob("Secondary", Direction.In, "LevelNode", ConnectionCount.Single, NodeSide.Right, 210)]
    5. public ValueConnectionKnob nextLevelKnob;
    The Value for the knobs are set in the Node's Calculate function, like this:

    Code (CSharp):
    1. previousLevelKnob.SetValue(this);
    But if I use the NextLevel() function in a test script, I get warnings saying:
    and the function won't work.

    Now if I modify this to use nextLevelKnob.connection(0).body then everything works great! So I already have the solution, but I'm just trying to understand why doing it using getvalue/setvalue just won't work
     
  28. jocyf

    jocyf

    Joined:
    Jan 30, 2007
    Posts:
    284
    I've got that code from this threath, you posted it like a year ago.
    I want to be able to add conection ports in the editor using a button inside the node (to add/remove connections).
     
  29. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ah ok then the node I linked above is for you (provided you use the latest develop branch).
     
    jocyf likes this.
  30. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ok so just checked, that can only be GetDefault in ValueConnectionKnob which returns the default value of the type if the type is null, and can't differentiate between normal classes and ScriptableObjects. So in a case where the value would be unassigned, it would try to create a SO from the normal constructor. But you said the value is assigned, so that result is unexpected (Expression "value?? Create" should only execute Create when value is null, right?).
    A long-term fix would be to handle ScriptableObjects in GetDefault (both versions) and return null. So add (pseudo):
    Code (csharp):
    1. if (typeof(ScriptableObject).IsAssignableFrom (typeof(T)))
    2. return null;
    for to the generic version and respectively
    Code (csharp):
    1. if (typeof(ScriptableObject).IsAssignableFrom (type))
    2. return null;
    to the normal below (always before the other code).
    Can you verify that works?
    Besides that, your workaround is recommended, don't see a reason to pass down the node when you can directly access it:) That kind of mis-uses the value-connection-system IMO :)
    Seneral
     
  31. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    617
    Thanks @Seneral!! So glad to know I'm not just crazy and have done something stupid somewhere.

    Will give it a go eventually and let you know
     
  32. jeremy97

    jeremy97

    Joined:
    Mar 16, 2011
    Posts:
    9
    Hey there @Seneral I'm wondering if there might be a way I could change the number of knobs on a node at runtime based on data I plug into the node. I'm trying to make a map system for my game with this node editor, but I see that the knobs are declared in the main class body rather than in a function.

    The idea is that each node will have a number of neighbors based on a number which is inputted on the node, so that way some nodes can connect to more nodes than others can. Then it'll save all of the info from the graph into a ScriptableObject for use ingame.
     
    Last edited: Mar 20, 2018
    Seneral likes this.
  33. jocyf

    jocyf

    Joined:
    Jan 30, 2007
    Posts:
    284
    There is an example included called ResizingNode in develop branch that does exactly that. It allows to add dynamic connection ports and the node automatically resizes to fit them.
     
    Seneral likes this.
  34. jeremy97

    jeremy97

    Joined:
    Mar 16, 2011
    Posts:
    9
    Thanks for that. So in terms of my use case, I'm trying to link all of these tiles with a single line per connection. I tried out Direction.None thinking that may do the trick, but Direction.None doesn't seem to accept any sort of connection from anything, including other nodes with Direction.None. Is there a way I can accomplish this without having an input and an output node per connection?

    Additionally, it seems that the Graph canvas requires this root node which I don't want. However, I get lots of errors on the Calculation canvas right now that may prevent my nodes from working there. That may be due to the recursion in my current implementation of an input and an output per connection between node though, so it could be resolved if the single connection can be made to work somehow. Otherwise, am I going to have to make my own canvas? Thanks. @Seneral
     
    Last edited: Mar 21, 2018
  35. JimRoot

    JimRoot

    Joined:
    Sep 11, 2013
    Posts:
    77
    Hey seneral!
    Love your framework! Do you somehow also have the source for the webgl demo somewhere?
     
  36. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Well, I literally just put the RTNodeEditor component on an empy object and saved some default canvases in the scene. That way, the RT node editor automatically draws a GUI and the user can load canvases from that scene. Not much setup really:)
    Seneral
     
  37. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Sorry for the super late answer first, somehow slipped through...
    Graph Canvas is just a non-practical example of how you could script your canvas, nothing to use in practice.
    True, Direction.None is an unhandled case in the editor right now (still old editing style, need to revamp that at some point to support all that previously unsupported stuff).
    You can manually allow recursion by setting AllowRecursion (overwrite property) to true on any node in the loop, although that will hinder the default calculation routine in the default canvas. So in the end, if you do not need any kind of 'Calculation' or a custom routine, yes I do recommend you to make an own canvas type. Not much to it, but allows you to do some custom things:)
    Seneral
     
  38. Sangemdoko

    Sangemdoko

    Joined:
    Dec 15, 2013
    Posts:
    191
    Hi Seneral,

    I just played with the framework for a few hours yesterday and I have ideas for some tools I could make with it.
    One is making a mindmap for brainstorming. I would like to know your thoughts on whether this is possible or not.
    To make a mind map I would like to implement three features:

    1) docking: right-click on an output knob to hide children nodes. This would require to recursively get children of a parent node and hide them in the canvas while still being active. And this should work with nested nodes. For this to work it should be impossible to do a feedback connection because that would break the parent/children relationship.

    2) groupMove: shift + left-click on a node to move the node and all its children. This would also require to recursively get all the children nodes

    3) no superpostion: This is less important but I think it does not only apply to this mindmap canvas idea, but all the node based editor. I think that nodes should not be able to superpose because it can create confusion.

    For the nodes I am thinking of a master node which is a starting point for all other nodes.

    So my question is how hard/easy would it be to implement this using your framework? And if you have any pointers on where I should start? I do not have much experience with unity editor script but I am more than happy to learn.

    @Seneral Thank you for your time.
     
  39. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Yes, all possible, but definitely requires changes to the framework source.
    Rendering of canvas, including nodes, is handled in NodeEditor.cs, so there you would handle the docking taking place.
    Docking and group move mechanics, though, can be easily implemented using the event system. Docking needs a simple node context entry to toggle a flag on a node which the NodeEditor.cs rendering can read. GroupMove would need a generic event handler that would need to execute BEFORE the normal node move (through priority). You can read more about how that works in corresponding documentation.
    In regard to superpostion, I think you want to prevent node overlap? Would require an edit to the normal node move function, there are probably some ways to do this, if it's worth for you you can give it a try...

    On the topic of functionality of that mindmap, you would likely want to create an own canvas type, similar to the graph canvas, so you can implement custom functionality like a root node. There's only little documentation for it, so just look into the example.

    Hope that gives you some hints!
    Seneral
     
  40. Sangemdoko

    Sangemdoko

    Joined:
    Dec 15, 2013
    Posts:
    191
    Thank you Seneral!
    I'll take the time to implement it this weekend. Hopefully, I won't run into too many issues.
     
  41. _MGB_

    _MGB_

    Joined:
    Apr 24, 2010
    Posts:
    74
    Hey,

    Sorry if I've missed this somewhere, but I'm trying to parse the structure of a calculation graph at runtime, but nothing appears to be connected to anything?
    All the node list members (in/outputports, in/outputknobs etc) are empty.
    What is the correct way of doing this?

    Cheers,
    M
     
  42. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Yes, that is expected. These lists are filled on demand (not sure if this was a good choice considering how unintuitive it is). Only dynamicConnectionPorts and the actual variables in the node are filled with the original port objects.
    Currently, you have to make sure the NodeEditor is initiated/setup, namely for this function to execute. Once this is done, you would have to populate these lists by calling ConnectionPortManager.UpdateConnectionPorts on each node of a canvas before you use it. Only once of course, when you edit the canvas this is updated.
    Sorry for that inconvenience!
     
  43. _MGB_

    _MGB_

    Joined:
    Apr 24, 2010
    Posts:
    74
    Ahhh ok, thanks for the quick response!
     
  44. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    617
    @Seneral I'm finding that performance of the node editor really starts to crawl very quickly once you start getting a lot of GUI things happening in NodeGUI and when you start filling up the canvas with lots of Nodes.

    So I created a new project and imported the node editor and nothing else, and inspected the node editor graph canvas sample in Profiler, there seems to be a lot of GC happening every frame, and a lot of time spent in GUIUtility.ProcessEvent() even when there's nothing happening:

    upload_2018-4-29_19-27-11.png

    The huge spike there showed in blue is when I started dragging nodes around the canvas and panned around in the canvas.

    Now back in my actual Unity project, I have a canvas with about 100 custom nodes, and whenever I have the node editor window open Unity slows down dramatically. If I tried dragging a node around in the canvas it's like the editor is running at 5 fps.

    My custom nodes do have a bit of custom NodeGUI() on them, if I comment out the entire NodeGUI() override I do get a good performance boost, BUT it's still very slow, so it seems that's only half the battle.

    Do you have any other tips on how I can improve things? Thanks!
     
  45. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Sorry for the late response.
    Do you have the latest version of the repo?
    I recently added node GUI clipping, so that nodes that are not in view are not rendered (since Unity GUI is so slow this should do alot).
    Tell me if that improves your performance.

    Regarding dragging, it should trigger a GUI repaint, that's probably why. Node dragging itself is nothing really, only moves that single node literally and repaints.

    Apart from nodes, connections play a big role, and unfortunately I didn't yet clipped them.
    Well, that is not entirely accurate, each time the whole connection is calculated and clipped on a segment-per-segment basis, but not the whole connection. This takes up all the rest most probably, should be easy to implement a simple AABB check before drawing...

    Also, I could include a change from my GUIScaleUtility gist from another developer who extended it to properly clip on the borders, which would allow me to remove the extra segment clipping, which itself is quite complex/expensive. So alot of room for improvement here:)

    Seneral
     
  46. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    617
    Just updated to the latest code. Wow, it's way better now! Really night and day difference. BUT it definitely still isn't as fast and snappy as it could be, so definitely still room for improvement, but it's gone from almost unbearably slow to being totally fine now!

    Another pain point for me is every time I switch the window focus away from the Node Editor, I think it does a save on LastSession which causes the editor to freeze up about 2 seconds. It doesn't seem like a big deal at first but when you're constantly switching back and forth between the Node editor window and other windows it becomes annoying real quick. Not too sure what the solution for that would be.
     
  47. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Cool, good to hear. As I said, I know what the next big bottleneck is, just need to find time, which might take some time. Lot's to do on lot's of places right now...

    Yes, when you unfocus it saves. This is a precaution to loosing progress, and is the only way to make sure it is saved before loading a new scene and some other occasions. I see it being annoying though. I could try out a version where it is saved as XML, maybe that will fix it, since most of the time is spent on Unity updating and stuff. Of course, you can disable it now, just comment that behaviour here.

    Seneral
     
  48. Redux

    Redux

    Joined:
    Nov 10, 2011
    Posts:
    141
    If anyone has experienced the issues with save/XML export failure in the dialog system example I'd love any input they had on fixing it.

    I'm currently using Unity 2018.1.1f1.

    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2. NodeEditorFramework.IO.XMLImportExport.SerializeObjectToXML (System.Xml.XmlElement parent, System.Object obj) (at Assets/Plugins/Node Editor/Node_Editor/Default/IO Formats/XMLImportExport.cs:360)
    3. NodeEditorFramework.IO.XMLImportExport.SerializeFieldToXML (System.Xml.XmlElement parent, System.Object obj, System.String fieldName) (at Assets/Plugins/Node Editor/Node_Editor/Default/IO Formats/XMLImportExport.cs:315)
    4. NodeEditorFramework.IO.XMLImportExport.ExportData (NodeEditorFramework.IO.CanvasData data, System.Object[] args) (at Assets/Plugins/Node Editor/Node_Editor/Default/IO Formats/XMLImportExport.cs:103)
    5. NodeEditorFramework.IO.StructuredImportExportFormat.Export (NodeEditorFramework.NodeCanvas canvas, System.Object[] locationArgs) (at Assets/Plugins/Node Editor/Node_Editor/Framework/SaveSystem/ImportExportFormat.cs:199)
    6. NodeEditorFramework.IO.ImportExportManager.ExportCanvas (NodeEditorFramework.NodeCanvas canvas, NodeEditorFramework.IO.ImportExportFormat formatter, System.Object[] args) (at Assets/Plugins/Node Editor/Node_Editor/Framework/SaveSystem/ImportExportManager.cs:59)
    7. NodeEditorFramework.Standard.NodeEditorInterface.ExportCanvasCallback (System.String formatID) (at Assets/Plugins/Node Editor/Node_Editor/Framework/Interface/NodeEditorInterface.cs:278)
    8. NodeEditorFramework.IO.ImportExportManager.unwrapExportFormatIDCallback (System.Object formatID) (at Assets/Plugins/Node Editor/Node_Editor/Framework/SaveSystem/ImportExportManager.cs:103)
    9. UnityEditor.GenericMenu.CatchMenu (System.Object userData, System.String[] options, Int32 selected) (at C:/buildslave/unity/build/Editor/Mono/GUI/GenericMenu.cs:111)
     
  49. Redux

    Redux

    Joined:
    Nov 10, 2011
    Posts:
    141
    It seems it was due to the Dialog Audio field in the test node I had created not being set to an audio clip. The core XML serializer doesn't check if the variable is null before including it as a savable element for the XML output. I have no idea where that code would be. The hacky solution would be to just check for null at that last moment and early out but it seems like wherever the variables are marshaled, but it seems like eliminating null(invalid) entries instead of trying to save them would be wise if they aren't supported.

    Textures are saved the same way in nodes which utilize them? I wonder if the dialog system example is breaking a rule somewhere.
     
  50. Redux

    Redux

    Joined:
    Nov 10, 2011
    Posts:
    141
    Any field meant to hold a unity Object derivative if not set breaks the XML exporter.