An alternative to Events is Triggered Systems. In control systems, Events imply asynchronous actions - they invoke when the event occurs, preempting execution. ECS uses a discrete step approach, so events are really fighting the system - you want to work within the steps. A Triggered System is a system that is gated by a control port. When the signal on the port is high, the system runs, when it is low the system doesn’t run. An event can be a pulse with a width of one step.
Here’s a crude way you can do it. I wouldn’t recommend this exactly, but it might get you started.
In your triggered system add a RequireForUpdate
in the OnUpdate method.
RequireForUpdate(EntityManager.CreateEntityQuery(typeof(EventTypeTrigger)));
This serves as the trigger port or “event subscriber”.
Your EventTypeTrigger can be something like
struct EventTypeTrigger : IComponentData {
public int eventSource;
public int eventType;
public int eventDataReference;
}
On the main thread, you can use the LateUpdate() to clear the events.
I used something like this for UI Buttons. I modified the UnityUI Button code to hold an identifier (eventSource, set in the inspector), and to produce a ButtonTrigger entity. The system ran when the component was present. I checked the eventType to see if I want to respond, and got cached event data using eventDataReference if needed. If the system was a dedicated handler I destroyed the entity there. Otherwise, in LateUpdate() I would just delete all Trigger Entities (brute-force, not really recommended), to ensure the event lifetime / pulse width was only one step.
What I am doing now is streaming entity definitions from the UI into a queue instead of creating then right away. On each update cycle I grab the queue data, flush the queue, and bulk create (or destroy) the Trigger Entities. This way I can debounce, etc. For a mouse click I create a 1-step pulse entity for the leading edge of mouse-down, and a long-lived “high” pulse to indicate the button is down, and the when the button is released I create a 1-step pulse for the trailing edge, and destroy the long-lived entity. My triggered systems run once on down/up, and continuously while the button is held down.
I also use this approach for system timings. I have a world-wide Timing Bus that generates pulse trains of 1-step entities on things like Second, Hour, Day, Year, etc. RequireForUpdate “listens” for the pulses and the system is triggered to run once every period.