Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Instantiate a FlowMacro and Run It?

Discussion in 'Visual Scripting' started by ModLunar, Sep 28, 2020.

  1. ModLunar

    ModLunar

    Joined:
    Oct 16, 2016
    Posts:
    374
    What I want to do is take a FlowMacro in my C# script,
    Instantiate it, and
    Run it.


    How do I do this in C#?
    EDIT: The short answer is you don't. You use a FlowMachine component.
    See below for a longer explanation and how I found this out.



    I have the following so far.
    This is a MonoBehaviour, and using the inspector, I can just drag and drop a reference to my FlowMacro asset on it.
    In the Start() method, I'm seeing if I can instantiate the FlowMacro and somehow run that instance.

    Code (CSharp):
    1. using UnityEngine;
    2. using Ludiq;
    3. using Bolt;
    4.  
    5. public class RunMacroExample : MonoBehaviour {
    6.     [SerializeField] private FlowMacro flowMacro;
    7.  
    8.     private void Start() {
    9.         GraphReference runtimeFlowGraph = GraphReference.New(flowMacro, true);
    10.         Flow runtimeFlow = Flow.New(runtimeFlowGraph);
    11.  
    12.         runtimeFlow.Run(null);
    13.         runtimeFlow.Invoke(null);
    14.     }
    15. }
    16.  
    Since Bolt's Scripting API isn't documented, I don't get what's happening with GraphReference.New(...) or Flow.New(...), but it returns a Flow, which I've heard myths about it possibly being the runtime version that I'm after.

    I saw I could call flow.Run(...) or flow.Invoke(...), which I think! are what I want!
    But both of those methods require 1 argument, which is a ControlOutput.

    After learning how to create a custom Unit (by inheriting from the Bolt.Unit class), I know what a ControlOutput is roughly (see this tutorial page from LASM).
    However, we've only created them while defining a Unit (in its Definition() method).
    Thus, when I try to create one with the constructor, it causes an internal NullReferenceException, so it clearly doesn't like this:

    Code (CSharp):
    1. using UnityEngine;
    2. using Ludiq;
    3. using Bolt;
    4.  
    5. public class RunMacroExample : MonoBehaviour {
    6.     [SerializeField] private FlowMacro flowMacro;
    7.  
    8.     private void Start() {
    9.         GraphReference runtimeFlowGraph = GraphReference.New(flowMacro, true);
    10.         Flow runtimeFlow = Flow.New(runtimeFlowGraph);
    11.  
    12.         ControlOutput output = new ControlOutput("testing 123");
    13.  
    14.         runtimeFlow.Run(output);
    15.         runtimeFlow.Invoke(output);
    16.     }
    17. }
    18.  
    So what is the right way to run this Flow?

    --- --- ---

    Extra Info:

    I do not mean invoke a custom event,
    I mean "run" as in -- the Start, Update, OnDisable, etc. life cycle events should be called on it, and the flow should be running wherever Control is routed to with the connections in my graph.


    P.S. - I'm basically asking what a FlowMachine does behind the scenes.
    However, I don't want to rely on a FlowMachine because:
    1. This requires a GameObject in my scene to add the FlowMachine component to.
    2. I can't seem to access the running Flow in it anyway. Only the original graph property.
     
    Last edited: Sep 28, 2020
  2. ModLunar

    ModLunar

    Joined:
    Oct 16, 2016
    Posts:
    374
    Ahh.. interesting..
    In short, it seems we're expected to use FlowMachines and not do this ourselves.
    FlowMachines are MonoBehaviours that use their Unity Messages (Awake, Start, Update, etc.) to invoke those messages on their own running Flow (runtime instance of a FlowGraph).



    The long-winded answer as to how I got to this conclusion was from the following testing code:
    Code (CSharp):
    1. using UnityEngine;
    2. using Ludiq;
    3. using Bolt;
    4.  
    5. public class RunMacroExample : MonoBehaviour {
    6.     [SerializeField] private FlowMacro flowMacro;
    7.  
    8.     private void Start() {
    9.         GraphReference runtimeFlowGraph = GraphReference.New(flowMacro, true);
    10.         Flow runtimeFlow = Flow.New(runtimeFlowGraph);
    11.  
    12.         //DON'T DO THIS!
    13.         //This ControlOutput will end up invalid and cause problems.
    14.         //ControlOutput output = new ControlOutput("testing 123");
    15.  
    16.         //Creating a brand-new graph with Start and Update units (nodes) would run that blank graph,
    17.         //which wouldn't be what we want.
    18.         //We want the graph from the flowMacro we have assigned in the inspector above.
    19.         //FlowGraph graph = FlowGraph.WithStartUpdate();
    20.  
    21.  
    22.         FlowGraph graph = flowMacro.graph;
    23.         ControlOutput graphOutput = null;
    24.  
    25.         //Get the first output in the graph, just to test if this works at all...
    26.         foreach (IUnit unit in graph.units) {
    27.             Debug.Log(unit.GetType().Name);
    28.             foreach (ControlOutput output in unit.controlOutputs) {
    29.                 Debug.Log("YAY!");
    30.                 graphOutput = output;
    31.                 break;
    32.             }
    33.             break;
    34.         }
    35.  
    36.         //This depends on if the event is marked as a coroutine in the Graph Inspector.
    37.         //If it is, call StartCoroutine(...) here.
    38.         //If NOT, call Invoke(...) here.
    39.         runtimeFlow.StartCoroutine(graphOutput);
    40.         //runtimeFlow.Invoke(graphOutput);
    41.     }
    42. }
    43.  
    I immediately knew because I shouldn't have to go through such hurdles just to run my graph.
    If I did it this way, this means I'd need to manually call the events (Awake, Start, Update, etc.) myself, which would be terrible!
    This is all stuff the FlowMachine already handles for me.
     
    Last edited: Sep 28, 2020
  3. ModLunar

    ModLunar

    Joined:
    Oct 16, 2016
    Posts:
    374
    If you're wondering, I did this experiment because:
    I wanted to see if I could make a graph of data, instead of focusing on functionality.
    Doing this however, would require me being able to traverse all nodes and grab values on them all.

    Getting the values of ValueInputs and ValueOutputs requires a reference to the currently-running Flow, which was confusing me for a while.
    I might have a lead now..