1.Event Queue
Intention Das Ziel ist es den Vorgang des Sendens und des Verarbeitens voneinander zu entkoppeln
GUI event loops while (running) { Event event = getNextEvent(); // Handle event... }
GUI event loops
Central event bus
static void playSound(SoundId id, int volume); Audio Beispiel class Audio { public: static void playSound(SoundId id, int volume); }; void Audio::playSound(Sound Id id, int volume) ResourceId resource = loadSound(id); int channel = findOpenChannel(); if (channel == -1) return; startSound(resource, channel, volume); }
Audio Beispiel class Menu { public: void onSelect(int index) Audio::playSound(SOUND_BLOOP, VOL_MAX); // Other stuff... } };
Audio Beispiel Problem 1: Die API blockiert den 'caller' bis die Audio Engine die Request komplett verarbeitet hat Problem 2: Requests können nicht zusammen verarbeitet werden Problem 3: Requests werden im falschen Thread bearbeitet
Die Strukturierung Eine Queue lagert eine Serie von Meldungen oder Requests in 'first-in first-out' Reiehenfolge. Wird eine Meldung gesendet, wird die Request eingereiht. Der Request Prozessor bearbeitet die Elemente der Queue zu einem späteren Zeitpunkt. Requests können direkt bearbeitet oder an andere Einheiten weitergeleitet werden → Dies entkoppelt den Sender und Empfänger, statisch und in der Zeit.
When to use it? Man braucht eine Queue nur, wenn man etwas zeitlich entkoppeln will
Keep in mind! Event Queues sind komplex und haben weitreichende Auswirkung auf die Architektur der Anwendung → d.h. man sollte sich gut überlegen wie – und ob man sie benutzt.
Keep in mind! Eine zentrale Event Queue ist eine globale Variable Der Status der Umgebung kann sich verändern Man kann in Feedback loops stecken bleiben
Beispiel Code struct PlayMessage { SoundId id; int volume; };
static const int MAX_PENDING = 16; Beispiel Code public: static void init() { numPending_ = 0; } // Other stuff... private: static const int MAX_PENDING = 16; static PlayMessage pending_[MAX _PENDING]; static int numPending_; }; void Audio::playS ound(SoundId id, int volume) assert(numPe nding_ < MAX_PENDING) ; pending_[num Pending_].id = id; pending_[num Pending_].vo lume = volume; numPending_+ +;
for (int i = 0; i < numPending_ ; i++) Beispiel Code class Audio { public: static void update() for (int i = 0; i < numPending_ ; i++) ResourceId resource = loadSound(p ending_[i]. id); int channel = findOpenCha nnel(); if (channel == -1) return; startSound( resource, channel, pending_[i] .volume); } numPending_ = 0; // Other stuff... };
A Ring buffer
class Audio { public: static void init() head_ = 0; tail_ = 0; } A Ring buffer class Audio { public: static void init() head_ = 0; tail_ = 0; } // Methods... private: static int head_; static int tail_; // Array... };
A Ring buffer void Audio::playSound(SoundId id, int volume) { assert(tail_ < MAX_PENDING); // Add to the end of the list. pending_[tail_].id = id; pending_[tail_].volume = volume; tail_++; }
A Ring buffer
A Ring buffer
A Ring buffer void Audio::playSound(SoundId id, int volume) { assert((tail_ + 1) % MAX_PENDING != head_); // Add to the end of the list. pending_[tail_].id = id; pending_[tail_].volume = volume; tail_ = (tail_ + 1) % MAX_PENDING; }
Aggregating requests void Audio::playSound(SoundId id, int volume) { // Walk the pending requests. for (int i = head_; i != tail_; i = (i + 1) % MAX_PENDING) if (pending_[i].id == id) // Use the larger of the two volumes. pending_[i].volume = max(volume, pending_[i].volume); // Don't need to enqueue. return; } // Previous code...
Design Decisions
Was kommt in die Queue? Events einreihen → Ein Event oder Benachrichtung beschreibt etwas was bereits passiert ist Meldungen einreihen → Eine Meldung oder Request beschreibt eine Aktion die in der Zukunft ausgeführt werden soll
Wer soll aus der Queue lesen? Single-cast queue Broadcast queue Work queue
Wer soll in die Queue schreiben? Nur ein Writer Mehrere Writer → Funktioniert mit allen read/write Konfigurationen
Lebenszeit eines Objektes in der Queue Pass ownership Share ownership The Queue owns it
2.Service Locator
Intention Das Ziel eines Service Locators ist es einen globalen Zugangspunkt zu einem Service zu schaffen ohne die Benutzer mit der implementierten Klasse zu verbinden
Begründung // Use a static class? AudioSystem::playSound(VERY_LOUD_BANG); // Or maybe a singleton? AudioSystem::instance()- >playSound(VERY_LOUD_BANG);
Keep in mind! Das Kernproblem bei einem Service Locator sind Abhängikeiten → Service Locator schaffen Flexibilität → Abhängikeiten beim Lesen des Codes schwer zu erkennen
Keep in mind! Service muss lokalisiert werden Service weiß nicht von wem/was es lokalisiert wird
Beispiel Code Der Service class Audio { public: virtual ~Audio() {} virtual void playSound(int soundID) = 0; virtual void stopSound(int soundID) = 0; virtual void stopAllSounds() = 0; };
Beispiel Code Der Service Provider class ConsoleAudio : public Audio { virtual void playSound(int soundID) // Play sound using console audio api... } virtual void stopSound(int soundID) // Stop sound using console audio api... virtual void stopAllSounds() // Stop all sounds using console audio api... };
Beispiel Code Der Service Locator class Locator { public: static Audio* getAudio() { return service_; } static void provide(Audio* service) service_ = service; } private: static Audio* service_; };;
Beispiel Code Registrierung des Service Provider ConsoleAudio *audio = new ConsoleAudio(); Locator::provide(audio); Die statische Funktion getAudio() übernimmt die Lokalisierung Audio *audio = Locator::getAudio(); audio->playSound(VERY_LOUD_BANG);
Null service class NullAudio: public Audio { public: virtual void playSound(int soundID) { /* Do nothing. */ } virtual void stopSound(int soundID) { /* Do nothing. */ } virtual void stopAllSounds() { /* Do nothing. */ } };
class LoggedAudio : public Audio Logging Decorator class LoggedAudio : public Audio { public: LoggedAudio( Audio &wrapped) : wrapped_(wra pped) {} virtual void playSound(in t soundID) log("play sound"); wrapped_.pla ySound(sound ID); } virtual void stopSound(in t soundID) log("stop sound"); wrapped_.sto pSound(sound ID); virtual void stopAllSound s() log("stop all sounds"); wrapped_.sto pAllSounds() ;
Logging Decorator private: void log(const char* message) { // Code to log message... } Audio &wrapped_; };
Logging Decorator void enableAudioLogging() { // Decorate the existing service. Audio *service = new LoggedAudio(Locator::getAudio()); // Swap it in. Locator::provide(service); }