Czy istnieje lepszy sposób na skonfigurowanie systemu zdarzeń?


9

Systemy zdarzeń są niesamowite, sprawiają, że kodowanie jest wyjątkowo niewygodne i naprawdę pozwala na dynamiczne tworzenie gier poprzez łatwą komunikację obiektów i pętli gry. Mam trudności z wydajnością mojego obecnego wdrożenia. Obecnie moja niewielka optymalizacja podziału list obiektów na zdarzenia, na które odpowiadają, dokonała cudów, ale mogę zrobić więcej.

Obecnie mam dwie metody:

  1. Najprostsze: wszystkie obiekty są dodawane do wektora, gdy wysyłane jest zdarzenie, wszystkie obiekty są wysyłane zdarzeniem za pomocą metody handle_event ()

  2. Bardziej skomplikowane: mam mapę z ciągiem znaków jako kluczem i liczbą całkowitą jako wartością. Gdy dodawany jest typ zdarzenia, jest on dodawany do tej mapy, przy czym int jest po prostu zwiększany (musi być lepszy sposób)
    wektor wektorów obiektów, a następnie wypycha nowy wektor, aby obsłużyć tego typu zdarzenie.
    Gdy wywoływane jest zdarzenie, po prostu wywołuje odpowiednią int w odwzorowaniu eventTypes do typu wewnątrz wektora wektorów obiektów i wysyła to zdarzenie do każdego obiektu obsługującego ten typ zdarzenia.

Ta pierwsza metoda jest dość wolna (oczywiście) dla wielu obiektów, ale dość szybka dla bardzo niewielu obiektów. Natomiast druga metoda jest dość szybka w przypadku dużych obiektów, które chciałyby obsługiwać różne typy zdarzeń, ale wolniej niż pierwsza metoda na obiekt z obiektami obsługującymi ten sam typ zdarzenia.

Czy istnieje szybszy (rozsądny w czasie) sposób? Czy istnieje szybszy sposób na wyszukanie int z typu ciągu? (Początkowo miałem wyliczenie, ale nie pozwalało na niestandardowe typy, które są konieczne ze względu na pożądany poziom dynamiki).


1
Do tego właśnie służą mapy Hash (lub tablica skrótu), ciąg znaków jest obliczany do liczby skrótu, która jest następnie używana do wyszukiwania bezpośrednio w tablicy. en.wikipedia.org/wiki/Hash_table
Patrick Hughes

Dobre pytanie brzmi: czy to naprawdę wąskie gardło w wydajności, czy po prostu martwisz się przedwcześnie?
Jari Komppa

Jest już zaimplementowany i występuje wąskie gardło, gdy używanych jest wiele obiektów. Mój poprzedni komentarz na temat braku wąskiego gardła nie był w samej aplikacji, ale w rzeczywistości różnicy prędkości między dwoma powyższymi implementacjami, w których faktycznie obsługiwano tę samą liczbę obiektów. Miałem nadzieję na inne metody tworzenia systemów zdarzeń ... Jednak niektóre pomysły w tym wątku znacznie zwiększą prędkość, szczególnie podczas ładowania systemu zdarzeń (czas ładowania 11-25 pełnych sekund przy 100000 obiektów)
ultifinitus

100 000 obiektów brzmi okropnie wysoko w samej grze. Czy jest to aplikacja serwera lub klienta końcowego?
Patrick Hughes,

To mój silnik, więc naprawdę dążę do elastyczności. Będzie wykorzystywany w aplikacjach serwerowych i aplikacjach użytkowników końcowych (rzadziej pierwsza, optymalizacja)
ultifinitus

Odpowiedzi:


5

Wygląda na to, że mówisz, że dużym wąskim gardłem wydajności jest wyszukiwanie identyfikatorów zdarzeń (liczb całkowitych) na podstawie ich nazw ciągów. Możesz wstępnie przetworzyć swoje dane gry, aby przekonwertować wszystkie nazwy zdarzeń na liczby całkowite przed uruchomieniem gry lub ewentualnie podczas ładowania poziomu; wtedy nie będziesz musiał dokonywać żadnych konwersji podczas gry.

Jeśli obiekty są często tworzone i niszczone, wektory obiektów mogą być bardzo zmienne. W takim przypadku możesz skorzystać z połączonych list zamiast wektorów; są szybsze w wstawianiu i usuwaniu.


Wąskie gardło nie jest duże (na 100 000 obiektów traci 0,0000076 ms / obiekt), ale myślę, że Twój pomysł jest świetny! Myślę, że faktycznie zrobię jednorazowe sprawdzenie identyfikatora i zapisuję identyfikator zdarzenia jako int, a nie oryginalne dane ciągu. I tak naprawdę nie myślałem o połączonych listach, dobry pomysł.
ultifinitus

1
+1 za wstępne przetwarzanie identyfikatorów. Możesz to zrobić leniwie, mając typ EventType, który każdy moduł może uzyskać za pomocą czegoś takiego EventType takeDamageEvent = EventSystem::CacheEventType("takeDamageEvent");. Ustaw go jako statycznego członka klas, a będziesz miał tylko jedną kopię pływającą w każdej klasie, która tego potrzebuje.
michael.bartnett

2
Pamiętaj, że przechowywanie identyfikatorów zamiast ciągów utrudni debugowanie. Zawsze warto zobaczyć tekst określający, skąd pochodzi obiekt. Możesz przejść do połowy, przechowując zarówno identyfikator, jak i ciąg znaków, które przechowujesz do celów debugowania (być może nawet zostały usunięte w kompilacjach wersji).
Nicol Bolas,

2

Cóż, najpierw usuńmy proste rzeczy. Masz to mapmiędzy ciągami znaków (prawdopodobnie nazwą zdarzenia) i liczbami całkowitymi (indeks zarejestrowanych detektorów zdarzeń).

Czas wyszukiwania w map zależy od dwóch rzeczy: liczby elementów na mapie oraz czasu potrzebnego na porównanie dwóch kluczy (ignorowanie problemów z pamięcią podręczną). Jeśli czas wyszukiwania stanowi problem, jednym ze sposobów radzenia sobie z nim jest zmiana funkcji porównywania poprzez zmianę typu łańcucha.

Załóżmy, że używasz std::stringi operator<do porównania. Jest to wysoce nieefektywne; porównuje bajty. Nie obchodzi cię rzeczywisty ciąg mniej niż porównanie; potrzebujesz tylko porównania, które daje ściśle słabe uporządkowanie (bo mapinaczej nie działa).

Dlatego powinieneś użyć 32-bajtowego ciągu o stałej długości zamiast std::string. Używam ich do identyfikacji. Testy porównawcze dla tych ustalonych ciągów nie przeprowadzają porównań bajtowych; zamiast tego wykonują porównania 32-bitowe (lub 64-bitowe). Pobiera co 4 bajty jako liczbę całkowitą bez znaku i porównuje je z odpowiednimi 4 bajtami drugiego łańcucha. W ten sposób porównanie zajmuje maksymalnie 8 porównań. Zapewnia bardzo słabe uporządkowanie, chociaż uporządkowanie nie ma nic wspólnego z danymi jako znakami.

Przechowywanie ciągów dłuższych niż 31 bajtów (wymaga znaku NULL) obcina ciąg (ale od środka, a nie na końcach. Uważam, że entropia jest zwykle największa na początku i na końcu). I ciągi krótsze niż to wypierają pozostałe znaki za pomocą \0.

Teraz możesz mapcałkowicie porzucić i użyć tabeli skrótów. Jeśli naprawdę masz ponad 100 000 różnych rodzajów zdarzeń, może to być dobry pomysł. Ale nie znam gry, w której byłoby to zdalnie rozsądne.


Dlatego powinieneś użyć 32-bajtowego ciągu o stałej długości zamiast std :: string. Fantastyczny! Nie pomyślałem o zmianie typu struny.
ultifinitus

0

Aby odpowiedzieć na ogólne pytanie:

Lepszy sposób na skonfigurowanie systemu zdarzeń

Nie ma żadnego. Wszystko, co możesz zrobić, to określić specyficzne potrzeby swoich systemów wydarzeń (może być ich kilka), a następnie użyć odpowiedniego narzędzia do odpowiedniej pracy.

Wdrożyłem i próbowałem uogólnić mnóstwo systemów zdarzeń i doszedłem do tego wniosku. Ponieważ kompromisy są naprawdę różne, jeśli ustawisz czas kompilacji lub wykonania, jeśli użyjesz hierarchii obiektów lub tablic itp.

Najlepszym sposobem jest przestudiowanie różnych rodzajów systemów zdarzeń, a następnie dowiesz się, który z nich użyć w danym przypadku.

Teraz, jeśli chcesz najbardziej elastycznego systemu, zaimplementuj system czarnej tablicy (środowisko wykonawcze) z dynamicznymi właściwościami zdarzeń, a będziesz mieć coś bardzo elastycznego, ale może bardzo powolnego.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.