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
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:
They are provided with:
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:
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.
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);
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
- 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
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));
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.