Skip to content

Gestalt Asset Core Quick Start

Immortius edited this page Jun 8, 2015 · 21 revisions

Gestalt Asset Core Quick Start

Defining an Asset type

There are two important classes when defining a new asset type.

Firstly, the AssetData class provides an implementation-agnostic 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. BookAsset::new

Establishing an AssetTypeManager

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.

Asset Formats

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));
        }
    }
}

Supplemental Formats

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);
        }
    }
}

Overrides

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.

Deltas

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.

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
        }
    }
}

Obtaining Assets

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);

Programatically creating/reloading Assets

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.

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

AssetDataProducers produce AssetData

Disposing Assets

Reload assets changed on disk

Contextual Asset Resolution

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);
}