Introdução

No desenvolvimento de sistemas e, principalmente de jogos, é comum precisarmos que diferentes sistemas se comuniquem sem criar dependências diretas entre eles. O padrão Event Bus é uma solução para esse problema, permitindo que eventos sejam publicados e consumidos sem que os emissores conheçam diretamente os ouvintes.

Neste artigo, vou apresentar um Event Bus que desenvolvi para o meu framework de desenvolvimento de jogos, explicando sua estrutura, funcionalidades e vantagens.


Arquitetura do Event Bus

O Event Bus que desenvolvi segue uma abordagem genérica e tipada, garantindo flexibilidade e segurança no tratamento de eventos. Ele é composto por três principais componentes:

  1. Interface IEvent: Define a estrutura base para todos os eventos.
  2. EventBinding: Responsável por armazenar callbacks para eventos específicos.
  3. EventBus: Um sistema estático que gerencia o registro, a remoção e a publicação de eventos.

1. Interface IEvent

A interface IEvent serve como um contrato para garantir que todos os eventos sigam uma estrutura comum.

namespace NoxStudios.Core.EventBus
{
    public interface IEvent { }
}

Todos os eventos que desejamos publicar no Event Bus devem implementar essa interface.

2. EventBinding: Armazenando Callbacks

O EventBinding<T> gerencia as ações que serão executadas quando um evento do tipo T for disparado.

public class EventBinding<T> : IEventBinding<T> where T : IEvent
{
    private Action<T> _onEvent = _ => { };
    private Action _onEventNoArgs = () => { };

    public EventBinding(Action<T> onEvent) => _onEvent = onEvent;
    public EventBinding(Action onEventNoArgs) => _onEventNoArgs = onEventNoArgs;

    public void Add(Action<T> onEvent) => _onEvent += onEvent;
    public void Remove(Action<T> onEvent) => _onEvent -= onEvent;
    public void Add(Action onEvent) => _onEventNoArgs += onEvent;
    public void Remove(Action onEvent) => _onEventNoArgs -= onEvent;
}

Com essa classe, podemos vincular múltiplas ações a um evento específico.

3. O Event Bus: Registro, Remoção e Publicação

A classe EventBus<T> gerencia o ciclo de vida dos eventos.

public static class EventBus<T> where T : IEvent
{
    private static readonly HashSet<IEventBinding<T>> Bindings = new();

    public static void Register(EventBinding<T> binding) => Bindings.Add(binding);
    public static void Unregister(EventBinding<T> binding) => Bindings.Remove(binding);

    public static void Publish(T tEvent)
    {
        foreach (var binding in Bindings)
        {
            binding.OnEvent(tEvent);
            binding.OnEventNoArgs();
        }
    }
}

Aqui, os eventos são armazenados em um HashSet, garantindo que cada binding seja registrado apenas uma vez.


Benefícios do Event Bus

  • Desacoplamento: Componentes podem se comunicar sem depender diretamente uns dos outros.
  • Flexibilidade: Podemos adicionar e remover ouvintes dinamicamente.
  • Escalabilidade: Reduz a complexidade do código conforme o projeto cresce.
  • Reutilização: A arquitetura facilita a reutilização de código entre diferentes projetos.

Exemplo de Uso

  1. Criamos um evento:
public class PlayerDiedEvent : IEvent { }
  1. Registramos um listener:
EventBus<PlayerDiedEvent>.Register(new EventBinding<PlayerDiedEvent>(() => Debug.Log("O jogador morreu!")));
  1. Publicamos o evento:
EventBus<PlayerDiedEvent>.Publish(new PlayerDiedEvent());

Conclusão

Esse sistema de Event Bus oferece uma abordagem poderosa para a comunicação entre componentes no seu sistema, reduzindo acoplamento e melhorando a organização do código. Se bem utilizado, pode tornar a manutenção e expansão do seu sistema muito mais eficiente.

Matheus Mendes

Competências