Como implementar um mundo de teste que nunca reinicia?

23

Estou procurando idéias sobre como fazer o seguinte: Quero escrever um "mundo" simples em Java. Um que eu poderia iniciar e, em seguida, adicionar novos objetos posteriormente para simular / observar comportamentos diferentes entre os objetos existentes. O plano é codificar os objetos mais novos depois de observar os antigos por um tempo e depois carregá-los / soltá-los no mundo existente. O problema é que eu nunca mais quero parar ou reiniciar o mundo depois que ele começar, quero que ele funcione por algumas semanas, mas eu preciso da capacidade de soltar objetos e refazer / reescrever / excluir / criar / mutar ao longo do tempo sem precisar de uma reinicialização. O mundo poderia ser tão simples quanto uma matriz 100 x 100 de locais X / Y, com uma possível GUI de mapa lado a lado para representar visualmente o mundo. Eu sei que preciso de algum tipo de processo de timer para monitorar objetos e dar a cada um uma 'chance de agir'

Exemplo: codifico World.java na segunda-feira e deixo em execução. Então, na terça-feira, escrevo uma nova classe chamada Rock.java (que não se move). Em seguida, carrego / solto (de alguma forma?) Neste mundo já em execução (que apenas o coloca em algum lugar aleatório na matriz mundial e nunca se move). Então, na quarta-feira, crio uma nova classe chamada Cat.java e a solto no mundo, novamente colocada aleatoriamente, mas esse novo objeto pode se mover pelo mundo (em alguma unidade de tempo); então, na quinta-feira, escrevo uma classe chamada Dog. java que também se move, mas pode "agir" em outro objeto se estiver no local vizinho e vice-versa.

Aqui está a coisa. Não sei que tipo de estrutura / design eu precisaria para codificar a classe mundial real para saber como detectar / carregar / rastrear objetos futuros (e atualmente inexistentes).

Alguma idéia de como você faria algo assim usando Java?

d33j
fonte
2
Soa muito como troca quente . Talvez haja alguma literatura sobre isso que possa ser útil. Enfim, pergunta muito interessante. +1 ...
Konrad Rudolph,

Respostas:

5

O que você está procurando basicamente é um sistema hot-plugável. Você executa um aplicativo principal e adiciona plug-ins em tempo de execução que são integrados no loop de eventos. Primeiro, comece pensando no que seu mundo espera de uma entidade do jogo. Por exemplo (com base na sua descrição):

interface Entity {
   void init(World world);
   // Called when loaded for the first time in the world and adds itself
   // to the world (correct position in the array, scheduler for updates)

   void update(World world);
   // Called when scheduler fires (allows the entity to think about its
   // next move)

   Image getImage();
   // Called by the GUI when it is time to draw the entity on the screen
}

Obviamente, você pode adicionar outros métodos que considerar necessários. Observe o parâmetro World com os dois métodos relevantes. Isso permite que sua nova entidade considere o mundo ao configurar ou atualizar. Na sua turma de cães, por exemplo, você pode pedir ao mundo todos os gatos da vizinhança. Em seguida, você cria seu mundo que funciona com essa interface e um sistema para compilar e carregar dinamicamente o código Java. Um exemplo disso pode ser encontrado aqui .

void injectEntity(String filename, World world) {
    // Load the class as described in the mentioned link
    Entity e = (Entity) loadClass(filename);
    e.init(world);
}

Chame esse método da GUI mundial para adicionar novas entidades. Dependendo da sua implementação no mundo, uma função init da Entity pode ter esta aparência:

void init(World world) {
   // Register entity with world for easy access
   world.register(this, "RockImpl A");

   // Place the entity on the world map
   Point initialLocation = Point(10,5);
   world.place(this, initialLocation);

   // Schedule its update method for every 5 seconds
   world.schedule(this, 5);
}
fantasma
fonte
1
Palavras-chave no google: "contêineres IoC", "inversão de controle", "injeção de dependência". Essas são técnicas para sistemas hot-plug genéricos, que podem descobrir e carregar componentes em tempo de execução.
Nevermind
16

Fizemos algo parecido em Stendhal para ataques.

Não pretendemos evitar completamente reinicializações. Portanto, as alterações em nossos principais serviços de infraestrutura, como a comunicação cliente / servidor, precisam ser reiniciadas. Mas adicionar entidades, criaturas e NPCs e modificar objetos existentes funciona. (Ah, e, às vezes, corrigindo bugs, a reflexão pode ser usada para manipular até mesmo campos particulares).

Como não queremos apenas um novo objeto com base em novos dados (como outra capa), mas queremos adicionar um novo comportamento, o programa mundial precisa ser capaz de carregar novos arquivos de classe . Nós os chamamos de "scripts", mas são classes Java realmente compiladas. Essas classes implementam a interface Script.java .

Maria.java é um exemplo simples. Ela é um novo NPC que vende bebidas e comida para os jogadores. Também podemos definir objetos muito complexos .

Uma nova classe é carregada desta maneira:

// create a new class loader, with the script folder as classpath.
final File file = new File("./data/script");
final ClassLoader loader = new URLClassLoader(new URL[]{file.toURI().toURL()});

// load class through new loader
final Class< ? > aClass = loader.loadClass(classname);
script = (Script) aClass.newInstance();

Se você pode garantir nomes exclusivos e nunca desejar descarregar classes, está pronto com as coisas de baixo nível.

Descarregar , no entanto, parece muito importante. Para conseguir isso, você precisa instanciar um novo carregador de classes sempre que quiser injetar um novo código. Para que o GC possa fazer seu trabalho após a última referência a esse código ser limpa.

Temos um comando / unload que chama um método de descarregamento em nossa interface para que os scripts possam fazer a limpeza. O descarregamento real é feito automaticamente pelo GC.

Costumamos criar muitos objetos temporários durante ataques. E queremos que todos eles sejam removidos após o término do ataque. Por exemplo, o Gnomes Raid, que gera vários gnomos perto do administrador invisível, usamos este código: GnomeRaid.java estende CreateRaid.java .

O script pode acessar o mundo diretamente (como mostra o primeiro exemplo) e fazer sua própria limpeza no método unload (). Mas os codificadores Java não são usados ​​para limpar, e isso é irritante. Por isso, criamos uma sandbox que os scripts podem usar. Ao descarregar, todos os objetos adicionados ao mundo através da classe Sandbox são removidos.

Hendrik Brummermann
fonte
3

Salve o mundo (sem trocadilhos) e todo o seu estado (posições, velocidades, todas as variáveis).

  • Feche o programa.
  • Implementar alterações (garantindo compatibilidade com versões anteriores).
  • Recompile o programa.
  • Reinicie o programa.
  • Carregue mundo e estado.
  • Repetir

Esta é uma solução geral, porém, não preciso especificar Java para saber se você pode implementar dinamicamente blocos e classes de código como você espera ...

A opção 2 é vincular uma linguagem de script que pode ser carregada em tempo real.

deceleratedcaviar
fonte
+1 para a linguagem de script. Se você não está vinculado ao Java, experimente também alguns dos drivers e mudlibs MUD baseados em LPC.
Martin Sojka
1

Para Java, tudo o que você precisa é de uma plataforma OSGi . Com isso, é trivial a troca de módulos ou aplicativos e até faz gerenciamento remoto ou atualizações parciais.


fonte