-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
* feat: event bus pattern * Create README.md * chore: meta update * chore: static bus * Update README.md * chore: event bus docs & tests * chore: PR feedback Co-authored-by: Koretsky Konstantin <[email protected]>
- Loading branch information
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using System; | ||
|
||
namespace StansAssets.Foundation.Patterns | ||
{ | ||
/// <summary> | ||
/// Basic implementation of the <see cref="IEventBus"/>. | ||
/// </summary> | ||
public sealed class EventBus : IEventBus | ||
{ | ||
/// <inheritdoc> | ||
/// <cref>IEventBus.Subscribe</cref> | ||
/// </inheritdoc> | ||
public void Subscribe<T>(Action<T> listener) where T : IEvent | ||
{ | ||
EventBusDispatcher<T>.Subscribe(this, listener); | ||
} | ||
|
||
/// <inheritdoc> | ||
/// <cref>IEventBus.Unsubscribe</cref> | ||
/// </inheritdoc> | ||
public void Unsubscribe<T>(Action<T> listener) where T : IEvent | ||
{ | ||
EventBusDispatcher<T>.Unsubscribe(this, listener); | ||
} | ||
|
||
/// <inheritdoc> | ||
/// <cref>IEventBus.Post</cref> | ||
/// </inheritdoc> | ||
public void Post<T>(T @event) where T : IEvent | ||
{ | ||
EventBusDispatcher<T>.Dispatch(this, @event); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace StansAssets.Foundation.Patterns | ||
{ | ||
static class EventBusDispatcher<T> where T : IEvent | ||
{ | ||
static readonly Dictionary<EventBus, Action<T>> s_Actions = new Dictionary<EventBus, Action<T>>(); | ||
|
||
public static void Subscribe(EventBus bus, Action<T> listener) | ||
{ | ||
if (!s_Actions.ContainsKey(bus)) | ||
{ | ||
s_Actions.Add(bus, delegate { }); | ||
} | ||
|
||
s_Actions[bus] += listener; | ||
} | ||
|
||
public static void Unsubscribe(EventBus bus, Action<T> listener) | ||
{ | ||
if (s_Actions.ContainsKey(bus)) | ||
{ | ||
s_Actions[bus] -= listener; | ||
} | ||
} | ||
|
||
public static void Dispatch(EventBus bus, T @event) | ||
{ | ||
if (s_Actions.TryGetValue(bus, out var action)) | ||
{ | ||
action.Invoke(@event); | ||
} | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace StansAssets.Foundation.Patterns | ||
{ | ||
/// <summary> | ||
/// Interface represents and even distributed via <see cref="IEventBus"/> | ||
/// </summary> | ||
public interface IEvent { } | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
namespace StansAssets.Foundation.Patterns | ||
{ | ||
/// <summary> | ||
/// An interface for the event bus pattern. | ||
/// </summary> | ||
public interface IEventBus : IReadOnlyEventBus | ||
{ | ||
/// <summary> | ||
/// Posts and event. | ||
/// </summary> | ||
/// <param name="event">An event instance to post.</param> | ||
/// <typeparam name="T">Event Type.</typeparam> | ||
void Post<T>(T @event) where T : IEvent; | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using System; | ||
|
||
namespace StansAssets.Foundation.Patterns | ||
{ | ||
/// <summary> | ||
/// Interface allows to subscribe and unsubscribe from event bus events. | ||
/// But hides and ability to dispatch an event. | ||
/// </summary> | ||
public interface IReadOnlyEventBus | ||
{ | ||
/// <summary> | ||
/// Subscribes listener to a certain event type. | ||
/// </summary> | ||
/// <param name="listener">Listener instance.</param> | ||
/// <typeparam name="T">An event type to subscribe for.</typeparam> | ||
void Subscribe<T>(Action<T> listener) where T : IEvent; | ||
|
||
/// <summary> | ||
/// Unsubscribes listener to a certain event type. | ||
/// </summary> | ||
/// <param name="listener">Listener instance.</param> | ||
/// <typeparam name="T">An event type to unsubscribe for.</typeparam> | ||
void Unsubscribe<T>(Action<T> listener) where T : IEvent; | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
## Event Bus | ||
|
||
This is a well-known pattern that can be very handy in-game and app development. The main reason we should use `EventBus` is loose coupling. Sometimes, you want to process specific events that are interested in multiple parts of your application, like the presentation layer, business layer, and data layer, so EventBus provides an easy solution. | ||
|
||
The package offers a very simple, light, and at the same time high-performant implementation of this pattern. | ||
|
||
### Best Practices | ||
Like any other pattern, it's very easy to misuse it or use it for not appropriate cases. Of course, it's up to you how to use it in own application, but here are few best practices when working with event buses. | ||
|
||
* Consider using event bus when it’s difficult to couple the communicating components directly | ||
* Avoid having components which are both publishers and subscribers. See `IReadOnlyEventBus` | ||
* Avoid “events chains” (i.e. flows that involve multiple sequential events) | ||
* Write tests to compensate for insufficient coupling and enforce inter-components integration | ||
|
||
### Use Examples | ||
|
||
#### Subscribe and Post | ||
|
||
First of all you need to make a new `EventBus` instance: | ||
``` | ||
var eventBus = new EventBus(); | ||
``` | ||
|
||
Before you can post any events make sure you declare few event classes to work with. | ||
``` | ||
public class SampleEvent : IEvent | ||
{ | ||
public string Data { get; set; } | ||
} | ||
public class AnotherSampleEvent : IEvent | ||
{ | ||
public string Data { get; set; } | ||
public int IntData { get; set; } | ||
} | ||
``` | ||
|
||
Now you can subscribe to event: | ||
``` | ||
eventBus.Subscribe<SampleEvent>((e) => | ||
{ | ||
Debug.Log(e.Data); | ||
}); | ||
``` | ||
|
||
And post an event: | ||
``` | ||
var e = new SampleEvent { Data = "Hello World" }; | ||
eventBus.Post(e); | ||
``` | ||
|
||
You may also use `Unsubscribe` method when you no longer need the subscription. | ||
|
||
#### Static Bus | ||
This is the simplest an fastest implementation for the event bus pattern. | ||
Since this is static bus *DO NOT USE* it when you making a package, since it may conflict with user project. | ||
|
||
It only make sense to use it inside the project you maintain and own. | ||
Here is how the same subscribe & post flow will look like when using `StaticBus`: | ||
``` | ||
var e = new SampleEvent { Data = "Hello World" }; | ||
StaticBus<SampleEvent>.Subscribe<SampleEvent>((e) => | ||
{ | ||
Debug.Log(e.Data); | ||
}); | ||
StaticBus<SampleEvent>.Post(e); | ||
``` | ||
|
||
#### Using with pool | ||
Another interesting example wold be to look at how you can combine events with pool: | ||
|
||
``` | ||
public class SamplePooledEvent : IEvent | ||
{ | ||
static readonly DefaultPool<SamplePooledEvent> s_EventsPool = new DefaultPool<SamplePooledEvent>(); | ||
public string Data { get; private set; } | ||
public static SamplePooledEvent GetPooled(string data) | ||
{ | ||
var e = s_EventsPool.Get(); | ||
e.Data = data; | ||
return e; | ||
} | ||
public static void Release(SamplePooledEvent e) | ||
{ | ||
s_EventsPool.Release(e); | ||
} | ||
} | ||
``` | ||
|
||
Now here is how posting event would look like: | ||
``` | ||
var e = SamplePooledEvent.GetPooled("Hello World"); | ||
StaticBus<SamplePooledEvent>.Post(e); | ||
SamplePooledEvent.Release(e); | ||
``` | ||
|
||
We can go further and add `IDisposable` wrapper around it: | ||
``` | ||
public class SamplePooledEvent : IEvent | ||
{ | ||
static readonly DefaultPool<SamplePooledEvent> s_EventsPool = new DefaultPool<SamplePooledEvent>(); | ||
public string Data { get; private set; } | ||
public static DefaultPool<SamplePooledEvent>.PooledObject GetPoolable(string data, out SamplePooledEvent e) | ||
{ | ||
e = s_EventsPool.Get(); | ||
e.Data = data; | ||
var poolable = new DefaultPool<SamplePooledEvent>.PooledObject(e, s_EventsPool); | ||
return poolable; | ||
} | ||
} | ||
``` | ||
Now it's even easier to post an event: | ||
``` | ||
using (SamplePooledEvent.GetPoolable("Hello World", out var evt)) | ||
{ | ||
StaticBus<SamplePooledEvent>.Post(e); | ||
} | ||
``` |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
using System; | ||
|
||
namespace StansAssets.Foundation.Patterns | ||
{ | ||
/// <summary> | ||
/// This is the simplest an fastest implementation for the event bus pattern. | ||
/// Since this is static bus DO NOT USE it when you making a package, | ||
/// Since it may conflict with user project. | ||
/// | ||
/// It only make sense to use it inside the project you maintain and own. | ||
/// </summary> | ||
/// <typeparam name="T">Event Type.</typeparam> | ||
public static class StaticBus<T> where T : IEvent | ||
{ | ||
static Action<T> s_Action = delegate { }; | ||
|
||
/// <summary> | ||
/// Subscribes listener to a certain event type. | ||
/// </summary> | ||
/// <param name="listener">Listener instance.</param> | ||
public static void Subscribe(Action<T> listener) | ||
{ | ||
s_Action += listener; | ||
} | ||
|
||
/// <summary> | ||
/// Unsubscribes listener to a certain event type. | ||
/// </summary> | ||
/// <param name="listener">Listener instance.</param> | ||
public static void Unsubscribe(Action<T> listener) | ||
{ | ||
s_Action -= listener; | ||
} | ||
|
||
/// <summary> | ||
/// Posts and event. | ||
/// </summary> | ||
/// <param name="event">An event instance to post.</param> | ||
public static void Post(T @event) | ||
{ | ||
s_Action.Invoke(@event); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.