Come implementare un mondo di test senza riavvio?


23

Sto cercando idee su come fare quanto segue: Voglio scrivere un semplice "mondo" in Java. Uno che ho potuto iniziare e quindi aggiungere nuovi oggetti in un secondo momento per simulare / osservare comportamenti diversi tra oggetti esistenti. Il piano è quindi quello di codificare gli oggetti più recenti dopo aver visto quelli vecchi per un po 'e quindi caricarli / rilasciarli nel mondo esistente. Il problema è che non voglio mai fermare o riavviare il mondo una volta avviato, voglio che funzioni per alcune settimane ma ho bisogno della possibilità di rilasciare oggetti e ripetere / riscrivere / cancellare / creare / mutare nel tempo senza bisogno di riavviare. Il mondo potrebbe essere semplice come una matrice 100 x 100 di posizioni X / Y, con una possibile GUI a mappe piastrellate per rappresentare visivamente il mondo. So di aver bisogno di una sorta di processo ticktimer per monitorare gli oggetti e dare a ciascuno una "possibilità di agire"

Esempio: codifico World.java lunedì e lo lascio in esecuzione. Quindi martedì scrivo una nuova classe chiamata Rock.java (che non si muove). Quindi lo carico / rilascialo (in qualche modo?) In questo mondo già in esecuzione (che lo fa semplicemente cadere in un posto casuale nell'array mondiale e non si muove mai). Quindi mercoledì creo una nuova classe chiamata Cat.java e la lancio nel mondo, posizionandola di nuovo in modo casuale, ma questo nuovo oggetto può spostarsi in tutto il mondo (per alcune unità di tempo), quindi giovedì scrivo una classe chiamata Dog. java che si muove anche ma può "agire" su un altro oggetto se si trova nella posizione vicina e viceversa.

Ecco la cosa. Non so che tipo di struttura / design avrei bisogno di codificare l'attuale classe mondiale per sapere come rilevare / caricare / tracciare oggetti futuri (e attualmente inesistenti).

Qualche idea su come faresti qualcosa del genere usando Java?


2
Sembra molto hot swap . Forse c'è della letteratura su questo che può essere utile. Comunque, domanda molto interessante. +1 ...
Konrad Rudolph

Risposte:


5

Quello che stai sostanzialmente cercando è un sistema hot plug. Si esegue un'applicazione principale e quindi si aggiungono plug-in in fase di esecuzione che vengono integrati nel ciclo degli eventi. Innanzitutto, inizia pensando a cosa si aspetta il tuo mondo da un'entità di gioco. Ad esempio (in base alla descrizione):

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
}

Naturalmente, puoi aggiungere altri metodi che ritieni necessari. Nota il parametro World con i due metodi pertinenti. Ciò consente alla tua nuova entità di considerare il mondo durante l'impostazione o l'aggiornamento. Nella tua classe Dog, ad esempio, puoi chiedere al mondo tutti i gatti del vicinato. Successivamente, crei il tuo mondo che funziona con questa interfaccia e un sistema per la compilazione e il caricamento dinamici del codice Java. Un esempio di questo può essere trovato qui .

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

Chiamare questo metodo dalla GUI mondiale per aggiungere nuove entità. A seconda dell'implementazione mondiale, una funzione init Entity può apparire come questa:

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);
}

1
Parole chiave per google: "Contenitori IoC", "Inversione del controllo", "Iniezione delle dipendenze". Si tratta di tecniche per sistemi generici hot plug, in grado di rilevare e caricare componenti in fase di esecuzione.
Nevermind

16

Abbiamo fatto qualcosa del genere a Stendhal per i raid.

Non miravamo a evitare completamente i riavvii. Pertanto, è necessario riavviare le modifiche ai nostri servizi di infrastruttura di base come le comunicazioni client / server. Ma l'aggiunta di entità, creature e NPC e la modifica di oggetti esistenti funziona. (Oh, e talvolta correzioni di bug live, la riflessione può essere usata per manipolare anche campi privati).

Poiché non vogliamo solo nuovi oggetti basati su nuovi dati (come un'altra skin), ma vogliamo aggiungere nuovi comportamenti, il programma mondiale deve essere in grado di caricare nuovi file di classe . Li chiamiamo "script", ma sono vere classi Java compilate. Queste classi implementano l' interfaccia Script.java .

Maria.java è un semplice esempio. È un nuovo NPC che vende bevande e cibo ai giocatori. Possiamo anche definire oggetti molto complessi .

Viene caricata una nuova classe in questo modo:

// 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 riesci a garantire nomi univoci e non vuoi mai scaricare le classi, hai finito con le cose di basso livello.

Lo scarico , tuttavia, sembra piuttosto importante. A tale scopo, è necessario creare un'istanza di un nuovo caricatore di classi ogni volta che si desidera iniettare nuovo codice. In modo che il GC possa fare il suo lavoro dopo aver cancellato l'ultimo riferimento a quel codice.

Abbiamo un comando / unload che richiama un metodo di scarico nella nostra interfaccia in modo che gli script possano eseguire la pulizia. Il vero scarico viene eseguito automaticamente dal GC.

Spesso creiamo molti oggetti temporanei durante i raid. E vogliamo che vengano rimossi tutti dopo la fine dell'incursione. Ad esempio il Gnomes Raid, che genera un certo numero di gnomi vicino all'amministratore invisibile, usiamo questo codice: GnomeRaid.java estende CreateRaid.java .

Lo script potrebbe accedere direttamente al mondo (come mostra il primo esempio) e fare il proprio cleanup nel metodo unload (). Ma i programmatori Java non vengono utilizzati per ripulire ed è fastidioso. Quindi abbiamo creato un sandbox che gli script possono usare. Al momento dello scarico, tutti gli oggetti aggiunti al mondo tramite la classe Sandbox vengono rimossi.


3

Salvare il mondo (nessun gioco di parole previsto) e tutto il suo stato (posizioni, velocità, tutte le variabili).

  • Chiudi il programma.
  • Implementare le modifiche (garantendo la retrocompatibilità con i salvataggi).
  • Ricompila programma.
  • Riavvia il programma.
  • Carica mondo e stato.
  • Ripetere

Questa è una soluzione generale, tuttavia, non ho le specifiche di Java per sapere se puoi implementare dinamicamente blocchi di codice e classi come speri ...

L'opzione 2 è quella di associare un linguaggio di script che può essere caricato al volo.


+1 per il linguaggio di scripting. Se non sei legato a Java, prova anche alcuni dei driver MUD e mudlib basati su LPC.
Martin Sojka,

1

Per Java, tutto ciò che serve è una piattaforma OSGi . È quindi banale sostituire moduli o applicazioni a caldo e persino eseguire la gestione remota o aggiornamenti parziali.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.