Настроить
У меня есть объектно-компонентная архитектура, в которой сущности могут иметь набор атрибутов (которые являются чистыми данными без поведения), и существуют системы, которые выполняют логику сущностей, которая воздействует на эти данные. По сути, в некотором псевдокоде:
Entity
{
id;
map<id_type, Attribute> attributes;
}
System
{
update();
vector<Entity> entities;
}
Система, которая просто движется по всем объектам с постоянной скоростью, может быть
MovementSystem extends System
{
update()
{
for each entity in entities
position = entity.attributes["position"];
position += vec3(1,1,1);
}
}
По сути, я пытаюсь распараллелить update () максимально эффективно. Это можно сделать, запустив целые системы параллельно или предоставив каждому update () одной системы пару компонентов, чтобы разные потоки могли выполнять обновление одной и той же системы, но для другого подмножества объектов, зарегистрированных в этой системе.
проблема
В случае показанной MovementSystem распараллеливание тривиально. Поскольку объекты не зависят друг от друга и не изменяют общие данные, мы можем просто переместить все объекты параллельно.
Однако эти системы иногда требуют, чтобы объекты взаимодействовали (считывали / записывали данные из / в) друг с другом, иногда в одной и той же системе, но часто между различными системами, которые зависят друг от друга.
Например, в физической системе иногда сущности могут взаимодействовать друг с другом. Два объекта сталкиваются, их положения, скорости и другие атрибуты считываются из них, обновляются, а затем обновленные атрибуты записываются обратно в оба объекта.
И прежде чем система рендеринга в движке сможет начать рендеринг сущностей, она должна подождать, пока другие системы завершат выполнение, чтобы убедиться, что все соответствующие атрибуты являются такими, какими они должны быть.
Если мы попытаемся слепо распараллелить это, это приведет к классическим гоночным условиям, когда разные системы могут одновременно считывать и изменять данные.
В идеале, должно существовать решение, при котором все системы могут считывать данные из любых сущностей, которые они хотят, не беспокоясь о том, что другие системы изменяют эти же данные одновременно, и не заботясь о том, чтобы программист заботился о правильном порядке выполнения и распараллеливания эти системы вручную (что иногда даже невозможно).
В базовой реализации этого можно достичь, просто поместив все данные для чтения и записи в критические секции (защищая их мьютексами). Но это вызывает большие накладные расходы времени выполнения и, вероятно, не подходит для приложений, чувствительных к производительности.
Решение?
На мой взгляд, возможным решением будет система, в которой чтение / обновление и запись данных разделены, так что на одном дорогостоящем этапе системы только читают данные и вычисляют то, что им нужно для вычисления, каким-то образом кэшируют результаты, а затем записывают все измененные данные возвращаются к целевым объектам в отдельном проходе записи. Все системы будут работать с данными в том состоянии, в котором они находились в начале кадра, а затем до конца кадра, когда все системы завершат обновление, происходит сериализованный этап записи, когда результаты кэширования получаются из всех разных системы повторяются и записываются обратно в целевые объекты.
Это основано на (возможно, неправильной?) Идее, что выигрыш в простом распараллеливании может быть достаточно большим, чтобы превзойти стоимость (как с точки зрения производительности во время выполнения, так и накладных расходов кода) кэширования результатов и прохода записи.
Вопрос
Как такая система может быть реализована для достижения оптимальной производительности? Каковы детали реализации такой системы и каковы предпосылки для системы Entity-Component, которая хочет использовать это решение?