Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] AssetID #7

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions rfcs/0002-assetid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Table of Contents

- [Motivation](#motivation)
- [Guide Level Explanation](#guide-level-explanation)
- [Reference Level Explanation](#reference-level-explanation)
- [Drawbacks](#drawbacks)
- [Rationale and Alternatives](#rationale-and-alternatives)
- [Prior Art](#prior-art)
- [Unresolved Questions](#unresolved-questions)

# Basic Info
[basic]: #basic-info

- Feature Name: asset_id
- Start Date: 2018-11-20
- RFC PR:
- [Tracking Issue](#tracking-issue):
- [Forum Thread](#forum-discussion):

# Summary
[summary]: #summary

AssetID is a unified way to reference any loadable asset in Amethyst. It proposes an AssetID enum with variants for v4 UUID (AssetUUID), a file path (PathBuf) or a custom URI format (see [Unresolved Questions](#unresolved-questions)).

Note that this is the first part in a series of RFCs I intend to write that details the technical aspects of my earlier [Asset Pipeline](https://github.com/amethyst/amethyst/issues/875) proposal.

# Motivation
[motivation]: #motivation

To realize the vision of the Asset Pipeline it will be important to be able to address loadable assets in a more powerful way than with a filesystem path.
Container formats such as GLTF and FBX can benefit from AssetID where an ID can be generated for each loadable asset within the file as opposed to referencing the entire source file.
It is also essential that cross-file references use UUID for Amethyst to handle renaming or moving of files without breaking these references.

The primary benefit of AssetID is to make it easier to write tools for Amethyst that handle assets. With a unified system for identifying assets, we can build a unified system for loading assets or metadata about assets which is super useful when creating editors and visualisers.

# Guide-Level Explanation
[guide-level-explanation]: #guide-level-explanation

AssetID is an identifier for loadable assets. It is a name and enum for any of three variants.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that we've got rid of the enum here. The ID was supposed to be unique and ideally every asset should have only one.

If you want to preserve the possibility to query by path, i'd say we could add a separate AssetQuery type that can hold either an ID or an alias. Dealing with OS paths in general is hard, and i'd leave that out completely if possible.

There is also no word about querying with static type in mind. Can i do import::<Texture>(assetAlias)? Or is the returned type always an opaque dyn Asset? It's especially noteworthy when you consider potential errors.

AssetUUID - Globally/universally unique identifier for an asset - v4 UUID. Generated by the Asset Pipeline when an asset is imported. Primary way to reference assets for tools and formats that are not edited by humans.
FilePath - The current way of referencing assets. It will still be supported in scenarios where it is possible.
URI - A way for users to plug in their own custom asset referencing scheme along with their own resolving systems.

I expect users that write code to primarily reference assets using the FilePath variant. References in data, for example those that are created by a Prefab editor when referencing meshes or textures would use UUID references. This is closer to the expectation of the user since the user did not specify a specific path, but a reference to an asset.

If a user created a prefab using a prefab editor and references an asset in a file that was later renamed or moved, it would be unexpected for the reference to no longer resolve since the user did not specify a path in the prefab editor. In the case of loading an asset by file path in code, it would be be expected behaviour for the load to fail if a file is removed however.

URI is a general way of referencing an asset that does not exist in the filesystem and was not imported by the asset pipeline. An example of this could be an asset stored in a database somewhere that is specific to the game it is resolved in. URIs are included in AssetID as a point of further extension for game developers and will not be used by any of the initial Asset Pipeline systems.
The intention is to provide traits that can be implemented to make it easier to integrate custom IDs into the asset loading process.
Copy link
Member

@Frizi Frizi Jan 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This custom trait (let's name it AsessetAlias) is redundant with everything except the raw UUID. This reinforces my point of dropping other representations (or simply refactor the existing enum into a AsessetAlias trait with bunch of impls)


# Reference-Level Explanation
[reference-level-explanation]: #reference-level-explanation

I propose to define the following in amethyst::assets.
```
pub type AssetUUID = [u8; 16];
#[derive(Clone, Serialize, Deserialize, Hash)]
pub enum AssetID {
UUID(AssetUUID),
FilePath(PathBuf),
URI(URI),
}
```

When an asset is imported from a file, an AssetUUID is generated for it and maintained by the Asset Hub daemon in metadata (see other, future RFC).

Once the Asset Pipeline proposal is fully realized, the AssetID will be used in various parts of the engine/asset pipeline. I don't believe there is value in implementing it before other parts of the Asset Pipeline is ready.

### Import
Each source file can be imported independently in parallel. When importing a single source file, possibly multiple resulting assets will be assigned an AssetUUID each and they may have AssetID dependencies of three kinds: Build, load and instantiate.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dependency kinds are a bit ambiguous for me. What does instantiate do?

In some cases these dependencies will be defined with the FilePath variant. After the import completes but before persisting metadata, the Asset Pipeline will attempt to resolve the FilePath dependencies of these assets to their AssetUUID equivalents. If the path cannot be resolved, the import will result in an error. This will ensure that the Asset Pipeline and all tools only have to handle stable AssetUUID references that map to specific assets regardless of the filesystem state.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some cases these dependencies will be defined with the FilePath variant

This sentence is contradictory to the rest of this paragraph. I was actually a bit surprised until i've read the rest of it. I'd actually avoid the FilePath name specifically here, as it might be confusing. There is nothing special about filesystem paths really.

How about that?

In some cases, the asset source file might specify dependencies in any way, e.g. relative paths or other format-specific methods. After the import completes but before persisting metadata, the Asset Pipeline will attempt to resolve the dependencies of these assets to their AssetUUID equivalents in a format-specific way. If the dependency cannot be resolved, the import will result in an error.


Attentive readers may have realized that a problem occurs in the case of a FilePath reference to a source file with multiple imported assets in them, such as a GLTF file. I propose that the Importer can specify a "main" asset that is to be used for the FilePath->Asset resolving process specified in [Load](#load)

### Build
When an asset is built for a target platform, the build dependencies of the asset will be included for the Builder to consume.

An example of this is shader #include statements. In the import step, the Importer can parse the shader for all include statements and convert these to AssetIDs. The builder will then receive both the source shader and its entire dependency set, including dependencies of dependencies recursively.
This allows the Builder to be a pure function which makes it a lot easier to implement things like build caching and distributed building.

### Load
[load]: #load
All loading will reference assets using AssetID. Different Resolver implementations will handle resolving an AssetID to a loadable asset blob depending on the runtime environment and the AssetID variant. The Resolver replaces the existing Source trait as a more general solution.

For example, in a development environment the Resolver will probably connect to AssetHub to resolve the AssetID to a build artifact hash based on the target platform, then load the build artifact and return it to the caller.

In a distributable build with optimized packed of assets, the Resolver may have an in-memory map of AssetUUID->FileLocation for knowing where an asset can be loaded from. It then performs the file read and returns the build artifact. For FilePath variants, the Resolver may have an in-memory map of FilePath->AssetUUID it uses before running the AssetUUID resolving process.
In the case of modding or asset overrides, a completely custom Resolver may be written that maintains a map of AssetUUID->FilePath to see if the user has provided a file override. If not, the resolver fallbacks to the resolver for packed assets.

# Drawbacks
[drawbacks]: #drawbacks

The drawbacks compared to no change at all are possibly increased complexity?

# Rationale, Alternatives and Prior Art
[rationale-and-alternatives]: #rationale-and-alternatives

This design for an asset identifier is primarily inspired by the Unity asset management system and most of the advantages can be seen by looking at how their builds work. The most important improvement upon their design being the following:
- Unity defines a UUID to reference a source file, then uses a "file-internal ID" to reference specific assets. This proposal improves upon that design with a general design that enables referencing specific assets within a source file with 16 bytes instead of 16 + 4 bytes.
[Reference here](https://docs.unity3d.com/ScriptReference/Build.Content.ObjectIdentifier.html)

The design is in my opinion superior to Godot, Unreal and Crytek asset management systems as their asset references use paths, leading to issues with renaming or moving files. Unreal's solution to moving/renaming only works if you move the file in the editor: put a "Redirector" at the path of a moved or renamed file. [Source here](https://docs.unrealengine.com/en-us/Engine/Basics/Redirectors)

# Unresolved Questions
[unresolved-questions]: #unresolved-questions

An unresolved question I have is whether URI should be included at all (not needed for most common use case, but has been requested previously) and if it is to be included, which URI library should be used.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we leave the special cases of paths or uris out of the system, this solves itself.