Autor Wątek: komponenty  (Przeczytany 5758 razy)

Offline kapustman

  • Użytkownik

# Maj 30, 2015, 00:52:25
Mam system komponentów, który przechowuje sobie wszystkie komponenty w tablicach. Dopóki wszystkie tworzone obiekty potrzebują identycznych komponentów, wszystko jest ładnie "zapakowane" w tablicy, np.

obiekt [1,2,3,4,...]
pos     [1,2,3,4,...]
vel      [1,2,3,4,...]
acc      [1,2,3,4,...]

Ale przechodzimy z teorii do praktyki. Mamy różne obiekty, nie wszystkie korzystają ze wszystkich komponentów. I wtedy powstaje nam coś takiego:

obiekt [1,2,3,4,...]
pos     [1,2, , ,...]
vel      [ ,2, ,4,...]
acc     [ , , 3, 4,...]

Jak mam upakować te komponenty, aby nie było żadnych dziur, żeby wszystkie dane były w blokach?
« Ostatnia zmiana: Maj 30, 2015, 00:58:20 wysłana przez kapustman »

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline Xender

  • Użytkownik

# Maj 30, 2015, 01:53:07
Najprościej: mapa/słownik/hashmapa/tablica asocjacyjna - C++'owe std::unordered_map, Pythonowe dict.
Mapowanie unikatowych ID obiektów (entity) na komponenty w tych mapach.

Takie najprostsze mapowanie może (lub nie) okazać się mieć zbyt duży overhead.
Wtedy można próbować innych cudów z alokacją (zarządzaniem wolnych i zajętych) elementów w tablicach.


BTW, przykład taki sobie.
Tu nie chodzi o to, żeby każdego inta/wektor trzymać w osobnej tablicy.

Pozycja i fizyka osobno - ok, z pozycji korzysta też renderer.
Ale prędkość i przyspieszenie raczej lepiej trzymać razem.
Jak już obsługujesz ruch, to niekoniecznie chcesz osobne przypadki dla liniowego i przyspieszonego.

Pewnie znasz klasyka, ale podlinkuję dla pewności - tu są chyba sensowniejsze przykłady komponentów: http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
« Ostatnia zmiana: Maj 30, 2015, 02:10:22 wysłana przez Xender »

Offline kapustman

  • Użytkownik

# Maj 30, 2015, 11:40:20
Przy takim podejściu powstaje nam inny problem. Dane po potraktowaniu mapą/słownikiem zostają "ściśnięte"  i wtedy wyglądają tak:

obiekt [1,2,3,4,5,6,...]
pos     [1,2,5,6...]
vel      [2,3,6,...]
acc     [2,4,6,...]

Dla obiektu 2 chcę obliczyć nową pozycję. Funkcje liczące wyglądają w uproszczeniu tak:

void update(float* a, const float* b, unsigned count)
{
      for(unsigned i = 0; i< count; ++i)
          a[i] += b[i];
}

Przy takim ułożeniu danych często będą błędne wyniki, bo dla tego obiektu 2, zostaną wzięte dane obiektu 3 i 4.

Ale prędkość i przyspieszenie raczej lepiej trzymać razem.
Jak już obsługujesz ruch, to niekoniecznie chcesz osobne przypadki dla liniowego i przyspieszonego.

Jak mam ruch liniowy, to obiekt posiada tylko komponent pos i vel. Jak przyspieszony to dokładamy jeszcze komponent acc. A jak chcę żeby się nie ruszał, to zastawiam mu tylko pos.
« Ostatnia zmiana: Maj 30, 2015, 11:51:17 wysłana przez kapustman »

Offline laggyluk

  • Użytkownik
    • http://laggyluk.com

# Maj 30, 2015, 12:24:16
prawdopodobnie chodziło o to że masz obiekt i on albo jego id jest kluczem w mapach z komponentami

i wtedy nie jest już data oriented ;(

Offline Kos

  • Użytkownik
    • kos.gd

# Maj 30, 2015, 13:40:58
Ale jaki masz właściwie problem w tym że niektóre obiekty nie mają acc? Daj im acc=0 i po problemie.

Jeśli masz obiekty statyczne i dynamiczne i czujesz że marnujesz Dużo Pamięci, to możesz je trzymać w osobnych tablicach. Ale myślę że nie warto...

Offline kapustman

  • Użytkownik

# Maj 30, 2015, 13:57:52
Wtedy problemem jest acc, który niepotrzebnie zajmuje cache. I jeszcze dodawanie do wszystkiego 0.

Moim problemem jest fragmentacja pamięci, która uniemożliwia przetwarzanie danych w blokach pamięci.
« Ostatnia zmiana: Maj 30, 2015, 18:25:39 wysłana przez kapustman »

Offline Xender

  • Użytkownik

  • +2
# Maj 31, 2015, 12:04:07
Wtedy problemem jest acc, który niepotrzebnie zajmuje cache.
Log z profilera or it didn't happen.

Jak zaczniesz robić cuda na kiju, bo wydaje Ci się, że coś powoduje problemy wydajnościowe, a nie masz na to dowodu, to prawdopodobieństwo jest przeciwko Tobie i prawdopodobnie optymalizujesz coś, co wcale nie jest wąskim gardłem. Jeśli masz szczęście.

Bo jeśli szczęścia nie masz, to okaże się, że Twoje cuda na kiju nie dość, że komplikują kod, to jeszcze pogarszają wydajność, zamiast ją poprawiać.

Offline kapustman

  • Użytkownik

# Czerwiec 01, 2015, 00:09:44
Całkiem możliwe że masz rację. Ale to co proponujesz, to po prostu zarzucenie komponentów, i powrót  do obiektów. Tak właśnie wygląda ECS + DOD. Dzielisz wszystko na małe podzespoły, jak własnie pozycja czy prędkość. Nie trzymasz tego wszystkiego razem.

Offline Xender

  • Użytkownik

# Czerwiec 01, 2015, 01:02:53
@up - Trzymasz osobno rzeczy potrzebne różnym systemom.

Pozycja może być potrzebne fizyce i rendererowi.

Prędkość i przyspieszenie raczej tylko fizyce (chyba, że robisz jakiegoś motion blura).
Jak będziesz miał fizykę bryły sztywnej, to też trzeba się zastanawiać, czy na dochodzące dane potrzebny jest dodatkowy komponent, czy nie lepiej je dopchać do fizyki (albo przepisać wszystko na podwójne kwaterniony, ale to inna sprawa :P).

Jak masz wektor, to nie chodzi o to, żeby mieć osobne komponenty pos_x, pos_y, pos_z, vec_x, vec_y, vex_z...

Jak będziesz każdy wektor trzymał w osobnym komponencie, to cache Ci chyba nie podziękuje za taki "DOD".

Offline kapustman

  • Użytkownik

# Czerwiec 01, 2015, 01:32:48
Dostosowuję komponenty do potrzeb. W moim przypadku akurat trzymanie wektorów pozycji,prędkości i przyspieszenia jako oddzielnych komponentów ułatwia mi pracę, bo mogę wtedy zaprząc do roboty SSE. Jakbym wrzucił wszystko do jednej struktury/klasy, to nie mam łatwego dostępu do konkretnych danych.

I zrezygnowałem z oddzielnych systemów. Wszystko jest wspólne, bo przy większej ilości komponentów albo trzeba trzymać wszędzie duplikaty tych samych danych, albo kombinować jak przekazać pozycję rendererowi, skoro trzymam ją w innym systemie.

Offline Xirdus

  • Redaktor

# Czerwiec 01, 2015, 10:03:26
Jakbym wrzucił wszystko do jednej struktury/klasy, to nie mam łatwego dostępu do konkretnych danych.
Nie, wcale. C++ w ogóle nie ma takiego operatora jak kropka, nie?

I zrezygnowałem z oddzielnych systemów. Wszystko jest wspólne, bo przy większej ilości komponentów albo trzeba trzymać wszędzie duplikaty tych samych danych, albo kombinować jak przekazać pozycję rendererowi, skoro trzymam ją w innym systemie.
Nie chodzi żeby dwa systemy miały całkowicie oddzielone obiekty - tylko o to, by poszczególne systemy dostawały tylko te dane które je interesują. Renderer nie potrzebuje prędkości, fizyka nie potrzebuje tekstury, ale pozycję potrzebują oba - więc robisz obiekt z trzema komponentami, z tym że fizyka zajmuje się dwoma z nich, a renderer też dwoma.

Offline kapustman

  • Użytkownik

# Czerwiec 01, 2015, 12:39:27
Nie, wcale. C++ w ogóle nie ma takiego operatora jak kropka, nie?

Miałem na myśli funkcję korzystającą z SSE. Tam mogę tylko użyć shuffle, które na starszych procesorach będzie wolne.

Nie chodzi żeby dwa systemy miały całkowicie oddzielone obiekty - tylko o to, by poszczególne systemy dostawały tylko te dane które je interesują. Renderer nie potrzebuje prędkości, fizyka nie potrzebuje tekstury, ale pozycję potrzebują oba - więc robisz obiekt z trzema komponentami, z tym że fizyka zajmuje się dwoma z nich, a renderer też dwoma.

W idealnym ECS poszczególne komponenty i odpowiadające im systemy są całkowicie niezależne od siebie.
Ale to tylko teoria. A ponieważ wszystkie komponenty mam  razem, to mam bardzo łatwy dostęp do nich, nie ważne czy chcę policzyć fizykę, narysować czy posprawdzać kolizje.

Offline Krzych

  • Użytkownik

  • +1
# Czerwiec 01, 2015, 12:52:01
http://bitsquid.blogspot.co.at/2011/09/managing-decoupling-part-4-id-lookup.html - tutaj jest opisana struktura danych pozwalająca uzyskać to co chcesz osiągnąć

Offline Xirdus

  • Redaktor

  • +2
# Czerwiec 01, 2015, 13:14:38
Miałem na myśli funkcję korzystającą z SSE. Tam mogę tylko użyć shuffle, które na starszych procesorach będzie wolne.
Tu już zaczynasz wchodzić w mikrooptymalizacje. Jak nie zrobiłeś benchmarka i nie wyszło ci, że bottleneckiem jest złe użycie SSE spowodowane złym podziałem na komponenty, to takie gdybanie jest nic nie warte. Pamiętaj że procesory to bardzo skomplikowane maszyny które nie działają w prosty do przewidzenia sposób.

W idealnym ECS poszczególne komponenty i odpowiadające im systemy są całkowicie niezależne od siebie.
Nie ma czegoś takiego jak "odpowiadający system". Poszczególny systemy korzystają z dowolnych komponentów, tych które je interesują, a resztę zlewają. To, że komponent jest używany przez kilka podsystemów nic nie zmienia.

Offline Xender

  • Użytkownik

# Czerwiec 01, 2015, 14:30:15
Tu już zaczynasz wchodzić w mikrooptymalizacje. Jak nie zrobiłeś benchmarka i nie wyszło ci, że bottleneckiem jest złe użycie SSE spowodowane złym podziałem na komponenty, to takie gdybanie jest nic nie warte. Pamiętaj że procesory to bardzo skomplikowane maszyny które nie działają w prosty do przewidzenia sposób.

Chyba już ustaliliśmy, że OP to ciężki przypadek trolla optymalizacyjnego, który optymalizuje na ślepo bez profilera, bo coś mu się wydaje, i bardzo chce spierniczyć architekturę swojej aplikacji, przy okazji być może pogarszając wydajność, miast ją poprawić.

(/me na nadzieję, że OP się wkurzy i pójdzie po rozum do głowy, jak to przeczyta.)


@Krzych - To, co podlinkowałeś, wygląda nieźle. Powinno być lepsze od haskmapy itp.
Tylko, że nie pozwala na współdzielenie danych, a to oznacza albo przepisywanie danych każdego entity z jednego komponentu do innego per obieg pętli głównej (ał) przy zachowaniu 1:1 mapowania systemy-komponenty, albo wielu lookupów danych z innych komponentów (tylko po jednym na raz można iterować bez zachowania kolejności, chociaż ID entity nadal trzeba znać). Hashmapa będzie miała ten sam problem.

A jeszcze co do hashmapy, idealne rozwiązanie to to nie jest, ale nie musi być aż tak złe - przy założeniu, że trzymamy komponenty przez wartość. Hashmapa trzyma obiekty w wielu "kubełkach" (buckets) o określonym rozmiarze, które są ciągłe w pamięci - więc przy iteracji po wszystkich komponentach danego rodzaju (gdy kolejność nie ma znaczenia) nie będzie chyba aż tak źle, a i dostęp swobodny będzie O(rozmiar kubełka).