khora_agents/asset_agent/
loader.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A registry for asset loaders, enabling dynamic loading of different asset types by name.
16
17use anyhow::{anyhow, Result};
18use khora_core::asset::Asset;
19use khora_lanes::asset_lane::AssetLoader;
20use std::{any::Any, collections::HashMap};
21
22/// Internal trait for loading any asset type.
23trait AnyLoader: Send + Sync {
24    fn load_any(&self, bytes: &[u8]) -> Result<Box<dyn Any + Send>>;
25}
26
27/// A "wrapper" that takes a generic `AssetLoader<A>` and implements `AnyLoader`.
28struct LoaderWrapper<A: Asset, L: AssetLoader<A>>(L, std::marker::PhantomData<A>);
29
30impl<A: Asset, L: AssetLoader<A> + Send + Sync> AnyLoader for LoaderWrapper<A, L> {
31    fn load_any(&self, bytes: &[u8]) -> Result<Box<dyn Any + Send>> {
32        // Call the GENERIC and TYPE-SAFE load() method...
33        let asset: A = self.0.load(bytes).map_err(|e| anyhow!(e.to_string()))?;
34        // ...and return the result in a Box<dyn Any>.
35        Ok(Box::new(asset))
36    }
37}
38
39/// The registry that manages complexity for the AssetAgent.
40pub(crate) struct LoaderRegistry {
41    loaders: HashMap<String, Box<dyn AnyLoader>>,
42}
43
44impl LoaderRegistry {
45    /// Creates a new `LoaderRegistry`.
46    pub(crate) fn new() -> Self {
47        Self {
48            loaders: HashMap::new(),
49        }
50    }
51
52    /// Registers a new asset loader.
53    pub(crate) fn register<A: Asset>(
54        &mut self,
55        type_name: &str,
56        loader: impl AssetLoader<A> + Send + Sync + 'static,
57    ) {
58        let wrapped = LoaderWrapper(loader, std::marker::PhantomData);
59        self.loaders
60            .insert(type_name.to_string(), Box::new(wrapped));
61    }
62
63    /// Loads an asset of the specified type from raw bytes.
64    pub(crate) fn load<A: Asset>(&self, type_name: &str, bytes: &[u8]) -> Result<A> {
65        let loader = self
66            .loaders
67            .get(type_name)
68            .ok_or_else(|| anyhow!("No loader registered for asset type '{}'", type_name))?;
69
70        let asset_any = loader.load_any(bytes)?;
71
72        let asset_boxed = asset_any.downcast::<A>().map_err(|_| {
73            anyhow!(
74                "Loader for type '{}' returned a different asset type than requested.",
75                type_name
76            )
77        })?;
78
79        Ok(*asset_boxed)
80    }
81}