No meu jogo, existem terrenos com edifícios (casas, centros de recursos). Prédios como casas têm inquilinos, quartos, complementos, etc., e há vários valores que precisam ser simulados com base em todas essas variáveis.
Agora, gostaria de usar o AndEngine para o material de front-end e criar outro encadeamento para fazer os cálculos de simulação (talvez também inclua a IA posteriormente nesse encadeamento). Isso é para que um thread inteiro não faça todo o trabalho e cause problemas como bloqueio. Isso introduz o problema de simultaneidade e dependência .
O problema da moeda é o meu thread principal da interface do usuário e o thread de cálculo precisaria acessar todos os objetos de simulação. Portanto, tenho que torná-los seguros para threads, mas não sei como armazenar e estruturar os objetos de simulação para permitir isso.
O problema da dependência é que, para calcular valores, meus cálculos dependem dos valores de outros objetos.
Qual seria a melhor maneira de vincular meu objeto de inquilino no prédio aos meus cálculos? Codifique-o para a classe de inquilino? Qual é uma boa maneira de "armazenar" algoritmos para que eles sejam facilmente ajustados?
Uma maneira simples e preguiçosa seria arrastar tudo para uma classe que contenha todo o objeto, como terrenos (que, por sua vez, mantêm os prédios, etc.). Essa classe também manteria o estado do jogo, como a tecnologia disponível para o usuário, conjuntos de objetos para itens como sprites. Mas essa é uma maneira preguiçosa e perigosa, correto?
Edit: Eu estava olhando para injeção de dependência, mas quão bem isso lida com como uma classe que contém outros objetos? ou seja, meu lote de terreno, com um prédio que possui inquilino e uma série de outros valores. O DI também parece uma dor no bumbum com o AndEngine.
fonte
Respostas:
Seu problema é inerentemente serial - você deve concluir uma atualização da simulação antes de poder renderizá-la. Transferir a simulação para um encadeamento diferente significa simplesmente que o encadeamento da interface do usuário principal não faz nada enquanto o encadeamento da simulação está marcado (o que significa que está bloqueado).
A "melhor prática" comumente aceita para simultaneidade não é colocar sua renderização em um thread e sua simulação em outro, como você está propondo. Eu recomendo fortemente contra essa abordagem, de fato. As duas operações são naturalmente relacionadas em série e, embora possam ser brutamente forçadas, não são ideais e não são dimensionáveis .
Uma abordagem melhor é tornar partes da atualização ou renderização simultâneas, mas deixe a atualização e a renderização sempre seriais. Por exemplo, se você tem um limite natural em sua simulação (por exemplo, se casas nunca se afetam na simulação), você pode enfiar todas as casas em baldes de N casas e girar um monte de threads para cada processo bucket e deixe esses threads se unirem antes que a etapa de atualização seja concluída. Isso é muito melhor e se encaixa muito melhor ao design simultâneo.
Você está pensando demais no restante da questão:
A injeção de dependência é um arenque vermelho aqui: tudo o que a injeção de dependência realmente significa é que você passa ("injeta") as dependências de uma interface para instâncias dessa interface, normalmente durante a construção.
Isso significa que, se você tem uma classe que modela a
House
, que precisa saber coisas sobre aCity
qual está, oHouse
construtor pode se parecer com:Nada especial.
O uso de um singleton é desnecessário (você costuma vê-lo em algumas das "estruturas de DI" extremamente complexas e com engenharia excessiva, como o Caliburn, projetadas para aplicativos GUI "corporativos" - isso não a torna uma boa solução). De fato, a introdução de singletons é frequentemente a antítese do bom gerenciamento de dependências. Eles também podem causar sérios problemas ao código multithread, porque geralmente não podem ser protegidos contra threads sem bloqueios - quanto mais bloqueios você precisar adquirir, pior será o seu problema adequado para lidar com uma natureza paralela.
fonte
A solução usual para problemas de simultaneidade é o isolamento de dados .
Isolamento significa que cada encadeamento possui seus próprios dados e não toca nos dados de outros encadeamentos. Dessa forma, não há problemas com simultaneidade ... mas então temos um problema de comunicação. Como esses threads podem trabalhar juntos se não compartilham dados?
Existem duas abordagens aqui.
O primeiro é a imutabilidade . Estruturas / variáveis imutáveis são aquelas que nunca mudam de estado. A princípio, isso pode parecer inútil - como alguém pode usar uma "variável" que nunca muda? No entanto, podemos trocar essas variáveis! Considere este exemplo: suponha que você tenha uma
Tenant
classe com vários campos, necessária para estar em um estado consistente. Se você alterar umTenant
objeto no segmento A e, ao mesmo tempo, observá-lo no segmento B, o segmento B poderá ver o objeto em estado inconsistente. No entanto, seTenant
for imutável, o segmento A não pode alterá-lo. Em vez disso, cria novasTenant
objeto com os campos configurados conforme necessário e o troca pelo antigo. Trocar é apenas uma alteração em uma referência, que provavelmente é atômica e, portanto, não há como observar o objeto em estado inconsistente.A segunda abordagem é a troca de mensagens . A idéia por trás disso é que, quando todos os dados são "de propriedade" de algum thread, podemos dizer a ele o que fazer com os dados. Cada encadeamento nesta arquitetura possui uma fila de mensagens - uma lista de
Message
objetos e uma bomba de mensagens - executando constantemente o método que remove uma mensagem da fila, interpreta e chama algum método manipulador. Por exemplo, suponha que você tenha explorado um terreno, sinalizando que ele precisa ser comprado. O segmento da interface do usuário não pode alterar oPlot
objeto diretamente, porque pertence ao segmento lógico (e provavelmente é imutável). Portanto, o thread da interface do usuário constrói umBuyMessage
objeto e o adiciona à fila do thread lógico. O encadeamento lógico, quando em execução, pega a mensagem da fila e chamaBuyPlot()
, extraindo os parâmetros do objeto de mensagem. Pode enviar uma mensagem de volta, por exemploBuySuccessfulMessage
, instruindo o thread da interface do usuário a colocar um "Agora você tem mais terreno!" janela na tela. Obviamente, o acesso à fila de mensagens deve ser sincronizado com o bloqueio, a seção crítica ou o que for chamado no AndEngine. Mas esse é um ponto único de sincronização entre os threads, e os threads são suspensos por um período muito curto, portanto não há problema.Essas duas abordagens são melhor usadas em combinação. Seus encadeamentos devem se comunicar com as mensagens e ter alguns dados imutáveis "abertos" para outros encadeamentos - por exemplo, uma lista imutável de plotagens para a interface do usuário desenhá-los.
Observe também que "somente leitura" não significa necessariamente imutável ! Qualquer estrutura de dados complexa, como uma hashtable, pode alterar seu estado interno nos acessos de leitura; portanto, verifique primeiro a documentação.
fonte
Provavelmente 99% dos programas de computador escritos no histórico usavam apenas 1 thread e funcionavam bem. Eu não tenho nenhuma experiência com o AndEngine, mas é muito raro encontrar sistemas que exijam encadeamento, apenas vários que poderiam ter se beneficiado com ele, dado o hardware certo.
Tradicionalmente, para fazer simulação e GUI / renderização em um encadeamento, você simplesmente faz um pouco da simulação, depois renderiza e repete, normalmente muitas vezes por segundo.
Quando alguém tem pouca experiência no uso de vários processos ou não entende completamente o que significa 'segurança' do segmento (que é um termo vago que pode significar muitas coisas diferentes), é muito fácil introduzir muitos erros no sistema. Então, pessoalmente, eu recomendaria adotar a abordagem de encadeamento único, intercalar simulação e renderização e salvar qualquer encadeamento para operações que você sabe que levarão muito tempo e exigirão absolutamente encadeamentos e não um modelo baseado em evento.
fonte