Por que o Unity usa a reflexão para obter o método de atualização?
12
Por que o Unity usar a reflexão para acessar MonoBehaviourmétodos de mensagens, como Awake, Update, Start, ...?
Não seria lento usar a reflexão? Por que ele não utiliza outras abordagens como Template Method? Ele poderia simplesmente definir os métodos como abstratos na MonoBehaviourclasse base e forçar as subclasses a implementá-las.
Não, o Unity não usa System.Reflection para encontrar um método mágico toda vez que precisar chamar um.
Em vez disso, na primeira vez que um MonoBehaviour de um determinado tipo é acessado, o script subjacente é inspecionado por meio do tempo de execução do script (Mono ou IL2CPP), se ele possui métodos mágicos definidos e essas informações são armazenadas em cache. Se um MonoBehaviour tiver um método específico, ele será adicionado a uma lista adequada; por exemplo, se um script tiver o método Update definido, ele será adicionado a uma lista de scripts que precisam ser atualizados a cada quadro.
Durante o jogo, o Unity apenas percorre essas listas e executa métodos a partir dela - simples assim. Além disso, é por isso que não importa se o seu método Update é público ou privado.
Quanto aos motivos pelos quais é feito dessa maneira, em grande parte o encaminharei (o leitor) à resposta do DMGregory , que se resume a um saldo de duas coisas concorrentes:
Otimização de performance
Facilidade de utilização de novos desenvolvedores
Um novo desenvolvedor quer apenas que funcione e não precisa descobrir "como faço para conectar isso ao sistema de eventos?" mas ainda deve correr rápido com o mínimo de sobrecarga.
A solução é provavelmente a melhor que pode ser alcançada dentro dessas duas restrições. Ou pelo menos, o melhor que a equipe de desenvolvedores do Unity conseguiu criar na época. Nós podemos nunca saber.
tl; dr mesmo processo, API diferente e mais rápida. Mas por que eles não escolheram um método diferente, conforme solicitado pelo OP?
Wondra
10
Não devemos negar a possibilidade de uma simples razão antiga de "mau design". Após o lançamento do código-fonte do c #, os usuários encontraram muitos erros e decisões inexplicáveis de design. Alguns gamedevs finalmente conseguiram entender os comportamentos inesperados com os quais lutavam há anos.
Exerion 29/10
Além dos aspectos técnicos, essa é uma das primeiras coisas que os recém-chegados aprendem, por isso deve ser fácil adicionar ou pular.
Nikaas 29/10/19
6
Também pode haver razões históricas para esse design. O Unity começou com sua própria linguagem de script, que eles gradualmente abandonaram em favor do C #. É natural que eles quisessem continuar usando os mesmos padrões, mesmo que isso significasse trapacear um pouco.
Philipp
3
Penso que podemos responder com utilidade "O que a Unidade realiza por este meio" - por exemplo. qual é a vantagem sobre uma coleção de métodos públicos abstratos que devem ser implementados. Não podemos responder "o que os desenvolvedores da Unidade estavam pensando?" mas podemos abordar "por que essa abordagem é útil para os usuários do mecanismo?"
DMGregory
10
Ele poderia simplesmente definir os métodos como abstratos na classe base MonoBehaviour e forçar as subclasses a implementá-las.
Uma das vantagens do sistema que o Unity usa é que você não precisa implementar todas as mensagens do MonoBehaviour, das quais existem mais de 60 .
Normalmente, precisamos apenas de um ou alguns desses métodos. Como alguns são específicos da física 2D / 3D, outros exclusivos para câmeras ou sistemas de partículas, é altamente improvável que qualquer script precise de todos eles.
Ao deixar as mensagens que não precisamos como não implementadas, podemos sinalizar claramente para o tempo de execução "nem se preocupe em chamar OnCollisionStay2D nesse objeto - não preciso disso"
Isso pode ser um ganho substancial em eficiência. Agora, o mecanismo pode filtrar uma lista curta para chamar Atualização apenas nos ~ 20 objetos em minha cena que precisam de lógica de atualização personalizada a cada quadro, e nem todos os ~ 200 objetos que compõem todo o cenário ou respondem apenas a eventos de física.
Apenas uma pergunta ... até onde eu sei (não pessoalmente), se você possui milhares de GameObjects, todos os quais usam um script com o método Update, você tem impactos significativos no desempenho. A alternativa é usar listas e iterar através delas com apenas um método Update em um script de gerente, que parece ter um desempenho significativamente maior. Ainda é esse o caso, ou isso já mudou? - Se nada mudasse, isso implicaria que a maneira da Unity fazer isso não está nem perto do ideal em relação ao desempenho.
Batalha
4
@Battle, o que você descreve é consistente com o link que Draco18s cita em 2015, e eu suspeito que ainda seja verdade. Ser capaz de deixar o método Update indefinido nesses milhares de objetos é precisamente o que permite que essa otimização funcione. Se todo MonoBehaviour tivesse um método Update definido por meio de uma classe base abstrata, conforme descrito na seção da pergunta que citei, não teríamos a capacidade de limitar nossas chamadas de atualização apenas à iteração da lista do gerente. Observe que estas perguntas e respostas não abordam "A abordagem da Unity é ideal?" mas apenas como a abordagem atual pode ser benéfica.
DMGregory
Ah, entendo, isso realmente faz sentido. Obrigado pela resposta!
Battle
@ Battle Ainda é verdade. Enquanto o que o Unity faz é mais eficiente do que as alternativas, ele ainda possui mais sobrecarga do que uma chamada de função regular.
Draco18s não confia mais no SE
@ Draco18s - Sim. Eu já implementei um UpdateManager para meus projetos, que cuida dessa otimização há muito tempo. Eu só queria uma confirmação de que ainda é necessário / útil.
Uma das vantagens do sistema que o Unity usa é que você não precisa implementar todas as mensagens do MonoBehaviour, das quais existem mais de 60 .
Normalmente, precisamos apenas de um ou alguns desses métodos. Como alguns são específicos da física 2D / 3D, outros exclusivos para câmeras ou sistemas de partículas, é altamente improvável que qualquer script precise de todos eles.
Ao deixar as mensagens que não precisamos como não implementadas, podemos sinalizar claramente para o tempo de execução "nem se preocupe em chamar OnCollisionStay2D nesse objeto - não preciso disso"
Isso pode ser um ganho substancial em eficiência. Agora, o mecanismo pode filtrar uma lista curta para chamar Atualização apenas nos ~ 20 objetos em minha cena que precisam de lógica de atualização personalizada a cada quadro, e nem todos os ~ 200 objetos que compõem todo o cenário ou respondem apenas a eventos de física.
fonte