Skip to content

Instantly share code, notes, and snippets.

@LambdaSix
Last active June 21, 2024 21:48
Show Gist options
  • Save LambdaSix/b916b09175808b6a44304dc21d1cc20c to your computer and use it in GitHub Desktop.
Save LambdaSix/b916b09175808b6a44304dc21d1cc20c to your computer and use it in GitHub Desktop.
Generic EventBus for Godot, allow generic registration and emitting of event data while remaining nicely typed.
// Records are nice for use with events, just change EventRecord to EventArgs if you'd
// rather use the traditional EventArgs class.
public record EventRecord
{
public static readonly EventRecord Empty = new EventRecord();
}
public class EventBus
{
public static readonly EventBus Instance = new EventBus();
private Dictionary<Type, List<EventHandler<EventRecord>>> channels = new();
public class Subscription : IDisposable
{
private EventHandler<EventRecord> _handler;
private Type _channel;
public bool IsDisposed { get; private set; } = false;
public Subscription(Type channel, EventHandler<EventRecord> handler)
{
_handler = handler;
_channel = channel;
}
public void Dispose()
{
if (IsDisposed)
return;
IsDisposed = true;
if (Instance.channels.TryGetValue(_channel, out var handlers))
handlers.Remove(_handler);
}
}
public Subscription Subscribe<T>(EventHandler<T> handler) where T : EventRecord
{
var channelType = typeof(T);
List<EventHandler<EventRecord>> listeners;
if (channels.TryGetValue(channelType, out var channel))
listeners = channel;
else
channels.Add(channelType, listeners = new());
listeners.Add(handler as EventHandler<EventRecord>);
return new Subscription(channelType, handler as EventHandler<EventRecord>);
}
public int Publish<T>(object sender, T arg) where T: EventRecord
{
if (!channels.TryGetValue(arg.GetType(), out var handlers)) return 0;
foreach (var handler in handlers)
{
handler(sender, arg);
}
return handlers.Count;
}
}
// The EventBus is arranged by 'channels', which are an EventRecord, this encourages/forces
// usage of proper objects for passing events, it's not compatable with
// Godot's Signal system out of the box though.
// Declare your data container, I prefer using Records but EventArgs work as well (with modifying EventBus)
public record EntityUpdateEventArgs(Node Entity, Vector2 Position) : EventRecord;
public class EventBusExample
{
void Example()
{
// Subscriptions are done via the type of EventArgs used, you get the same
// type back as your arg, along with the identify of who sent the message.
EventBus.Instance.Subscribe<EntityUpdateEventArgs>((sender, arg) =>
{
Console.WriteLine($"Hello from EntityUpdateHandler! {arg.Entity}");
});
// Call Publish to notify any subscribers of an event, providing an appropriate instance of
// the subscribed type. All subscribers to that message type will be notified in no particular
// order
EventBus.Instance.Publish(this, new EntityUpdateEventArgs(new Node2D(), Vector2.Zero));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment