System encji składowych - Aktualizacje i zamówienia połączeń

10

Aby komponenty mogły aktualizować każdą ramkę (i pozostawić tę funkcjonalność poza komponentami, które nie muszą), wpadłem na pomysł, aby utworzyć komponent UpdateComponent. Inne elementy, takie jak MovableComponent(które utrzymują prędkość), dziedziczyłyby po IUpdatableklasie abstrakcyjnej. Wymusza MovableComponentto implementację Update(gametime dt)metody i innej, RegisterWithUpdater()która daje UpdateComponentwskaźnik do MovableComponent. Wiele komponentów może to zrobić, a następnie UpdateComponentmoże wywołać wszystkie swoje Update(gametime dt)metody bez konieczności dbania o to, kim lub czym są.

Moje pytania to:

  1. Czy to wydaje się być czymś normalnym lub używanym przez kogokolwiek? Nie mogę nic znaleźć na ten temat.
  2. Jak mogę utrzymać porządek w elementach takich jak fizyka, a następnie zmienić pozycję? Czy to w ogóle konieczne?
  3. Jakie są inne sposoby zapewnienia, że ​​komponenty, które powinny być przetwarzane w każdej ramce, są w rzeczywistości przetwarzane?

EDYCJA
Myślę, że będę zastanawiał się, jak dać menadżerowi encji listę typów, które można aktualizować. Wtedy WSZYSTKIE komponenty tego typu mogą się aktualizować zamiast zarządzać nimi dla poszczególnych jednostek (które i tak są tylko indeksami w moim systemie).

Nadal. Moje pytania pozostają dla mnie ważne. Nie wiem, czy jest to uzasadnione / normalne, lub co robią inni.

Również ludzie w Insomniac są niesamowici! /EDYTOWAĆ

Gotowany kod dla poprzedniego przykładu:

class IUpdatable
{
public:
    virtual void Update(float dt) = 0;
protected:
    virtual void RegisterAsUpdatable() = 0;
};

class Component
{
    ...
};

class MovableComponent: public Component, public IUpdatable
{
public:
    ...
    virtual void Update(float dt);
private:
    ...
    virtual void RegisterWithUpdater();
};

class UpdateComponent: public Component
{
public:
    ...
    void UpdateAll();
    void RegisterUpdatable(Component* component);
    void RemoveUpdatable(Component* component);
private:
    ...
    std::set<Component*> updatables_;
};
ptpaterson
źródło
Czy masz przykład komponentu, którego nie można aktualizować? To wydaje się dość bezużyteczne. Umieść funkcję aktualizacji jako funkcję wirtualną w komponencie. Następnie po prostu zaktualizuj wszystkie komponenty w encji, nie potrzebujesz „UpdateComponent”
Maik Semder
Niektóre komponenty bardziej przypominają posiadaczy danych. Podobnie jak PositionComponent. Wiele obiektów może mieć pozycję, ale jeśli są one nieruchome, co może stanowić większość, wszystkie dodatkowe połączenia wirtualne mogą powstać. Lub powiedz HealthComponent. To nie musi robić nic dla każdej klatki, wystarczy zmodyfikować, kiedy trzeba. Być może narzut nie jest taki zły, ale mój UpdateComponent był próbą braku aktualizacji () w KAŻDYM składniku.
ptpaterson
5
Składnik, który przechowuje tylko dane i nie ma funkcji umożliwiającej ich zmianę w czasie, z definicji nie jest składnikiem. W składniku musi być kilka funkcjonalnie zmieniających się w czasie, dlatego wymaga funkcji aktualizacji. Jeśli twój komponent nie potrzebuje funkcji aktualizacji, wiesz, że nie jest to komponent i powinieneś przemyśleć projekt. Funkcja aktualizacji w komponencie zdrowia ma sens, na przykład chcesz, aby twój NPC leczył przez pewien czas z obrażeń.
Maik Semder
@ Maik Miałem na myśli, że NIE są to elementy, które nigdy / nigdy nie mogą się zmienić. Zgadzam się, to głupie. Po prostu nie muszą aktualizować każdej ramki, zamiast tego są powiadamiani o konieczności zmiany informacji w razie potrzeby. W przypadku zdrowia w miarę upływu czasu pojawiłby się składnik premii zdrowia, który DOSTOSUJE aktualizację. Nie sądzę, żeby istniał jakiś powód, by łączyć te dwa elementy.
ptpaterson
Chciałbym również zauważyć, że kod, który opublikowałem, zawiera tylko to, co jest potrzebne do wyjaśnienia pojęcia UpdateComponent. Wyklucza to wszelkie inne formy komunikacji między komponentami.
ptpaterson

Odpowiedzi:

16

Jedną z głównych zalet systemu komponentów jest możliwość korzystania z wzorców buforowania - dobry icache i predykcja, ponieważ ciągle uruchamiasz ten sam kod, dobry dcache, ponieważ możesz alokować obiekty w jednorodne pule i ponieważ tabele vtable, jeśli każdy, bądź gorący.

Sposób, w jaki ustrukturyzowałeś swoje komponenty, ta przewaga znika całkowicie i w rzeczywistości może stać się obciążeniem wydajnościowym w porównaniu do systemu opartego na dziedziczeniu, ponieważ wykonujesz o wiele więcej wirtualnych połączeń i więcej obiektów za pomocą vtables.

To, co powinieneś zrobić, to przechowywać pule według typu i iterować każdy typ niezależnie, aby wykonać aktualizacje.

Czy to wydaje się być czymś normalnym lub używanym przez kogokolwiek? Nie mogę nic znaleźć na ten temat.

Nie jest tak powszechne w dużych grach, ponieważ nie jest korzystne. Jest powszechny w wielu grach, ale nie jest interesujący technicznie, więc nikt o tym nie pisze.

Jak mogę utrzymać porządek w elementach takich jak fizyka, a następnie zmienić pozycję? Czy to w ogóle konieczne?

Kod w językach takich jak C ++ ma naturalny sposób wykonywania poleceń: wpisz instrukcje w tej kolejności.

for (PhysicsComponent *c : physics_components)
    c->update(dt);
for (PositionComponent *c : position_components)
    c->update(dt);

W rzeczywistości nie ma to sensu, ponieważ żaden solidny system fizyki nie ma takiej struktury - nie można zaktualizować jednego obiektu fizyki. Zamiast tego kod wyglądałby bardziej jak:

physics_step();
for (PositionComponent *c : position_components)
    c->update(dt);
// Updates the position data from the physics data.

źródło
Dzięki. Cały ten projekt rozpocząłem z myślą o danych (a nie o danych), zapobiegając błędom pamięci podręcznej i tym podobne. Ale kiedy zacząłem kodować, postanowiłem po prostu stworzyć coś, co zadziałałoby na com. Okazuje się, że to utrudniło mi życie. A tak na poważnie, ten bezsenny artykuł był świetny!
ptpaterson
@Joe +1 za bardzo dobrą odpowiedź. Chociaż chciałbym wiedzieć, czym jest icache i dcache. Dzięki
Ray Dey,
Pamięć podręczna instrukcji i pamięć podręczna danych. Powinien je obejmować każdy tekst wprowadzający na temat architektury komputera.
@ptpaterson: Zauważ, że w prezentacji Insomniac występuje niewielki błąd - klasa komponentów musi zawierać indeks roster lub potrzebujesz osobnego odwzorowania indeksów puli na indeksy roster.
3

Myślę, że to, o czym mówisz, jest rozsądne i dość powszechne. To może dać ci więcej informacji.

hojny
źródło
Myślę, że natknąłem się na ten artykuł kilka tygodni temu. Udało mi się napisać razem kilka bardzo prostych wersji systemu encji opartej na komponentach, z których każda bardzo się różni, gdy próbuję różnych rzeczy. Próbuję zebrać wszystkie artykuły i przykłady w coś, co chcę i jestem w stanie zrobić. dzięki!
ptpaterson
0

Lubię te podejścia:

Krótko: Unikaj zachowania aktualizacji w obrębie komponentów. Składniki nie są zachowaniem. Zachowanie (w tym aktualizacje) można wdrożyć w niektórych podsystemach z pojedynczą instancją. Takie podejście może również pomóc w przetwarzaniu wsadowym podobnych zachowań dla wielu składników (być może przy użyciu instrukcji parallel_for lub SIMD na danych składników).

Pomysł IUpdatable i metoda aktualizacji (gametime dt) wydają się nieco zbyt restrykcyjne i wprowadzają dodatkowe zależności. Może być dobrze, jeśli nie używasz podejścia podsystemów, ale jeśli go używasz, IUpdatable to nadmiarowy poziom hierarchii. W końcu MovingSystem powinien wiedzieć, że musi aktualizować komponenty Location i / lub Velocity bezpośrednio dla wszystkich encji, które mają te komponenty, więc nie ma potrzeby stosowania jakiegoś pośredniego komponentu IUpdatable.

Możesz jednak użyć komponentu Aktualizowalnego jako mechanizmu pomijania aktualizacji niektórych jednostek, mimo że mają one komponenty lokalizacji i / lub prędkości. Składnik aktualizowalny może mieć flagę bool, którą można ustawić falsei która zasygnalizuje każdemu podsystemowi obsługującemu aktualizację, że ten konkretny byt nie powinien być obecnie aktualizowany (chociaż w takim kontekście nazwa Freezable wydaje się bardziej odpowiednią nazwą dla komponentu).

JustAMartin
źródło