Configuração
Eu tenho uma arquitetura de componente de entidade em que as entidades podem ter um conjunto de atributos (que são dados puros sem comportamento) e existem sistemas que executam a lógica da entidade que atua sobre esses dados. Essencialmente, em algum pseudocódigo:
Entity
{
id;
map<id_type, Attribute> attributes;
}
System
{
update();
vector<Entity> entities;
}
Um sistema que apenas se move ao longo de todas as entidades a uma taxa constante pode ser
MovementSystem extends System
{
update()
{
for each entity in entities
position = entity.attributes["position"];
position += vec3(1,1,1);
}
}
Essencialmente, estou tentando paralelizar update () da maneira mais eficiente possível. Isso pode ser feito executando sistemas inteiros em paralelo ou fornecendo a cada atualização () de um sistema alguns componentes para que threads diferentes possam executar a atualização do mesmo sistema, mas para um subconjunto diferente de entidades registradas nesse sistema.
Problema
No caso do MovementSystem mostrado, a paralelização é trivial. Como as entidades não dependem umas das outras e não modificam os dados compartilhados, poderíamos simplesmente mover todas as entidades em paralelo.
No entanto, esses sistemas às vezes exigem que as entidades interajam (leiam / gravem dados de / para) umas às outras, às vezes dentro do mesmo sistema, mas geralmente entre sistemas diferentes que dependem uns dos outros.
Por exemplo, em um sistema de física, algumas vezes as entidades podem interagir umas com as outras. Dois objetos colidem, suas posições, velocidades e outros atributos são lidos a partir deles, são atualizados e, em seguida, os atributos atualizados são gravados novamente nas duas entidades.
E antes que o sistema de renderização no mecanismo possa iniciar a renderização de entidades, é necessário aguardar que outros sistemas concluam a execução para garantir que todos os atributos relevantes sejam o que precisam ser.
Se tentarmos paralelizar cegamente isso, isso levará a condições clássicas de corrida, nas quais diferentes sistemas podem ler e modificar dados ao mesmo tempo.
Idealmente, existiria uma solução em que todos os sistemas possam ler dados de qualquer entidade que desejarem, sem ter que se preocupar com outros sistemas modificando esses mesmos dados ao mesmo tempo e sem que o programador se preocupe em ordenar adequadamente a execução e paralelização de esses sistemas manualmente (o que às vezes nem é possível).
Em uma implementação básica, isso pode ser conseguido colocando todas as leituras e gravações de dados em seções críticas (protegendo-as com mutexes). Mas isso induz uma grande quantidade de sobrecarga de tempo de execução e provavelmente não é adequado para aplicativos sensíveis ao desempenho.
Solução?
Em minha opinião, uma solução possível seria um sistema onde a leitura / atualização e gravação de dados são separadas, de modo que, em uma fase cara, os sistemas apenas leiam dados e calculem o que precisam calcular, de alguma forma, armazenem em cache os resultados e depois escrevam todos os dados alterados de volta para as entidades de destino em um passe de gravação separado. Todos os sistemas atuariam com os dados no estado em que estavam no início do quadro e, em seguida, antes do final do quadro, quando todos os sistemas terminassem de atualizar, uma passagem de gravação serializada acontecerá onde o cache resulta de todos os diferentes os sistemas são iterados e gravados de volta para as entidades de destino.
Isso se baseia na (talvez errada?) Idéia de que a vitória fácil da paralelização poderia ser grande o suficiente para superar o custo (tanto em termos de desempenho de tempo de execução quanto de sobrecarga de código) do cache de resultados e da passagem de gravação.
A questão
Como esse sistema pode ser implementado para alcançar o desempenho ideal? Quais são os detalhes de implementação desse sistema e quais são os pré-requisitos para um sistema Entity-Component que deseja usar esta solução?