unity,

Comparando delegates e Unity Events

Bruno Cicanci Bruno Cicanci Seguir 9 de Setembro de 2018 · 5 min de leitura
Comparando delegates e Unity Events
Compartilhe

No meu vídeo sobre um padrão para enviar mensagens na Unity utilizando delegate tive um comentário perguntando se não seria melhor utilizar o UnityEvents por questões de performance. Eu respondi o comentário dizendo que não era um problema tão grande, até por que o envio de mensangens não é algo que quero fazer o tempo todo, mas fiquei pensando sobre outro ponto que comentei em relação a flexibilidade. Decidi fazer este post comparando o código do meu post com o UnityEvents.

Você pode ver o código completo da classe MessageManager no meu post, mas vou postar um dos métodos aqui para ficar mais fácil de escrever sobre ele. Este é o SendMessage(), responsável por executar o DynamicInvoke, que usa reflection para resolver o tipo do delegate e aloca memória pra isso. Neste link do StackOverflow existe uma boa explicação comparando o DynamicInvoke com o Invoke, porém para a minha classe MessageManager precisei utilizá-lo sacrificando um pouco de performance por mais flexibilidade, além de deixar o código mais claro.

public void SendMessage(object message)
{
    List<Delegate> messages = null;
    if(_listeners.TryGetValue(message.GetType(), out messages))
    {
        for(int i = 0; i < messages.Count; i++)
        {
            messages[i].DynamicInvoke(message);
        }
    }
}

Para comparar com o UnityEvents criei duas classes de teste, uma para chamar ele e o MessageManager uma vez por Update e outra para escutá-los. Para deixar o resultado mais limpo no profiler eu enviei sempre o mesmo objeto que foi criado no Awake da classe.

public static UpdateTestEvent TestEvent;
private UpdateTestData _updateTestData;

private void Awake()
{
    TestEvent = new UpdateTestEvent();
    _updateTestData = new UpdateTestData { Counter = 1 };
}

private void Update()
{
    MessageManager.Instance.SendMessage(_updateTestData);
    TestEvent.Invoke(_updateTestData);
}

UpdateTestEvent e UpdateTestData são uma struct e um UnityEvent, respectivamente. Ambos MessageManager e UnityEvents enviam o mesmo objeto.

public struct UpdateTestData
{
    public int Counter;
}

public class UpdateTestEvent : UnityEvent<UpdateTestData> { }

E abaixo está o resto do código, responsável por adicionar e remover os listeners. Eu não adicionar todo do código pra não deixar o post ainda maior, mas o que falta abaixo é só a parte que soma Counter em outra variável e mostrar isso em um Text na UI.

private void OnEnable()
{
    MessageManager.Instance.AddListener<UpdateTestData>(OnUpdateTestMessage);
    TestSender.TestEvent.AddListener(OnUpdateTestEvent);
}

private void OnDisable()
{
    MessageManager.Instance.RemoveListener<UpdateTestData>(OnUpdateTestMessage);
    TestSender.TestEvent.RemoveListener(OnUpdateTestEvent);
}

private void OnUpdateTestMessage(UpdateTestData message) { }
public void OnUpdateTestEvent(UpdateTestData message) { }

Agora vamos o resultado do profiler. Eu marquei dois momentos em que há a chamada do GC.Alloc, o primeiro é relacionado ao DynamicInvoke, e o segundo não consegui entender da onde veio mas está relacionado ao SendMessage() também, alocando um total de 68 bytes por cada vez que o MessageManager é usado para enviar um objeto e demora 0.03 milisegundos. Já o UnityEvents não está alocando nada de memória extra e nem demora para ser executado.

Então isso quer dizer que o UnityEvents é o melhor para este caso? Depende. Evitar usar um delegate com DynamicInvoke para enviar um objeto como mensagem de vez em quando não vai causar impacto suficiente para reduzir o uso de memória ou aumentar o FPS do seu jogo. Neste exemplo, e no exemplo do meu vídeo, o uso do UnityEvents é praticamente o mesmo que o MessageManager, então seria um bom caso para evitar o DynamicInvoke desnecessário.

Eu não consegui pensar em um exemplo onde o meu código se encaixaria melhor do que o UnityEvents, mas como escrevi no outro post meu objetivo era mostrar uma implementação do design patter Observer. É importante entender como os padrões funcionam para saber qual pode ser útil em uma determinada situação, mesmo que seja para usar uma “solução pronta” como o UnityEvents.

Gostou do post?
Como muitos programadores, aprecio um bom café. Se curtiu este conteúdo, que tal me presentear com um café?
Bruno Cicanci
Escrito por Bruno Cicanci Seguir
Bacharel em Ciência da Computação e pós-graduado em Produção e Programação de Jogos. Atuo profissionalmente com desenvolvimento de jogos desde 2010. Já trabalhei na Glu Mobile, Electronic Arts, 2Mundos, Aquiris, e atualmente na Ubisoft em Londres. Escrevo neste blog desde 2009.