10

I am developing a component based game engine in java, right now when i have changes to the components I need to rebuild and restart the editor for the changes to take effect (or i can use some limited hot code injection if the application is running in debug mode).

I am looking for a way to allow the user to modify the source of the components and reload them without having to restart the application (maybe just exit and enter game mode instead). Also a important feature that I need is that the final exported code should be native Java code(so no interpreter should be used in the final result)).

Can you give me any pointers on how to integrate the beanshell interpreter in the project? I can manually monitor the source folder for changes and feed the updated java classes to it, but how is the hotswap going to occur really?

Anton Banchev
  • 535
  • 6
  • 25

3 Answers3

4

First of all, the title is a bit confusing. You don't need to integrate a BeanShell. What you actually need are:

  1. to define a proper architecture
  2. to use Java Compiler API in order to work with java classes

Architecture

Let's say you have an object graph. There are lots of objects, references, etc. So it will be really a tricky task to replace some instance with the new one. Instead of solving this problem you can hide dynamic part behind a "static" proxy. Proxy will handle all reloading stuff (including source folder monitoring).

Before reload:

enter image description here

After reload:

enter image description here

Having that done you can easily track changes and update dynamic part when needed.

Java Compiler API

Instead of using interpreted languages you can use Java, compiling it on the fly and loading using 'Class.forName()'. There are a lot of different examples due to the fact this approach was there for a while.

Here are some details:

Community
  • 1
  • 1
Renat Gilmanov
  • 17,223
  • 5
  • 35
  • 54
  • My question was very specific, how to integrate *beanshell* to do the hot swapping , well nobody answered it. I don't want to manually recompile the code and manually manage the dynamic reloading, also worth noting is that having a proxy between the dynamic and static parts is a no go as it will incur extra overhead on the final devices (which include some old j2me devices). – Anton Banchev Dec 16 '14 at 08:27
  • Your answer looks most complete for other people with similar problems thats why I select it for the bounty. – Anton Banchev Dec 16 '14 at 08:29
0

Basically you want to implement extensibility or plugin design pattern. There are multiple ways to implement this scenario.

Which ever the component you want to allow someone else to reload the module, define an interface and implement your own implementation as a default one. For example, Here I am trying to provide a HelloInterface which each country can implement and load anytime,

 public interface HelloInterface {

    public String sayHello(String input);
    ..   
 }

 public class HelloImplDefault implements HelloInterface {

    public String sayHello(String input) {
         return "Hello World";
    }
 }

Now allow user to add a plugin (custom implementation) files to a pre-configured path. You can either user FileSystemWatcher or a background thread to scan this path and try to compile and load the file.

To compile java file,

    private void  compile(List<File> files) throws IOException{


        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
        Iterable<? extends JavaFileObject> compilationUnits = fileManager
            .getJavaFileObjectsFromFiles(files);
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null,
            null, compilationUnits);
        boolean success = task.call();
        fileManager.close();

}

To load class file,

private void load(List<File> files) throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException{

     ClassLoader cl = Thread.currentThread().getContextClassLoader();

    for(File f: files){
        if(f.getName().endsWith(".class") && !loadedClass.contains(f.getName())){
             URL url = f.toURL();
             URL[] urls = new URL[]{url};
             Object obj = cl.loadClass(f.getName().replace(".class", "")).newInstance();
             if(obj instanceof HelloInterface){
                 HelloProviders.addProvider((HelloInterface)obj);
                 System.out.println("Loaded "+ ((HelloInterface)obj).getProviderName());
             }else{
                 //Add more classes if you want
             }
             loadedClass.add(f.getName());
        }
    }
}

At this point you can read custom implementation and loaded in system class loader. Now you are ready to go. There are security implications to this approach which you need learn from internet.

I implemented one sample code and posted in github, please take a look. Happy coding!

kamoor
  • 2,769
  • 2
  • 16
  • 32
0

Take a look at the tapestry-ioc inversion of control container which supports live-reloading.

When in development mode (tapestry.production-mode=false) you can live reload your services. Note that if a service interface changes, you will need a restart. But any changes to the service implementation that do not alter the service interface can be live-reloaded.

lance-java
  • 21,832
  • 3
  • 45
  • 81