- AssetSource (Trait): A source that bytes can be extracted from and/or written to. E.g. filesystem, remote, embedded, etc.
- AssetReader (Trait): Translates from Source data to byte data.
- AssetWriter (Trait): Translates from byte data to Source data.
- AssetLoader (Trait): Translates from bytes to an Asset.
- AssetSaver (Trait): Translates from an Asset to bytes, possibly also processing the Asset.
- AssetServer (Resource): Used to load assets.
- AssetProcessor (Resource): A background processer that loads all assets from a "source" AssetSource, processes them, and then saves them to a "destination" AssetSource.
- Meta File: Produced when Assets are processed and saved. Stored in the same location as the Asset they correspond to.
graph LR
asset_source -->|source data| asset_reader
asset_reader -->|byte data| asset_loader
asset_loader --> asset
asset["Asset"]
asset_source["AssetSource"]
asset_reader["AssetReader:\nTranslates source data into byte data"]
asset_loader["AssetLoader:\nTranslates byte data into an Asset"]
graph RL
asset -->|read by| asset_saver
asset_saver -->|byte data| asset_writer
asset_writer -->|source data| asset_source
asset["Asset"]
asset_source["AssetSource"]
asset_writer["AssetWriter:\nTranslates byte data into source data"]
asset_saver["AssetSaver:\nTranslates an Asset to bytes, possibly processing it"]
graph TD
asset_reader -->|byte data| process_trait
process_trait -->|processed byte data| asset_writer
asset_loader -->|loaded asset| load_save_processor
load_save_processor -->|processed asset| asset_saver
asset_reader["AssetReader"]
asset_writer["AssetWriter"]
asset_loader["AssetLoader"]
asset_saver["AssetSaver"]
process_trait["impl Process"]
load_save_processor["LoadAndSave<L: AssetLoader, S: AssetSaver>"]
Using bevy's asset system first involves setting up the types that your Asset needs:
graph LR
custom_asset_loader -->|impl| asset_loader
custom_asset_saver -->|impl| asset_saver
custom_asset -->|impl/derive| asset
custom_asset_reader -->|impl| asset_reader
custom_asset_writer -->|impl| asset_writer
custom_asset_loader["CustomAssetLoader"]
custom_asset_saver["CustomAssetSaver"]
custom_asset_reader["CustomAssetReader"]
custom_asset_writer["CustomAssetWriter"]
asset_loader["AssetLoader"]
asset_saver["AssetSaver"]
asset_reader["AssetReader"]
asset_writer["AssetWriter"]
custom_asset["CustomAsset"]
asset["Asset"]
Secondly, you must set up your App. In this diagram, start from the "App" node in the lower left, and call the necessary methods for your custom asset implementation:
graph LR
app -->|call| init_asset
app -->|call| init_asset_loader
app -->|call| register_asset_source
app -.->|call| register_asset_processor
app -.->|call| set_default_asset_processor
register_asset_source -->|id is| asset_source_id
register_asset_source -->|source is| asset_source_builder
register_asset_source -->|has requirement| register_asset_source_note_requirement
register_asset_source -->|has side effect| register_asset_source_note_side_effect
register_asset_processor -.->|extension is| asset_processor_extension
register_asset_processor -.->|for P| processor_note
set_default_asset_processor -.->|extension is| asset_processor_extension
set_default_asset_processor -.->|has side effect| set_default_asset_processor_side_effect
set_default_asset_processor -.->|for P| processor_note
app["App"]
init_asset["app.init_asset::<CustomAsset>()"]
init_asset_loader["app.init_asset_loader::<CustomAssetLoader>()"]
register_asset_source["app.register_asset_source(id, source)"]
register_asset_processor["app.register_asset_processor::<P: Process>(extension)"]
set_default_asset_processor["app.set_default_asset_processor::<P: Process>(extension)"]
set_default_asset_processor_side_effect["Replaces any existing asset processor for this extension with the given asset processor."]
register_asset_source_note_requirement["Must be called before adding the AssetPlugin"]
register_asset_source_note_side_effect["If an asset source already exists with the given Id, it is replaced."]
asset_processor_extension["A &str that represents a file extension.\nE.g. "cstm" would apply to files with the ".cstm" extension"]
processor_note["Although you can implement Process on your own type,\nit is recommended to use LoadAndSave<L: AssetLoader, S: AssetSaver<Asset = L::Asset>>"]
subgraph AssetSourceId
asset_source_id -->|with variant| asset_source_id_default
asset_source_id -->|with variant| asset_source_id_name
asset_source_id_default --> asset_source_id_default_explanation
asset_source_id_name --> asset_source_id_name_explanation
asset_source_id["Enum"]
asset_source_id_default["AssetSourceId::Default"]
asset_source_id_name["AssetSourceId::Name"]
asset_source_id_default_explanation["When an AssetPath does not provide a source, this source is used.\nE.g. "/path/to/asset.cstm""]
asset_source_id_name_explanation["When an AssetPath does provide a source, this source is used.\nE.g. "custom://path/to/asset.cstm""]
end
subgraph AssetSourceBuilder
asset_source_builder_get -->|gets you| asset_source_builder
asset_source_builder -->|has method| asset_source_builder_with_reader
asset_source_builder -->|has method| asset_source_builder_with_writer
asset_source_builder -->|has method| asset_source_builder_with_watcher
asset_source_builder -->|has method| asset_source_builder_with_processed_reader
asset_source_builder -->|has method| asset_source_builder_with_processed_writer
asset_source_builder -->|has method| asset_source_builder_with_processed_watcher
asset_source_builder -->|has method| asset_source_builder_with_watcher_warning
asset_source_builder -->|has method| asset_source_builder_with_processed_watcher_warning
asset_source_builder -->|has method| asset_source_builder_platform_default
asset_source_builder["AssetSourceBuilder"]
asset_source_builder_get["AssetSource::build()"]
asset_source_builder_with_reader["with_reader(...)"]
asset_source_builder_with_writer["with_writer(...)"]
asset_source_builder_with_watcher["with_watcher(...)"]
asset_source_builder_with_processed_reader["with_processed_reader(...)"]
asset_source_builder_with_processed_writer["with_processed_writer(...)"]
asset_source_builder_with_processed_watcher["with_processed_watcher(...)"]
asset_source_builder_with_watcher_warning["with_watcher_warning(...)"]
asset_source_builder_with_processed_watcher_warning["with_processed_watcher_warning(...)"]
asset_source_builder_platform_default["platform_default(...)"]
end
Lastly, to load your custom asset you can use the AssetServer:
graph LR
world -->|gets you| asset_server
query -->|gets you| asset_server
asset_server -->|has method| asset_server_load
world["World: world.get_resource::<AssetServer>();"]
query["Query: Res<AssetServer>"]
asset_server["AssetServer"]
asset_server_load["asset_server.load("an_asset.cstm")"]
- contains an exclusive AssetServer which is configured for asset processing requirements
- Such as?
- contains 'data' which represents the state of the AssetProcessor. This state includes data on
- what processors and default processors are added
- the current state of the AssetProcessor (Initializing, Processing, or Finished)
- asset sources
- Senders and recievers for initialization and finished "events" that are emitted when the AssetProcessor has its state changed.
- A log which tracks the state of asset processing to ensure that processing operations are atomic.
- When the AssetProcessor is started, it immediately starts processing all configures assets. Once it is done it blocks on listening for source change events.
- Processing all files involves
- When it recieves a source change event, it takes action based on the event recieved. I.e. if an asset added event is received, it processes that asset.
- Files that communicate to the AssetServer or AssetProcessor how an asset should be handled.
- Contains:
- AssetMetaMinimal
- The bare minimum that a .meta file may contain.
- Lists meta information, such as meta format version and an:
- AssetActionMinimal, which is an enum with variants Load, Process, and Ignore. Each variant stores the required information for the action it represents.
- The bare minimum that a .meta file may contain.
- AssetMetaMinimal
- Meta files are generated:
- When processing assets, if an asset does not have a meta file then one is generated from the default meta file for the default processor for an asset's file extension if it exits, otherwise from the loader for that file extension.
- From the "to be processed' directory
- File is processed using the local meta file, or the default meta file for this laoder?processor? if local meta file does not exist
- File is saved to the "not to be processed" directory with a meta file telling bevy what loader it should use to load this file. PR: future possibilities? AssetTransformer chaining?: Any tuple of AssetTransformers with maching inputs an outputs should also be an AssetTransformer. Problem: How to generically combine Settings and Error types. Best guess
/// Blanket implementation of AssetTransformer for any tuple of AssetTransformers who have the
impl<T: AssetTransformer<AssetOutput = U::AssetInput>, U: AssetTransformer> AssetTransformer for (T, U) {
type AssetInput = T::AssetInput;
type AssetOutput = U::AssetOutput;
type Settings = (T::Settings, U::Settings);
type Error = ?;
fn transform<'a>(
&'a self,
asset: Self::AssetInput,
settings: &'a Self::Settings,
) -> Result<Self::AssetOutput, Box<dyn std::error::Error + Send + Sync + 'static>> {
Ok(U::transform(&self.1, T::transform( &self.0, asset, &settings.0)?, &settings.1)?)
}
}
- Runtime loading options settable at load time (currently only settable via .meta file or when adding the loader to the app)
- Runtime processing
- Set processor options at process time (currently only accessible via .meta file)
- Runtime saving
- Set saver options at save time (currently only accessible via .meta file)
- Savable/Loadable Handles
- One source of "truth" for assets location?
- Asset path for loaded assets.
- What about manually inserted assets?
- Asset uuids that point to additional data, such as path, manual construction, etc?
- Should this be flexible so that users can add new "sources" for assets outside of the asset server or manual construction?
- Should manual construction just be its own
AssetSource
from the asset server?- This begs some questions about save dependencies. Is this supported?
- Should manual construction just be its own
- Should this be flexible so that users can add new "sources" for assets outside of the asset server or manual construction?
- Currently
UntypedAssetId
uses asset uuids so that if an asset that fails to load later succeeds, all handles to it will be properly updated. We want to maintain this feature.
- One source of "truth" for assets location?
AssetReader
andAssetWriter
currently don't have custom error types and instead use a shared error type. Some asset sources will have unique errors, such as http errors for remote://, soAssetReader
s andAssetWriter
s should define their own error types.
-
The Extension system must be able to:
- Allow user defined extensions
- Allow alterations to other extensions? (maybe as a solution to the verification problem?)
- Allow user defined ways to extract stuff (Just Assets, or more than just that?) from the Gltf file.
- Allow alterations to other people's way to extract stuff? (what if your extension added additional behavior, ideally you want to be able to integrate that into existing stuff (such as extra components added to a bevy Scene))
- Be able to verify whether or not a given gltf is gltf compliant (both when loading and saving)
- Be able to load any gltf compliant file, so long as it doesn't require an unsupported extension.
- Be able to save a gltf compliant file with any extensions.
- Be able to create a gltf file to save in code from stuff.
- Ideally, and most pressingly, have a nice api for interacting with gltf extensions.
- Allow user defined extensions
-
What is a Gltf extension?
- In regards to a Gltf file:
- A Gltf extension is any additional data that can be appended to any json object in the Gltf file
- In addition to appending additional data, Gltf extensions can make some part of the json objects they are attached to redundant, affecting verification if the extension is listed as required
- There is also a rarer behavior in which a Gltf extension changes the structure of the data of the core Gltf specification. To my knowledge, this only occures in the Mesh Quantization extension. Whether or not this unique behavior should be supported is up for debate.
- In regards to the stuff the Gltf file represents:
- A Gltf extension may offer an alternative form of data for some stuff (ie compression).
- A Gltf extension may indicate additional properties or behavior for some stuff (ie emissiveness, an index or refraction, some game particular behavior indicator, etc).
- For a Gltf extensions to be "supported", it must be supported on the file side and on the "stuff" side.
- To give an example as to why this is necessary, the KHR_materials_unlit extension is effectively a marker that a particular material should be affected by an alternative unlit shading model, usually for performance reasons. Stuff must be able to support these behaviors in order for the extension to be truly supported.
- Warn whenever stuff tries to extract something from the Gltf and doesn't use all extension data provided?
- To give an example as to why this is necessary, the KHR_materials_unlit extension is effectively a marker that a particular material should be affected by an alternative unlit shading model, usually for performance reasons. Stuff must be able to support these behaviors in order for the extension to be truly supported.
- In regards to a Gltf file: