Система компонентов объекта - Обновления и заказы вызовов

10

Для того чтобы компоненты могли обновлять каждый кадр (и исключать эту функциональность из компонентов, которые не нужны), у меня появилась идея создать компонент UpdateComponent. Другие компоненты, такие как MovableComponent(который содержит скорость) будут наследоваться от IUpdatableабстрактного класса. Это заставляет MovableComponentреализовать Update(gametime dt)метод и другой, RegisterWithUpdater()который дает UpdateComponentуказатель на MovableComponent. Многие компоненты могут сделать это, а затем UpdateComponentмогут вызывать все свои Update(gametime dt)методы, не заботясь о том, кто или что они есть.

Мои вопросы:

  1. Кажется ли это чем-то нормальным или кем-то используемым? Я не могу найти что-нибудь по этому вопросу.
  2. Как я могу поддерживать порядок для таких компонентов, как физика, а затем менять положение? Это даже необходимо?
  3. Каковы другие способы обеспечения того, чтобы компоненты, которые должны обрабатываться в каждом кадре, действительно обрабатывались?

РЕДАКТИРОВАТЬ
Я думаю, что я решу, как дать менеджеру сущностей список типов, которые можно обновлять. Тогда ВСЕ компоненты этого типа могут обновлять, а не управлять ими для каждой сущности (которые в любом случае являются просто индексами в моей системе).

По-прежнему. Мои вопросы остаются в силе для меня. Я не знаю, является ли это разумным / нормальным или что другие склонны делать.

Кроме того, люди в Insomniac потрясающие! /РЕДАКТИРОВАТЬ

Код выкипания для предыдущего примера:

class IUpdatable
{
public:
    virtual void Update(float dt) = 0;
protected:
    virtual void RegisterAsUpdatable() = 0;
};

class Component
{
    ...
};

class MovableComponent: public Component, public IUpdatable
{
public:
    ...
    virtual void Update(float dt);
private:
    ...
    virtual void RegisterWithUpdater();
};

class UpdateComponent: public Component
{
public:
    ...
    void UpdateAll();
    void RegisterUpdatable(Component* component);
    void RemoveUpdatable(Component* component);
private:
    ...
    std::set<Component*> updatables_;
};
ptpaterson
источник
У вас есть пример для компонента, который не обновляется? Это кажется довольно бесполезным. Поместите функцию обновления как виртуальную функцию в компонент. Затем просто обновите все компоненты в сущности, нет необходимости в "UpdateComponent"
Maik Semder
Некоторые компоненты больше похожи на держатели данных. Как PositionComponent. Многие объекты могут иметь положение, но если они являются стационарными, что может составлять большинство, все эти дополнительные виртуальные вызовы могут накапливаться. Или, скажем, HealthComponent. Для этого не нужно ничего делать в каждом кадре, просто измените его, когда это необходимо. Возможно, накладные расходы не так уж и плохи, но мой UpdateComponent был попыткой не иметь Update () в КАЖДОМ компоненте.
ptpaterson
5
Компонент, который содержит только данные и не имеет функций для их изменения во времени, по определению не является компонентом. В компоненте должны присутствовать некоторые функциональные возможности, которые со временем меняются и, следовательно, нуждаются в функции обновления. Если ваш компонент не нуждается в функции обновления, то вы знаете, что это не компонент, и вам следует переосмыслить дизайн. Функция обновления в компоненте здоровья имеет смысл, например, вы хотите, чтобы ваш NPC излечивался от повреждений в течение некоторого времени.
Maik Semder
@Maik Моя точка зрения была НЕ в том, что это компоненты, которые никогда не изменятся. Я согласен, что это глупо. Им просто не нужно обновлять каждый фрейм, а получать уведомления об изменении их информации при необходимости. В случае здоровья с течением времени, будет компонент бонуса здоровья, который ДОЛЖЕН иметь обновление. Я не думаю, что есть какая-либо причина, чтобы объединить оба.
ptpaterson
Я также хотел бы отметить, что код, который я разместил, содержит только то, что необходимо для объяснения концепции UpdateComponent. Это исключает все другие формы связи между компонентами.
ptpaterson

Ответы:

16

Одним из основных преимуществ системы компонентов является возможность воспользоваться шаблонами кэширования - хороший icache и прогнозирование, потому что вы выполняете один и тот же код снова и снова, хороший dcache, потому что вы можете размещать объекты в однородных пулах и потому, что виртуальные таблицы, если любой, останься горячим.

То, как вы структурировали свои компоненты, это преимущество полностью исчезает, и фактически может стать причиной снижения производительности по сравнению с системой на основе наследования, поскольку вы делаете гораздо больше виртуальных вызовов и больше объектов с помощью vtables.

Что вы должны делать, это хранить пулы для каждого типа и выполнять итерацию каждого типа независимо для выполнения обновлений.

Кажется ли это чем-то нормальным или кем-то используемым? Я не могу найти что-нибудь по этому вопросу.

Это не так часто встречается в больших играх, потому что это не выгодно. Это часто встречается во многих играх, но технически не интересно, поэтому никто не пишет об этом.

Как я могу поддерживать порядок для таких компонентов, как физика, а затем менять положение? Это даже необходимо?

Код в таких языках, как C ++, имеет естественный способ упорядочения выполнения: введите операторы в таком порядке.

for (PhysicsComponent *c : physics_components)
    c->update(dt);
for (PositionComponent *c : position_components)
    c->update(dt);

На самом деле это не имеет смысла, потому что ни одна надежная физическая система не структурирована таким образом - вы не можете обновить один физический объект. Вместо этого код будет выглядеть примерно так:

physics_step();
for (PositionComponent *c : position_components)
    c->update(dt);
// Updates the position data from the physics data.

источник
Спасибо. Я начал весь этот проект, ориентируясь на данные (а не на данные), предотвращая ошибки кэширования и тому подобное. Но как только я начал писать код, я решил просто сделать что-то, что сработало бы dot com. Оказывается, это усложнило мне жизнь. А если серьезно, эта статья бессонница была отличной!
ptpaterson
@Joe +1 за очень хороший ответ. Хотя я хотел бы знать, что такое icache и dcache. Спасибо
Рэй Дей
Кеш инструкций и кеш данных. Любой вводный текст по компьютерной архитектуре должен охватывать их.
@ptpaterson: Обратите внимание, что в представлении Insomniac есть небольшая ошибка: класс компонента должен содержать свой индекс реестра, или вам необходимо отдельное сопоставление индексов пула с индексами реестра.
3

То, о чем вы говорите, является разумным и довольно распространенным, я думаю. Это может дать вам больше информации.

необычайно щедрый
источник
Я думаю, что наткнулся на эту статью пару недель назад. Мне удалось кодировать вместе несколько очень простых версий основанной на компонентах системы сущностей, каждая из которых сильно отличалась, когда я пробовал разные вещи. Пытаюсь собрать все статьи и примеры в то, что я хочу и могу сделать. Спасибо!
ptpaterson
0

Мне нравятся эти подходы:

Кратко: избегайте сохранения поведения обновления внутри компонентов. Компоненты не являются поведением. Поведение (включая обновления) может быть реализовано в некоторых подсистемах одного экземпляра. Этот подход также может помочь в пакетной обработке аналогичного поведения для нескольких компонентов (возможно, с использованием инструкций parallel_for или SIMD для данных компонентов).

Метод IUpdatable idea and Update (gametime dt) кажется слишком ограничительным и вводит дополнительные зависимости. Это может быть хорошо, если вы не используете подход подсистем, но если вы используете их, то IUpdatable является избыточным уровнем иерархии. В конце концов, MovingSystem должна знать, что она должна обновлять компоненты Location и / или Velocity напрямую для всех объектов, которые имеют эти компоненты, поэтому нет необходимости в некотором промежуточном компоненте IUpdatable.

Но вы можете использовать компонент Updatable в качестве механизма пропуска обновления некоторых объектов, несмотря на то, что они имеют компоненты Location и / или Velocity. У компонента Updatable может быть флаг bool, который может быть установлен falseи который будет сигнализировать каждой подсистеме, поддерживающей обновление, о том, что данный конкретный объект в настоящее время не должен обновляться (хотя в таком контексте Freezable представляется более подходящим именем для компонента).

JustAMartin
источник