Mods and AssetManagement

Immortius

Lead Software Architect
Contributor
Architecture
GUI
I've been doing some work on the AssetManager and adding Mod support, building on t3hk0d3's work. Thought I'd take a moment to describe what I've done and get some feedback.

The overall goal I'm heading for is a centralised but extendible system for finding and loading assets, with support for mods that can be toggled on or off, or reloaded at runtime.

AssetURI

I've put together a simple scheme for identifying assets - basically a triple of AssetType, Package and Name. For instance, the click sound in Terasology's resources (that gets bundled into the jar) has the AssetURI "sound:engine:click", where engine is the name given to the built-in data package. A texture may be "texture:core:monkeyFace".

Often the type can be determined by the context, say when reading a prefab's setting for a Sound variable. In these cases the type can be omitted, so a shortened uri form can be used: "engine:click".

The class AssetURI provides an object representation for the uri.

AssetManager

AssetManager acts as a front end for finding and loading assets. The main methods are

Code:
// Loads an asset, given a uri
public static Asset load(AssetUri uri);

// Lists all available assets
public static Iterable<AssetUri> list();

// Lists all available assets of a given type
public static Iterable<AssetUri> list(AssetType type);
load() is cached, so if you have previously loaded a texture, the same texture object is returned.

Additionally there are type specific helper methods like loadTexture(String shortURI), and some leftover methods that need to be tidied up. But that is the gist of it.

AssetManager does not handle creating the asset objects itself, nor does it know how to find assets in the file system. Instead this behavior is delegated to AssetLoaders and AssetSources. AssetSources can be added and removed from the AssetManager to add and remove access to a mod. AssetLoaders are registered against an AssetType, and a file extension.

Still to be implemented is proper asset disposal, purging and reloading. I'm also considering the ability to register a NullObject for different asset types, to be returned when an asset fails to load. This would reduce the need for null checks.

Asset

Asset is an interface common for all assets that can be loaded. At the moment it just has a getter for the AssetURI of the asset, which can be used for serialization.

Later there will probably be a dispose() method as well. And maybe a Abstract base class to automatically register procedural assets.

AssetType

At the moment this is an enum. Probably could use the extendible enum trick to allow extension of the system if desirable, but doesn't seem to much point at the moment. The enum keeps the name of the package subfolder to find that type of asset in.

AssetSource

AccessSources provide access to different packages, with each type of AssetSource handling a different storage mechanism - primarily a directory or a zip/jar archive. So basically the AssetManager is given a source for each mod, and a source for the engine package.

Each asset source enumerates the available assets and produces a look up map of AssetURI -> URL(s). There can be multiple urls, to support assets composed of multiple files (but more on that later).

AssetLoader

AssetLoaders provide the logic to load a specific type of asset. They implement a single method:

Code:
public T load(InputStream stream, AssetUri uri, List<URL> urls) throws IOException
They are provided with:
  • The steam of the file associated with both the uri being loaded and the extension the loader is registered with
  • The AssetURI
  • A list of all the urls associated with the uri, with the one providing the stream at the front
URLs are used because Java handily has handlers for both files (file:) and archives (jar:, also works fine for zip files), so we can refer to either. Could replace with something else.

The stream is automatically close and all IOExceptions handled by the asset manager, for simplicity.

Serialization

This is pretty simple, each asset type needs to be registered with the ComponentLibrary:
Code:
componentLibrary.registerTypeHandler(Mesh.class, new AssetTypeHandler(AssetType.MESH, Mesh.class));
It then is serialized as a shortened uri, and when a Mesh is deserialized an AssetUri is generated combining the type and shortened uri, and loaded from the asset manager.

Texture

To give an example of how this all pulls together in practice:

A texture asset is composed of an image file (currently a .png) and optionally a metadata file specifying extra info (currently .json). The extra information is Filter and Wrapping modes for the texture when it is loaded by opengl, and could later have other things like whether to generate mipmaps. For each texture these files must have the same name, excluding extension.

When an asset source scans the directory/archive, it will record an AssetURI("texture:core:face"), and all the urls associated with it.

When the asset is loaded, an asset loader registered against AssetType.Texture and the extension of one of the urls is searched for. The PNGTextureLoader is found an provided with a stream for the png, "texture:core:face", and all of the urls.

PNGTextureLoader both reads the json file (if any) and the png and produces a Texture, which is returned to the asset manager. The asset manager will cache this and return it for future requests.
 

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
Thanks for the nice intro! Very informative :)

Let me know when you feel this is stable enough for develop. I'm thinking about doing a push to master soon and am wondering if this should go to develop before or after.
 

dei

Java Priest
Contributor
Design
Art
A general note on modding interface design:
In my oppinion the MC-Modding system is a bloody mess, where players don't know what works with what (collisions in item ids etc.), inconsistent gameplay... Please don't design it the way notch did and learn from his faults!

On the opposite side stands the modding framework of TA-Spring. They managed to use their engine for more than 5 well maintained mods which are very different from each other but are only written in scripts. You can find the modding ressources here: http://springrts.com/wiki/Game_and_Unit_development

Hope you can create something similar like the guys designed for spring.
 

ironchefpython

Member
Contributor
Architecture
dei said:
In my oppinion the MC-Modding system is a bloody mess, where players don't know what works with what (collisions in item ids etc.), inconsistent gameplay... Please don't design it the way notch did and learn from his faults!
Indeed

dei said:
On the opposite side stands the modding framework of TA-Spring. They managed to use their engine for more than 5 well maintained mods which are very different from each other but are only written in scripts. You can find the modding ressources here: http://springrts.com/wiki/Game_and_Unit_development
I'm working on something similar, mods that are generated from JSON data and Javascript scripts.
 

dei

Java Priest
Contributor
Design
Art
Why not use Java with well-defined interfaces for every type of a 'moddable' class and load it with class.forName...?
This way you could profit from your IDE's syntax-highlighting and completion features and check syntax-conformance directly with the java-compiler and you wouldn't have to write your own interpreter for each moddable property/behaviour.

To limit the moddable functionality you can perform type-checks for loaded classes using reflection and limit the visibility of non-moddable properties to the core package.
 
Top