Asset Database

Let's dive into central point of the library - asset database.

AssetDatabase is a thin, higher level abstraction for underlying (public) ECS storage where assets data live.

ECS as storage allowed to treat assets as rows in database tables, where single asset can have multiple data columns associated with given asset. This opened up a possibility for fast queries and lookups on multiple entities that pass given requirements (set of components).

This is very important for systems which process and modify assets in specific for them ways (in Keket those systems are asset fetch engines and asset protocols - more about them later in the book).

We can use queries and lookups to process only assets that have given set of requirements based on the data they store. For example when we load assets with FileAssetFetch, we get not only asset data, but also meta data as PathBuf and Metadata components, so if we want to for example scan database for all assets that come from file system, we can just query them with PathBuf component and use that fast query for asset statistics report.

use keket::{
    database::{path::AssetPath, AssetDatabase},
    fetch::file::FileAssetFetch,
    protocol::{bundle::BundleAssetProtocol, bytes::BytesAssetProtocol, text::TextAssetProtocol},
};
use serde::Deserialize;
use serde_json::Value;
use std::{error::Error, fs::Metadata, path::PathBuf};

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct Person {
    age: u8,
    home: PersonHome,
    friends: Vec<String>,
}

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct PersonHome {
    country: String,
    address: String,
}

fn main() -> Result<(), Box<dyn Error>> {
    let mut database = AssetDatabase::default()
        // Asset protocols tell how to deserialize bytes into assets.
        .with_protocol(TextAssetProtocol)
        .with_protocol(BytesAssetProtocol)
        // Bundle protocol allows for easly making a protocol that takes
        // bytes and returns bundle with optional dependencies.
        .with_protocol(BundleAssetProtocol::new("json", |bytes: Vec<u8>| {
            let asset = serde_json::from_slice::<Value>(&bytes)?;
            Ok((asset,).into())
        }))
        .with_protocol(BundleAssetProtocol::new("person", |bytes: Vec<u8>| {
            let asset = serde_json::from_slice::<Person>(&bytes)?;
            Ok((asset,).into())
        }))
        // Asset fetch tells how to get bytes from specific source.
        .with_fetch(FileAssetFetch::default().with_root("resources"));

    // Ensure method either gives existing asset handle or creates new
    // and loads asset if not existing yet in storage.
    let lorem = database.ensure("text://lorem.txt")?;
    // Accessing component(s) of asset entry.
    // Assets can store multiple data associated to them, consider them meta data.
    println!("Lorem Ipsum: {}", lorem.access::<&String>(&database));

    let json = database.ensure("json://person.json")?;
    println!("JSON: {:#}", json.access::<&Value>(&database));

    let person = database.ensure("person://person.json")?;
    println!("Person: {:#?}", person.access::<&Person>(&database));

    let trash = database.ensure("bytes://trash.bin")?;
    println!("Bytes: {:?}", trash.access::<&Vec<u8>>(&database));

    // We can query storage for asset components to process assets, just like with ECS.
    for (asset_path, file_path, metadata) in database
        .storage
        .query::<true, (&AssetPath, &PathBuf, &Metadata)>()
    {
        println!(
            "Asset: `{}` at location: {:?} has metadata: {:#?}",
            asset_path, file_path, metadata
        );
    }

    Ok(())
}

Since storage is just an ECS, every asset entity always must store at least AssetPath component to be considered valid asset - every component other than asset path is considered asset data that is either usable for outside systems, or for asset fetch engines and asset protocols as meta data for them in asset resolution process.

When an asset entity stores some components without asset path, then it is never considered an actual asset, and rather an entity not important to asset management - some systems might find it useful for them, but it is highly not advised to put there anything other than assets.