-
Notifications
You must be signed in to change notification settings - Fork 23
Gestalt Asset Core Quick Start
There are two important classes when defining a new asset type.
Firstly, the AssetData class provides an implementation-agnostic (so not tied to a particular technology like OpenGL) representation of the data needed to create the asset:
public class BookData implements AssetData {
private String title;
private String body;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
Then there is the Asset class itself, which may be implementation specific (e.g. OpenGLTexture):
public class Book extends Asset<BookData> {
private String title;
private String body;
public Book(ResourceUrn urn, AssetType<?, BookData> type, BookData data) {
super(urn, type);
reload(data);
}
@Override
protected void doReload(BookData data) {
title = data.getTitle();
body = data.getBody();
}
@Override
protected void doDispose() {
title = "";
body = "";
}
public String getTitle() {
return title;
}
public String getBody() {
return body;
}
}
Additionally, you may need a AssetFactory class depending on how the asset type will be registered. A factory class looks like:
public class BookFactory implements AssetFactory<Book, BookData> {
@Override
public Book build(ResourceUrn urn, AssetType<? super Book, BookData> type, BookData data) {
return new Book(urn, type, data);
}
}
although if your constructor meets the signature, you may be able to get away with a method reference (e.g. (AssetFactory<Book, BookData>) BookAsset::new
)
AssetTypeManager is is the central manager for all asset types. ModuleAwareAssetTypeManager is the recommended asset type manager that hooks into a ModuleEnvironment and makes available assets from within the module's files.
When setting up the ModuleAwareAssetTypeManager, you can register core asset types - these will survive environment switches with their assets either reloaded (if still available in the new environment) or disposed.
ModuleAwareAssetTypeManager assetTypeManager = new ModuleAwareAssetTypeManager();
assetTypeManager.registerCoreAssetType(Book.class, Book::new, "books");
assetTypeManager.switchEnvironment(moduleEnvironment);
You can also have asset types automatically register using the @RegisterAssetType annotation:
import org.terasology.assets.module.annotations.RegisterAssetType;
@RegisterAssetType(folderName = "books", factoryClass = BookFactory.class)
public class Book extends Asset<BookData> {
\\ ...
}
When using the ModuleAwareAssetTypeManager, modules are expected to have the following directory structure for assets:
\assets\assetFolderName
- normal assets
\deltas\moduleName\assetFolderName
- for asset deltas
\overrides\moduleName\assetFolderName
- for overrides
where the assetFolderName is the folderName in the annotation or registerCoreAssetType()
method call.
Specific to the ModuleAwareAssetTypeManager is support for Asset Formats - these are used to load assets from files within modules and convert them into asset data.
For simple formats, this can be done as simple as:
@RegisterAssetFileFormat
public class BookFileFormat extends AbstractAssetFileFormat<BookData> {
public BookFileFormat() {
// Supported file extensions
super("book");
}
@Override
public BookData load(ResourceUrn urn, List<AssetDataFile> inputs) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputs.get(0).openStream(), Charsets.UTF_8))) {
return new BookData(CharStreams.readLines(reader));
}
}
}
It can be desirable to support files that provide additional information regardless of what format is being used (e.g. you might have metadata files to go with textures to provide settings on how they are loaded). This can be achieved with supplemental formats:
@RegisterAssetSupplementalFileFormat
public class TextMetadataFileFormat extends AbstractAssetAlterationFileFormat<TextData> {
public TextMetadataFileFormat() {
// File extensions
super("info");
}
@Override
public void apply(AssetDataFile input, TextData assetData) throws IOException {
try (InputStreamReader reader = new InputStreamReader(input.openStream(), Charsets.UTF_8)) {
String metadata = Joiner.on("/n").join(CharStreams.readLines(reader));
assetData.setMetadata(metadata);
}
}
}
ModuleAwareAssetTypeManager supports modules providing overrides for asset defined by other modules in their
dependency tree - asset files that will be used instead of the original files. These simply go in the appropriate \overrides\moduleName\assetFolder
path, where moduleName is the name of the module providing the original asset.
Overrides use the same formats as normal assets, including supplemental formats.
ModuleAwareAssetTypeManager supports modules providing deltas for asset defined by other modules in their
dependency tree - these are applied to the asset data after it is loaded from the original module, and before they are loaded into assets. These go in the appropriate \deltas\moduleName\assetFolder
path, where moduleName is the name of the module providing the original asset.
Deltas are more flexible than overrides because multiple modules can provide deltas, and changes to the original asset can be retained. However they can be harder to implement.
To support deltas specific formats are needed:
@RegisterAssetDeltaFileFormat
public class TextDeltaFileFormat extends AbstractAssetAlterationFileFormat<TextData> {
public TextDeltaFileFormat() {
// File extensions
super("delta");
}
@Override
public void apply(AssetDataFile input, TextData assetData) throws IOException {
try (InputStreamReader reader = new InputStreamReader(input.openStream(), Charsets.UTF_8)) {
// Apply changes to the provided TextData based on the input
}
}
}
Assets can most easily be obtained by using an AssetManager - a wrapper for an AssetTypeManager providing convenience methods for working with assets. Assets are referred to with a ResourceUrn typically with the structure "moduleName:assetName". If the asset is not yet loaded but available from an AssetDataProducer, the asset will be loaded and returned.
AssetManager assetManager = new AssetManager(assetTypeManager);
Optional<Book> myBook = assetManager.getAsset("engine:mybook", Book.class);
It is also possible to get a set of available asset urns:
Set<ResourceUrn> bookUrns = assetManager.getAvailableAssets(Book.class);
Assets can be programatically created by creating the appropriate asset data object and then loading it through an AssetManager. If the asset with the given ResourceUrn already exists it will be reloaded with the new data.
BookData data = new BookData();
Book myBook = assetManager.loadAsset(new ResourceUrn("engine:mybook"), data, Book.class);
In addition to loading over an existing asset, reloading can be triggered directly:
BookData data = new BookData();
myBook.reload(data)
AssetDataProducers produce AssetData programmatically based on a requesting urn - this provides a way to support procedural assets that are identified, reused, loaded on demand, and can be saved and reloaded via asset urn.
@RegisterAssetDataProducer
public class BookDataProducer implements AssetDataProducer<BookData> {
@Override
public Set<ResourceUrn> getAvailableAssetUrns() {
// Optionally provide any AssetUrns this producer can produce
return Collections.emptySet();
}
@Override
public Set<Name> getModulesProviding(Name resourceName) {
// Provide the modules that can produce an asset with that name, if any (used for urn resolution)
return Collections.emptySet();
}
@Override
public ResourceUrn redirect(ResourceUrn urn) {
// Provides the urn of an asset to actually load when a request is made to load the given urn
return urn;
}
@Override
public Optional<BookData> getAssetData(ResourceUrn urn) throws IOException {
// Produces asset data for the given urn
if (urn.getResourceName().equals(new Name("procedural")) && !urn.getFragmentName().isEmpty()) {
BookData data = new BookData();
data.setTitle(urn.getFragmentName().toString());
return Optional.of(data);
}
return Optional.empty();
}
}
It is possible to attempt to obtain an asset with an incomplete urn (missing the moduleName).
// Will return an asset iff there is only one module providing an asset with the resourceName "myBook"
Optional<Book> myBook = assetManager.getAsset("mybook", Book.class);
// Will return a list of all possible ResourceUrns with an resourceName "myBook"
Set<ResourceUrn> options = resolve("mybook", Book.class);
This can be further tailored by providing a module context to resolve within. This will restrict the search for possible assets to the specified module and its dependency tree. Additionally if an asset from that module has the desired resourceName it will be used.
Optional<Book> myBook = assetManager.getAsset("mybook", Book.class, new Name("engine));
Set<ResourceUrn> options = resolve("mybook", Book.class, new Name("engine));
It is also possible to specify a context using the ContextManager - this will automatically be used if no context is specified. This can be leveraged to set the context for all resolutions to a desired module before executing code from that module.
try (Context ignored = ContextManager.beginContext(new Name("engine")) {
Optional<Book> myBook = assetManager.getAsset("mybook", Book.class);
}