Ustawiać
Mam architekturę encji-komponentu, w której encje mogą mieć zestaw atrybutów (które są czystymi danymi bez zachowania) i istnieją systemy, które uruchamiają logikę encji, która działa na te dane. Zasadniczo w nieco pseudo-kodzie:
Entity
{
id;
map<id_type, Attribute> attributes;
}
System
{
update();
vector<Entity> entities;
}
Może to być system, który porusza się po wszystkich jednostkach ze stałą szybkością
MovementSystem extends System
{
update()
{
for each entity in entities
position = entity.attributes["position"];
position += vec3(1,1,1);
}
}
Zasadniczo próbuję zrównoleglać aktualizację () tak skutecznie, jak to możliwe. Można to zrobić, uruchamiając całe systemy równolegle lub przez przekazanie każdej aktualizacji () jednego systemu kilku komponentów, aby różne wątki mogły wykonać aktualizację tego samego systemu, ale dla innego podzbioru jednostek zarejestrowanych w tym systemie.
Problem
W przypadku pokazanego MovementSystem równoległość jest banalna. Ponieważ jednostki nie są od siebie zależne i nie modyfikują udostępnionych danych, moglibyśmy po prostu przenieść wszystkie jednostki równolegle.
Jednak systemy te czasami wymagają od jednostek interakcji (odczytu / zapisu danych z / do) nawzajem, czasem w tym samym systemie, ale często między różnymi systemami, które są od siebie zależne.
Na przykład w systemie fizyki byty mogą czasem oddziaływać na siebie. Dwa obiekty zderzają się, ich pozycje, prędkości i inne atrybuty są od nich odczytywane, są aktualizowane, a następnie zaktualizowane atrybuty są zapisywane z powrotem do obu jednostek.
Zanim system renderujący w silniku będzie mógł rozpocząć renderowanie jednostek, musi poczekać, aż inne systemy zakończą wykonywanie, aby upewnić się, że wszystkie odpowiednie atrybuty są tym, czym powinny być.
Jeśli spróbujemy to na ślepo zrównoważyć, doprowadzi to do klasycznych warunków wyścigu, w których różne systemy mogą jednocześnie odczytywać i modyfikować dane.
Idealnie byłoby istnieć rozwiązanie, w którym wszystkie systemy mogłyby odczytywać dane z dowolnych encji, które chcą, bez martwienia się o to, że inne systemy modyfikują te same dane w tym samym czasie i bez dbania przez programistę o właściwe zamówienie wykonania i równoległości systemy te ręcznie (co może czasem nawet nie być możliwe).
W podstawowej implementacji można to osiągnąć poprzez umieszczenie wszystkich odczytów i zapisów danych w krytycznych sekcjach (ochrona ich za pomocą muteksów). Jednak powoduje to znaczne obciążenie środowiska wykonawczego i prawdopodobnie nie nadaje się do aplikacji wrażliwych na wydajność.
Rozwiązanie?
Moim zdaniem możliwym rozwiązaniem byłby system, w którym odczytywanie / aktualizowanie i zapisywanie danych są oddzielone, tak że w jednej kosztownej fazie systemy tylko odczytują dane i obliczają to, co muszą obliczyć, w jakiś sposób buforują wyniki, a następnie zapisują wszystkie zmienione dane z powrotem do jednostek docelowych w osobnym zapisie. Wszystkie systemy działałyby na danych w stanie, w jakim znajdowały się na początku ramki, a następnie przed końcem ramki, gdy wszystkie systemy zakończyły aktualizację, następuje szeregowe przejście zapisu, w którym buforowane wyniki pochodzą z różnych systemy są iterowane i zapisywane do jednostek docelowych.
Jest to oparte na (być może błędnym?) Założeniu, że wygrana w łatwej równoległości może być wystarczająco duża, aby przewyższyć koszty (zarówno pod względem wydajności środowiska uruchomieniowego, jak i narzutu kodu) wynikającego z buforowania wyników i zapisu.
Pytanie
Jak taki system można wdrożyć, aby osiągnąć optymalną wydajność? Jakie są szczegóły wdrożenia takiego systemu i jakie są warunki wstępne dla systemu Entity-Component, który chce korzystać z tego rozwiązania?