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?
źródło
Słyszałem o ciekawym rozwiązaniu tego problemu: Chodzi o to, że byłyby 2 kopie danych bytu (marnotrawstwo, wiem). Jedna kopia byłaby kopią obecną, a druga kopią przeszłą. Obecna kopia jest przeznaczona wyłącznie do zapisu, a poprzednia kopia jest przeznaczona tylko do odczytu. Zakładam, że systemy nie chcą pisać do tych samych elementów danych, ale jeśli tak nie jest, systemy te powinny mieć ten sam wątek. Każdy wątek miałby dostęp do zapisu do obecnych kopii wzajemnie wykluczających się części danych, a każdy wątek miałby dostęp do odczytu do wszystkich poprzednich kopii danych, a zatem może aktualizować obecne kopie przy użyciu danych z poprzednich kopii bez zamykający. Pomiędzy poszczególnymi ramkami obecna kopia staje się kopią przeszłą, jednak chcesz poradzić sobie z zamianą ról.
Ta metoda usuwa również warunki wyścigu, ponieważ wszystkie systemy będą działać ze starym stanem, który nie zmieni się przed / po przetworzeniu przez system.
źródło
Znam 3 projekty oprogramowania obsługujące równoległe przetwarzanie danych:
Oto kilka przykładów każdego podejścia, które można zastosować w systemie encji:
CollisionSystem
że czytaPosition
iRigidBody
składniki i powinien zaktualizowaćVelocity
. Zamiast manipulowaćVelocity
bezpośrednio,CollisionSystem
zamiast tego umieści aCollisionEvent
w kolejce roboczej anEventSystem
. To wydarzenie będzie następnie przetwarzane sekwencyjnie z innymi aktualizacjamiVelocity
.EntitySystem
Definiuje zestaw składników, które potrzebuje do odczytu i zapisu. Dla każdego zEntity
nich uzyska blokadę odczytu dla każdego komponentu, który chce odczytać, oraz blokadę zapisu dla każdego komponentu, który chce zaktualizować. W ten sposób każdyEntitySystem
będzie mógł jednocześnie odczytywać komponenty podczas synchronizacji operacji aktualizacji.MovementSystem
,Position
komponent jest niezmienny i zawiera numer wersji .MovementSystem
Savely czytaPosition
iVelocity
komponent i oblicza nowyPosition
, zwiększając odczytu wersji numer i próbuje aktualizującePosition
składnik. W przypadku równoczesnej modyfikacji, struktura wskazuje to na aktualizację iEntity
zostanie ponownie umieszczona na liście podmiotów, które muszą zostać zaktualizowane przezMovementSystem
.W zależności od systemów, jednostek i częstotliwości aktualizacji każde podejście może być dobre lub złe. Struktura systemu encji może pozwolić użytkownikowi wybierać między tymi opcjami, aby poprawić wydajność.
Mam nadzieję, że mógłbym dodać kilka pomysłów do dyskusji i proszę o informację, czy są jakieś wiadomości na ten temat.
źródło