jojoleprowebsite

[Done] The Sauce of https://jojolepro.com
git clone https://git.jojolepro.com/jojoleprowebsite.git
Log | Files | Refs | README | LICENSE

commit 51be1d08b49c6d5a753cb50f53fa797298d3342f
parent 6bfa6e223de9f6550d0c14543a95b63ae1fe6069
Author: Joël Lupien (Jojolepro) <jojolepro@jojolepro.com>
Date:   Thu, 20 Aug 2020 11:50:21 -0400

Add event chaining

Diffstat:
Asrc/blog/2020-08-20_event_chaining/graph1.png | 0
Asrc/blog/2020-08-20_event_chaining/graph2.png | 0
Asrc/blog/2020-08-20_event_chaining/graph3.png | 0
Asrc/blog/2020-08-20_event_chaining/index | 315+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/blog/index.html | 1+
5 files changed, 316 insertions(+), 0 deletions(-)

diff --git a/src/blog/2020-08-20_event_chaining/graph1.png b/src/blog/2020-08-20_event_chaining/graph1.png diff --git a/src/blog/2020-08-20_event_chaining/graph2.png b/src/blog/2020-08-20_event_chaining/graph2.png diff --git a/src/blog/2020-08-20_event_chaining/graph3.png b/src/blog/2020-08-20_event_chaining/graph3.png diff --git a/src/blog/2020-08-20_event_chaining/index b/src/blog/2020-08-20_event_chaining/index @@ -0,0 +1,315 @@ +Event Chaining as a Decoupling Method in Entity-Component-System +Joël Lupien (Jojolepro, jojolepro@jojolepro.com) +https://patreon.com/jojolepro +================================================================================ + +Context +================================================================================ + +In game engines, we often have a lot of dependencies between modules. +For example, the user interface often depends on the renderer, window and +input systems. +This means if we are not careful, we will end up with a user interface that +works only with a single type of input. +When you add a new input method (a controller for example), then you have +to also edit the user interface fonctionality +to be able to use this new input device. What is surprising however, is that +not only do we need to change +how input works, but also we are modifying user interface code to add a +device that... probably does the same thing we are already doing. + +This situation isn't unique to user interfaces. In fact, this is very often +how dependencies are modeled. +It is intuitively what we think of when we think of logical of dependencies. A +depends on B, thus A refers to B. +As we have just seen however, it can cause problems of maintainability, +because we have strong coupling. + +Reducing Coupling +================================================================================ + +There are multiple ways to reduce coupling, depending on which paradigm +you use. +Here, I will be specifically exposing a way to reduce coupling in the +context of +an Entity Component System (ECS). + +Foundamentals +================================================================================ + +First of all, let's see the building pieces that we have in a ECS. +I will be using terminology from the Amethyst Game Engine, since this +is what I am most familiar with. + +Entities +-------------------------------------------------------------------------------- + +Entities are "units" of the game. Each player, item, user interface element, +audio source, etc.. are entities. +We will not discuss much about entities in this paper. + +Components +-------------------------------------------------------------------------------- + +Components are properties of entities. Again, we will not be discussing those. + +Resources +-------------------------------------------------------------------------------- + +Resources are simple data that is stored inside of the ECS' context. + +Systems +-------------------------------------------------------------------------------- + +Systems are what drives the changes in the game. They are functions that +take data from the ECS context +(ressources and entity/components), perform some computation and finally +modify the ECS context. +Systems can depend on each other to sequentially perform computation. If +they don't explicitely +depend on each other, they will be ordered automatically (and in parallel +when possible) in a more or less optimal ordering +depending on how they use ressources or components (whether they read or +write to them). +If a system writes to a ressource or component, no other system can access +this same ressource or component at the same time (in parallel). + +Event Channels +-------------------------------------------------------------------------------- + +Single writer/Multiple readers FIFO queues. Most data types can be inserted +through those (they only need to be thread safe). +You need a registered instance of ReaderId to read from them. ReaderId +instances can be created by getting a mutable +reference to an Event Channel and calling register_reader(). + +Additional Terminology +================================================================================ + +Let's introduce distinct names depending on how those previous concepts are +used to improve the clarity of this paper. + +Driver +-------------------------------------------------------------------------------- + +A System used to "drive" the execution of another system, often through the +use of Signals. + +Signal +-------------------------------------------------------------------------------- + +A Signal is simply an event written in an Event Channel that has for only +purpose to drive the execution of one or +multiple Systems. If we look at it the opposite way, some System will look +for Signals as a way to know if and what +kind of work they need to do. + +Event Chaining +================================================================================ + +What Are They? +-------------------------------------------------------------------------------- + +Very similar to the concept of message passing, event chains are a way to +communicate between Systems. +Simply put, you have one event that is created, +which create a second event, +which create a third event, +which is then consumed (received) by a System. + +Compared to Message Passing. +-------------------------------------------------------------------------------- + +In traditional message passing, you often have some System A that sends a +message to System B. +We say that System A is "aware" of System B's existence. +A way to decouple this is through the use of some method of broadcasting +the message to anyone who is registered +to receive it. This is how mailling lists work. + +Here however, we don't have a list of who should receive each message. Instead, +each System that wants to receive +the event has a ReaderId which it can use to read from the Event Channel. The +reason for this is so that we +don't have a single place where all of the message buffers are and where +every system tries to get a mutable +reference (which would heavily reduce performance). + +How They Solve The Coupling Problem +================================================================================ + +If we continue with the example from the beginning, we can see a way to make +the user interface unaware of +the input system. +- The Input System creates input Events (MouseClicked, KeyboardPress, +ControllerPadLeft). +- A Driver (System) converts input Events into user interface Signals +according to +a configuration structure stored as a Ressource and other contextual Ressources +(are we inserting text? selecting user interface elements? dragging something?) +- The User Interface System makes changes using a combination of +- Signals (Press(x,y), InsertCharacter(char), SelectLeft) +- ECS Entity and Components (Ui elements, like labels and text fields) +- Ressources (ScreenSize, SelectedUIElements) +- The User Interface System creates Events based on the changes. For example, +UiEvent::Clicked(label1_entity). + +./graph1.png + +Drawbacks +================================================================================ + +Of course, we have to talk about drawbacks. Let me preface this with the +following: +There is a performance cost. It is small, but it is there. +If we take the last example, we would now have one more System running +(the UI Driver) +and we would have to create the Signals for the User Interface System. In +addition, in almost all +cases of a Driver being present, there is a conversion step when converting +events to signals, usually a +HashMap lookup or similar. + +However, we surprisingly are also getting some performance gains from this. In +the last example, instead of the +User Interface System looking each frame for the status (statuses?!) of the +input device(s), we now have +this System running only when signals are present. + +Other usages +================================================================================ + +We have seen how this concept of Event Chaining allows to decouple user +interfaces from input handling. +Now, what else can we apply it to? +Well... a lot of things actually. Here are some examples: + +Asset Hot Reloading +-------------------------------------------------------------------------------- + +- A FileWatcher System creates a signal when a file is updated on disk. +Watched files are configured through a ressource, which could itself +be loaded from disk and hot reloaded. +- A FileLoader loads the file from disk (triggered by the signal) and converts +the data into a shared format. +(RGBA for images, vector data for svg, vertices for meshes, etc) +A Signal is then sent created (DataLoaded, DataUpdated, DataDestroyed). +- An AssetLoader picks up this signal and does some module specific action. For +example, loading or updating +a mesh into the GPU memory. + +In addition to simplifying asset reloading by dividing it in steps, it now +also creates a way to notify +system of data changes (a modified mesh) so that they can update their +internal states (GPU memory). +This gives more power to users, as they can now change data midway through +without having to modify +the code of what depends on this data (the renderer's mesh to gpu loader). + +./graph2.png + +Audio Processing +-------------------------------------------------------------------------------- + +- As described in the previous point, we have a system that can load files +(including audio) and +store them in a shared format. +- We have a AudioPlayer Driver which will create Signals as time passes with +audio data to play. +For performance reasons, we can use references or indexes to the audio data +instead of copying +the data into the Signal. (AudioPlay{audio_data, from_point, speed}) +- We have a AudioSink System that forwards the corresponding audio data into +a raw audio sink. + +Additional Benefit: Configurability +================================================================================ + +In this paper, I mentionned that Drivers can use configuration and context +data from ECS Ressources. +This allows for a super easy way to configure complex behaviors. Let's +take input +as an example. The Input System creates raw events. The Driver converts +those into signals (or events) +for other systems to use. Let's see how we can use multiple Drivers to create +complex behaviors. + +- UIDriver: Converts input Events into UI Signals. +- UserDriver: Converts input Events to User-Defined Events. (KP_Escape -> +UserEvent::PauseGame) +- PlayerControllerDriver: Converts input Events to Player Movement +Signals. (KP_Left -> Move::Left) +- InputToAssetDriver: Converts input Events to FileLoader Signals. (KP_R -> +FileUpdated(fun_file.jpg)) +- WtfDriver: Converts input Events to ResolveWorldHunger Signals. (KP_P -> +Please::SolveIt) + +As you can see, lots of Drivers are possible. +Now we have two choices. Either the Drivers decide based on the context if +they should +create the signals OR they always create the signals according to their +configuration. +In the second case, the configurations can be changed by other Systems or +by external code, +depending on your preference. +Personally, I find the second option more versatile, as it is rare that the +Drivers can +be made context aware in a way that fits all use cases. +In this case, it is possible to have code that, for example, disables the +UiDriver when the +player is controlling their character, saving both performance and code +complexity. + +./graph3.png + +Conclusion +================================================================================ + +In conclusion, Event Chains are a powerful and versatile tool to decouple +logical dependencies +between Systems. They add a bit of complexity and performance overhead, +but they are well +worth their cost in the context of a general game engine. + +General Recommendations +================================================================================ + +For those who already worked with something similar to EventChannel and +ReaderId, +you might have noticed that there are issues when a System that consume Signals +is paused. When this happens, the System stops consuming the Signals and +they stay +in memory forever, causing memory leaks. The solution for this is to store +the System's ReaderId inside of a Ressource and to nullify/destroy it when the +System gets paused. + +Testing! You can test Systems in isolation by manually sending Signals. +Take advantage of this and test <3 + +Use generics! Drivers almost always do the same job: +Convert one event type into another event (or signal) type +by using a table or HashMap. +This means that using generics to specify the +input and output types as well as the HashMap +key and value types, creating Drivers can be done in a single line. + +Document as much as possible the Systems. Since we have System and Driver +which are both Systems and Event and Signal which are both Events, it can +be quite confusing without the proper documentation to understand the role +of each piece of the puzzle. +Here are some ideas of what to document: +- The configuration ressources of each System. +- The events that each System creates. +- The signals that each System consumes. + +Supporting Me +================================================================================ + +I released this for free/without limitations because I want to contribute +to the greater good. + +If you like the work I do, please consider donating on Patreon: +https://patreon.com/jojolepro +Or by Bitcoin: +15NDruDUDr3KaMjt87BvUJaayEzy5c765Z + diff --git a/src/blog/index.html b/src/blog/index.html @@ -1,3 +1,4 @@ +<a href="2020-08-20_event_chaining">Event Chaining as a Decoupling Method in ECS Game Engines</a> <a href="2020-07-07_minimalist_alternatives">Minimalist Alternatives to Popular Applications</a> <a href="2020-05-04_why_i_moved_from_wordpress">Why I Moved Away From Wordpress</a> <a href="2020-04-09_simple_school_documents">Simple School Documents</a>