Search Unity

Question Is it possible to run several coroutines nodes at the same time (basically split flow in several)?

Discussion in 'Visual Scripting' started by JacobFast, Apr 28, 2022.

  1. JacobFast

    JacobFast

    Joined:
    Apr 29, 2014
    Posts:
    47
    TLDR:
    One Output control connected to many Input controls

    I mean if there is not hacky way to do that, right now i do it like on the picture. What i am looking for is a better way to do the same, like one entrance node with multiple branches, like xnode example.

    So the problem is, i have coroutine node that is running, so while it is running it cant flow control to the next node and the reason it is running in coroutine, so i can have a control over the node. Say the NPC is walking and this coroutine controls it, so at some point i want to send stop walking command, etc. And while this node controls NPC, i need other nodes to do their thing, maybe control other NPC. Obviously it is impossible to chain the nodes, since they never stop running. Whats the solution (right now i think about triggering an event and then have multiple OnEvent nodes to run them in parallel, but this is makes graph very messy looking and hard to see whats happening.

    This is from manual "You also can't connect a single Control Output port to multiple Control Input ports, because Visual Scripting wouldn't know which node to run first."

    Which i find somewhat confusing, if i connect output to many input this is already means i dont care in which order nodes are executed, if i were i would connect them one after another. Plus other VS tools handle this situation just fine.
     

    Attached Files:

    Last edited: Apr 28, 2022
  2. munchmo

    munchmo

    Joined:
    May 20, 2019
    Posts:
    85
    Not sure if this is what you're looking for, but you can use a Sequence node to branch your flow. Technically, this wouldn't work as you're describing, because it runs step one, then step two, etc. But this does allow you to take a single flow output and connect it to multiple inputs, sorta.
     
  3. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
    You could write your own Coroutine output splitter nodes and see if that works well.

    I wrote this one to split into 4 outputs and barely tested it


    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using Unity.VisualScripting;
    6.  
    7. [UnitCategory("OutputSplitter")]
    8. [UnitSubtitle("Split4")]
    9. public class SplitOutputNode : Unit
    10. {
    11.     // CONTROL PORTS //
    12.     [DoNotSerialize]
    13.     [PortLabelHidden]
    14.     public ControlInput inputTrigger;
    15.  
    16.     [DoNotSerialize]
    17.     [PortLabelHidden]
    18.     public ControlOutput outputTrigger1;
    19.  
    20.     [DoNotSerialize]
    21.     [PortLabelHidden]
    22.     public ControlOutput outputTrigger2;
    23.  
    24.     [DoNotSerialize]
    25.     [PortLabelHidden]
    26.     public ControlOutput outputTrigger3;
    27.  
    28.     [DoNotSerialize]
    29.     [PortLabelHidden]
    30.     public ControlOutput outputTrigger4;
    31.  
    32.  
    33.     protected override void Definition()
    34.     {
    35.         inputTrigger = ControlInputCoroutine("inputTrigger", OnUpdate);
    36.  
    37.         outputTrigger1 = ControlOutput("outputTrigger1");
    38.         outputTrigger2 = ControlOutput("outputTrigger2");
    39.         outputTrigger3 = ControlOutput("outputTrigger3");
    40.         outputTrigger4 = ControlOutput("outputTrigger4");
    41.  
    42.         //relationships
    43.         Succession(inputTrigger, outputTrigger1);
    44.         Succession(inputTrigger, outputTrigger2);
    45.         Succession(inputTrigger, outputTrigger3);
    46.         Succession(inputTrigger, outputTrigger4);
    47.  
    48.     }
    49.  
    50.     public IEnumerator OnUpdate(Flow flow)
    51.     {
    52.         while(true)
    53.         {
    54.             yield return outputTrigger1;
    55.             yield return outputTrigger2;
    56.             yield return outputTrigger3;
    57.             yield return outputTrigger4;
    58.             yield return null;
    59.         }
    60.     }
    61. }
    62.  
     
  4. JacobFast

    JacobFast

    Joined:
    Apr 29, 2014
    Posts:
    47
    Unfortunately it does work the same way the sequencer node does. It does not flow control to next output until current output finished the flow.
     
  5. Starpaq2

    Starpaq2

    Joined:
    Mar 14, 2013
    Posts:
    77
    I do not believe splitting the flow is currently possible. I believe that would imply that the node flow is multi-thread capable. At some point the flow is always going to be sequential.

    However, if I understand well enough for this situation, I would approach it by using 3 custom event triggers at the end of the coroutine output flow. Each trigger can call an event set as a Co-routine. Somewhat simulating an output of 3 flows without being interrupted or dependent upon the other coroutines to finish.

    Use the argument parameter (change it from 0 to 1 or more) for the trigger node to feed a variable and control the other coroutines. On the custom event coroutine change the argument value (from 0 to 1 or more to match).

    Perhaps this still may not accomplish what you are thinking. And you may consider linking a c# script.

    sidenote: I have extensively used xnode a few years back. It's framework was modifiable to have multiple outputs because the output could be set as a list/array of (generic) connections. But I could not find a way to do this in Unity Visual Script.
     
  6. marcuslelus

    marcuslelus

    Joined:
    Jun 18, 2018
    Posts:
    67
    I'm not a fan of "hard coded" code. Instead of a Split4, I would simply do a Split with infinite amount of outputs that you set in the inspector, like the SwitchUnit or Sequence Node, like the code below. Also, I'm not sure why the while(true). It's gonna retrigger all outputs infinitely, the yield return null; does not stop the coroutine from running... Maybe I'm missing something. Also like others mentioned, this will run one coroutine until it is completed, then move to the others instead of running them side by side.

    That would work. AFAIK, UVS is not multi-threaded, but coroutines are never multi-threaded, so you can start a bunch of them in C# and they run them in "parallel-ish".

    Here, that's a variation of the Switch Unit (I could've used the Sequence one, but life...). So I tried the Sequence Node, and from the Graph below, it printed 1, 2, 3, 4, which was disappointing. Here's my implementation that will print 2, 1, 4, 3, meaning it ran all coroutines side by sides.

    Code:
    (Full disclosure, it's not fully tested and might break. I'll test more later (gotta work), tell me if it crashes)
    Also, I made it in a way that let's you give a name to your output. I find that it is usually safer when you add and remove outputs. I could provide another one with automatic output names, like the Sequence Node

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.VisualScripting;
    3.  
    4. [UnitCategory("Control")]
    5. [UnitTitle("Multi-Split")]
    6. [TypeIcon(typeof(Sequence))]
    7. public class SplitOutputNode : Unit
    8. {
    9.     [DoNotSerialize] public List<KeyValuePair<string, ControlOutput>> branches { get; private set; }
    10.  
    11.     [DoNotSerialize] [PortLabelHidden] public ControlInput inputTrigger { get; private set; }
    12.  
    13.     [Inspectable, Serialize] public List<string> options { get; set; } = new List<string>();
    14.  
    15.     protected override void Definition()
    16.     {
    17.         inputTrigger = ControlInput(nameof(inputTrigger), OnEnter);
    18.  
    19.         branches = new List<KeyValuePair<string, ControlOutput>>();
    20.  
    21.         foreach (var option in options)
    22.         {
    23.             if (string.IsNullOrEmpty(option)) continue;
    24.        
    25.             var key = $"%{option}";
    26.  
    27.             if (controlOutputs.Contains(key)) continue;
    28.        
    29.             var branch = ControlOutput(key);
    30.             branches.Add(new KeyValuePair<string, ControlOutput>(option, branch));
    31.             Succession(inputTrigger, branch);
    32.         }
    33.     }
    34.  
    35.     private ControlOutput OnEnter(Flow arg)
    36.     {
    37.         var reference = arg.stack.ToReference();
    38.  
    39.         foreach (var branch in branches)
    40.         {
    41.             var temp = Flow.New(reference);
    42.             temp.StartCoroutine(branch.Value);
    43.         }
    44.  
    45.         return null;
    46.     }
    47. }
    multi-coroutine.png logs.png
     
    Last edited: May 10, 2022
  7. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
    Didn't even know that they included the nodes in code form so that you can look at how they are programmed.
    Way more useful than anything you can find on the internet
     
    Last edited: May 10, 2022
  8. marcuslelus

    marcuslelus

    Joined:
    Jun 18, 2018
    Posts:
    67
    Quite useful, indeed! Even more awesome is that they provide the documentation, which is often way more up-to-date than the one you'll find online.

    For instance, they just released UVS 1.7.8, which you can install via the Package Manager. Yet, as of this minute, there is no changelog for that (which might change before you actually read this comment), But you can go in the folder Visual Scripting in the packages and look at Changelog.md. Here you go:
    Code (CSharp):
    1. ## [1.7.8] - 2022-02-22
    2. ### Fixed
    3. - Handle ReflectionTypeLoadException for TypeUtility to remove warning [BOLT-1900](https://issuetracker.unity3d.com/product/unity/issues/guid/BOLT-1900)
    4. - Fixed drag inconsistency in Graph Variables [BOLT-2113](https://issuetracker.unity3d.com/product/unity/issues/guid/BOLT-2113)
    5. - Fixed exception after creating a graph from the Welcome Window on Linux [BOLT-1828](https://issuetracker.unity3d.com/product/unity/issues/guid/BOLT-1828)
    6. - Fixed the Cooldown node not becoming "Ready" when the "Reset" port is triggered
    7. - Fixed exception thrown after changing Hierarchy selection after removing Saved variable [BOLT-1919](https://issuetracker.unity3d.com/product/unity/issues/guid/BOLT-1919)
    8. - Fixed old Bolt saved variables not loading when using a build created using a newer version of Visual Scripting [BOLT-2052](https://issuetracker.unity3d.com/product/unity/issues/guid/BOLT-2052)
    9. - Fixed a performance issue when using lots of Get/Set Scene variable nodes in an open graph
    10. - Fixed zooming out in the Graph to be relative to the mouse cursor [BOLT-1667](https://issuetracker.unity3d.com/product/unity/issues/guid/BOLT-1667)
    11. - Fixed a compilation error when migrating from Visual Scripting 1.7.6 to 1.7.7 with InputSystem-1.1.1 or below installed.
    12. - Fixed a performance issue when using lots of Get/Set Scene variable nodes in an open graph
    13. - Fixed default inspectors for nodes not appearing in the correct position after a connected node is deleted [BOLT-1457](https://issuetracker.unity3d.com/product/unity/issues/guid/BOLT-1457)
    14. - Fixed Scene variables drag and drop in graph having wrong scope [BOLT-2247](https://issuetracker.unity3d.com/product/unity/issues/guid/BOLT-2247)
    15.  
    16. ### Changed
    17. - Small optimization of load times involving generic types.
    18. - Renamed ContinuousNumberDrawer.cs.cs to ContinuousNumberDrawer.cs [BOLT-2288](https://issuetracker.unity3d.com/product/unity/issues/guid/BOLT-2288)
    19.  
    20. ### Added
    21. - TextMeshPro assembly is now added by default in Project Settings/Visual Scripting/Node Library
    22. - Added highlight to new VS graph drop down items [BOLT-2205](https://issuetracker.unity3d.com/product/unity/issues/guid/BOLT-2205)
    23. - Added margins to the UI for project settings and editor preferences
     
  9. Trindenberg

    Trindenberg

    Joined:
    Dec 3, 2017
    Posts:
    398
    Surely you want to Wait Next Frame, with some kind of controller/manager/observer on what should be running/stopped. Wait Next Frame continues the loop the next frame, hence passing control to the next flow in the frame. eg.

    (Controller) Dictionary<key: gameObject, value: boolean> movingEntities, "Start Movement" Trigger Event |MoveEntity @targetEntity|
    (MoveEntity Event) movingEntities.Add (@thisEntity, true) While |movingEntities[thisEntity] = true| -> moveEntity -> Wait Next Frame.
    (Controller) "Stop Movement" movingEntities[targetEntity] = false; "Target coroutine ends"

    That's pseudo-code but hopefully makes sense (less any error checking). value could also be an Enum for various states of movement, although requires some coding an enum and adding as a type