Comunicación entre componentes - SeriousGamesStudio/Serious-Train-2-3D GitHub Wiki

Para la comunicación entre componentes utilizaremos paso de mensajes.

Módulos de información

Entre ellos podemos diferenciar entre los mensajes y las notificaciones:

Mensaje:

Es un objeto que hereda del tipo Msg_Base el cual consta de los siguientes campos:

  • Id: es de tipo enum class MsgId, (el cual internamente es un unsigned int).
  • sender: es el identificador de la entidad que ha generado el mensaje.
  • receiver: es el identificador de la entidad a la que va dirigida el mensaje.

Además de estos campos que recibe por herencia implementa atributos que contienen la información que transporta el mensaje.
Cada objeto heredero de Msg_Base debe de añadir su identificador al enum MsgId

En función del valor de receiver podemos los siguientes casos de comunicación:

  • self (0xffffff): el mensaje va dirigido a la misma entidad que la ha producido, por lo cual se manda directamente a la cola de mensajes de la entidad.
  • broadcast (0x000000): el mensaje no tiene un receptor concreto, por lo que es enviado a todas las entidades suscritas a ese mensaje.
  • to other: cualquier otro valor implicará que ese mensaje es enviado a la entidad portadora de ese id a través de escena.

La responsabilidad del receptor el hacer casting del tipo de mensaje que espera para acceder a la información que almacena

Notificación:

No utiliza mensajes, el generador de la notificación llama directamente a los componentes suscritos a la notificación. La información ya va formateada en los parámetros de la función que sea llamada.


Idealmente los mensajes están pensados para la comunicación entre la entidad y ella misma u otras entidades, mientras que las notificaciones tienen el propósito de comunicar componentes concretos con los "Managers" de los que dependan.

Servicio de mensajería

Envío de mensajes

El servicio de mensajería se puede descomponer en dos módulos:

  • Cola de Mensajes: Es quien se encarga de almacenar los mensajes. Para que funcione con seguridad se descompone a su vez en dos submódulos:
    • messages: es la cola principal de mensajes. Guarda todos los mensajes producidos durante un tick de juego.
    • buffer: es un vector secundario, su función es almacenar los mensajes que lleguen a la entidad(o escena) mientras se está vaciando la cola principal. De esta forma evitamos ciclos.
  • Tabla de listeners: Se trata de un map donde la clave es un MsgId y el valor es un vector de listeners. Los listener son componentes los cuales se han suscrito a ese tipo de mensaje.

¿Cómo funciona el sistema?

  • El sistema dota de una interfaz para poder recibir mensajes, los cuales se introducen en la cola de mensajes correspondiente.
  • El sistema dota de una interfaz para que los componentes se puedan suscribir o desuscribir a un tipo de mensaje.
  • Entrega de mensajes: primero se comprueban si hay mensajes en el buffer, de ser así se vacían a la cola principal. A continuación se empieza a recorrer la cola de mensajes, enviando a todos los componentes suscritos al tipo de mensaje el mensaje en cuestión. Cuando se termine la cola debería haber quedado vacía.

Recepción de mensajes

La recepción de mensajes se hace centralizada un mismo método "listen(Msg_Base* msg)" el cual debe ser obligatoriamente implementado por el componente en cuestión. Este método se debe encargar de reconocer el tipo de mensaje (mediante un switch) y saber qué hacer con él.
Es reponsabilidad del componente de darse de alta a los mensajes que le interesen en el método "start()" así como de rellenar completamente el método listen(msg) con todos los mensajes a los que está o estará suscrito durante su ciclo de vida

Cómo usarlo

Crear un nuevo mensaje

En el archivo Notifications.h:

  • Añadir un identificador único:
enum class MsgId: unsigned int 
{
    EXAMPLE
}
  • Creamos el nuevo tipo de mensaje dentro del namespace Msg heredando de Msg_Base de la siguiente manera:
namespace Msg
{
    struct MsgExample:
         public Msg_Base
    {
        MsgExample(EntityId sender, EntityId reciever, ...Args/*Atributos propios*/):
            Msg_Base(MsgId::EXAMPLE, sender, reciever), /*Inicializar los atributos propios*/
        {}
        ~MsgExample(){};
         //Atributos propios del tipo de mensaje
         ExampleType a;
         ExampleType2 b;
         ...

    }
}

Mandar un mensaje

Simplemente se llama a la función sendMsg de la siguiente manera:

EntityId target;
sendMsg(new MsgExample(_myEntity->getId(), target, /*Los datos que queramos enviar*/));

Recordamos que target puede tener los siguientes valores:

  • target = Msg_Base::self -> el mensaje será enviado a la propia entidad.
  • target = Msg::broadcast -> el mensaje será enviado a todas las entidades.
  • target = someValue -> el mensaje será enviado a un entidad en concreto. Notas:
  • Si se quiere enviar un mensaje a la propia entidad es mucho más eficiente utilizar Msg::self como target en ver de volver a poner la id de la entidad.

Recibir un mensajes

Para recibir mensajes simplemente hay que implementar la virtual function listen(Msg_Base*) de la siguiente manera:

  • En el .h: virtual void listen(Msg_Base* msg);
  • En el .cpp:
void listen(Msg_Base* msg)
{
    switch(msg->id)
    {
    case MsgId::EXAMPLE:
        MsgExample* p = static_cast<MsgExample*>(msg);
        /* DO STUFF */
        break:
    }
}