0
Answered

How to recreate "event"-like unit in Bolt 1.4

Real World 2 years ago updated 2 years ago 12

In 1.3 I wrote some custom units that had a single ControlInput. When entered, the unit would setup a few callbacks on an object and then do nothing. When the event was called, the ControlOutput for that event would be triggered. It would be possible for a single unit to receive control input once but fire multiple outputs. I used this for listening to events on an object and triggering an appropriate flow from those events.

The update to 1.4 has made it seemingly impossible to do this anymore since either the callback on input function needs to return a ControlOutput immediately or I can set up a coroutine to call the output at a later date. The latter option works partially but deactivates the coroutine after the first output is called so other events are not handled.

I looked into subclassing an EventUnit but I cant set up multiple outputs on this unit as the trigger has a private set function and Run is not virtual

Bolt Version:
Unity Version:
Platform(s):
Scripting Backend:
.NET Version (API Compatibility Level):

I think what you want is custom event unit to respond to the event, and trigger custom event unit to call it?

Or you can hook it up to existing UnityEvents

https://support.ludiq.io/knowledge-bases/4/articles/137-events

No. This is definitely not what I want. CustomEvent units are active as long as the graph is active. I can't turn them off on a particular event. They also have a GameObject parameter that is checked every frame for changes causing framerate drop while they're active. They also only have one output where I need multiple so would need multiple CustomEvents instead. I want something event-like but not CustomEvent

+1
Pending Review

Hi Real World!

Can you paste the source code for your unit in v.1.3? I could try to see how to convert it to the new flow architecture in v.1.4.

using Ludiq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Bolt
{
   [UnitCategory( "Game" )]
   [UnitShortTitle( "AlienEventWatcher" )]
   [UnitTitle( "Alien Event Watcher" )]
   class AlienEventWatcher : Unit
   {
      // Using L<KVP> instead of Dictionary to allow null key
      private List<KeyValuePair<string, ControlOutput>> branches;
      private List<string> _menuEvents = new List<string>();
      [Inspectable, Serialize]
      public List<string> menuEvents
      {
         get { return _menuEvents; }
         set { _menuEvents = value; }
      }
      /// <summary>
      /// The entry point for the switch.
      /// </summary>
      [DoNotSerialize]
      [PortLabelHidden]
      public ControlInput enter { get; private set; }
      protected override void Definition()
      {
         enter = ControlInput( "enter", StartAction );
         branches = new List<KeyValuePair<string, ControlOutput>>();
         foreach( var option in menuEvents )
         {
            var key = option;
            if( !controlOutputs.Contains( key ) )
            {
               var branch = ControlOutput( key );
               branches.Add( new KeyValuePair<string, ControlOutput>( option, branch ) );
               Relation( enter, branch );
            }
         }
      }
      private void StartAction( Flow obj )
      {
         AlienNotificationManager.Instance.OnAlienEvent += OnAlienEvent;
      }
      private void OnAlienEvent( string eventName )
      {
         foreach( var branch in branches )
         {
            if( branch.Key.Equals( eventName ) )
            {
               using( Flow flow = Flow.New() )
               {
                  flow.Invoke( branch.Value );
               }
            }
         }
      }
   }
}

So if I get this right, only one event should match the eventName, right?

Change your StartAction method to:

      private void StartAction( Flow flow )
      {
         AlienNotificationManager.Instance.OnAlienEvent += (eventName) => OnAlienEvent(flow, eventName);
      }

Then:

      private void OnAlienEvent( Flow flow, string eventName )
      {
         foreach( var branch in branches )
         {
            if( branch.Key.Equals( eventName ) )
            {
               return branch.Value;
            }
         }
 
         return null;
      } If multiple branches could match the name, you could do:
      private void OnAlienEvent( Flow flow, string eventName )
      {
         foreach( var branch in branches )
         {
            if( branch.Key.Equals( eventName ) )
            {
               flow.Invoke( branch.Value );
            }
         }
 
         return null;
      }

Thanks. Will this still work if the OnAlienEvent function is called multiple times? I thought I'd tried something similar to that where the first call worked fine but future calls threw exceptions. If so then I'll try it when I get chance but I've had to shelve plans for updating Bolt for now.

Ah wait actually, nevermind what I wrote. The structure here isn't good. I'll have to post another example that extends EventUnit and StartListening.

+1
Answered

Here's how I would implement it:

using System;
using System.Collections.Generic;
using Ludiq;
namespace Bolt
{
    [UnitCategory("Events")]
    public class OnAlienEvent : EventUnit<emptyeventargs>
    {
        protected override bool register => false;
        
        private List<keyvaluepair<string, controloutput="">> branches;
        [Inspectable]
        [Serialize]
        public List<string> eventNames { get; set; } = new List<string>();
        protected override void Definition()
        {
            branches = new List<keyvaluepair<string, controloutput="">>();
            foreach (var eventName in eventNames)
            {
                var key = eventName;
                if (!controlOutputs.Contains(key))
                {
                    var branch = ControlOutput(key);
                    branches.Add(new KeyValuePair<string, controloutput="">(eventName, branch));
                }
            }
        }
        public override void StartListening(GraphStack stack)
        {
            base.StartListening(stack);
            var data = stack.GetElementData<data>(this);
            var reference = stack.ToReference();
            Action<string> handler = (eventName) => Handle(reference, eventName);
            data.handler = handler;
            
            AlienNotificationManager.Instance.OnAlienEvent += handler;
        }
        public override void StopListening(GraphStack stack)
        {
            base.StopListening(stack);
            
            var data = stack.GetElementData<data>(this);
            
            AlienNotificationManager.Instance.OnAlienEvent -= data.handler;
        }
        
        private void Handle(GraphReference reference, string eventName)
        {
            foreach (var branch in branches)
            {
                if (branch.Key == eventName)
                {
                    using (var flow = Flow.New(reference))
                    {
                        flow.Invoke(branch.Value);
                    }
                }
            }
        }
    }
}

Thanks again. I'll test it out as soon as I get time in the schedule.

Argh I just realized UserEcho messed up some of the formatting in generics, sorry. Otherwise it should work!