Architecture Spring 2017 03. Event systems for game engines

Juha Vihavainen University of Helsinki [ [ McShaffry,McShaffry , Ch. 6: Game actors and component architecture ,, Ch. 11: Game event management ]] [ Gregory, Ch. 5.4: Strings, unique ids ,, localization , etc., 274, Ch.15.2: Runtime object model architectures , 873 Ch. 15.7: Events and message passing, 933 ]] [Meyers (2015): Effective Modern ++++]]

Lecture outline

 On events and event handling  using Observer (MVC) and Command design patterns

 Updates are not enough for game

 Typed Message design pattern (Vlissides, 1997)  breaking dependencies between senders & receivers

 Queueing of messages for later delivery

 Using unique object ids for game entities and types

24.2.2017 Juha Vihavainen / University of Helsinki 22 The Observer design pattern

 Problem  certain objects need to be informed about the changes occurring in in other objects  a subject has to be observed by one or more observers  decouple as much as possible and reduce the dependencies

 Solution  define a oneone--toto--manymany dependency between objects so that when one object changes its state,state , all its dependents are automatically notified and updated

 A cornerstone of the ModelModel--ViewView--ControllerController architectural design, where the Model implements the mechanics of the program, and the Views are implemented as Observers that are kept uncoupled from the Model components Modified from Joey Paquet, 20072007--20142014

24.2.2017 University of Helsinki 33

Erich Gamma et al., Design Patterns ( 1994) Observer pattern: class design

""View "" classes

""ModelModel""classesclasses

Updates multiple ( seems Notifies all its observers complicated..) observers on changes

24.2.2017 University of Helsinki 44 The participants

 Subject --abstractabstract class defining the operations for attaching and de--de attaching observers to the clientclient;; often referred to as " Observable ""

 ConcreteSubject -- concrete Subject class. It maintains the state of the observed object and when a change in its state occurs it notifies the attached Observers. If used as part of MVC, the ConcreteSubjectConcreteSubject classes are the Model classes that have Views attached to themthem

 Observer --interfaceinterface or abstract class defining the operations to be used to notify the registered Observer objects

 ConcreteObserver -- concrete observer subclasses that are attached to a particular subject class

 There may be multiple different concrete observers attached to a single subject that will provide a different view of that subject

24.2.2017 University of Helsinki 55

On behaviour of Observer pattern

 The client class instantiates the ConcreteObservable object

 Then it instantiates and attaches the concrete observers to it usingusing the methods defined in the Observable interface

 Each time the (observable) state of the subject is changing, it notifies all the attached observers using the methods defined in the Observer interface

 When a new Observer is added to the application, all we need to do is to instantiate it in the client class and to attach it to the Observable object

 The classes already created (framework) will remain mostly unchanged

24.2.2017 University of Helsinki 66 77 Observer pattern: interface classes

// Observable.h #pragma once // Visual Studio include guard class Observer ; // name stub needed only // Observer.h class Observable { // an abstract class #pragma once public : virtual void attach ( Observer *); class Observer { // an abstract class virtual void detach ( Observe r *); public : virtual void notify (); virtual void update () = 0; virtual ~Observable (); virtual void detach () = 0; // added Observable (Observable const&) = delete; virtual ~Observer (); ... Observer (Observer const&) = delete; protected : ... Observable (); // called by subclass protected : private : Observer (); // called by subclass struct Observers ; // name stub only }; Observers * observers_; }; Note . If a destructor is declared, the generation of default copy operations is deprecated (to be removed from standard). Pimpl idiom hides std::list The may give a warning. For now we need delete .. (C++11)

Meyers S. (2015): On C++ technicalities Effective Modern C++, p. 115  The special member functions are those may generate on their own (if needed, i.e., called): default constructor , destructor , copy operations , and move operations  Move operations (T &&) are generated only for classes lacking explicitly declared move operations, copy operations, and a destructor  The copy constructor (T const &) is generated only for classes lacking an explicitly declared copy constructor, and it’s deleted if a move operation is declared  The copy assignment operator == (T const &) ) is generated only for classes lacking an explicitly declared copy assignment operator, and it’s deleted if a move operation is declared  Generation of the copy operations in classes with an explicitly declared destructor is deprecated (i.e., to be removed from C++ standard)  Member function templates never suppress generation of special member functions (even if templates could produce these copy operations)

24.2.2017 University of Helsinki 88 99 Observer PatternPattern:: Concrete Subject

// ClockTimer.cpp #include "ClockTimer.h " // ClockTimer.h ClockTimer :: ClockTimer () #pragma once : hour_( 0), minute_(0 ), second_(0 ) {} #include "Observable.h " void ClockTimer :: start ( int time ) { class ClockTimer : public Observable for (int i = 1; i <= time ; ++i) { tick (); public : } ClockTimer (); void ClockTimer :: tick () { // by one sec int hour () const { return hour_; } ++second_; int minute () const { return minute_; } if (second_>= 60 ) { int second () const { return second_; } ++minute_; second_= 0; void start ( int time); if (minute_>= 60 ) { void tick (); ++hour_; minute_= 0; private : if (hour_>= 24 ) int hour_; hour_= 0; int minute_; } int second_; } }; // the Observable object notifies // all its registered observers notify (); }

1010 Observer PatternPattern:: // DigitalClock.cpp Concrete Observer #include "DigitalClock.h " #include "ClockTimer.h " // DigitalClock.h #include #pragma once #include "Observer.h " DigitalClock :: DigitalClock ( ClockTimer * s) { class ClockTimer; // name stub if (s != nullptr ) s ->attach ( this ); subject _ = s; class DigitalClock : public Observer } { DigitalClock ::~ DigitalClock () { public : if (subject_!= nullptr) void update () override; subject_ ->detach (this); void detach () override; } void display () const; void DigitalClock ::update () { DigitalClock ( ClockTimer * s display (); = nullptr); } ~DigitalClock () override; void DigitalClock ::detach () { private : subject_= nullptr; ClockTimer * subject_; } }; void DigitalClock :: display () const { int hour = subject_ ->hour (); int minute = subject_ ->minute (); int second = subject_ ->second (); Note . Strong dependencies: if one is std::cout << hour << ":" << minute << ":" destroyed, must update other . << second << std::endl ; } Observer patternpattern:: driver program

// ClockDriver.cpp #include "ClockTimer.h " #include "DigitalClock.h " #include // standard IO streams int main () Objects can be local { // create a ClockTimer to be observed ClockTimer timer;

// create a DigitalClock that is connected to the ClockTimer DigitalClock digitalClock (&timer );

// advancing the ClockTimer updates the DigitalClock // as tick() calls Update() after it changed its state int secs ; std::cout << "Enter number of seconds to count: " ; std::cin >> secs ; timer.start ( secs );

int j; std::cin >> j; // pause at end of program }

24.2.2017 University of Helsinki 1111

Implementation of the Observer pattern ( Solution ))

// Observable.cpp #include "Observable.h " Note . Should #include "Observer.h " additionally enclose #include // only here, not in Observable .h file everything in our custom namespace.. struct Observable::Observers : std::list < Observer *> {}; // define implementation class

Observable :: Observable () : observers_(new Observers) { } Observable ::~ Observable () { for (auto& x : *observers_) detach (x); delete observers_; } // Observer.cpp void Observable ::attach ( Observer * o) { #include "Observer.h " observers_ ->push_back (o); Observer :: Observer () {} } Observer ::~ Observer () {} void Observable ::detach ( Observer * o) { observers_ ->remove (o); o->detach (); // will break link to subject } void Observable ::notify () { for (auto& x : *observers_) x ->update (); } Background: updates are not enough

 Games are inherently very much eventevent--drivendriven  an explosion goes off  the player is sighted by an enemy, or enters a trigger area  a health pack is getting picked up

 Games need to  notify interested game objects when an event occurs  arrange those objects to respond to significant events

 Different kinds of game objects will respond in different ways to an event; for example, a Pong ball is governed/affected both by  updates in its physical "state", determined by its velocity  hitting another object (paddle/wall) and changing its velocity ((raterate and direction of change))change  being missed by one of the players --andand hitting the back wall

24.2.2017 Juha Vihavainen / University of Helsinki 1313

Common game events [McShaffry, p. 325325--326]326]

1 1 General game events  a game object has moved  the event system is ready  a collision has occurred  the sound system is ready  a character has changed states  the network system is ready  player character has changed  a human view has been attached  a new game object is created  the game is paused  a game object is destroyed  the game is resumed  player character is dead  the game is about to be saved (presave)  player death animation is over (game over)  the game has been saved (postsave)

2 2 Map/mission events 4 Animation and sound effects  a new level is about to be loaded (preload)  an animation has begun  a new level has finished loading (loaded)  an animation has looped to beginning  a character entered a trigger volume  a timing signal from animation to sound  a character exited a trigger volume  an animation has ended  the player has been teleported  a new sound effect has started  a sound effect has looped back 3 3 Game startup events  a sound effect has completed  the graphics system is ready  a cinematic (video) has started  the physics system is ready  a cinematic has ended 24.2.2017 1414 The normal object interaction doesn't work

 Consider calling a method of an object to handle an event void Explosion ::doDamage() { // this is pseudocode var damagedObjects = theGame.theGame.getObjectsgetObjects(getDamagesSphere ());

for each object in damagedObjects do Gregory, p.934 object.object.onExplosiononExplosion ((**this) }}

 This kind of object interaction style does not scale up  Here , game objects need to be inherited from a base class that defines onExplosion() -- even if not all objects respond to explosions  Such virtual functions would be needed for all events in the game

 Better to register for only interesting events and handle those ones

24.2.2017 Juha Vihavainen / University of Helsinki 1515

Registering interest in events

 We don't want to unnecessarily send events / call event handlers

 can maintain a list of interested listeners ( observers ) for each distinct type of event, and/or  each "potential" receiver could keep data to show the particular events this receiver is interested in (say, as a bit array)..

 The classic version is The Observer design pattern: define 11--toto--manymany dependency (Gamma et al., 1994), described earlier

 Instead, we could use callbacks (function pointers), or  up special event records to send around

24.2.2017 1616 Use of an event system

 E.g., a subsystem (say, physics) cannot keep track of all other subsystems that need to know about moving objects (e.g., the game renderer)  they can have different APIs and different parameter lists (messy)

 An event system centralizes and uniforms all interactions  supports modularization and separation of concerns  enables central tracing and debugging of all activities

 In a wellwell--designeddesigned game engine  an event system is a part of the higher support layer  each subsystem is responsible for subscribing to and handling game events as they pass through the system  an event system manages all communications between the game logic and game views (e.g., display, network interactions)

24.2.2017 Juha Vihavainen / University of Helsinki 1717

Encapsulating an event in an object

 An event ~ a command ~ a message  event signaling is similar to sending a message or a command

 An event may consist of two parts  type : explosion , picking up a thing , player being spotted , etc.  arguments : specifics about the event (where, how large, who/what)

struct Event { // often a base class EventKind kind; // meaning of this message EventArgs arguments ;;// linked list or array, giving . . . . . // . . arguments of various types };}; Gregory, p.935  Usually, different kinds of events can be derived from an base event class -- depending on the platform and its restrictions (memory, etc.)

24.2.2017 Juha Vihavainen / University of Helsinki 1818 Event handlers (one version) (Gregory, p. 940)

 An event handler is some piece of code that handles a received event: virtual void SomeObject::onEvent (Event& event) { switch (event.kind) { // illustrative only case EventKind::Attack: respondToAttack (event.getInfo ()); break; case EventKind::HealthPack: addHealth (event.getInfo ()); break; . . . }} }} MMayay need to downcast (for "type recovery")

 One single method vsvs . many handlers: OnAttack (), etc. ?  For strings or hashed string IDs, use cascaded ifif //elseelse--ifif statements ..  Other approach: function pointers ("(" delegates ") to virtual or nonnon-- virtual functions, mapped from event types (used, e.g. with old MFC ))

24.2.2017 Juha Vihavainen / University of Helsinki 1919

Notes about an implementation (McShaffry, 2009)

 (McShaffry) uses a separate EventManager class ((--reallyreally necessary?)  Again, smart pointers can automate garbage collection (mostly)

 Note the "loosely typed" events: handlers need to inspect type info  vsvs . Typed Message that uses clean typetype--specificspecific event handlers  but static typing not always possible: e.g., msgs from network..

 The implemention can use custom meta--objectsmeta objects ( EventType ))  identify different event kinds for specific processing  in a simple prototype system could well use a C++ enumeration enum Kind class { ObjMove, ObjCollision, PlayerDeath.. };

 however, this strategy scales up badly in practice (recompilations)  better to use unique hashed ID s or something similar ( GUID ))  possibly could augment/integrate with the C++ builtbuilt--inin RTTI (..?)

24.2.2017 Juha Vihavainen / University of Helsinki 2020 Typed Message design pattern

 In the Observer design pattern, the participants are still tightly coupled  the subject ""knows"knows" its observers (any number of them)  an observer maintains a ref to the (concrete) subject to get its state

 John Vlissides, ""MulticastMulticast --ObserverObserver = Typed Message",Message ", 1997  in C++ Report , Nov/Dec 1997; also later described in the book by J. Vlissides: Pattern Hatching . AddisonAddison--Wesley,Wesley, 1998

 A minimal type--safe type safe solution; can be used to implement other versions..

 Described also in the master's thesis of M. Väänänen: Olioarkkitehtuurit peliohjelmistoissa (Univ. of Helsinki, Dept. of CS, 2008)  the original version uses immediate dispatching (as usual for GUI)  M.V. added a queue per handler; handleBufferedEvents () delivered messages that were earlier sent to a specific handler

24.2.2017 Juha Vihavainen / University of Helsinki 2121

Features of Typed Message

 Actually minimizes coupling : senders don't know about handlers, and handlers don't know about senders  handlers are associated with events TT, not with subjects/senders

 A TT handler is derived from TypedMsgHandler <>  a kind of mixin class (providing selected limited capabilities)  no extra registration is required (enabled by default)  a TT message is received by an operation " handle (T const& msg) ""  can later remove a handler from the handler list (to disable it)  and later insert the handler back into the handler list (to enable it)

 A TT message is sent by a separate: " DeliverTypedMsg (myMsg) "" template void DeliverTypedMsg (T const& msg) { TypedMsgHandler ::Deliver (msg); } } // infers TT from msg and just hides the more complicated call

24.2.2017 Juha Vihavainen / University of Helsinki 2222 TypedMsgHandler < T > (one version)

template // the message type TT class TypedMsgHandler { // works for all and any TT public: // ((well almost )) virtual void handle (T const& msg) = 0; // handle a message void attach (); // insert into handlers void detach (); // remove from handlers TypedMsgHandler () { attach (); } // listening by default virtual ~TypedMsgHandler () { detach (); } // but not any more private: same as if typedef Raw pointers OK --oror need smart ptrs ? using Handlers = std::list ; // alias def. static Handlers GetHandlers (); // get a copy of handlers static void Deliver (T const&); // do the actual delivery };}; No manager used here

24.2.2017 Juha Vihavainen / University of Helsinki 2323

Use of Typed Message  To receive a message need only to derive a subclass: class GameEntity: public TypedMsgHandler <> . . . { public: . . . void handle ( std::string const& msg) override { std::cout << "GameEntity received: " + msg << std::endl; } . . . WWorksorks for built--inbuilt in or };}; user types/classes  A message is simply sent by calling the routine: std::string myMsg = "Hello, handler!"; // create local data DeliverTypedMsg ((myMsgmyMsg););// send it to all  Now, prints out: "" GameEntity received :: Hello, handler !";  Note that message binding is safely typed (at signature level)  C++ provides multiple inheritance to handle different message kinds

24.2.2017 Juha Vihavainen / University of Helsinki 2424 Implementing delivery of typed messages

 A list of message handlers is kept perper a message type

 To send a message, get its handlers and call their handle function template // private utility function void TypedMsgHandler ::Deliver (T const& msg) { for (auto& handler : GetHandlers ()) // traverse copy safely handler -->>handle (msg); // virtual call }}  Efficient: the message data can be kept on the call stack  the queued version needs to allocate ( new ) msgs (of course)

 Note that handlers register and unregister themselves automatically  but still, handler objects need to be destroyed in proper manner  e.g., first mark then as dead, then clear them out later

24.2.2017 Juha Vihavainen / University of Helsinki 2525

Event sending

 In GUI system, events are (usually) immediately processed

 sometimes events cause sending new events to other objects/parts  immediate event sending can cause deep call chains  object AA sends to object BB that sends to object CC (that sends to AA..)..)  in the worst case, immediate sending may cause an eternal loop

 Often, event handlers need to be rere--entrantentrant (= no harmful extra global updates/sideupdates/side--effects)effects) --if they can be called multiple times..

 In games, events can be handled

 immediately, or later during the same frame (animation blending),  during the next frame (game loop cycle), or  even at some arbitrary future time, say, after n seconds (or frames)

24.2.2017 Juha Vihavainen / University of Helsinki 2626 Event queuing  Queuing helps to manage when events are handled  Can post events into the future: each Event would have a delivery time to indicate the dispatch time; consider how to process such events void EventQueue::dispatchTimedEvents (float currTime) { EventPtr pEvent = peekNextEvent (); while (pEvent && pEvent -->>deliveryTime <=<= currTime) { removeNextEvent (); // got one to be triggered pEvent -->>dispatch (); // DeliverTypedMsg (pEvent( pEvent)) pEvent = peekNextEvent (); }} (GEA, p. 945 ) }}  also, could prioritize events to control the order during a frame  drawbacks: complexity, and need for dyn. mem. allocations

19.2.2013 Juha Vihavainen / University of Helsinki 2727

Extra services  TThehe queued events need to bebe dynamically allocated : should ensurethis

 EEventvent kindscould bebe validated bybyrequiringrequiringthat event types must bebe registered before useuse=> could guard against mismatches and typos

 IIf event processing loop takes tootoo much time ((given a system --dependent time limit), limit ), cancan trytryto push spilled--over spilled overevents onto the other queue  but what ififqueues just keep growing and staying full..?  perhaps need some optimization and/or down--grade down grade effect ss

 (McShaffry) proposesa specialevent kind ("("AnyAny")")  such handler could convenientlytrace allallevents and activities

 AAhandlerhandlercould somehow indicate that the message hashasbeen processed (to implement Chain of Command : input is not propagated anyany moremore))  TTimedimed events (kept in a separate queue) resemble scheduling coroutines

24.2.2017 Juha Vihavainen / University of Helsinki 2828 Summary: distinguishing events from tasks

 AA task is an activitythat goes on overmultiple frames  e.ge.g.,., animationsand soundeffects, effects , control of gameflow, and so on  AAnn event is a notificationof a relevantchange in the gameworld that requires processing  an AI character is spawn,spawn , a gametrigger fires, fires , etc.  often causes changes in onon--goinggoingactivities ((taskstasks))  Further study : how to receive with any functions / to use closures ??

 (Gregory) implies that events can be send to and are processed by almost any game entities, to specify all possible interactions..  In constrast, (McShaffry) advises that  we don't want giant lists of listeners to go through for each event  events should be mostly used between subsystems (managers)  game objects are in turn handled by these subsystems 24.2.2017 Juha Vihavainen / University of Helsinki 2929

DataData--drivendriven event systems (Gregory, p. 950950--954)954)  Event types may need to be defined externally, e.g., in a textual scripting language (or similar)  to generate, send, and handle events  need some symbolic form to name them uniformly  the whole game logic could then be expressed by scripting only  Event objects may need to be saved and loaded  must support serialization of events ( object IO )  in online multiplayer games , need to send them out there anyway  Note that we are kind of wiring up a game with events and handlers  At extreme, graphical scripting systems provide components (operations/blocks) with "ports" to be manually connected (wired)  can be configured and combined to express new interactions  events are used to trigger (start) sequences of actions  and Game Engine provide such facilities 24.2.2017 Juha Vihavainen / University of Helsinki 3030 How to define types ( tags ) for events

A simple and efficient --butbut does not scale well for large games enum class EventKind { (Gregory) suggests using hashed LevelStarted, strings ids as global identifiers (names) EnemySpawned, PlayerSpotted, In C++, we have built-in RTTI BulletHit . . (Run-Time Type Info) but its };}; implementation is partly undefined.. Drawbacks of enum class :  info about all events kinds is centralized and hard --coded => changes cause systemsystem--widewide recompilations  event codes are implementationimplementation--dependentdependent ordered integers => does not support datadata--drivendriven event definitions (in scripts)

24.2.2017 Juha Vihavainen / University3131 of Helsinki

EEncodingncoding event types via string name ss

 A very freefree--formform and easy to use: just name a new event type to add it

 Some problems:  increased memory requirements (need char arrays/buffers)  highhigh--costcost of comparing actual strings (takes linear time)  can have name conflicts (e.g., when introducing new event types)  possibility for aliases , and mismatches or typographical errors

 Some remedies:  use a central repository to register ("declare") event type names  can store additional info: documentation, use of arguments..  useuse hashed string IDs : generate unique integer IDs for strings  faster comparison, uses less memory (to pass around)  when needed can (easily) recover the original name

24.2.2017 Juha Vihavainen / University of Helsinki 3232 Unique ids for game entities [Greg, Ch.5.4, 277277--279]279]

 Say, in PacMan we might encounter game entities named "pacman", "blinky", "pinky", "inky", and "clyde"

 Also, the assets from which our game objects are constructed -- meshes, materials, textures, audio clips, animations, etc. -- need unique identifiers of their own

 We want to hash strings into string ids (~ unique integer codes)  Interning a string means hashing it and adding it to a global string table; the original string can be recovered from the hash code later

 Another idea used by the Unreal Engine is to wrap the string id and a pointer to the corresponding CC--stylestyle char array in a tiny class  in the Unreal Engine , a "name table" of FNames maps unique strings to indices; provides a lightweight system for strings, where a given string is stored once only in a table ---- eevenven if reused

24.2.2017 Juha Vihavainen / University of Helsinki 3333

using SId = unsigned32; // modified from (Gregory, 278) using StringIdTable = std::unordered_map ;>; static StringIdTable stringIdTable; . . . // a singleton, again

SId internString (char const ** str) { Must ensure uniqueness.. SId sid = hashCrc32 (str); // custom hash creates unique ids if (stringIdTable.find (sid) == stringIdTable.end ()) // not found stringIdTable [sid] = std::strdup (str);

return sid; See also: Gregory, Section 5.4.3.2 } } . . . Some implementation ideas, p.277 static SId sidFoo = internString ("foo"); // to use SIdSId as a name static SId sidBar = internString ("bar"); . . . if (sid == sidFoo) . . . // then handle the ""foofoo "" case ..

24.2.2017 Juha Vihavainen / University of Helsinki 3434