Event Queue¶
Intent: Decouple when a message is sent from when it is processed, allowing events to be buffered, reordered, or coalesced.
Acron Mapping¶
Pattern role |
Acron equivalent |
|---|---|
Queue |
|
Producer |
Any code that appends to the deque or calls |
Consumer |
Task that pops from the deque each frame |
messenger.send() dispatches synchronously within the same frame’s task, which is fine for most cases. Use a manual queue only when you need deferred processing, coalescing, or cross-frame buffering.
How to Implement (manual queue)¶
Create a
dequeon the system that owns the queue:self._queue = deque().Producers call
self._queue.append(Event(type, payload))— no direct call to the consumer.Add a consumer task:
taskMgr.add(self._drain, 'eventQueue', sort=50)(high sort number = runs after game logic).In
_drain, process up to N events per frame to avoid frame spikes: pop, dispatch, repeat.For coalescing (e.g., “only process the last health update”): use a
dictkeyed by event type instead of a deque.
Code Sketch (Python)¶
from collections import deque
from dataclasses import dataclass, field
from typing import Any
@dataclass
class GameEvent:
type: str
payload: Any = None
class EventQueue:
MAX_PER_FRAME = 20
def __init__(self):
self._queue = deque()
self._handlers = {}
taskMgr.add(self._drain, 'eventQueue', sort=50)
def push(self, event_type, payload=None):
self._queue.append(GameEvent(event_type, payload))
def subscribe(self, event_type, handler):
self._handlers.setdefault(event_type, []).append(handler)
def _drain(self, task):
processed = 0
while self._queue and processed < self.MAX_PER_FRAME:
evt = self._queue.popleft()
for handler in self._handlers.get(evt.type, []):
handler(evt.payload)
processed += 1
return task.cont
def destroy(self):
taskMgr.remove('eventQueue')
Pitfalls¶
Unbounded growth: without
MAX_PER_FRAME, a burst of events can freeze a frame. Cap processing per frame and let the queue drain over multiple frames.Ordering assumptions: unlike
messenger.send()(synchronous), queued events arrive one frame late. Code that expects immediate state changes after sending will break.Using the queue for audio: sound triggered via
messenger.send()fires synchronously — acceptable. But ifplay()is deferred to next frame via a queue, the audio cue lags behind the visual, which is usually wrong. Play audio immediately; queue only logic.