Building Game Engine using Rust: Event System Part 1
In this part we will describe, what are events and how we have implemented event systems and what was my intutation behind it.
Subhro Acharjee
12/15/20246 min read



Event System Part 1
What is an event?
In programming, an event is an action or occurrence that a software program recognizes and can respond to. These are often triggered by user interactions, such as clicking a button, typing on a keyboard, closing a window, or even a system-generated response such as a timer running out or a program finishing its execution.
What are the parts of a Event System ?
1. Event Source: This is the object that an event originates from. For example, a button in a user interface can be an event source.
2. Event Listener: This is a procedure or function in a computer program that waits for an event to occur. For example, you might have a function in your program that's designed to run whenever a button is clicked.
3. Event Handler: When an event occurs, the event listener detects it and passes it to the event handler. The event handler is a piece of code that is executed in response to the event. For example, if the event is a button click, the event handler might be a function that changes the color of the button.
My Intuition
So now that the basics of what is an event and parts of event system are out, we can focus on "what was i thinking" (something that I ask myself regularly XD). So what i know from my research there are two main ways to handle events, and event based systems, they are dispatchers and event loop or event queue.
Dispatcher is a system in where we pass the event directly to the dispatcher and it calls out the handler for that event immediately. Dispatcher is used when there are events that might be on priority and needs to handle it as soon as they appear, like lets say exit command. In operating systems there is a concept of signals where abort signal has the highest priority and hence when we key press ctrl + c in terminal that immediately aborts the running program, that is handled, through dispatcher setup in os.
Event Loop this is a more indirect way of handling the events, the main philosophy behind is to queue all the events in one cycle of the application run operation, and in the next cycle we handle them all of them, immediately. This might look like a better approch than dispatcher, especially if you work with nodejs given nodejs is build on this principle only, but this is slower than the dispatcher hence, we have to wait one run cycle to handle all the events. It is quite scalable but it should be only used with unimportant events, like cronjobs or system checks, not with quit command or similar operation.
Now, if you know me, me likes challenge, so i have decided that i will be building both of them, just to provide the end user a system where they can work with both of the systems, as required.
Event trait
So the event interface will have two methods,
get_name provides the identifier of the event
get_data provides data if there is any in form of Dynamic Store.
Dynamic Store
This class we will be using to store any type of data into a box pointer. In Rust, a Box<T> is a smart pointer type provided by the standard library. It is used for heap allocation. A box points to data stored on the heap and can be used to store a value of a given type. It also has two methods,
new is the constructor that takes a dynamic value of any type, and then stores in it.
get_ref is a method that returns a typecasted value from the box pointer.






EventDispatcher
The idea behind the dispatcher is to have multiple handlers for a single event. So thats why we have event_name which will hold the unique identifier of the events, it needs to hold the handlers for , and handlers is a thread safe array of Dispatcher Callbacks. Dispatcher Callbacks is a type of thread safe function pointers that takes any implementation of events. The Dispatcher has 3 methods,
new the constructor
add_handlers which takes a dispatcher callback, locks the handlers array for the running thread and then adds the new callback to the end of the handlers. In case of an error, it throws EventDispatcherErrors.
dispatch which takes dynamic implementation of event interface, and checks if the dispatcher is suited for the event or not by checking two unique identifier, if suited then we lock all the handlers array and call all the handlers using the event, if everything works out we return true, else we return false and in case or an error we throw EventDispatchErrors.
I have added alot of test cases for the following code, so if you are interested check it out. So now what will happen is we will have an instance dispatcher inside our application and will have some function like emit which will immediately pass the event to all the dispatchers and call the required event.


Event Queue
The event queue, has two private variables sender and receiver which are mpsc channels. MPSC (Multiple Producers, Single Consumer) channels are a type of communication tool used in concurrent programming. They allow multiple “producer” threads to send data into the channel, while a single “consumer” thread receives the data from the channel. This is useful in situations where we need to coordinate data flow between different parts of our program. TLDR they are the head node and tail node of our thread safe queue that rust provides. This has four methods,
new is our constructor
initialize creates an atomic reference to the globally defined event queue in our application.
emit takes a boxed event, and then pushes it into the sender variable( head of the queue). In case of any error it throws EventQueueErrors.
get_events loops through the all the pending events in the queue and adds those events to an array and returns the array. In case of error it also throws a EventQueueErrors.
Now as you can see, there is now system to handle the events that are queued in this event queue, as the event queue is works as a datastore for the events and nothing more than that. Below i will explain event loop which actually handles the event from the event queue.


Event Loop
This code snippet is from application class of the aloy engine, and as you can see what we do is initialize the global event loop and then inside infinite loop we get events, if there are events then we iterate through the array of events and we call dispatch on each event.
The dispatch method loops through all the dispatcher that are registered in the application class and calls dispatch on all of them. This, as anyone can see, is will make the system quite slow, but for now we will be using this, later we will move to a event dispatcher map, so that the fetching operation becomes fast.
Sandbox


So in the sand box we have defined few function to show the working of the engine.
add_handlers_for_example_event: This method registers a handler for the ExampleEvent in the application engine, printing "called from handler of example event...." when the event occurs, while also handling potential registration errors.
add_handlers_for_example_event_with_data: This method registers a handler for the ExampleEventWithData event in the application engine, which retrieves associated data (like coordinates), calculates their sum if valid, logs the result, and handles errors during both event registration and data access.
add_event_without_data: This method spawns a new thread that initializes an event queue, emits the ExampleEvent three times (with a 1-second interval between each emission), and logs any errors during the emission process.
add_events_with_data: This method spawns a new thread that emits ten ExampleEventWithData events (with incrementing data values), sleeps for 1 second after every even-numbered event, and finally emits an Exit event, logging any errors encountered during the emission process.
Okay, thats it, thank you!!