Sistema de entidad componente: actualizaciones y pedidos de llamadas

10

Para que los componentes puedan actualizar cada marco (y dejar esta funcionalidad fuera de los componentes que no lo necesitan), tuve la idea de hacer un componente UpdateComponent. Otros componentes como MovableComponent(que tiene velocidad) heredarían de la IUpdatableclase abstracta. Esto obliga MovableComponenta implementar un Update(gametime dt)método y otro RegisterWithUpdater()que le da UpdateComponentun puntero al MovableComponent. Muchos componentes podrían hacer esto y luego UpdateComponentllamar a todos sus Update(gametime dt)métodos sin tener que preocuparse por quiénes o qué son.

Mis preguntas son:

  1. ¿Parece esto algo normal o usado por alguien? No puedo encontrar nada sobre el tema.
  2. ¿Cómo podría mantener un orden para los componentes como la física y luego el cambio de posición? ¿Es esto incluso necesario?
  3. ¿Cuáles son otras formas de asegurar que los componentes que deben procesarse en cada cuadro se procesen realmente?

EDITAR
Creo que voy a considerar cómo darle al administrador de la entidad una lista de tipos que se pueden actualizar. Luego, TODOS los componentes de ese tipo pueden actualizarse en lugar de administrarlo por entidad (que de todos modos son solo índices en mi sistema).

Todavía. Mis preguntas siguen siendo válidas para mí. No sé si esto es razonable / normal, o qué tienden a hacer los demás.

Además, ¡la gente de Insomniac es increíble! /EDITAR

Código resumido para el ejemplo anterior:

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
fuente
¿Tiene un ejemplo para un componente que no es actualizable? Eso parece bastante inútil. Coloque la función de actualización como una función virtual en el componente. Luego, simplemente actualice todos los componentes de la entidad, no necesita el "UpdateComponent"
Maik Semder
Algunos componentes son más como titulares de datos. Me gusta PositionComponent. Muchos objetos pueden tener una posición, pero si son estacionarios, lo que podría ser una mayoría, todas esas llamadas virtuales adicionales podrían acumularse. O diga un componente de salud. Eso no necesita hacer nada en cada cuadro, solo modifíquelo cuando sea necesario. Tal vez la sobrecarga no es tan mala, pero mi UpdateComponent fue un intento de no tener Update () en CADA componente.
ptpaterson
55
Un componente que solo contiene datos y ninguna funcionalidad para cambiarlo con el tiempo no es, por definición, un componente. Debe haber algo funcional en el componente que cambie con el tiempo, por lo tanto, necesita una función de actualización. Si su componente no necesita una función de actualización, entonces sabe que no es un componente y debe repensar el diseño. Una función de actualización en el componente de salud tiene sentido, por ejemplo, desea que su NPC se cure del daño durante algún tiempo.
Maik Semder
@Maik Mi punto NO fue que son componentes que nunca / nunca podrán cambiar. Estoy de acuerdo que es tonto. Simplemente no necesitan actualizar cada fotograma, más bien se les notificará para cambiar su información cuando sea necesario. En el caso de la salud a lo largo del tiempo, habría un componente de bonificación de salud que sí tiene una actualización. No creo que haya ninguna razón para combinar los dos.
ptpaterson
También me gustaría señalar que el código que publiqué solo tiene lo necesario para explicar el concepto UpdateComponent. Excluye cualquier otra forma de comunicación entre componentes.
ptpaterson

Respuestas:

16

Uno de los principales beneficios de un sistema de componentes es la capacidad de aprovechar los patrones de almacenamiento en caché: buena icache y predicción porque ejecuta el mismo código una y otra vez, buena dcache porque puede asignar los objetos en grupos homogéneos y porque las tablas v cualquiera, mantente caliente.

La forma en que ha estructurado sus componentes, esta ventaja desaparece por completo y, de hecho, puede convertirse en una responsabilidad de rendimiento en comparación con un sistema basado en herencia, ya que está haciendo muchas más llamadas virtuales y más objetos con vtables.

Lo que debe hacer es almacenar grupos por tipo e iterar cada tipo de forma independiente para realizar actualizaciones.

¿Parece esto algo normal o usado por alguien? No puedo encontrar nada sobre el tema.

No es tan común en los juegos grandes porque no es ventajoso. Es común en muchos juegos, pero técnicamente no es interesante, por lo que nadie escribe al respecto.

¿Cómo podría mantener un orden para los componentes como la física y luego el cambio de posición? ¿Es esto incluso necesario?

El código en lenguajes como C ++ tiene una forma natural de ordenar la ejecución: escriba las declaraciones en ese orden.

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

En realidad, eso no tiene sentido porque ningún sistema de física robusto está estructurado de esa manera: no se puede actualizar un solo objeto de física. En cambio, el código se vería más como:

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

fuente
Gracias. Comencé todo este proyecto con la información orientada (no es lo mismo que la información impulsada) en mente, evitando errores de caché y demás. Pero una vez que comencé a codificar, decidí hacer algo que funcionara punto com. Resulta que me hizo las cosas más difíciles. Y en serio, ¡ese artículo insomne ​​fue genial!
ptpaterson
@Joe +1 para una muy buena respuesta. Aunque me gustaría saber qué son un icache y un dcache. Gracias
Ray Dey
Caché de instrucciones y caché de datos. Cualquier texto introductorio sobre arquitectura de computadoras debería cubrirlos.
@ptpaterson: Tenga en cuenta que hay un pequeño error en la presentación de Insomniac: la clase de componente debe contener su índice de lista, o necesita una asignación separada de los índices de grupo a los índices de lista.
3

De lo que estás hablando es razonable y bastante común, creo. Esto podría darle más información.

munificente
fuente
Creo que me encontré con ese artículo hace un par de semanas. He podido codificar algunas versiones muy simples de un sistema de entidad basado en componentes, cada una muy diferente a medida que intento cosas diferentes. Tratando de reunir todos los artículos y ejemplos en algo que quiero y puedo hacer. ¡Gracias!
ptpaterson
0

Me gustan estos enfoques:

En breve: evite mantener el comportamiento de actualización dentro de los componentes. Los componentes no son comportamiento. El comportamiento (incluidas las actualizaciones) se puede implementar en algunos subsistemas de instancia única. Ese enfoque también podría ayudar a procesar por lotes comportamientos similares para múltiples componentes (tal vez usando instrucciones parallel_for o SIMD en datos de componentes).

El método IUpdatable idea y Update (gametime dt) parece demasiado restrictivo e introduce dependencias adicionales. Puede estar bien si no está utilizando el enfoque de subsistemas, pero si los utiliza, IUpdatable es un nivel de jerarquía redundante. Después de todo, el MovingSystem debe saber que debe actualizar los componentes de Ubicación y / o Velocidad directamente para todas las entidades que tienen estos componentes, por lo que no hay necesidad de algún componente intermedio IUpdatable.

Pero podría usar el componente Actualizable como un mecanismo para omitir la actualización de algunas entidades a pesar de que tengan componentes de Ubicación y / o Velocidad. El componente Actualizable podría tener un indicador de bool que se puede establecer falsey que indicaría a cada subsistema compatible con Actualizable que esta entidad en particular actualmente no debe actualizarse (aunque en ese contexto, Freezable parece ser el nombre más apropiado para el componente).

JustAMartin
fuente