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}