Resource Injection

Danny02 edited this page Dec 30, 2012 · 8 revisions
Clone this wiki locally

In a game project one need to load a lot of resources of different types, like textures, sounds etc. This problem is at a first look a very simple one, but if one want some more advanced features and keep it still simple to use, it gets more difficult.

This Implementation is a first try, open for any ideas of improvement. I hope I got quite far with this first release.

Features

  • Injection of fully created resources
  • hot-reload of any resource
  • list of used resources at compile-time
  • caching of build resources

Injection of Resources

Annotations

Here is a short Code snipped, which shows the minimal usage of injecting resources

@InjectBundle(files = {"sphere.frag", "sphere.vert"})
private Shader sphereShader;

@InjectResource(file = "fire.png")
private Texture fireTexture;

Both shown Annotations describe the file(s) from which a resource should be loaded. (The Annotations can also have the optional attributes; path-prefix and options)

Injection

The injection of resources happens after the constructor of the object, which holds the resources, is called. This can be done with a Dependency-Injection framework of your choosing(it exists only a Module for Google Guice) or by doing the injection manually with the ResourceInjector class.

ResourceInjector in = new ResourceInjector(...);
ExampleEntity e = new ExampleEntity();
in.injectResources(e);

Resource Loader

To load a specific type of resource one has to provide the system with an implementation of the ResourceFromHandle/Bundle> interface. Such an implementation is i.e. the TextureLoader class which creates a Texture from a provided ResourceHandle.

To register such a Loader to the system a final step has to be done, one need to register a factory for the Loader class as a Java Service-Provider(see Service Provider). These factories have to extend the ResourceFrom<Handle/Bundle>Provider class, which holds the resource class these Loaders create. With this information the system can automatically register appropriated loaders to different resource types.

Hot-Reload

The term hot-reload describes the functionality to reload resource while the application is running. This feature enables i.e. shader live-coding or changing of textures while developing a game.

The implementation of this feature is build around a simple file path wrapper, the ResourceHandle class. This class provides on the one hand the possibility to retrieve a InputStream of a file and on the other hand one can register a Listener which gets called if the file has changed.

To enable this each ResourceHandle will get automatically(with the Injection stuff) or can manually be registered with a Java 7 WatchService which will handle throwing ChangeEvents.

When using the Injection part of this lib, the only thing one has to do to make use of this feature with an own resource type is to implement the update methods of the resource loader classes correctly.

public void update(ResourceHandle changed, final Texture old) {
      TextureData data = TextureIO.newTextureData(changed.getStream());
      old.updateImage(data);
}

Compile-Time resource list

I think this feature is quite interesting, because it can enable some interesting features which aren't developed yet. An annotation-processor(AP) UsedResourceProcessor is provided which can collect at compile-time every instance of @Inject<Handle/Bundle>. This list can be used for a lot of different things, like deleting unused resources from the build, creating texture-atlases as a build goal(I did an implementation for this use-case here:AutoAtlas) or compressing resources depending on there usage. i.e. if an image is only used as an icon one could scale the image down to use less disk space.

To enable this Annotation processor one has to add this to the maven build (pom.xml):

    <build>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
                <compilerArgument>-proc:none</compilerArgument>
                <source>1.7</source>
                <target>1.7</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.bsc.maven</groupId>
            <artifactId>maven-processor-plugin</artifactId>
            <executions>
                <execution>
                    <id>processResources</id>
                    <goals>
                        <goal>process</goal>
                    </goals>
                    <phase>process-resources</phase>
                    <configuration>
                        <processors>
                            <processor>darwin.resourcehandling.UsedResourceProcessor</processor>
                        </processors>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </build>

Resource Dependencies

Some resources have definde dependecy to other resources(files). To enable the AP to find these, one has to provide an implementation of the ResourceDependencyInspector interface and register it with the Java Service Provider System.

For example the OBJ model format defines in it main file dependencies to material library files. An implementation for the inspector could look like (link ObjDependencyInspector)

Caching

Caching of resources can be quite tricky and very application depending, because of this I just provide a simple ResourceCache interface which everybody can implement as they wish. Of course two simple implementations are provided MapResourceCache and NoResourceCaching which give basic functionality. Would be very happy if anyone would provide some advanced implementations.

Usage

The usage of the injection annotations was already described at the beginning, but how to get everything up in running will be explained here.

As said before everything can be done manually or with a DI framework, I will show to use this lib with both, but first how to do it by hand.

Manual-Setup

The only thing you need to get going by hand is to create a ResourceInjector, which you will provide with a caching implementation and the FileHandleCache.

FileHandleCache fac = new FileHandleCache();
ResourceCache cache = new NoResourceCache();
ResourceInjector inj = new ResourceInjector(fac, cache);

inj.injectResources(someObject);

The FileHandleCache can be configured to enable hot-reload and loading resources from a development folder, like when using maven as a build system resources would be in 'src/main/resources' at development time. To use these features one can just add them to a Builder like:

FileHandleCache fac = FileHandleCache.build()
                                     .withDevFolder()
                                     .withChangeNotification()
                                     .create();

DI-Setup

To use the custom injection annotations with your DI framework you have to register something similar to the Google Guice TypeListener. The implementation for Guice is the ResourceTypeListener class.

As said before I only support Guice atm. To use Guice just create your Injector with the ResourceHandlingModul module class. I use the Stage to decide if to use hot-reload and the dev-folders, so I do something like this in my main:

boolean debug = false;
for (String arg : args) {
    if (arg.equals("-devmode")) {
        debug = true;
        break;
    }
}
Stage stage = (RuntimeUtil.IS_DEBUGGING || debug) ? Stage.DEVELOPMENT : Stage.PRODUCTION;
Injectror inj = Guice.createInjector(stage, new ResourceHandlingModul());

inj.getInstance(App.class).start();

Things still left to do

  • useful implementations for the compile time resource list
  • no hot-reload for resource dependencies yet
  • advanced caching