Skip to content

Instantly share code, notes, and snippets.

@FairlySadPanda
Created October 14, 2024 23:24
Show Gist options
  • Save FairlySadPanda/86aacac58fc705670716acd348b63f35 to your computer and use it in GitHub Desktop.
Save FairlySadPanda/86aacac58fc705670716acd348b63f35 to your computer and use it in GitHub Desktop.
Event Bus pattern in UdonSharp for VRChat :)
using UdonSharp;
using UnityEngine;
using VRC.SDK3.Data;
using VRC.SDKBase;
using VRC.Udon;
using Event = VRCBilliardsCE.Packages.com.vrcbilliards.vrcbce.Runtime.ScriptsV2.Core.Domain.Event;
namespace VRCBilliardsCE.Packages.com.vrcbilliards.vrcbce.Runtime.ScriptsV2.Core.Infrastructure
{
// An event bus. This manager handles discrete events being issued to it from VRCBilliards, and dispatches these
// events to delegates.
public class EventBus : LocalBehaviour
{
// Dictionary<string, Dictionary<UdonBehaviour, Dictionary<string, bool>>>
// A dictionary of registered events to registered UdonBehaviours to registered methods.
private DataDictionary registeredDelegates = new DataDictionary();
// List<string>
private DataList eventBuffer = new DataList();
public void LateUpdate()
{
Tick();
}
public bool Register(Event ev, UdonSharpBehaviour behaviour, string delegateMethod)
{
Debug.Log($"registering {ev} with delegate {behaviour} and method {delegateMethod}");
var eventKey = new DataToken(ev.ToString());
var behaviourKey = new DataToken(behaviour);
var delegateKey = new DataToken(delegateMethod);
if (!registeredDelegates.ContainsKey(eventKey))
{
Debug.Log($"event {eventKey} has no dictionary: assigning");
registeredDelegates.Add(eventKey, new DataDictionary());
}
DataDictionary eventDict = registeredDelegates[eventKey].DataDictionary;
if (!eventDict.ContainsKey(behaviourKey))
{
Debug.Log($"event {eventKey} behaviour {behaviourKey} has no dictionary: assigning");
eventDict.Add(behaviourKey, new DataDictionary());
}
if (eventDict[behaviourKey].DataDictionary.ContainsKey(delegateKey))
{
Debug.Log($"event {eventKey} behaviour {behaviourKey} already registered function {delegateKey}: discarding registration");
return false;
}
Debug.Log($"event {eventKey} behaviour {behaviourKey} registered function {delegateKey}");
eventDict[behaviourKey].DataDictionary[delegateKey] = true;
return true;
}
public void Raise(Event ev)
{
Debug.Log($"raising event ID {ev}");
eventBuffer.Add(new DataToken(ev.ToString()));
}
public void RaiseImmediate(Event ev)
{
HandleEvent(ev.ToString());
}
private void Tick()
{
RemoveDeadDelegates();
// Drain the buffer.
foreach (var ev in eventBuffer.ToArray())
{
Debug.Log($"Handling event {ev}");
HandleEvent(ev.String);
}
eventBuffer.Clear();
}
private void RemoveDeadDelegates()
{
// TODO: Assert that this roundabout way of having to access VRC data structures is intentional.
foreach (var value in registeredDelegates.GetValues().ToArray())
{
foreach (var key in value.DataDictionary.GetKeys().ToArray())
{
if (!Utilities.IsValid(key.Reference))
{
value.DataDictionary.Remove(key);
}
}
}
}
private void HandleEvent(string ev)
{
var eventToken = registeredDelegates[ev];
if (eventToken.Error != DataError.None)
{
Debug.Log($"discarding {ev}: {eventToken.Error}");
return;
}
var eventDictionary = eventToken.DataDictionary;
// Each key is an Udon(Sharp)Behaviour.
foreach (var key in eventDictionary.GetKeys().ToArray())
{
Debug.Log($"transmitting to delegates on {key.Reference}");
// Each key is a function to call.
foreach (var funcName in eventDictionary[key].DataDictionary.GetKeys().ToArray())
{
Debug.Log($"transmitting to delegate {funcName.String} on {key.Reference}");
((UdonBehaviour)key.Reference).SendCustomEvent(funcName.String);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment