Asset Reference
AssetRef
is a thin wrapper around asset path and optional cached asset handle.
The goal here was to allow to reduce resolving asset handles to one resolution for given asset reference - searching for asset handle by asset path is unnecessary work to do everytime we point to an asset by path and need to perform operations on its handle, so with asset reference we can resolve asset handle once and reuse its cached handle for future database operations.
Additional justification for asset references is serialization - this is preferred way to reference assets in serializable data, so once container gets deserializaed into typed data, asset handle resolution will happen only at first time.
use keket::{
database::{reference::AssetRef, AssetDatabase},
fetch::file::FileAssetFetch,
protocol::bundle::{
BundleAssetProtocol, BundleWithDependencies, BundleWithDependenciesProcessor,
},
};
use serde::Deserialize;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let mut database = AssetDatabase::default()
.with_protocol(BundleAssetProtocol::new("custom", CustomAssetProcessor))
.with_fetch(FileAssetFetch::default().with_root("resources"));
let handle = database.ensure("custom://part1.json")?;
while database.is_busy() {
database.maintain()?;
}
let contents = handle.access::<&CustomAsset>(&database).contents(&database);
println!("Custom chain contents: {:?}", contents);
Ok(())
}
#[derive(Debug, Default, Deserialize)]
struct CustomAsset {
content: String,
// Asset references are used to store path and cached handle. They serialize as asset paths.
// They can be used where we need to have an ability to reference asset by path, and ask
// for handle once instead of everytime as with asset paths.
#[serde(default)]
next: Option<AssetRef>,
}
impl CustomAsset {
fn contents(&self, database: &AssetDatabase) -> String {
let mut result = self.content.as_str().to_owned();
if let Some(next) = self.next.as_ref() {
result.push(' ');
if let Ok(resolved) = next.resolve(database) {
result.push_str(&resolved.access::<&Self>().contents(database));
}
}
result
}
}
struct CustomAssetProcessor;
impl BundleWithDependenciesProcessor for CustomAssetProcessor {
type Bundle = (CustomAsset,);
fn process_bytes(
&mut self,
bytes: Vec<u8>,
) -> Result<BundleWithDependencies<Self::Bundle>, Box<dyn Error>> {
let asset = serde_json::from_slice::<CustomAsset>(&bytes)?;
let dependency = asset
.next
.as_ref()
.map(|reference| reference.path().clone());
Ok(BundleWithDependencies::new((asset,)).maybe_dependency(dependency))
}
}
use keket::{
database::{reference::AssetRef, AssetDatabase},
fetch::file::FileAssetFetch,
protocol::bundle::{
BundleAssetProtocol, BundleWithDependencies, BundleWithDependenciesProcessor,
},
};
use serde::Deserialize;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let mut database = AssetDatabase::default()
.with_protocol(BundleAssetProtocol::new("custom", CustomAssetProcessor))
.with_fetch(FileAssetFetch::default().with_root("resources"));
let handle = database.ensure("custom://part1.json")?;
while database.is_busy() {
database.maintain()?;
}
let contents = handle.access::<&CustomAsset>(&database).contents(&database);
println!("Custom chain contents: {:?}", contents);
Ok(())
}
#[derive(Debug, Default, Deserialize)]
struct CustomAsset {
content: String,
// Asset references are used to store path and cached handle. They serialize as asset paths.
// They can be used where we need to have an ability to reference asset by path, and ask
// for handle once instead of everytime as with asset paths.
#[serde(default)]
next: Option<AssetRef>,
}
impl CustomAsset {
fn contents(&self, database: &AssetDatabase) -> String {
let mut result = self.content.as_str().to_owned();
if let Some(next) = self.next.as_ref() {
result.push(' ');
if let Ok(resolved) = next.resolve(database) {
result.push_str(&resolved.access::<&Self>().contents(database));
}
}
result
}
}
struct CustomAssetProcessor;
impl BundleWithDependenciesProcessor for CustomAssetProcessor {
type Bundle = (CustomAsset,);
fn process_bytes(
&mut self,
bytes: Vec<u8>,
) -> Result<BundleWithDependencies<Self::Bundle>, Box<dyn Error>> {
let asset = serde_json::from_slice::<CustomAsset>(&bytes)?;
let dependency = asset
.next
.as_ref()
.map(|reference| reference.path().clone());
Ok(BundleWithDependencies::new((asset,)).maybe_dependency(dependency))
}
}
SmartAssetRef
is a thin wrapper arount asset reference, that uses database
asset reference counting feature to automatically handle asset lifetime and
despawn asset when counter drops to 0.
use keket::{
database::{reference::SmartAssetRef, AssetDatabase, AssetReferenceCounter},
fetch::file::FileAssetFetch,
protocol::text::TextAssetProtocol,
};
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let mut database = AssetDatabase::default()
.with_protocol(TextAssetProtocol)
.with_fetch(FileAssetFetch::default().with_root("resources"));
// Load asset and mark its first reference.
let first = SmartAssetRef::new("text://lorem.txt", &mut database)?;
println!(
"Lorem Ipsum: {}",
first.resolve(&database)?.access::<&String>()
);
database.maintain()?;
assert_eq!(database.storage.len(), 1);
assert_eq!(
first
.resolve(&database)?
.access::<&AssetReferenceCounter>()
.counter(),
1
);
// Clone the asset and mark its second reference.
let second = first.clone();
database.maintain()?;
assert_eq!(database.storage.len(), 1);
assert_eq!(
second
.resolve(&database)?
.access::<&AssetReferenceCounter>()
.counter(),
2
);
// Ensure asset again from scratch and mark its third reference.
let third = SmartAssetRef::new("text://lorem.txt", &mut database)?;
database.maintain()?;
assert_eq!(database.storage.len(), 1);
assert_eq!(
third
.resolve(&database)?
.access::<&AssetReferenceCounter>()
.counter(),
3
);
// Drop the first reference and expect 2 references.
drop(first);
database.maintain()?;
assert_eq!(database.storage.len(), 1);
assert_eq!(
third
.resolve(&database)?
.access::<&AssetReferenceCounter>()
.counter(),
2
);
// Drop the second reference and expect 1 reference.
drop(second);
database.maintain()?;
assert_eq!(database.storage.len(), 1);
assert_eq!(
third
.resolve(&database)?
.access::<&AssetReferenceCounter>()
.counter(),
1
);
// Drop the third reference and expect no references and asset no more existing.
drop(third);
database.maintain()?;
assert_eq!(database.storage.len(), 0);
Ok(())
}