Skip to content

Subgraph Node API

CoffeeVampir3 edited this page Apr 11, 2021 · 2 revisions
public class SubgraphNode : RuntimeNode

This is an early feature, it's provided in the API but is unfinished, likely to change, and not tested or stable

SubgraphNodes are just like RuntimeNodes, but they have special evaluation semantics, and, when added to the graph, they create an entirely new subgraph within the graph which you can access via the SubgraphNode. Some important points about this:

  1. When the subgraph is created, it creates a copy of this subgraph node and places it in the new graph. The copy is totally unique and has it's own properties, it does not reflect changes made to the parent after its creation.
  2. You can have any number of nested subgraphs-within-subgraphs
  3. When a subgraph node is visited, the parent node pushes itself to the top of the context and delegates execution to the (copied) child.
  4. Subgraphs are treated as their own standalone instance for change tracking, so you must manually open each subgraph right now to prompt change-tracking to run. The plan is to have the entire graph structure role through changes at the same time, but it's not implemented yet.
  5. You don't need to treat subgraph nodes in any special way, they act just like normal nodes would, however, a best practice recommendation is keep mutable data out of subgraph nodes, because the child-copy semantics are potentially confusing.
  6. Subgraph Node's don't have a drawer, this is to prevent people putting data on them. But, if you want one, here's an example
    [RegisterViewFor(typeof(SubgraphTester))]
    public class TestModelView : CustomNodeView {
        public void Generate(SerializedObject so, RuntimeNode node, VisualElement generateTo)
        {
            var it = so.GetIterator();
            if (!it.NextVisible(true))
                return;
            
            //Descends through serialized property children & allows us to edit them.
            do
            {
                var copiedProp = it.Copy();

                //Otherwise lay out like normal.
                var propertyField = new PropertyField(copiedProp) 
                    { name = it.propertyPath };

                //Bind the property so we can edit the values.
                propertyField.Bind(so);

                //This ignores the label name field, it's ugly.
                if (it.propertyPath == "m_Script" && 
                    so.targetObject != null) 
                {
                    propertyField.SetEnabled(false);
                    propertyField.visible = false;
                    continue;
                }
                
                generateTo.Add(propertyField);
            }
            while (it.NextVisible(false));
        }
        
        public override void CreateView(SerializedObject serializedRuntimeData, 
            RuntimeNode forNode, NodeView forView)
        {
            Generate(serializedRuntimeData, forNode, this);
            
            Button button = new Button {text = "Submerge"};
            this.Add(button);

            button.clicked += () =>
            {
                var view = this.GetFirstAncestorOfType<GraphifyView>();
                var model = view.graphModel;
                if (model == null)
                    return;
                
                var sgNode = forNode as SubgraphNode;
                var childBp = GraphModel.GetBlueprintByGuid(model, sgNode.childBpGuid);
                var parentBp = GraphModel.GetBlueprintByGuid(model, sgNode.parentBpGuid);
                if (childBp == null || parentBp == null) return;
                
                var parentModel = GraphModel.GetModelFromBlueprint(parentBp);
                var childModel = GraphModel.GetModelFromBlueprint(childBp);
                if (parentModel != null)
                {
                    if (parentModel.view == null)
                    {
                        Debug.Log("Null view.");
                    }
                    parentModel.view.LoadGraph(childModel);
                }
            };
            
            Button button2 = new Button {text = "Unsubmerge"};
            this.Add(button2);
            
            button2.clicked += () =>
            {
                var view = this.GetFirstAncestorOfType<GraphifyView>();
                var model = view.graphModel;
                if (model == null)
                    return;
                
                var sgNode = forNode as SubgraphNode;
                var childBp = GraphModel.GetBlueprintByGuid(model, sgNode.childBpGuid);
                var parentBp = GraphModel.GetBlueprintByGuid(model, sgNode.parentBpGuid);
                if (childBp == null || parentBp == null) return;
                
                var parentModel = GraphModel.GetModelFromBlueprint(parentBp);
                var childModel = GraphModel.GetModelFromBlueprint(childBp);
                if (parentModel != null)
                {
                    childModel.view.LoadGraph(parentModel);
                }
            };
        }
    }