Os jogos de varejo usam "inversão de controle" e "injeção de dependência"?

30

Muitos dos desenvolvedores de software mais diligentes que conheço estão adotando a inversão do controle e a injeção de dependência para manipular referências a objetos. Vindo de uma perspectiva de jogos em Flash, não conheço todos os meandros dos estúdios AAA; portanto, eles são usados ​​no mundo dos jogos de varejo?

Iain
fonte
Penso que nesse mundo o desempenho importa acima de tudo, pois mais usuários serão atingidos pelo baixo desempenho do que os programadores pelo código incorreto.
Bart van Heukelom
A injeção de dependência soa como um sistema baseado em componentes, mas você teria um exemplo de como seria a inversão de controle?
ADB
Como a maior parte desse segmento parece estar mal informada sobre o que é a IoC e o que ela resolve, o OP não está realmente pedindo que, em primeiro lugar, aponte aqui para futuros visitantes: sebaslab.com/ioc-container-unity-part-1
Boris Callens
Sim, eles fazem. De fato, a Rare falou sobre como eles estão implementando seus testes no Sea of ​​Thieves - youtube.com/watch?v=KmaGxprTUfI&t=1s . Infelizmente eles não tinham muito a dizer sobre Inversão de Controle no Unreal Engine, no entanto, escrevi um projeto que demonstra como ele funciona: github.com/jimmyt1988/UE-Testing
Jimmyt1988

Respostas:

45

Você chama isso de 'desenvolvimento diligente de software', eu chamo de 'superengenharia dolorosa'. Isso não quer dizer que a inversão do controle seja ruim - na verdade, a definição básica dele é boa -, mas a proliferação de estruturas e métodos inteiros de trabalho para alcançar tudo isso é pouco insana, especialmente combinada com a maneira como as pessoas estão destruindo interfaces perfeitamente boas e limpas para injetar componentes intercambiáveis ​​que você nunca trocará 99% do tempo. É o tipo de coisa que só poderia se originar do ambiente corporativo Java e fico feliz por não ter tanto apoio em outros lugares.

Frequentemente, o argumento é que, mesmo que você não troque componentes, você poderá testá-los isoladamente com objetos simulados e similares. No entanto, tenho medo de nunca comprar o argumento de que vale a pena inchar e complicar uma interface para poder testá-la melhor. O teste prova apenas uma coisa - que seus testes funcionam. Interfaces limpas e mínimas, por outro lado, ajudam muito a provar que seu código funciona.

Então, a resposta curta: sim, mas não da maneira que você está pensando. Às vezes, quando você precisa de um comportamento intercambiável, passa um objeto para um construtor que determina parte do comportamento do novo objeto. É sobre isso.

Kylotan
fonte
5
+1 - Concordo que a comunidade Java parece ter um excesso de complexidade, o que parece ser uma idéia muito simples.
Chris Howe
4
A ideia não é necessariamente que você precise trocar diferentes implementações na produção, mas que talvez você queira injetar implementações especiais para facilitar o teste automatizado. Nesse sentido, DI / IoC certamente pode ser útil em jogos, mas você deseja mantê-lo com um escopo razoável. Você não deve precisar de mais do que algumas resoluções de serviço únicas em um nível alto - você não deseja resolver serviços para cada pequeno objeto no jogo e, certamente, nem todos os quadros ou algo assim.
quer
2
@ Thomas Dufour: o conceito de teste nunca pode provar nada. É apenas um ponto de dados. Você pode passar no teste 100000 vezes sem provar que será executado na execução 100001. A prova vem da análise lógica do assunto. Eu argumentaria que esses contêineres de IoC e similares facilitam os testes às custas de dificultar a comprovação da correção, e acredito que isso é uma coisa ruim.
Kylotan
13
@Kylotan, o conceito de teste nunca tenta provar nada. Ele tenta demonstrar que o comportamento com determinadas entradas é o esperado. Quanto mais o comportamento for mostrado correto, maior será sua crença de que a função geral está correta, mas nunca é 100%. A afirmação de que uma interface mínima prova alguma coisa é ridícula.
Dash-tom-bang
5
@Alex Schearer: Receio que nunca possa aceitar que a necessidade de usar um sistema de contêiner IoC formalizado e normalmente criar argumentos construtores adicionais ou classes de fábrica para facilitar o teste esteja, de alguma forma, tornando o código mais fatorado, e certamente não tornando-o mais flexível acoplado. De fato, praticamente atropela o encapsulamento, um aspecto fundamental do OOP. Em vez de confiar no TDD para conduzir seu projeto e esperar que um bom resultado surja, as pessoas podem simplesmente seguir os princípios do SOLID em primeiro lugar.
Kylotan
17

Padrão de Estratégia , Composição , Injeção de Dependência , todos estão intimamente relacionados.

Como o Padrão de Estratégia é uma forma de Injeção de Dependência, se você observar mecanismos como o Unity, por exemplo, eles serão completamente baseados nesse princípio. O uso de Componentes (Padrão de Estratégia) está profundamente incorporado a todo o mecanismo.

Um dos principais benefícios, além da reutilização de componentes, é evitar as temidas hierarquias profundas de classe.

Aqui está um artigo de Mick West que fala sobre como ele introduziu esse tipo de sistema na série de jogos Tony Hawk da Neversoft.

Evolua sua hierarquia

Até anos bastante recentes, os programadores de jogos usavam consistentemente uma hierarquia profunda de classe para representar entidades de jogos. A maré está começando a mudar do uso de hierarquias profundas para uma variedade de métodos que compõem um objeto de entidade do jogo como uma agregação de componentes ...

David Young
fonte
2
+1, Evolve Your Hierarchy é um ótimo link que eu tinha esquecido completamente. Os slides de Scott Bilas também são bons - o link correto para esses é ( scottbilas.com/files/2002/gdc_san_jose/game_objects_slides.pdf ). Há mais um conjunto de slides relacionados, mas esqueci onde ...
Leander
Também o artigo em Games Gems 5 (acho) de Bjarne Rene.
Chris Howe
13

Parece haver muita confusão sobre o padrão de Inversão de Controle (IoC). Várias pessoas o equipararam ao Padrão de Estratégia ou a um Modelo de Componentes, mas essas comparações não captam realmente o que é a IoC. IoC é realmente sobre como uma dependência é obtida. Deixe-me lhe dar um exemplo:

class Game {
    void Load() {
        this.Sprite.Load(); // loads resource for drawing later
    }
}

class Sprite {
    void Load() {
        FileReader reader = new FileReader("path/to/resource.gif");
        // load image from file
    }
}

No exposto acima, é claro que Sprite.Loaddepende de a FileReader. Quando você deseja testar o método, precisa do seguinte:

  1. Um sistema de arquivos em vigor
  2. Um arquivo de teste para carregar do sistema de arquivos
  3. Capacidade de acionar erros comuns do sistema de arquivos

Os dois primeiros são óbvios, mas se você deseja garantir que o tratamento de erros funcione conforme o esperado, você também precisa do número 3. Nos dois casos, você potencialmente diminuiu bastante os testes, pois agora eles precisam ir para o disco e você provavelmente tornou seu ambiente de teste mais complicado.

O objetivo da IoC é dissociar o uso do comportamento de sua construção. Observe como isso difere do padrão de estratégia. Com o padrão Estratégia, o objetivo é encapsular um pedaço reutilizável de comportamento, para que você possa estendê-lo facilmente no futuro; não tem nada a dizer sobre como as estratégias são construídas.

Se reescrevéssemos o Sprite.Loadmétodo acima, provavelmente terminaríamos com:

class Sprite {
    void Load(IReader reader) {
        // load image through reader
    }
}

Agora, dissociamos a construção do leitor de seu uso. Portanto, é possível trocar um leitor de teste durante o teste. Isso significa que seu ambiente de teste não precisa mais de um sistema de arquivos, arquivos de teste e pode simular facilmente eventos de erro.

Observe que eu fiz duas coisas na minha reescrita. Eu criei uma interface IReaderque encapsulava algum comportamento - ou seja, implementava o padrão de estratégia. Além disso, mudei a responsabilidade de criar o leitor certo para outra classe.

Talvez não precisemos de um novo nome de padrão para descrever o acima. Parece-me uma mistura dos padrões de Estratégia e Fábrica (para contêineres IoC). Dito isto, não tenho certeza de que motivos as pessoas estão contestando esse padrão, pois fica claro que ele resolve um problema real e, certamente, não é óbvio para mim o que isso tem a ver com Java.

Alex Schearer
fonte
2
Alex: ninguém se opõe a passar objetos já criados para direcionar funcionalidades personalizadas sempre que necessário. Todo mundo faz isso, como no seu exemplo. O problema é que as pessoas estão usando estruturas inteiras que existem apenas para lidar com essas coisas e codificam religiosamente todos os aspectos da funcionalidade para depender dessa funcionalidade. Eles primeiro adicionar iReader como parâmetro para construção Sprite, e depois acabar precisando de objetos especiais SpriteWithFileReaderFactory para facilitar este, etc. Esta atitude se origina de Java e coisas como os recipientes Primavera IoC, etc.
Kylotan
2
Mas não estamos falando sobre contêineres IoC - pelo menos não o vejo na postagem original. Estamos apenas falando sobre o próprio padrão. Seu argumento, como posso dizer, é: "Como algumas ferramentas na natureza são ruins, não devemos usar os conceitos subjacentes". Isso me parece ter jogado o bebê fora com a água do banho. Atingir o equilíbrio certo entre simplicidade, realização e manutenção / testabilidade é um problema difícil, provavelmente melhor tratado em um projeto por projeto.
Alex Schearer
Eu suspeito que muitas pessoas são contra IoC porque isso significa:
phtrivier
4

Eu diria que é uma ferramenta entre muitas, e é usada ocasionalmente. Como tenpn disse, qualquer método que introduza vtables (e geralmente qualquer indireção extra) pode ter uma penalidade de desempenho, mas isso é algo com o qual devemos nos preocupar apenas com código de baixo nível. Para código estrutural de alto nível que não é realmente um problema, e a IoC pode ter benefícios positivos. Tudo para reduzir dependências entre classes e tornar seu código mais flexível.

Chris Howe
fonte
2
Para ser mais preciso, eu me preocuparia com as penalidades da tabela de códigos para qualquer código chamado muitas vezes por quadro. Isso pode ou não ser de baixo nível.
Dezpn
4

Tenho que concordar com Kylotan neste. "Injeção de dependência" é uma solução Java feia para uma falha conceitual feia de Java, e a única razão pela qual alguém está olhando para ela em outras linguagens é porque o Java está se tornando a primeira linguagem para muitas pessoas, quando realmente não deveria.

Inversão de controle, por outro lado, é uma idéia útil que existe há muito tempo e é muito útil quando feita corretamente. (Não da maneira Java / Injection de Dependency.) Na verdade, você provavelmente faz isso o tempo todo se estiver trabalhando em uma linguagem de programação sã. Quando eu li pela primeira vez toda essa coisa do "COI", todo mundo estava falando, fiquei completamente desapontado. Inversão de controle nada mais é do que passar um ponteiro de função (ou ponteiro de método) para uma rotina ou objeto para ajudar a personalizar seu comportamento.

Essa é uma ideia que existe desde os anos 50. Está na biblioteca padrão C (o qsort vem à mente) e está em todo lugar no Delphi e .NET (manipuladores / delegados de eventos). Ele permite que o código antigo chame o novo código sem precisar recompilar o código antigo e é usado o tempo todo nos mecanismos de jogo.

Mason Wheeler
fonte
2
Eu também era do "então é isso que as pessoas estão animadas?" quando li sobre DI, mas o fato é que a maioria dos programadores de jogos poderia aprender sobre isso e sobre como isso se aplicaria ao trabalho que fazemos.
traço-tom-bang
2

Não na minha experiência. O que é uma pena, pois o código dos jogos precisa ser muito adaptável, e essas abordagens definitivamente ajudariam.

No entanto, deve-se dizer que os dois métodos podem, em C ++, introduzir tabelas virtuais onde antes não existiam, trazendo consigo um desempenho adequado.

tenpn
fonte
1

Eu não sou um desenvolvedor profissional de jogos, e só tentei implementar a IoC em C ++ uma vez, então isso é apenas especulação.

No entanto, eu suspeito que os desenvolvedores de jogos suspeitariam da IoC porque isso significa:

1 / projetando muitas interfaces e muitas classes pequenas

2 / tendo praticamente todas as chamadas de função sendo vinculadas ultimamente

Agora, pode ser uma coincidência, mas ambos tendem a ser um pouco mais complicados e / ou problemáticos para performances em C ++ (que é amplamente usado em jogos, não é?) Do que em Java, onde a IoC se generalizou (provavelmente porque era relativamente fácil de fazer, ajuda as pessoas a projetar hierarquias de objetos mais saudáveis ​​e porque alguns acreditavam que poderia transformar a escrita de um aplicativo em Java em escrita em XML, mas esse é outro debate: P)

Estou interessado nos comentários, se isso não faz nenhum sentido;)

phtrivier
fonte