Czy mogę zwariować na punkcie obsługi zdarzeń? Czy idę z moim projektem „w niewłaściwy sposób”?

12

Chyba zdecydowałem, że naprawdę lubię programy obsługi zdarzeń. Być może cierpię z powodu paraliżu analizy, ale martwię się, że mój projekt będzie niewygodny lub napotkam jakieś inne nieprzewidziane konsekwencje moich decyzji projektowych.

Mój silnik gry obecnie wykonuje podstawowe renderowanie oparte na ikonkach za pomocą panoramowanej kamery. Mój projekt wygląda trochę tak:

SceneHandler

Zawiera listę klas, które implementują interfejs SceneListener (obecnie tylko Sprites). Wywołuje render () raz na tyknięcie i wysyła onCameraUpdate (); wiadomości do SceneListeners.

InputHandler

Sonduje dane wejściowe raz na tyknięcie i wysyła prosty komunikat „onKeyPressed” do InputListeners. Mam Camera InputListener, który przechowuje instancję SceneHandler i wyzwala updateCamera (); zdarzenia oparte na danych wejściowych.

AgentHandler

Wywołuje domyślne akcje na dowolnych agentach (AI) raz na tyknięcie i sprawdzi stos pod kątem wszelkich nowych zarejestrowanych zdarzeń, wysyłając je do określonych agentów w razie potrzeby.

Mam więc podstawowe obiekty sprite, które mogą poruszać się po scenie i używać podstawowych funkcji sterowania podczas podróży. Wykryłem kolizję i nie jestem pewien, czy kierunek, w którym zmierza mój projekt, jest dobry. Czy dobrą praktyką jest posiadanie wielu małych programów obsługi zdarzeń? Wyobrażam sobie, że jestem taki, że musiałbym wdrożyć jakiś CollisionHandler.

Czy lepiej byłoby, gdyby był bardziej skonsolidowany EntityHandler, który obsługuje sztuczną inteligencję, aktualizacje kolizji i inne interakcje bytu w jednej klasie? Czy też będę w porządku po prostu wdrażając wiele różnych podsystemów obsługi zdarzeń, które przesyłają sobie komunikaty w zależności od rodzaju zdarzenia? Czy powinienem napisać EntityHandler, który jest po prostu odpowiedzialny za koordynację wszystkich tych procedur obsługi zdarzeń podrzędnych?

W niektórych przypadkach, takich jak InputHandler i SceneHandler, zdaję sobie sprawę, że są to bardzo specyficzne typy zdarzeń. Duża część mojego kodu gry nie będzie dbała o dane wejściowe, a duża część nie będzie dbała o aktualizacje, które będą miały miejsce wyłącznie podczas renderowania sceny. Dlatego uważam, że moja izolacja tych systemów jest uzasadniona. Zadaję jednak to pytanie konkretnie w związku ze zdarzeniami typu logika gry.

sensae
źródło

Odpowiedzi:

21

Projektowanie oparte na zdarzeniach polega głównie na wdrożeniu wzorca projektowego Reactor .

Zanim zaczniesz projektować komponenty, powinieneś zapoznać się z ograniczeniami takiego wzoru (możesz znaleźć wiele informacji na ten temat).

Przede wszystkim problem polega na tym, że procedury obsługi muszą szybko powrócić , ponieważ każdy, kto wykonał szereg seriali, pracuje nad strukturami opartymi na GUI i aplikacjami opartymi na javascript.

Kiedy opracujesz każdą aplikację z przyzwoitą złożonością , prędzej czy później spotkasz się z jakimś trenerem, który wykonuje złożone zadanie wymagające czasu .

W takich przypadkach można wyróżnić dwie sytuacje (niekoniecznie wzajemnie się wykluczające):

Złożone obowiązki związane ze zdarzeniem i złożone obowiązki niezwiązane ze zdarzeniem .

Złożone obowiązki związane ze zdarzeniem:

W pierwszym przypadku połączyłeś zbyt wiele obowiązków w jeden moduł obsługi : w odpowiedzi na zdarzenie podejmowanych jest zbyt wiele działań lub na przykład trwają synchronizacje.

W takim przypadku rozwiązaniem jest podzielenie procedury obsługi na różne procedury obsługi (w razie potrzeby w różnych obiektach) i pozostawienie uchwytu wyzwalaniu zdarzeń zamiast bezpośredniego wywoływania kodu obsługi. Umożliwi to zarządzanie zdarzeniem o wyższym priorytecie wcześniej, ponieważ podstawowa procedura obsługi powróciła po dołączeniu zdarzeń do kolejki zdarzeń.

Innym rozwiązaniem jest zezwolenie zewnętrznemu bytowi na uruchamianie zdarzeń i pozwalanie innym na subskrybowanie, jeśli są zainteresowani (zdarzenie czasowe jest najbardziej trywialnym przykładem, jaki można uogólnić).

Złożone obowiązki niezwiązane ze zdarzeniem:

Drugi przypadek zdarza się, gdy po zdarzeniu występuje nieodłączna złożoność : musisz na przykład obliczyć liczbę fibonacci po zdarzeniu.

Tutaj wzór Reaktora zasadniczo zawodzi , niewiele warte jest podzielenie algorytmu generowania fibonacciego na małe kawałki, które uruchamiają zdarzenia po zakończeniu w celu uruchomienia następnego kroku.

Tutaj rozwiązaniem jest połączenie projektu gwintowanego z reaktorem , dobrą wiadomością jest to, że jeśli złożoność jest nieodłączna (więc czytasz właściwą sekcję), jest bardzo prawdopodobne, że możliwe jest uruchomienie niezależnego wątku, który działa i który musi wiedzieć niewiele lub nic o reszcie komponentów związanych z reaktorem.

Aby poradzić sobie z tego rodzaju sytuacjami, przydało mi się przyjęcie kolejki zadań nad elastyczną pulą wątków .

Jeśli obsługa musi rozpocząć długą akcję, to oddaje , że działanie na jednostkę pracy należy umieścić w kolejce. Ta enkapsulowana akcja musi mieć środek do wyzwalania zdarzeń w wątku reaktora. Menedżer kolejek zadań może działać we własnym wątku lub w wątku reaktora i reagować na zdarzenie „newJob”.

Menedżer kolejek zadań wykonuje następujące czynności:

  • przydziela jednostkę zadania do wątku z elastycznej puli wątków za pomocą algorytmu harmonogramu, jeśli pula jest w stanie zapewnić jeden wątek (istnieje wolny wątek lub dozwolone jest tworzenie nowego wątku)

  • nasłuchuj samej puli, aby zobaczyć, czy jednostka zadania zakończyła się, aby można było odzyskać wątek, aby wykonać jeszcze jedną oczekującą jednostkę, jeśli taka istnieje.

Za pomocą mechanizmu wyzwalającego zdarzenia jednostka zadania może wyzwalać zdarzenia w celu powiadomienia o zakończeniu, stanie aktualizacji, ostrzeżeniach, błędach lub w razie potrzeby. Elastyczna pula wątków zapewnia dobre wykorzystanie zasobów, unikając martwego wątku do zawieszenia całego systemu; kolejka zadań umożliwia wybranie rodzaju priorytetu przypisanego do różnego rodzaju działań, ponieważ wiadomo, że będą one wymagać stałej ilości zasobów obliczeniowych.

FxIII
źródło
3
+1 za bycie bardzo dokładnym. Kilka linków dla ciekawskiego czytelnika byłoby niesamowite.
sam hocevar
1

W większości sytuacji użycie zdarzeń w porównaniu z innymi opcjami może często poprawić szybkość i decentralizację. Myślę, że jeśli możesz użyć wielu wydarzeń, powinieneś się na to zdecydować.

anonimowo
źródło