Comparando delegates e Unity Events

Comparando delegates e Unity Events

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.

comments powered by Disqus