23
\$\begingroup\$

Am looking for ideas on how to do the following: I want to write a simple "world" in Java. One which I could start and then add new objects later at a later date to simulate/observe different behaviours between existing objects. The plan is then to code up newer objects after watching the old ones for a while and then load/drop them into the existing world. The problem is that I don't want to ever stop or restart the world once it's started, I want it to run for a few weeks but I do need the ability to drop in objects and redo/rewrite/delete/create/mutate them over time without needing a reboot. The world could be as simple as a 100 x 100 array of X/Y locations, with a possible tiled-map GUI to visually represent the world. I know I need some kind of ticktimer process to monitor objects and give each one a 'chance to act' (if they wanted to? (and if it was their turn to act?)).

Example: I code up World.java on Monday and leave it running. Then on Tuesday I write a new class called Rock.java (that doesn't move). I then load/drop it (somehow?) into this already running world (which just drops it someplace random in the world array and never moves). Then on Wednesday I create a new class called Cat.java and drop that into the world, again placed randomly, but this new object can move around the world (over some unit of time), then on Thursday i write a class called Dog.java which also moves around but can 'act' on another object if it's in the neighbour location and vice versa.

Here's the thing. I don't know what kinda of structure/design I would need to code the actual world class to know how to detect/load/track future (and currently non-existing) objects.

Any ideas on how you would do something like this using Java?

\$\endgroup\$
1
  • 2
    \$\begingroup\$ Sounds a lot like hot swapping. Perhaps there is some literature on this that can be helpful. Anyway, very interesting question. +1 … \$\endgroup\$ Commented Apr 1, 2011 at 12:24

4 Answers 4

5
\$\begingroup\$

What you are basically looking for is a hot-pluggable system. You run a main application and then add plug-ins at runtime that get integrated in the event loop. First, start off thinking about what your world expects from a game entity. For example (based on your description):

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
}

Of course, you can add other methods you deem necessary. Note the World parameter with the two relevant methods. This allows your new entity to consider the world when setting up or updating. In your Dog class, for example, you can ask the world for all cats in the neighborhood. Next, you create your world that works with this interface and a system for dynamically compiling and loading Java code. An example of this can be found here.

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

Call this method from the World GUI to add new entities. Depending on your World implementation, an Entity init function can look like this:

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);
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Keywords to google: "IoC containers", "Inversion of control", "Dependency injection". These are techniques for generic hot-pluggable systems, that can discover and load components at runtime. \$\endgroup\$
    – Nevermind
    Commented Apr 1, 2011 at 6:57
16
\$\begingroup\$

We did something like that in Stendhal for raids.

We did not aim for completely avoiding restarts. So changes to our core infrastructure services such as client/server communication do need a restart. But adding entities, creatures and NPCs, and modifying existing objects does work. (Oh, and sometimes live bug fixing, reflection can be used to manipulate even private fields).

Since we do not only want new object based on new data (like another skin), but want to add new behaviour, the world program needs to be able to load new class files. We call them "scripts", but they are real compiled Java classes. Those classes implement the Script.java interface.

Maria.java is a simple example. She is a new NPC that sells drinks and food to players. We can define very complex objects in there, too.

A new class is loaded this way:

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

If you can ensure unique names and never want to unload classes, you are done with the low level stuff.

Unloading, however, seems pretty important. In order to achieve that, you have to instantiate a new class loader whenever you want to inject new code. So that the GC can do its work after the last reference to that code is cleared.

We have a /unload command that invokes an unload method in our interface so that scripts can do cleanup. The real unloading is done automatically by the GC.

We often create lots of temporary objects during raids. And we want all of them to be removed after the raid ends. For example the Gnomes Raid, which spawns a number of gnomes near the invisible admin, we use this code: GnomeRaid.java extends CreateRaid.java.

The script could access the world directly (as the first example shows) and do its own clean up in the unload() method. But Java coders are not used to do clean up, and it is annoying. So we created a sandbox that scripts may use. On unload all objects added to the world through the Sandbox class are removed.

\$\endgroup\$
3
\$\begingroup\$

Save the world (no pun intended) and all its state (positions, velocities, all variables).

  • Close the program.
  • Implement changes (ensuring backward compatibility with saves).
  • Recompile program.
  • Restart program.
  • Load world and state.
  • Repeat

This is a general solution though, I don't Java specifics to know if you can dynamically implement code blocks and classes like you hope...

Option 2, is to bind a scripting language that can be loaded on the fly.

\$\endgroup\$
1
  • \$\begingroup\$ +1 for the scripting language. If you aren't bound to Java, try out some of the LPC-based MUD drivers and mudlibs, too. \$\endgroup\$ Commented Jun 11, 2011 at 20:38
1
\$\begingroup\$

For Java, all you need is an OSGi platform. With that it's trivial to hot-swap modules or applications, and even do remote management or partial upgrades.

\$\endgroup\$
1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .