Game Programming Patterns Event Queue From the book

Game Programming Patterns Event Queue From the book by Robert Nystrom http: //gameprogrammingpatterns. com

Upcoming Schedule • Tuesday, Feb. 2, from 3: 30 pm – 4: 50 pm – CGS Capstone course play testing – In DBH 5011 • Wednesday, Feb. 3 – regular discussions and lecture • Friday, Feb. 5 – test 1

Event Queue pattern • The problem: how do game systems communicate, and yet stay decoupled? • The pattern: A queue stores a series of notifications or requests in first-in, first-out order. The request processor later processes items from the queue.

The Event Loop • Operating Systems and GUIs use events. • We’ve seen mouse events, keyboard events, window closing events, etc. • The events have been put into a queue by the OS. while (running) { Event event = get. Next. Event(); // Handle event. . . }

An example • Your game has a tutorial system to display help boxes after specific in-game events. • The first time the player kills a monster, you want to show a “Press X to grab the loot!” message. • But you don’t want the tutorial code mixed into the already complex gameplay code. • So, combat code can add an “enemy died” event every time a monster is slayed. • The tutorial code listens for “enemy died” events.

Another example - sound class Audio { public: static void play. Sound(Sound. Id id, int volume); }; void Audio: : play. Sound(Sound. Id id, int volume) { Resource. Id resource = load. Sound(id); int channel = find. Open. Channel(); if (channel == -1) return; start. Sound(resource, channel, volume); }

Suppose we use play. Sound like this, to play a bloop when the selected menu item changes: class Menu { public: void on. Select(int index) { Audio: : play. Sound(SOUND_BLOOP, VOL_MAX); // Other stuff. . . } }; Problem 1: The API blocks the caller until the audio engine has completely processed the request. play. Sound is synchronous – it doesn’t return back to the caller until bloops are coming out of the speaker.

In the AI system, we use play. Sound() to let out a wail of anguish when an enemy takes damage from the player. But sometimes the mighty player hits two or more enemies at exactly the same time. Double play. Sound()s may be too loud, or jarring. Problem 2: Requests cannot be processed in aggregate. Hey – we’re running on a multicore processor. Audio should run in a separate thread. Problem 3: Requests are processed in the wrong thread. (The same one as the caller. )

Solution: decouple making a request for sound and processing the request. Problem 1: The API blocks the caller until the audio engine has completely processed the request. Let’s make play. Sound() defer the work and return almost immediately. void Audio: : play. Sound(Sound. Id id, int volume) { pending_[num. Pending_] = {id, volume}; num. Pending_++; } The private audio-only event queue! And add to the Audio class’s private area: static Play. Message pending_[MAX_PENDING]; static int num. Pending_; And define the struct Play. Message: struct Play. Message { Sound. Id id; int volume; };

Of course, the sound still has to be played somewhere – in an update() method: class Audio { public: static void update() { for (int i = 0; i < num. Pending_; i++) { Resource. Id resource = load. Sound(pending_[i]. id); int channel = find. Open. Channel(); if (channel == -1) return; start. Sound(resource, channel, pending_[i]. volume); } num. Pending_ = 0; } }; Call update() from the main game loop, or from a dedicated audio thread.

Once we have the event queue – so far just a simple array – many improvements and variations are possible. 1. Use a ring buffer to efficiently remove single items from the beginning of the queue. (See the book for details. ) 2. Aggregate multiple instances of the same request (problem 2). 3. Ensure that play. Sound() and update() are thread safe, so that multiple cores can be utilized (problem 3). 4. In this example, only the Audio class could read from the “single-cast” queue. A multi-reader “broadcast” queue is another option. 5. The items in the queue can be • messages: requests for actions to be performed in the future • events: notifications of something that already happened

Event Queues in Game Engines • From gamedev. stackexchange. com: • Messaging systems generally work well when: 1. The event sender doesn’t care (too much) if it gets received. 2. The sender does not need an immediate response back from the receiver. 3. There may be multiple receivers listening to a single sender. 4. Events are sent infrequently or unpredictably (e. g. not every frame). • http: //gamedev. stackexchange. com/questions/7718/event-driven-communication-in-a-game-engine-yes-or-no

Considerations • Probably want a “broadcast” type queue. • An Event class that will be subclassed. • Often a listener will register itself so that it only receives certain kinds of / subclasses of Event. • Make sure an Event can be processed by more than one listener. • Make sure an Event that has no listeners still gets deleted eventually.

Don’t forget • Take the EEE Quiz before midnight (end of day on Monday, Feb. 1). • URL: https: //eee. uci. edu/quiz/Es. N 3 g. E 00 QD
- Slides: 14