jojoleprowebsite

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

index.txt (12897B)


      1 Event Chaining as a Decoupling Method in Entity-Component-System
      2 Joël Lupien (Jojolepro, jojolepro@jojolepro.com)
      3 https://patreon.com/jojolepro
      4 ================================================================================
      5 
      6 Context
      7 ================================================================================
      8 
      9 In game engines, we often have a lot of dependencies between modules.
     10 For example, the user interface often depends on the renderer, window and
     11 input systems.This means if we are not careful, we will end up with a
     12 user interface that works only with a single type of input.When you add a
     13 new input method (a controller for example), then you have to also edit the
     14 user interface functionality to be able to use this new input device. What
     15 is surprising however, is that not only do we need to change how input works,
     16 but also we are modifying user interface code to add a device that... probably
     17 does the same thing we are already doing.
     18 
     19 This situation isn't unique to user interfaces. In fact, this is very often
     20 how dependencies are modelled.It is intuitively what we think of when we
     21 think of logical dependencies. A depends on B, thus A refers to B.As we
     22 have just seen however, it can cause problems of maintainability, because
     23 we have strong coupling.
     24 
     25 Reducing Coupling
     26 ================================================================================
     27 
     28 There are multiple ways to reduce coupling, depending on which paradigm
     29 you use.Here, I will be specifically exposing a way to reduce coupling in
     30 the context of an Entity Component System (ECS).
     31 
     32 Fundamentals
     33 ================================================================================
     34 
     35 First of all, let's see the building pieces that we have in a ECS.I will
     36 be using terminology from the Amethyst Game Engine, since this is what I am
     37 most familiar with.
     38 
     39 Entities
     40 --------------------------------------------------------------------------------
     41 
     42 Entities are "units" of the game. Each player, item, user interface element,
     43 audio source, etc.. are entities.We will not discuss much about entities
     44 in this paper.
     45 
     46 Components
     47 --------------------------------------------------------------------------------
     48 
     49 Components are properties of entities. Again, we will not be discussing those.
     50 
     51 Resources
     52 --------------------------------------------------------------------------------
     53 
     54 Resources are simple data that is stored inside of the ECS' context.
     55 
     56 Systems
     57 --------------------------------------------------------------------------------
     58 
     59 Systems are what drives the changes in the game. They are functions that
     60 take data from the ECS context (resources and entity/components), perform
     61 some computation and finally modify the ECS context.Systems can depend on
     62 each other to sequentially perform computation. If they don't explicitly
     63 depend on each other, they will be ordered automatically (and in parallel
     64 when possible) in a more or less optimal ordering depending on how they use
     65 resources or components (whether they read or write to them).If a system
     66 writes to a resource or component, no other system can access this same
     67 resource or component at the same time (in parallel).
     68 
     69 Event Channels
     70 --------------------------------------------------------------------------------
     71 
     72 Single writer/Multiple readers FIFO queues. Most data types can be inserted
     73 through those (they only need to be thread safe).You need a registered
     74 instance of ReaderId to read from them. ReaderId instances can be created by
     75 getting a mutable reference to an Event Channel and calling register_reader().
     76 
     77 Additional Terminology
     78 ================================================================================
     79 
     80 Let's introduce distinct names depending on how those previous concepts are
     81 used to improve the clarity of this paper.
     82 
     83 Driver
     84 --------------------------------------------------------------------------------
     85 
     86 A System used to "drive" the execution of another system, often through the
     87 use of Signals.
     88 
     89 Signal
     90 --------------------------------------------------------------------------------
     91 
     92 A Signal is simply an event written in an Event Channel that has for only
     93 purpose to drive the execution of one or multiple Systems. If we look at it
     94 the opposite way, some System will look for Signals as a way to know if and
     95 what kind of work they need to do.
     96 
     97 Event Chaining
     98 ================================================================================
     99 
    100 What Are They?
    101 --------------------------------------------------------------------------------
    102 
    103 Very similar to the concept of message passing, event chains are a way to
    104 communicate between Systems.Simply put, you have one event that is created,
    105 which create a second event, which create a third event, which is then consumed
    106 (received) by a System.
    107 
    108 Compared to Message Passing.
    109 --------------------------------------------------------------------------------
    110 
    111 In traditional message passing, you often have some System A that sends a
    112 message to System B.We say that System A is "aware" of System B's existence.
    113 A way to decouple this is through the use of some method of broadcasting
    114 the message to anyone who is registered to receive it. This is how mailing
    115 lists work.
    116 
    117 Here however, we don't have a list of who should receive each message. Instead,
    118 each System that wants to receive the event has a ReaderId which it can use
    119 to read from the Event Channel. The reason for this is so that we don't have
    120 a single place where all of the message buffers are and where every system
    121 tries to get a mutable reference (which would heavily reduce performance).
    122 
    123 How They Solve The Coupling Problem
    124 ================================================================================
    125 
    126 If we continue with the example from the beginning, we can see a way to make
    127 the user interface unaware of the input system.
    128 - The Input System creates input Events (MouseClicked, KeyboardPress,
    129 ControllerPadLeft).
    130 - A Driver (System) converts input Events into user interface Signals according
    131 to a configuration structure stored as a Resource and other contextual Resources
    132 (are we inserting text? selecting user interface elements? dragging something?)
    133 - The User Interface System makes changes using a combination of
    134 - Signals (Press(x,y), InsertCharacter(char), SelectLeft)
    135 - ECS Entity and Components (Ui elements, like labels and text fields)
    136 - Resources (ScreenSize, SelectedUIElements)
    137 - The User Interface System creates Events based on the changes. For example,
    138 UiEvent::Clicked(label1_entity).
    139 
    140 ./graph1.png
    141 
    142 Drawbacks
    143 ================================================================================
    144 
    145 Of course, we have to talk about drawbacks. Let me preface this with
    146 the following: There is a performance cost. It is small, but it is there.
    147 If we take the last example, we would now have one more System running (the
    148 UI Driver) and we would have to create the Signals for the User Interface
    149 System. In addition, in almost all cases of a Driver being present, there
    150 is a conversion step when converting events to signals, usually a HashMap
    151 lookup or similar.
    152 
    153 However, we surprisingly are also getting some performance gains from this. In
    154 the last example, instead of the User Interface System looking each frame
    155 for the status of the input device(s), we now have this System running only
    156 when signals are present.
    157 
    158 Other usages
    159 ================================================================================
    160 
    161 We have seen how this concept of Event Chaining allows to decouple user
    162 interfaces from input handling.Now, what else can we apply it to?Well... a
    163 lot of things actually. Here are some examples:
    164 
    165 Asset Hot Reloading
    166 --------------------------------------------------------------------------------
    167 
    168 - A FileWatcher System creates a signal when a file is updated on disk.
    169 Watched files are configured through a resource, which could itself
    170 be loaded from disk and hot reloaded.
    171 - A FileLoader loads the file from disk (triggered by the signal) and converts
    172 the data into a shared format. (RGBA for images, vector data for svg, vertices
    173 for meshes, etc) A Signal is then sent (DataLoaded, DataUpdated, DataDestroyed).
    174 - An AssetLoader picks up this signal and does some module specific action. For
    175 example, loading or updating
    176 a mesh into the GPU memory.
    177 
    178 In addition to simplifying asset reloading by dividing it in steps, it now
    179 also creates a way to notify system of data changes (a modified mesh) so that
    180 they can update their internal states (GPU memory).This gives more power
    181 to users, as they can now change data midway through without having to modify
    182 the code of what depends on this data (the renderer's mesh to gpu loader).
    183 
    184 ./graph2.png
    185 
    186 Audio Processing
    187 --------------------------------------------------------------------------------
    188 
    189 - As described in the previous point, we have a system that can load files
    190 (including audio) and
    191 store them in a shared format.
    192 - We have a AudioPlayer Driver which will create Signals as time passes with
    193 audio data to play. For performance reasons, we can use references or indexes
    194 to the audio data instead of copying the data into the Signal. 
    195 (AudioPlay{audio_data, from_point, speed})
    196 - We have a AudioSink System that forwards the corresponding audio data into
    197 a raw audio sink.
    198 
    199 Additional Benefit: Configurability
    200 ================================================================================
    201 
    202 In this paper, I mentioned that Drivers can use configuration and context data
    203 from ECS Resources.This allows for a super easy way to configure complex
    204 behaviors. Let's take input as an example. The Input System creates raw
    205 events. The Driver converts those into signals (or events) for other systems
    206 to use. Let's see how we can use multiple Drivers to create complex behaviors.
    207 
    208 - UIDriver: Converts input Events into UI Signals.
    209 - UserDriver: Converts input Events to User-Defined Events. (KP_Escape ->
    210 UserEvent::PauseGame)
    211 - PlayerControllerDriver: Converts input Events to Player Movement
    212 Signals. (KP_Left -> Move::Left)
    213 - InputToAssetDriver: Converts input Events to FileLoader Signals. (KP_R ->
    214 FileUpdated(fun_file.jpg))
    215 - WtfDriver: Converts input Events to ResolveWorldHunger Signals. (KP_P ->
    216 Please::SolveIt)
    217 
    218 As you can see, lots of Drivers are possible.Now we have two choices. Either
    219 the Drivers decide based on the context if they should create the signals
    220 OR they always create the signals according to their configuration.In the
    221 second case, the configurations can be changed by other Systems or by external
    222 code, depending on your preference.Personally, I find the second option
    223 more versatile, as it is rare that the Drivers can be made context aware in
    224 a way that fits all use cases.In this case, it is possible to have code
    225 that, for example, disables the UiDriver when the player is controlling
    226 their character, saving both performance and code complexity.
    227 
    228 ./graph3.png
    229 
    230 Conclusion
    231 ================================================================================
    232 
    233 In conclusion, Event Chains are a powerful and versatile tool to decouple
    234 logical dependencies between Systems. They add a bit of complexity and
    235 performance overhead, but they are well worth their cost in the context of
    236 a general game engine.
    237 
    238 General Recommendations
    239 ================================================================================
    240 
    241 For those who already worked with something similar to EventChannel and
    242 ReaderId, you might have noticed that there are issues when a System that
    243 consume Signals is paused. When this happens, the System stops consuming
    244 the Signals and they stay in memory forever, causing memory leaks. The
    245 solution for this is to store the System's ReaderId inside of a Resource
    246 and to nullify/destroy it when the System gets paused.
    247 
    248 Testing! You can test Systems in isolation by manually sending Signals.
    249 Take advantage of this and test <3
    250 
    251 Use generics! Drivers almost always do the same job: Convert one event type
    252 into another event (or signal) type by using a table or HashMap.This means
    253 that using generics to specify the input and output types as well as the
    254 HashMap key and value types, creating Drivers can be done in a single line.
    255 
    256 Document as much as possible the Systems. Since we have System and Driver
    257 which are both Systems and Event and Signal which are both Events, it can
    258 be quite confusing without the proper documentation to understand the role
    259 of each piece of the puzzle.
    260 Here are some ideas of what to document:
    261 - The configuration resources of each System.
    262 - The events that each System creates.
    263 - The signals that each System consumes.
    264 
    265 Supporting Me
    266 ================================================================================
    267 
    268 I released this for free/without limitations because I want to contribute
    269 to the greater good.
    270 
    271 If you like the work I do, please consider donating on Patreon:
    272 https://patreon.com/jojolepro
    273 Or by Bitcoin:
    274 15NDruDUDr3KaMjt87BvUJaayEzy5c765Z
    275