Installieren
Ich habe eine Entitätskomponentenarchitektur, in der Entitäten eine Reihe von Attributen haben können (die reine Daten ohne Verhalten sind), und es gibt Systeme, die die Entitätslogik ausführen, die auf diese Daten einwirken. Im Wesentlichen in etwas Pseudocode:
Entity
{
id;
map<id_type, Attribute> attributes;
}
System
{
update();
vector<Entity> entities;
}
Ein System, das sich nur mit einer konstanten Geschwindigkeit entlang aller Entitäten bewegt, könnte es sein
MovementSystem extends System
{
update()
{
for each entity in entities
position = entity.attributes["position"];
position += vec3(1,1,1);
}
}
Im Wesentlichen versuche ich, update () so effizient wie möglich zu parallelisieren. Dies kann erreicht werden, indem ganze Systeme parallel ausgeführt werden oder indem jedem Update () eines Systems mehrere Komponenten zugewiesen werden, damit verschiedene Threads das Update desselben Systems ausführen können, jedoch für eine andere Teilmenge von Entitäten, die bei diesem System registriert sind.
Problem
Im Fall des gezeigten MovementSystems ist die Parallelisierung trivial. Da Entitäten nicht voneinander abhängig sind und keine gemeinsam genutzten Daten ändern, können wir einfach alle Entitäten parallel verschieben.
Diese Systeme erfordern jedoch manchmal, dass Entitäten miteinander interagieren (Daten von / nach lesen / schreiben), manchmal innerhalb desselben Systems, aber häufig zwischen verschiedenen Systemen, die voneinander abhängen.
Beispielsweise können in einem Physiksystem Entitäten manchmal miteinander interagieren. Zwei Objekte kollidieren, ihre Positionen, Geschwindigkeiten und andere Attribute werden von ihnen gelesen, aktualisiert und dann werden die aktualisierten Attribute in beide Entitäten zurückgeschrieben.
Bevor das Rendering-System in der Engine mit dem Rendern von Entitäten beginnen kann, muss es warten, bis andere Systeme die Ausführung abgeschlossen haben, um sicherzustellen, dass alle relevanten Attribute den Anforderungen entsprechen.
Wenn wir versuchen, dies blind zu parallelisieren, führt dies zu klassischen Rennbedingungen, bei denen verschiedene Systeme gleichzeitig Daten lesen und ändern können.
Im Idealfall gibt es eine Lösung, bei der alle Systeme Daten von beliebigen Entitäten lesen können, ohne sich Sorgen machen zu müssen, dass andere Systeme dieselben Daten gleichzeitig ändern, und ohne dass der Programmierer sich um die ordnungsgemäße Anordnung der Ausführung und Parallelisierung von kümmert diese Systeme manuell (was manchmal gar nicht möglich ist).
In einer grundlegenden Implementierung könnte dies erreicht werden, indem einfach alle Datenlesevorgänge und -schreibvorgänge in kritischen Abschnitten abgelegt werden (wobei sie mit Mutexen geschützt werden). Dies führt jedoch zu einem hohen Laufzeitaufwand und ist wahrscheinlich nicht für leistungsempfindliche Anwendungen geeignet.
Lösung?
Meiner Meinung nach wäre eine mögliche Lösung ein System, bei dem das Lesen / Aktualisieren und Schreiben von Daten getrennt ist, sodass Systeme in einer teuren Phase nur Daten lesen und berechnen, was sie zum Berechnen benötigen, die Ergebnisse irgendwie zwischenspeichern und dann alle schreiben Die geänderten Daten werden in einem separaten Schreibdurchlauf an die Zielentitäten zurückgesendet. Alle Systeme würden auf die Daten in dem Zustand reagieren, in dem sie sich am Anfang des Rahmens befanden, und dann vor dem Ende des Rahmens, wenn alle Systeme die Aktualisierung abgeschlossen haben, wird ein serialisierter Schreibdurchlauf durchgeführt, bei dem die zwischengespeicherten Ergebnisse aus allen unterschiedlichen Ergebnissen resultieren Systeme werden durchlaufen und in die Zielentitäten zurückgeschrieben.
Dies basiert auf der (möglicherweise falschen?) Idee, dass der einfache Parallelisierungsgewinn groß genug sein könnte, um die Kosten (sowohl hinsichtlich der Laufzeitleistung als auch des Code-Overheads) für das Zwischenspeichern der Ergebnisse und den Schreibdurchlauf zu übertreffen.
Die Frage
Wie könnte ein solches System implementiert werden, um eine optimale Leistung zu erzielen? Was sind die Implementierungsdetails eines solchen Systems und was sind die Voraussetzungen für ein Entity-Component-System, das diese Lösung verwenden möchte?