Налаштування
У мене є архітектура компонентної сутності, в якій сутності можуть мати набір атрибутів (які є чистими даними без поведінки) і існують системи, що керують логікою сутності, яка діє на ці дані. По суті, дещо псевдокодом:
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);
}
}
По суті, я намагаюся паралелізувати оновлення () якомога ефективніше. Це можна зробити, запустивши паралельно цілі системи, або давши кожне оновлення () однієї системи пару компонентів, щоб різні потоки могли виконати оновлення тієї самої системи, але для іншого підмножини суб'єктів, зареєстрованих у цій системі.
Проблема
У випадку показаної системи MovementS паралелізація є тривіальною. Оскільки сутності не залежать один від одного і не змінюють спільних даних, ми можемо просто переміщувати всі об'єкти паралельно.
Однак ці системи іноді вимагають, щоб суб'єкти взаємодіяли (читали / записували дані з / в) один з одним, іноді в межах однієї системи, але часто між різними системами, які залежать один від одного.
Наприклад, у фізичній системі іноді сутності можуть взаємодіяти один з одним. Два об'єкти стикаються, з них зчитуються їхні позиції, швидкості та інші атрибути, оновлюються, а потім оновлені атрибути списуються до обох сутностей.
І перш ніж система візуалізації в двигуні може почати візуалізацію об'єктів, потрібно дочекатися завершення виконання інших систем, щоб переконатися, що всі відповідні атрибути є такими, якими вони повинні бути.
Якщо ми спробуємо сліпо паралелізувати це, це призведе до класичних умов перегонів, коли різні системи можуть читати та змінювати дані одночасно.
В ідеалі, існувало б рішення, де всі системи можуть читати дані від будь-яких об'єктів, які вона бажає, не турбуючись про те, щоб інші системи одночасно змінювали ці самі дані, а також програміст не піклувався про те, щоб правильно впорядкувати виконання та паралелізацію ці системи вручну (що іноді може бути навіть неможливим).
У базовій реалізації цього можна досягти, просто розмістивши всі зчитувані та записані дані в критичних розділах (захищаючи їх мютексами). Але це спричиняє велику кількість накладних витрат і, ймовірно, не підходить для програм, залежних від продуктивності.
Рішення?
На мій погляд, можливим рішенням була б система, де читання / оновлення та запис даних розділено, так що на одній дорогій фазі системи лише зчитують дані та обчислюють те, що потрібно для обчислення, якось кешують результати, а потім записують усі змінені дані повертаються цільовим об'єктам в окремий пропуск. Усі системи діятимуть на дані в тому стані, в якому вони знаходилися на початку кадру, а потім до кінця кадру, коли всі системи закінчуються оновленням, відбувається серіалізований пропуск для запису, де кешується результат усіх різних системи повторюються і записуються до цільових об'єктів.
Це ґрунтується на (може, неправильній?) Думці, що виграш від легкої паралелізації може бути достатньо великим, щоб перевершити витрати (як з точки зору виконання часу виконання, так і з точки зору коду) кешування результатів та проходження запису.
Питання
Як така система може бути впроваджена для досягнення оптимальних показників? Які деталі реалізації такої системи та які передумови для системи Entity-Component, яка хоче використовувати це рішення?