¿Cómo implementar un mundo de prueba que nunca se reinicia?

23

Estoy buscando ideas sobre cómo hacer lo siguiente: Quiero escribir un simple "mundo" en Java. Uno que podría comenzar y luego agregar nuevos objetos más adelante para simular / observar diferentes comportamientos entre los objetos existentes. El plan es codificar los objetos más nuevos después de mirar los viejos por un tiempo y luego cargarlos / soltarlos en el mundo existente. El problema es que no quiero detener o reiniciar el mundo una vez que haya comenzado, quiero que se ejecute durante unas semanas, pero necesito la capacidad de soltar objetos y rehacer / reescribir / eliminar / crear / mutarlos con el tiempo sin necesidad de reiniciar. El mundo podría ser tan simple como una matriz de 100 x 100 de ubicaciones X / Y, con una posible GUI de mapa en mosaico para representar visualmente el mundo. Sé que necesito algún tipo de proceso de temporizador para monitorear objetos y darles a cada uno una 'oportunidad de actuar'

Ejemplo: codifico World.java el lunes y lo dejo en funcionamiento. Luego, el martes escribo una nueva clase llamada Rock.java (que no se mueve). Luego lo cargo / lo dejo (¿de alguna manera?) En este mundo que ya se está ejecutando (que simplemente lo deja en algún lugar al azar en la matriz mundial y nunca se mueve). Luego, el miércoles, creo una nueva clase llamada Cat.java y la dejo caer al mundo, nuevamente colocada al azar, pero este nuevo objeto puede moverse alrededor del mundo (durante una unidad de tiempo), luego el jueves escribo una clase llamada Perro. Java, que también se mueve pero puede "actuar" en otro objeto si está en la ubicación vecina y viceversa.

Aquí está la cosa. No sé qué tipo de estructura / diseño necesitaría para codificar la clase mundial real para saber cómo detectar / cargar / rastrear objetos futuros (y actualmente no existentes).

¿Alguna idea de cómo harías algo así usando Java?

d33j
fuente
2
Suena mucho a intercambio en caliente . Quizás haya algo de literatura sobre esto que pueda ser útil. De todos modos, una pregunta muy interesante. +1 ...
Konrad Rudolph el

Respuestas:

5

Lo que básicamente está buscando es un sistema conectable en caliente. Ejecuta una aplicación principal y luego agrega complementos en tiempo de ejecución que se integran en el bucle de eventos. Primero, comienza a pensar en lo que tu mundo espera de una entidad de juego. Por ejemplo (según su descripción):

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
}

Por supuesto, puede agregar otros métodos que considere necesarios. Tenga en cuenta el parámetro World con los dos métodos relevantes. Esto le permite a su nueva entidad considerar el mundo al configurar o actualizar. En tu clase de perro, por ejemplo, puedes preguntarle al mundo por todos los gatos del vecindario. Luego, crea su mundo que funciona con esta interfaz y un sistema para compilar y cargar dinámicamente código Java. Un ejemplo de esto se puede encontrar aquí .

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

Llame a este método desde la GUI mundial para agregar nuevas entidades. Dependiendo de su implementación mundial, una función de inicio de entidad puede verse así:

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
fuente
1
Palabras clave para google: "Contenedores de IoC", "Inversión de control", "Inyección de dependencia". Estas son técnicas para sistemas genéricos de conexión en caliente, que pueden descubrir y cargar componentes en tiempo de ejecución.
importa el
16

Hicimos algo así en Stendhal para las redadas.

No buscamos evitar por completo los reinicios. Por lo tanto, los cambios en nuestros servicios básicos de infraestructura, como la comunicación cliente / servidor, necesitan reiniciarse. Pero agregar entidades, criaturas y NPC y modificar objetos existentes sí funciona. (Ah, y a veces la corrección de errores en vivo, la reflexión se puede utilizar para manipular incluso campos privados).

Dado que no solo queremos nuevos objetos basados ​​en nuevos datos (como otra máscara), sino que queremos agregar un nuevo comportamiento, el programa mundial debe poder cargar nuevos archivos de clase . Los llamamos "scripts", pero son verdaderas clases Java compiladas. Esas clases implementan la interfaz Script.java .

Maria.java es un ejemplo simple. Ella es una nueva APN que vende bebidas y comida a los jugadores. También podemos definir objetos muy complejos allí.

Una nueva clase se carga de esta manera:

// 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();

Si puede garantizar nombres únicos y nunca quiere descargar clases, ya ha terminado con las cosas de bajo nivel.

La descarga , sin embargo, parece bastante importante. Para lograr eso, debe crear una instancia de un nuevo cargador de clases cada vez que desee inyectar un nuevo código. Para que el GC pueda hacer su trabajo después de borrar la última referencia a ese código.

Tenemos un comando / unload que invoca un método de descarga en nuestra interfaz para que los scripts puedan realizar la limpieza. La descarga real se realiza automáticamente por el GC.

A menudo creamos muchos objetos temporales durante las redadas. Y queremos que todos se eliminen una vez que finalice la redada. Por ejemplo, el Gnomes Raid, que genera varios gnomos cerca del administrador invisible, usamos este código: GnomeRaid.java extiende CreateRaid.java .

El script podría acceder al mundo directamente (como muestra el primer ejemplo) y hacer su propia limpieza en el método unload (). Pero los codificadores Java no están acostumbrados a limpiar, y es molesto. Entonces creamos un sandbox que los scripts pueden usar. Al descargar, se eliminan todos los objetos agregados al mundo a través de la clase Sandbox.

Hendrik Brummermann
fuente
3

Salva el mundo (sin juego de palabras) y todo su estado (posiciones, velocidades, todas las variables).

  • Cierra el programa.
  • Implementar cambios (asegurando la compatibilidad con versiones anteriores de los guardados).
  • Programa de recompilación.
  • Reiniciar programa.
  • Carga mundo y estado.
  • Repetir

Sin embargo, esta es una solución general, no sé los detalles de Java para saber si puede implementar dinámicamente bloques de código y clases como espera ...

La opción 2 es enlazar un lenguaje de scripts que se puede cargar sobre la marcha.

caviar desacelerado
fuente
+1 para el lenguaje de script. Si no está vinculado a Java, pruebe también algunos de los controladores MUD y mudlibs basados ​​en LPC.
Martin Sojka
1

Para Java, todo lo que necesita es una plataforma OSGi . Con eso es trivial para los módulos o aplicaciones de intercambio en caliente, e incluso hacer administración remota o actualizaciones parciales.


fuente