Skip to main content

goud_engine/assets/server/
core.rs

1//! Core `AssetServer` type definition and construction helpers.
2
3use crate::assets::dependency::DependencyGraph;
4#[cfg(feature = "native")]
5use crate::assets::AssetLoadError;
6use crate::assets::{AssetId, AssetStorage, ErasedAssetLoader};
7use std::collections::HashMap;
8use std::fmt;
9use std::path::{Path, PathBuf};
10
11// =============================================================================
12// LoadResult (native-only)
13// =============================================================================
14
15/// Result of an async asset load, sent from a background thread to the main thread.
16#[cfg(feature = "native")]
17pub(super) struct LoadResult {
18    /// Index component of the asset handle.
19    pub(super) handle_index: u32,
20    /// Generation component of the asset handle.
21    pub(super) handle_generation: u32,
22    /// Type identifier for the asset.
23    pub(super) asset_id: AssetId,
24    /// The loaded asset data or an error.
25    pub(super) result: Result<Box<dyn std::any::Any + Send>, AssetLoadError>,
26}
27
28// =============================================================================
29// AssetServer
30// =============================================================================
31
32/// Central coordinator for asset loading and caching.
33///
34/// The `AssetServer` manages:
35/// - Asset loaders (registered by file extension)
36/// - Asset storage (cached loaded assets)
37/// - Loading queue (assets being loaded)
38/// - Hot reloading (watching for file changes)
39///
40/// # Thread Safety
41///
42/// `AssetServer` is `Send` but NOT `Sync` - it should be accessed from a single
43/// thread (typically the main thread). For multi-threaded asset loading, use
44/// async handles and check loading state from the main thread.
45///
46/// # Example
47///
48/// ```
49/// use goud_engine::assets::{Asset, AssetServer};
50///
51/// struct MyAsset { data: String }
52/// impl Asset for MyAsset {}
53///
54/// let mut server = AssetServer::new();
55///
56/// // Load returns a handle immediately
57/// let handle = server.load::<MyAsset>("data/config.json");
58///
59/// // Asset loads in background, check state
60/// match server.get_load_state(&handle) {
61///     Some(state) => println!("Loading: {}", state),
62///     None => println!("Not found"),
63/// }
64/// ```
65pub struct AssetServer {
66    /// Base directory for asset files (e.g., "assets/").
67    pub(super) asset_root: PathBuf,
68
69    /// Asset storage (cache).
70    pub(super) storage: AssetStorage,
71
72    /// Registered asset loaders by extension.
73    pub(super) loaders: HashMap<String, Box<dyn ErasedAssetLoader>>,
74
75    /// Loader registry by AssetId (for lookup without extension).
76    pub(super) loader_by_type: HashMap<AssetId, Box<dyn ErasedAssetLoader>>,
77
78    /// Dependency graph for cascade reloading.
79    pub(super) dependency_graph: DependencyGraph,
80
81    /// Sender for background load results (used by native load_async).
82    #[cfg(all(feature = "native", not(feature = "web")))]
83    pub(super) load_sender: std::sync::mpsc::Sender<LoadResult>,
84
85    /// Receiver for background load results (native-only).
86    #[cfg(feature = "native")]
87    pub(super) load_receiver: std::sync::mpsc::Receiver<LoadResult>,
88}
89
90impl AssetServer {
91    /// Creates a new asset server with the default asset root ("assets/").
92    ///
93    /// # Example
94    ///
95    /// ```
96    /// use goud_engine::assets::AssetServer;
97    ///
98    /// let server = AssetServer::new();
99    /// ```
100    pub fn new() -> Self {
101        Self::with_root("assets")
102    }
103
104    /// Creates a new asset server with a custom asset root directory.
105    ///
106    /// # Arguments
107    ///
108    /// * `root` - Base directory for asset files (relative or absolute)
109    ///
110    /// # Example
111    ///
112    /// ```
113    /// use goud_engine::assets::AssetServer;
114    ///
115    /// let server = AssetServer::with_root("game_assets");
116    /// ```
117    pub fn with_root(root: impl AsRef<Path>) -> Self {
118        #[cfg(all(feature = "native", not(feature = "web")))]
119        let (load_sender, load_receiver) = std::sync::mpsc::channel();
120        #[cfg(all(feature = "native", feature = "web"))]
121        let (_load_sender, load_receiver) = std::sync::mpsc::channel::<LoadResult>();
122
123        Self {
124            asset_root: root.as_ref().to_path_buf(),
125            storage: AssetStorage::new(),
126            loaders: HashMap::new(),
127            loader_by_type: HashMap::new(),
128            dependency_graph: DependencyGraph::new(),
129            #[cfg(all(feature = "native", not(feature = "web")))]
130            load_sender,
131            #[cfg(feature = "native")]
132            load_receiver,
133        }
134    }
135
136    /// Returns the asset root directory.
137    ///
138    /// # Example
139    ///
140    /// ```
141    /// use goud_engine::assets::AssetServer;
142    ///
143    /// let server = AssetServer::with_root("game_assets");
144    /// assert_eq!(server.asset_root().to_str().unwrap(), "game_assets");
145    /// ```
146    #[inline]
147    pub fn asset_root(&self) -> &Path {
148        &self.asset_root
149    }
150
151    /// Sets the asset root directory.
152    ///
153    /// # Arguments
154    ///
155    /// * `root` - New base directory for asset files
156    pub fn set_asset_root(&mut self, root: impl AsRef<Path>) {
157        self.asset_root = root.as_ref().to_path_buf();
158    }
159}
160
161impl Default for AssetServer {
162    #[inline]
163    fn default() -> Self {
164        Self::new()
165    }
166}
167
168impl fmt::Debug for AssetServer {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        f.debug_struct("AssetServer")
171            .field("asset_root", &self.asset_root)
172            .field("total_assets", &self.storage.total_len())
173            .field("registered_types", &self.storage.type_count())
174            .field("loaders", &self.loader_by_type.len())
175            .finish()
176    }
177}