Preparar
Tengo una arquitectura de entidad-componente donde las entidades pueden tener un conjunto de atributos (que son datos puros sin comportamiento) y existen sistemas que ejecutan la lógica de la entidad que actúa sobre esos datos. Esencialmente, en algo pseudocódigo:
Entity
{
id;
map<id_type, Attribute> attributes;
}
System
{
update();
vector<Entity> entities;
}
Un sistema que simplemente se mueve a lo largo de todas las entidades a una velocidad constante podría ser
MovementSystem extends System
{
update()
{
for each entity in entities
position = entity.attributes["position"];
position += vec3(1,1,1);
}
}
Esencialmente, estoy tratando de paralelizar update () de la manera más eficiente posible. Esto se puede hacer ejecutando sistemas completos en paralelo, o dando a cada actualización () de un sistema un par de componentes para que diferentes subprocesos puedan ejecutar la actualización del mismo sistema, pero para un subconjunto diferente de entidades registradas con ese sistema.
Problema
En el caso del Movimiento mostrado, la paralelización es trivial. Como las entidades no dependen unas de otras y no modifican los datos compartidos, podríamos mover todas las entidades en paralelo.
Sin embargo, estos sistemas a veces requieren que las entidades interactúen (lean / escriban datos de / a) entre sí, a veces dentro del mismo sistema, pero a menudo entre diferentes sistemas que dependen unos de otros.
Por ejemplo, en un sistema de física a veces las entidades pueden interactuar entre sí. Dos objetos colisionan, sus posiciones, velocidades y otros atributos se leen, se actualizan y luego los atributos actualizados se vuelven a escribir en ambas entidades.
Y antes de que el sistema de representación en el motor pueda comenzar a representar entidades, debe esperar a que otros sistemas completen la ejecución para garantizar que todos los atributos relevantes sean lo que necesitan ser.
Si intentamos paralelizar ciegamente esto, dará lugar a condiciones de carrera clásicas donde diferentes sistemas pueden leer y modificar datos al mismo tiempo.
Idealmente, existiría una solución en la que todos los sistemas puedan leer datos de las entidades que deseen, sin tener que preocuparse de que otros sistemas modifiquen esos mismos datos al mismo tiempo, y sin que el programador se preocupe por ordenar correctamente la ejecución y la paralelización de estos sistemas de forma manual (que a veces puede que ni siquiera sea posible).
En una implementación básica, esto podría lograrse simplemente colocando todas las lecturas y escrituras de datos en secciones críticas (protegiéndolas con mutexes). Pero esto induce una gran cantidad de sobrecarga en tiempo de ejecución y probablemente no sea adecuado para aplicaciones sensibles al rendimiento.
¿Solución?
En mi opinión, una posible solución sería un sistema en el que la lectura / actualización y la escritura de datos estén separadas, de modo que en una fase costosa, los sistemas solo lean datos y calculen lo que necesitan para computar, de alguna manera guarden en caché los resultados y luego escriban todos los datos modificados vuelven a las entidades de destino en un pase de escritura separado. Todos los sistemas actuarían sobre los datos en el estado en que se encontraban al principio del marco, y luego antes del final del marco, cuando todos los sistemas hayan terminado de actualizarse, se produce un pase de escritura serializado donde el caché resulta de todos los diferentes Los sistemas se repiten y se vuelven a escribir en las entidades de destino.
Esto se basa en la idea (¿quizás errónea?) De que la ganancia de paralelización fácil podría ser lo suficientemente grande como para superar el costo (tanto en términos de rendimiento de tiempo de ejecución como de sobrecarga de código) del almacenamiento en caché de resultados y el pase de escritura.
La pregunta
¿Cómo podría implementarse un sistema de este tipo para lograr un rendimiento óptimo? ¿Cuáles son los detalles de implementación de dicho sistema y cuáles son los requisitos previos para un sistema de entidad-componente que quiere usar esta solución?