/distill

Asset pipeline system for game engines & editor suites.

Primary LanguageRust

Rust

Distill

Distill is an asset pipeline for games, reading artist-friendly formats from disk, processing them into your engine-ready formats, and delivering them to your game runtime. Distill handles dependencies between assets, import & build caching, cross-device hot reloading during development, packing assets for a shippable game build, and more.

Vision

To create an open-source go-to solution for asset processing in games.

Features

The project contains a number of different components, and some can be used independently of others. You can combine them in different ways to tailor them to your workflow. Checkmarks indicate feature support - some features are dreamed up but not implemented.

Daemon

The daemon watches for filesystem events, imports source files to produce assets, manages metadata and serves asset load requests. It is primarily intended for use during development, but can also be used in a distributed game if appropriate. The daemon is very resource efficient and only does work when either a file changes or work is requested. Other components interact with the daemon through a transport-agnostic RPC protocol.

Asset UUIDs & Dependency Graphs

Every asset is identified by a 16-byte UUID that is generated when a source file is imported for the first time. Importers also produce an asset's build and load dependencies in terms of UUIDs which can be used to efficiently traverse the dependency graph of an asset without touching the filesystem.

Source file change detection

The daemon watches for filesystem changes and ensures source files are only imported when they change. Metadata and hashes are indexed locally in LMDB and version controlled in .meta files. Filesystem modification time and hashes are used to reduce redundant imports across your whole team to the greatest extent possible.

Import Caching

Assets imported from a source file are cached by a hash of their source file content and its ID, avoiding expensive parsing and disk operations.

Asset Change Log

Asset metadata is maintained in LMDB, a transactional database. The database's consistency guarantees and snapshot support provides a way to synchronize external data stores with the current state of the asset metadata using the Asset Change Log of asset changes.

Metadata Tracking & Caching

When assets are imported from source files, metadata is generated and stored in `.meta` files together with source file, as well as cached in a database. Commit these to version control along with your source files.

Move & Rename Source Files Confidently

Since metadata is stored with the source file and UUIDs are used to identify individual assets, users can move, rename and share source files with others without breaking references between assets.

Bring Your Own Asset Types

Asset types are not included in this project. You define your own asset types and source file formats by implementing the `Importer` trait and registering these with a file extension. The Daemon will automatically run your `Importer` for files with the registered extension as required. All asset types must implement `serde::Serialize` + `serde::Deserialize` + `TypeUuidDynamic` + `Send`.

RON Importer - *OPTIONAL*

An optional Importer and derive macro is included to simplify usage of serialized Rust types as source files using `serde`.

Type definition:

#[derive(Serialize, Deserialize, TypeUuid, SerdeImportable)]
#[uuid = "fab4249b-f95d-411d-a017-7549df090a4f"]
pub struct CustomAsset {
    pub cool_string: String,
    pub handle_from_path: Handle<crate::image::Image>,
    pub handle_from_uuid: Handle<crate::image::Image>,
}

custom_asset.ron:

{
    "fab4249b-f95d-411d-a017-7549df090a4f": 
    (
        cool_string: "thanks",
        // This references an asset from a file in the same directory called "amethyst.png"
        handle_from_path: "amethyst.png", 
        // This references an asset with a UUID (see associated .meta file for an asset's UUID)
        handle_from_uuid: "6c5ae1ad-ae30-471b-985b-7d017265f19f"
    )
}

Loader

The Loader module loads assets and their dependencies for a user-implemented AssetStorage trait to handle. Loader supports a pluggable LoaderIO trait for customizing where assets and their metadata are loaded from.

Hot Reloading

The built-in `RpcIO` implementation of `LoaderIO` talks to the `Daemon` and automatically reloads assets when an asset has changed.

Automatic Loading of Dependencies

When a source file is imported and an asset is produced, dependencies are gathered for the asset and saved as metadata. The Loader automatically ensures that dependencies are loaded before the asset is loaded, and that dependencies are unloaded when they are no longer needed.

serde` Support for Handles 🎉💯

An optional Handle type is provided with support for deserialization and serialization using `serde`. Handles can be deserialized as either a UUID or a path.

Automatic Registration of Handle Dependencies 🎉💯

Handle references that are serialized as part of an asset are automatically registered and the referenced assets are guaranteed to be loaded by the Loader before the depending asset is loaded. This means Handles in assets are always guaranteed to be valid and loaded.

Packing for distribution

To distribute your game, you will want to pack assets into files with enough metadata to load them quickly. The CLI supports packing assets into a file format which the `PackfileIO` implementation supports loading.

TODO

Networked artifact caching

Results of imports and builds can be re-used across your whole team using a networked cache server.

Platform-specific builds

Provide customized build parameters when building an asset and tailor the build artifact for a specific platform.

Scalable build pipeline

Once assets are imported from sources, the build system aims to be completely pure in the functional programming sense. Inputs to asset builds are all known and declared in the import step. This design enables parallelizable and even distributed builds.

Searching

Search tags can be produced at import and are automatically indexed by tantivy which enables super fast text search. The search index is incrementally maintained by subscribing to the Asset Change Log.

Cross-Platform Support

The project aims to support as many platforms as possible with the Loader module, while the Daemon may never be able to run on platforms without solid filesystem support such as WASM. Current known supported platforms:

Linux/Mac/Windows: Loader + Daemon

iOS: Loader

Examples

To run:

  • cd examples/handle_integration
  • cargo run
  • The example includes an image asset type, so try to put some images (png, jpg, tga) in the assets folder!

Have a look at the generated .meta files in the assets folder!

Get involved

This project is primarily used by Amethyst and casual communication around development happens in the #engine-general channel of the Amethyst Discord server. Feel free to drop by for a chat. Contributions or questions are very welcome!

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

See LICENSE-APACHE and LICENSE-MIT.

License

Licensed under either of

at your option.

PLEASE NOTE that some dependencies may be licensed under other terms. These are listed in deny.toml under licenses.exceptions on a best-effort basis, and are validated in every CI run using cargo-deny.