Skip to main content

12_custom_protocol_advanced/
12_custom_protocol_advanced.rs

1use anput::world::World;
2use keket::{
3    database::{
4        AssetDatabase,
5        handle::{AssetDependency, AssetHandle},
6        path::AssetPathStatic,
7    },
8    fetch::{AssetAwaitsResolution, AssetBytesAreReadyToProcess, file::FileAssetFetch},
9    protocol::AssetProtocol,
10};
11use serde::Deserialize;
12use std::error::Error;
13
14fn main() -> Result<(), Box<dyn Error>> {
15    let mut database = AssetDatabase::default()
16        // Register custom asset protocol.
17        .with_protocol(CustomAssetProtocol)
18        .with_fetch(FileAssetFetch::default().with_root("resources"))
19        .with_event(|event| {
20            println!("Asset closure event: {event:#?}");
21            Ok(())
22        });
23
24    let handle = database.ensure("custom://part1.json")?;
25
26    while database.is_busy() {
27        database.maintain()?;
28    }
29
30    let contents = handle.access::<&CustomAsset>(&database).contents(&database);
31    println!("Custom chain contents: {contents:?}");
32
33    Ok(())
34}
35
36// Custom asset type.
37#[derive(Debug, Default, Deserialize)]
38struct CustomAsset {
39    content: String,
40    #[serde(default)]
41    next: Option<AssetPathStatic>,
42}
43
44impl CustomAsset {
45    fn contents(&self, database: &AssetDatabase) -> String {
46        let mut result = String::new();
47        let mut current = Some(self);
48        while let Some(asset) = current {
49            result.push_str(asset.content.as_str());
50            current = current
51                .as_ref()
52                .and_then(|asset| asset.next.as_ref())
53                .and_then(|path| path.find(database))
54                .and_then(|handle| handle.access_checked::<&Self>(database));
55            if current.is_some() {
56                result.push(' ');
57            }
58        }
59        result
60    }
61}
62
63/* ANCHOR: custom_asset_protocol */
64struct CustomAssetProtocol;
65
66impl AssetProtocol for CustomAssetProtocol {
67    fn name(&self) -> &str {
68        "custom"
69    }
70
71    fn process_asset_bytes(
72        &mut self,
73        handle: AssetHandle,
74        storage: &mut World,
75    ) -> Result<(), Box<dyn Error>> {
76        // Always remember that in order for asset to be considered done with processing
77        // bytes, it has to remove AssetBytesAreReadyToProcess component from that asset.
78        // We are doing that by taking its bytes content first and removing it after.
79        let bytes = {
80            let mut bytes =
81                storage.component_mut::<true, AssetBytesAreReadyToProcess>(handle.entity())?;
82            std::mem::take(&mut bytes.0)
83        };
84        storage.remove::<(AssetBytesAreReadyToProcess,)>(handle.entity())?;
85
86        // Once we have asset bytes, we decode them into asset data.
87        let asset = serde_json::from_slice::<CustomAsset>(&bytes)?;
88
89        // We also have to extract dependencies if it has some.
90        if let Some(path) = asset.next.clone() {
91            // For every dependency we get, we need to spawn an asset entity with
92            // AssetAwaitsResolution component to tell asset database to start loading it.
93            let entity = storage.spawn((path, AssetAwaitsResolution))?;
94
95            // We should also relate processed asset with its dependency asset.
96            // Dependency relations are useful for traversing asset graphs.
97            storage.relate::<true, _>(AssetDependency, handle.entity(), entity)?;
98        }
99
100        storage.insert(handle.entity(), (asset,))?;
101        Ok(())
102    }
103}
104/* ANCHOR_END: custom_asset_protocol */