Könnte ich mit Event-Handlern verrückt werden? Gehe ich mit meinem Design den „falschen Weg“?


12

Ich denke, ich habe beschlossen, dass ich Event-Handler wirklich mag. Möglicherweise leide ich ein bisschen an einer Analyse-Lähmung, aber ich mache mir Sorgen, dass mein Design unhandlich wird oder dass ich auf eine andere unvorhergesehene Konsequenz für meine Designentscheidungen stoße.

Meine Spiel-Engine rendert derzeit auf Basis von Sprites mit einer Schwenk-Overhead-Kamera. Mein Design sieht ungefähr so ​​aus:

SceneHandler

Enthält eine Liste von Klassen, die die SceneListener-Schnittstelle implementieren (derzeit nur Sprites). Ruft render () einmal pro Tick auf und sendet onCameraUpdate (); Nachrichten an SceneListener.

InputHandler

Fragt die Eingabe einmal pro Tick ab und sendet eine einfache "onKeyPressed" -Nachricht an InputListeners. Ich habe einen Camera InputListener, der eine SceneHandler-Instanz enthält und updateCamera () auslöst. Ereignisse basierend auf der Eingabe.

AgentHandler

Ruft einmal pro Häkchen Standardaktionen für Agenten (AI) auf und überprüft einen Stapel auf neu registrierte Ereignisse und sendet sie nach Bedarf an bestimmte Agenten.

Ich habe also grundlegende Sprite-Objekte, die sich in einer Szene bewegen und rudimentäres Lenkverhalten zum Reisen verwenden können. Ich bin zur Kollisionserkennung gekommen, und hier bin ich mir nicht sicher, in welche Richtung mein Design gehen soll. Ist es eine gute Praxis, viele kleine Event-Handler zu haben? Ich stelle mir vor, ich müsste eine Art CollisionHandler implementieren.

Wäre ich mit einem konsolidierten EntityHandler, der KI, Kollisionsaktualisierungen und andere Entitätsinteraktionen in einer Klasse verarbeitet, besser dran? Oder ist es in Ordnung, einfach viele verschiedene Subsysteme für die Ereignisbehandlung zu implementieren, die basierend auf der Art des Ereignisses Nachrichten aneinander weiterleiten? Soll ich einen EntityHandler schreiben, der einfach für die Koordination all dieser Sub-Event-Handler zuständig ist?

In einigen Fällen, wie z. B. meinem InputHandler und SceneHandler, stelle ich fest, dass dies sehr spezifische Ereignistypen sind. Ein großer Teil meines Spielcodes kümmert sich nicht um Eingaben, und ein großer Teil kümmert sich nicht um Aktualisierungen, die nur beim Rendern der Szene auftreten. Daher halte ich meine Isolation dieser Systeme für gerechtfertigt. Ich stelle diese Frage jedoch speziell für Ereignisse vom Typ Spielelogik.

Antworten:


21

Das ereignisbasierte Design umfasst hauptsächlich die Implementierung des Reactor- Designmusters.

Bevor Sie mit dem Entwerfen von Komponenten beginnen, sollten Sie sich mit den Einschränkungen eines solchen Musters befassen (möglicherweise finden Sie hierzu zahlreiche Informationen).

Das Hauptproblem ist, dass die Handler schnell zurückkehren müssen , wie jeder, der serielle Arbeit an GUI-basierten Frameworks und Javascript-basierten Anwendungen geleistet hat.

Wenn Sie jede Anwendung mit einer anständigen Komplexität entwickeln, werden Sie früher oder später mit einem Handler konfrontiert , der komplexe Aufgaben ausführt , die Zeit erfordern .

In diesen Fällen können Sie zwei Situationen unterscheiden (die sich nicht unbedingt gegenseitig ausschließen):

Komplexe ereignisbezogene Verantwortlichkeiten und komplexe ereignisunabhängige Verantwortlichkeiten.

Komplexe ereignisbezogene Aufgaben:

Im ersten Fall haben Sie zu viele Zuständigkeiten in einem einzelnen Komponenten-Handler zusammengefasst : Es werden zu viele Aktionen als Reaktion auf ein Ereignis ausgeführt, oder es werden beispielsweise Synchronisierungen ausgeführt.

In diesem Fall besteht die Lösung darin, den Handler in verschiedene Handler (bei Bedarf in verschiedene Objekte) aufzuteilen und einem Handler das Auslösen von Ereignissen zu überlassen, anstatt den Handlungscode direkt aufzurufen. Auf diese Weise können Sie die Verwaltung eines Ereignisses mit höherer Priorität zulassen, da Ihr primärer Handler zurückgekehrt ist, nachdem Ereignisse an die Ereigniswarteschlange angehängt wurden.

Eine andere Lösung besteht darin , eine externe Entität Ereignisse auslösen zu lassen und die anderen bei Interesse abonnieren zu lassen (das Timing-Ereignis ist das trivialste Beispiel, das Sie verallgemeinern können).

Komplexe ereignisunabhängige Verantwortlichkeiten:

Der zweite Fall tritt auf, wenn die nach einem Ereignis auszuführende Aktion eine gewisse Komplexität aufweist : Sie müssen beispielsweise nach einem Ereignis eine Fibonacci-Zahl berechnen.

Hier versagt das Reaktormuster im Grunde genommen , was wenig wert ist, um den Fibonacci-Generierungsalgorithmus in kleine Blöcke aufzuteilen, die Ereignisse bei Beendigung auslösen, um den nächsten Schritt auszulösen.

Hier besteht die Lösung darin, ein Gewindedesign mit dem Reaktor zu mischen. Die gute Nachricht ist, dass es bei einer intrinsischen Komplexität (Sie lesen also den richtigen Abschnitt) sehr wahrscheinlich ist, dass ein unabhängiger Thread gestartet werden kann, der die Arbeit erledigt und Das muss wenig oder gar nichts über den Rest der Reaktorkomponenten wissen.

Um diese Art von Situationen zu bewältigen, fand ich es hilfreich, eine Jobwarteschlange über einen flexiblen Thread-Pool zu übernehmen .

Wenn ein Handler eine lange Aktion starten muss, kapselt er diese Aktion in eine Jobeinheit , um sie in die Jobwarteschlange zu stellen. Diese gekapselte Aktion muss einen Mittelwert haben, um Ereignisse in den Reaktorthread auszulösen . Der Jobwarteschlangenmanager kann in seinem eigenen Thread oder im Reaktorthread ausgeführt werden und auf das Ereignis "newJob" reagieren.

Der Jobwarteschlangenmanager führt Folgendes aus:

  • Er weist einem Thread aus dem flexiblen Thread-Pool mithilfe seines Zeitplan-Algorithmus eine Job-Unit zu, wenn der Pool einen Thread bereitstellen kann (es ist ein freier Thread vorhanden oder die Erstellung eines neuen Threads ist zulässig).

  • Überwachen Sie den Pool selbst, um festzustellen, ob eine Jobeinheit beendet wurde, damit ein Thread wiederhergestellt werden kann, um gegebenenfalls eine weitere ausstehende Einheit auszuführen.

Bei Verwendung des Ereignisauslösemechanismus kann eine Jobeinheit Ereignisse auslösen, um den Abschluss, den Aktualisierungsstatus, Warnungen, Fehler oder wann immer erforderlich zu benachrichtigen. Der flexible Thread-Pool sorgt für eine gute Ressourcennutzung und verhindert, dass das gesamte System durch tote Threads hängen bleibt. In der Job-Warteschlange können Sie auswählen, welche Art von Priorität verschiedenen Arten von Aktionen zugewiesen werden soll, da Sie wissen, dass diese eine konsistente Menge an Rechenressourcen erfordern.


3
+1 für sehr gründlich. Ein paar Links für den neugierigen Leser wären genial.
Sam Hocevar

1

In den meisten Situationen kann die Verwendung von Ereignissen über andere Optionen häufig die Geschwindigkeit und Dezentralisierung verbessern. Ich denke, wenn du viele Events nutzen kannst, solltest du es versuchen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.