Lumberyard
Guía del usuario (Version 1.18)

Buses de eventos en profundidad

Los buses de eventos (o EBus, abreviado) son un sistema general para enviar mensajes. Los Ebuses tienen muchas ventajas:

  • Abstracción: reduzca al mínimo las dependencias fuertes entre sistemas.

  • Programación basada en eventos: elimine los patrones de sondeo para obtener un software más escalable y de alto rendimiento.

  • Código de aplicación más limpio: envíe mensajes con seguridad sin preocuparse de qué los controla o si los están controlando.

  • Simultaneidad: pone en cola eventos de varios subprocesos para una ejecución segura en otro subproceso o para aplicaciones de sistema distribuidas.

  • Previsibilidad: proporciona asistencia para solicitar controladores en un bus determinado.

  • Depuración: intercepta mensajes para informar, crear perfiles y realizar labores de introspección.

Puede utilizar EBuses de muchas maneras diferentes. A continuación se muestran algunos ejemplos:

  • Como una llamada de función global directa

  • Para enviar procesamiento a varios controladores

  • Para poner en cola todas las llamadas, actuando como un búfer de comandos

  • Como un buzón al que se pueden dirigir solicitudes

  • Para la entrega forzosa

  • Para la entrega con colas

  • Para la clasificación automática de una llamada de función en un mensaje de red u otro búfer de comandos

El código fuente del EBus se puede encontrar en la ubicación del directorio de Lumberyard <root>\dev\Code\Framework\AZCore\AZCore\EBus\EBus.h.

Configuraciones de bus

Puede configurar EBuses para varios patrones de uso. En esta sección se presentan configuraciones comunes y sus aplicaciones.

Controlador único

La configuración más sencilla es un bus de comunicación de muchos a uno (o a cero), muy parecida a un patrón singleton.


                    Patrón de muchos a uno

Hay como máximo un controlador al que puede enviar eventos cualquier remitente. Los remitentes no tienen por qué comprobar ni eliminar referencias de punteros. Si no hay ningún controlador conectado al bus, el evento se pasa por alto simplemente.

// One handler is supported. static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; // The EBus uses a single address. static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;

Muchos controladores

Otra configuración habitual es una en la que puede haber presentes muchos controladores. Puede utilizar esta configuración para implementar patrones de observador, suscripciones a eventos de sistema o a una difusión de uso general.


                    Muchos controladores

Los eventos que se envían a los controladores se pueden recibir en un orden definido o indefinido. Puede especificar cuál de los dos en la característica HandlerPolicy.

Ejemplo sin clasificación de controladores

Para administrar eventos sin un orden específico, solo tiene que utilizar la palabra clave Multiple en la característica HandlerPolicy, como en el siguiente ejemplo:

// Multiple handlers. Events received in undefined order. static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; // The EBus uses a single address. static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;

Ejemplo con clasificación de controladores

Para administrar los eventos en un orden específico, utilice la palabra clave MultipleAndOrdered en la característica HandlerPolicy y, a continuación, implemente una función de clasificación de controladores personalizada, como en el siguiente ejemplo:

// Multiple handlers. Events received in defined order. static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::MultipleAndOrdered; // The EBus uses a single address. static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; // Implement a custom handler-ordering function struct BusHandlerOrderCompare : public AZStd::binary_function<MyBusInterface*, MyBusInterface*, bool> { AZ_FORCE_INLINE bool operator()(const MyBusInterface* left, const MyBusInterface* right) const { return left->GetOrder() < right->GetOrder(); } };

EBus con direcciones y un único controlador

Los EBuses también son compatibles con el direccionamiento basado en un ID personalizado. Los eventos dirigidos a un ID se reciben por parte de los controladores conectados a dicho ID. Si se difunde un evento sin ID, lo reciben los controladores de todas las direcciones.

Un uso común para este enfoque es la comunicación entre los componentes de una única entidad o entre componentes de una entidad distinta pero relacionada. En este caso, el ID de la entidad es la dirección.


                    Direccionamiento en función de ID específicos

Ejemplo sin clasificación de direcciones

En el siguiente ejemplo, los mensajes difundidos con un ID llegan a cada dirección sin un orden específico.

// One handler per address is supported. static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; // The EBus has multiple addresses. Addresses are not ordered. static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; // Messages are addressed by EntityId. using BusIdType = AZ::EntityId;

Ejemplo con clasificación de direcciones

En el siguiente ejemplo, los mensajes difundidos con un ID llegan a cada dirección con un orden específico.

// One handler per address is supported. static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; // The EBus has multiple addresses. Addresses are ordered. static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ByIdAndOrdered; // Messages are addressed by EntityId. using BusIdType = AZ::EntityId; // Addresses are ordered by EntityId. using BusIdOrderCompare = AZStd::greater<BusIdType>;

EBus con direcciones y muchos controladores

En la configuración anterior, solo se permite un controlador por dirección. Esto suele ser deseable para forzar la propiedad de un EBus para un ID específico, como en el caso del singleton que aparece anteriormente. Sin embargo, si quiere contar con más de un controlador por dirección, puede configurar el EBus correspondientemente:


                    Más de un controlador por dirección

Ejemplo: sin clasificación de direcciones

En el siguiente ejemplo, los mensajes difundidos con un ID llegan a cada dirección sin un orden específico. En cada dirección, el orden en el que los controladores reciben el mensaje se define mediante EBusHandlerPolicy, que en este ejemplo es sencillamente ById:

// Allow any number of handlers per address. static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; // The EBus has multiple addresses. Addresses are not ordered. static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; // Messages are addressed by EntityId. using BusIdType = AZ::EntityId;

Ejemplo: con clasificación de direcciones

En el siguiente ejemplo, los mensajes difundidos con un ID llegan a cada dirección con un orden específico. En cada dirección, el orden en el que los controladores reciben el mensaje se define mediante la EBusHandlerPolicy, que en este ejemplo es sencillamente ByIdAndOrdered.

// Allow any number of handlers per address. static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; // The EBus has multiple addresses. Addresses are ordered. static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ByIdAndOrdered; // We address the bus EntityId. using BusIdType = AZ::EntityId; // Addresses are ordered by EntityId. using BusIdOrderCompare = AZStd::greater<BusIdType>;

Políticas de Asíncrono

EBus es compatible tanto con mensajería síncrona como asíncrona (en cola).

Mensajería síncrona

Los mensajes síncronos se envían a todos y cada uno de los controladores cuando se invoca un evento de EBus. Los mensajes síncronos limitan las oportunidades para la programación asíncrona, pero ofrecen los siguientes beneficios:

  • No necesitan almacenar un cierre. Los argumentos se envían directamente a los remitentes.

  • Permiten recuperar un resultado inmediato desde un controlador (valor de devolución de evento).

  • No tienen latencia.

Mensajería asíncrona

Los mensajes asíncronos tienen las siguientes ventajas:

  • Crean muchas más oportunidades de paralelismos y son más resistentes a los cambios futuros.

  • Permiten colocar en cola mensajes desde cualquier subproceso y los envía en un subproceso seguro (como el subproceso principal, o cualquier subproceso que elija).

  • El código que se utiliza para escribirlos es inherentemente tolerante a la latencia y se puede migrar fácilmente a los modelos de actores y otros sistemas distribuidos.

  • El desempeño del código que inicia los eventos no depende de la eficacia del código que administra los eventos.

  • En el código esencial para el desempeño, los mensajes asíncronos pueden mejorar el desempeño del i-cache y el d-cache porque requieren menos llamadas de función virtuales.

Para obtener información acerca de cómo declarar un EBus para poner en cola y enviar mensajes de forma asíncrona, consulte Buses asíncronos/en cola.

Características adicionales

Los EBuses contienen otras características que abordan varios patrones y casos de uso:

  • Guardar en caché un puntero al que puedan enviarse mensajes: útil para EBuses que tengan ID. En lugar de buscar la dirección del EBus por su ID para cada evento, puede utilizar el puntero que está en caché para enviar los mensajes con mayor rapidez.

  • Guardar en cola cualquier función que se pueda llamar en un EBus: cuando utilice los mensajes en cola, puede poner en cola una función Lambda o una función enlazada con un EBus para ejecutarlo en otro subproceso. Esto es útil para colocar elementos generales en cola con seguridad.