Existem casos em que globals / singletons são úteis no desenvolvimento de jogos? [fechadas]

11

Eu sei que ter variáveis ​​globais ou classes singleton cria casos que podem ser difíceis de testar / gerenciar e eu fui impedido de usar esses padrões no código, mas muitas vezes você precisa enviar.

Existem casos em que variáveis ​​globais ou singletons são realmente úteis no desenvolvimento de jogos?

Ólafur Waage
fonte

Respostas:

30

Essas coisas sempre podem ser úteis . Se é ou não a solução mais bonita ou mais segura, é outra questão, mas acho que o desenvolvimento de jogos envolve um certo grau de pragmatismo.

JasonD
fonte
Muito perto do meu mantra.
Ólafur Waage
15

Definitivamente uma lista não exaustiva, mas aqui vai:

Contras

  • Gerenciamento de vida . Singletons e globais podem ser iniciados antes da inicialização dos principais sistemas (por exemplo, heap), dependendo de como você os configura. Se você quiser derrubá-los (útil se estiver rastreando vazamentos, por exemplo), tenha cuidado com a ordem de desmontagem ou comece a entrar em coisas como singletons de fênix . (Consulte o fiasco da ordem de inicialização estática .)
  • Controle de acesso . A renderização deve ter apenas acesso const aos dados do jogo, enquanto a atualização fica não const? Isso pode ser mais difícil de aplicar com singletons e globais.
  • Estado . Como Kaj apontou, é mais difícil decompor funcionalmente e transformar singletons para funcionarem em uma arquitetura de memória não compartilhada. Ele também tem implicações de desempenho em potencial para outros tipos de sistemas NUMA (acessando a memória que não é local). Singletons geralmente representam estado centralizado e, portanto, são a antítese de transformações que são facilitadas pela pureza.

Prós ou contras

  • Concorrência . Em um ambiente simultâneo, os singletons podem ser uma dor (você deve considerar problemas de corrida / reentrada de dados) ou uma bênção (é mais fácil centralizar e raciocinar sobre bloqueio e gerenciamento de recursos). O uso inteligente de coisas como armazenamento local de encadeamentos pode atenuar um pouco os possíveis problemas, mas geralmente não é um problema fácil.
  • Codegen (dependendo do compilador, arquitetura, etc): Para uma implementação singleton simples de criar no primeiro uso, você pode avaliar uma ramificação condicional extra para cada acesso. (Isso pode aumentar.) Vários globais usados ​​em uma função podem inchar o pool literal . Uma abordagem "estrutura de todos os globais" pode economizar espaço no pool literal: apenas uma entrada na base da estrutura e, em seguida, compensa as codificações nas instruções de carregamento. Por fim, se você estiver evitando globals e singletons a todo custo, geralmente precisará usar pelo menos um pouco de memória extra (registros, pilha ou pilha) para passar ponteiros, referências ou mesmo cópias.

Prós

  • Simplicidade . Se você sabe que os contras listados acima não o afetam tanto (por exemplo, você está trabalhando em um ambiente de thread único para uma única plataforma portátil de CPU), evita alguma arquitetura (como a passagem de argumentos acima mencionada). Singletons e globais podem ser mais fáceis de entender para codificadores menos experientes (embora possa ser mais fácil usá-los incorretamente).
  • Gerenciamento de vida útil (novamente). Se você usar uma "estrutura de globais" ou algum mecanismo que não seja o de criar na primeira solicitação, poderá ter um controle fácil de ler e refinar a ordem de inicialização e destruição. Você automatiza isso em algum grau ou gerencia manualmente (a necessidade de gerenciá-lo pode ser uma vantagem ou uma desvantagem, dependendo do número de globais / singletons e de suas interdependências).

Usamos muito uma "estrutura de singletons globais" em nossos títulos portáteis. Os títulos de PC e console tendem a confiar neles menos; mudaremos mais para uma arquitetura orientada a eventos / de mensagens. Dito isto, os títulos de pc / console ainda costumam usar um TextureManager central; como geralmente envolve um único recurso compartilhado (a memória de textura), isso fez sentido para nós.

Se você mantiver sua API relativamente limpa, talvez não seja muito difícil refatorar (ou entrar!) Em um padrão singleton quando você precisar ...

leander
fonte
Ótima lista. Pessoalmente, encontro apenas valor negativo em singletons e globais devido aos problemas que vêm do estado compartilhado (provavelmente mutável). Eu não acho que o problema "codegen" seja um problema; ou você precisa passar os ponteiros pelos registradores ou precisa executar alguma sequência de operações para obtê-lo, acho que altera o código em relação ao tamanho dos dados, mas a soma será a mesma.
traço-tom-bang
Sim, no codegen, a única coisa que realmente vimos fazer uma diferença significativa foi passar de globais individuais para uma tabela global: reduziu os pools literais e, portanto, o tamanho da seção .text, em vários por cento. O que é uma coisa muito agradável em pequenos sistemas. =) Os benefícios de desempenho de não atingir o dcache tanto para literais eram apenas um benefício lateral agradável (embora pequeno na maioria dos casos). Uma outra vantagem de se mudar para uma tabela global foi a capacidade de realmente facilmente movê-lo para uma seção que residia na memória mais rápido ...
Leander
4

Eles podem ser muito úteis, especialmente durante a criação de protótipos ou a implementação experimental, mas, em geral, preferimos passar referências para estruturas como gerente, dessa forma, pelo menos você tem algum controle sobre de onde eles são acessados. O maior problema com globals e singletons (na minha opinião) é que eles não são muito compatíveis com multithread, e o código que os utiliza é muito mais difícil de portar para a memória não compartilhada, como as SPUs.

Kaj
fonte
2

Eu diria que o design singleton em si não é útil. As variáveis ​​globais podem definitivamente ser úteis, mas prefiro vê-las ocultas atrás de uma interface bem escrita, para que você não esteja ciente da existência delas. Com singletons, você está definitivamente ciente de sua existência.

Costumo usar variáveis ​​globais para coisas que precisam acessar todo o mecanismo. Minha ferramenta de desempenho é um bom exemplo que eu uso em todo o mecanismo para chamar. As chamadas são simples; ProbeRegister (), ProbeHit () e ProbeScoped (). Seu acesso real é um pouco mais complicado e usa variáveis ​​globais para algumas de suas coisas.

Simon
fonte
2

O principal problema com os globais e mal implementados singletons são erros obscuros de construção e desconstrução.

Portanto, se você trabalha com primitivas que não têm esses problemas ou estão muito conscientes do problema com um ponteiro. Então eles podem ser usados ​​com segurança.

Globais têm seu lugar, o mesmo que gotos, e não devem ser descartados imediatamente, mas usados ​​com cuidado.

Há uma boa explicação sobre isso no Guia de estilos do Google C ++

Kimau
fonte
Não concordo que esse seja o principal problema; "não use globais" remonta mais longe do que os construtores de existência. Eu diria que existem dois grandes problemas com eles. Primeiro, eles complicam o raciocínio sobre um programa. Se eu tenho uma função que acessa uma global, o comportamento dessa função depende não apenas de seus próprios argumentos, mas também de todas as outras funções que acessam a global. Isso é muito mais para se pensar. Segundo, mesmo que você consiga manter tudo isso em sua cabeça (você não pode), eles ainda resultam em problemas de concorrência mais complicados.
@ Joe, a palavra-chave é "dependências". Uma função que acessa globais (ou singletons ou qualquer outro estado compartilhado) tem dependências implícitas em todas essas coisas. É muito mais fácil raciocinar sobre um pouco de código se todas as suas dependências forem explícitas, e é isso que você obtém quando a lista completa de dependências é documentada na lista de argumentos.
Dash-tom-bang
1

Globais são úteis ao prototipar rapidamente um sistema que requer algum estado entre as chamadas de função. Depois de estabelecer que o sistema funciona, mova o estado para uma classe e transforme as funções em métodos dessa classe.

Singletons são úteis para causar problemas a você e aos outros. Quanto mais estado global você apresentar, maiores serão os problemas com correção, manutenção, extensibilidade, concorrência de código, etc. Apenas não faça isso.

Kylotan
fonte
1

Costumo recomendar o uso de algum tipo de contêiner de DI / IoC com gerenciamento de vida útil personalizado em vez de singletons (mesmo se você usar um gerenciador de vida útil de "instância única"). Pelo menos, é fácil trocar a implementação para facilitar o teste.

Mike Strobel
fonte
Para aqueles que não sabem, DI é "Injeção de Dependência" e IoC é "Inversão de Controle". Link: martinfowler.com/articles/injection.html (eu já havia lido sobre isso antes, mas nunca tinha visto o acrônimo, então levei um pouco de pesquisa.) +1 por mencionar essa excelente transformação.
quer
0

Se você deseja que os recursos de economia de memória de um singleton possam experimentar o padrão de design flyweight?

http://en.wikipedia.org/wiki/Flyweight_pattern

No que diz respeito aos problemas de vários segmentos mencionados acima, deve ser bastante simples, com alguma previsão, implementar um mecanismo de bloqueio de recursos que possam ser compartilhados entre os segmentos. http://en.wikipedia.org/wiki/Read/write_lock_pattern

lathomas64
fonte
0

Singletons são uma ótima maneira de armazenar um estado compartilhado em um protótipo inicial.

Eles não são uma bala de prata e vêm com alguns problemas, mas são um padrão muito útil para determinados estados de interface do usuário / lógica.

Por exemplo, no iOS, você usa singletons para obter o [UIApplication sharedApplication], no cocos2d você pode usá-lo para obter referências a determinados objetos como [CCNotifications sharedManager] e, pessoalmente, geralmente começo com um singleton [Game sharedGame] onde posso estado de armazenamento compartilhado entre vários componentes diferentes.

DFectuoso
fonte
0

Uau, isso é interessante para mim, pois nunca tive um problema pessoal com o padrão singleton. Meu projeto atual é um mecanismo de jogo em C ++ para o Nintendo DS e estou implementando muitos utilitários de acesso a hardware (por exemplo, VRAM Banks, Wifi, os dois mecanismos gráficos) como instâncias singleton, porque pretendem envolver C global funções na biblioteca subjacente.


fonte
Minha pergunta seria então: por que usar singletons? Vejo que as pessoas gostam de agrupar APIs com singletons ou classes que consistem apenas em funções estáticas, mas por que se preocupar com a classe? Parece-me ainda mais fácil ter apenas as chamadas de função (agrupadas como desejar), mas depois internas a elas acessam o estado "global". Por exemplo, Log :: GetInstance () -> LogError (...) também poderia ser LogError (...) que acessa internamente qualquer estado global sem exigir que o código do cliente saiba sobre ele.
Dash-tom-bang
0

Somente quando você tem um item que possui apenas um controlador, mas é tratado por vários módulos.

Como, por exemplo, a interface do mouse. Ou interface de joystick. Ou leitor de música. Ou leitor de som. Ou a tela. Ou o gerenciador de arquivos salvos.

zaratustra
fonte
-1

Globais são muito mais rápidos! Portanto, ele se encaixaria perfeitamente para um aplicativo com alto desempenho, como um jogo.

Singletons são uma IMO global melhor e, portanto, a ferramenta certa.

Use com moderação!

jacmoe
fonte
Muito mais rápido que o que ? Os globais estarão em alguma página de memória aleatória, se for um ponteiro, e em uma página de memória fixa, mas provavelmente distante, se forem um valor. O compilador não pode usar nenhuma otimização útil do olho mágico neles. Por qualquer medida que eu possa pensar, os globais são lentos .
Mais rápido do que getters e setters e passando referências. Mas não muito. Ajuda a manter os tamanhos baixos, o que ajudaria em alguns sistemas. Provavelmente exagerei quando falei 'muito', mas sou muito cético em relação a quem diz que não deve usar algo. Você só precisa usar seu bom senso. Afinal, singletons nada mais são do que um membro estático da classe, e esses são globais.
jacmoe
Mas, para resolver qualquer problema de sincronização com globais, você precisa de getters / setters para eles, portanto isso não é realmente relevante. O problema com (e raro benefício de) estado global é que é estado global, sem preocupações com velocidade ou (efetivamente) aspectos de sintaxe da interface.