unity_asset_binary/bundle/
loader.rs

1//! Bundle resource loading and management
2//!
3//! This module provides functionality for loading and managing
4//! resources from Unity AssetBundles.
5
6use super::parser::BundleParser;
7use super::types::{AssetBundle, BundleLoadOptions};
8use crate::asset::Asset;
9use crate::error::{BinaryError, Result};
10use std::collections::HashMap;
11use std::path::Path;
12
13#[cfg(feature = "async")]
14use tokio::fs;
15
16/// Bundle resource loader
17///
18/// This struct provides high-level functionality for loading and managing
19/// AssetBundle resources, including caching and async loading support.
20pub struct BundleLoader {
21    /// Loaded bundles cache
22    bundles: HashMap<String, AssetBundle>,
23    /// Loading options
24    options: BundleLoadOptions,
25}
26
27impl BundleLoader {
28    /// Create a new bundle loader
29    pub fn new() -> Self {
30        Self {
31            bundles: HashMap::new(),
32            options: BundleLoadOptions::default(),
33        }
34    }
35
36    /// Create a new bundle loader with options
37    pub fn with_options(options: BundleLoadOptions) -> Self {
38        Self {
39            bundles: HashMap::new(),
40            options,
41        }
42    }
43
44    /// Load a bundle from file path
45    pub fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<&AssetBundle> {
46        let path_ref = path.as_ref();
47        let path_str = path_ref.to_string_lossy().to_string();
48
49        // Check if already loaded
50        if self.bundles.contains_key(&path_str) {
51            return Ok(self.bundles.get(&path_str).unwrap());
52        }
53
54        // Read file data
55        let data = std::fs::read(path_ref)
56            .map_err(|e| BinaryError::generic(format!("Failed to read bundle file: {}", e)))?;
57
58        // Parse bundle
59        let bundle = BundleParser::from_bytes_with_options(data, self.options.clone())?;
60
61        // Cache and return
62        self.bundles.insert(path_str.clone(), bundle);
63        Ok(self.bundles.get(&path_str).unwrap())
64    }
65
66    /// Load a bundle from memory
67    pub fn load_from_memory(&mut self, name: String, data: Vec<u8>) -> Result<&AssetBundle> {
68        // Check if already loaded
69        if self.bundles.contains_key(&name) {
70            return Ok(self.bundles.get(&name).unwrap());
71        }
72
73        // Parse bundle
74        let bundle = BundleParser::from_bytes_with_options(data, self.options.clone())?;
75
76        // Cache and return
77        self.bundles.insert(name.clone(), bundle);
78        Ok(self.bundles.get(&name).unwrap())
79    }
80
81    /// Async load a bundle from file path
82    #[cfg(feature = "async")]
83    pub async fn load_from_file_async<P: AsRef<Path>>(&mut self, path: P) -> Result<&AssetBundle> {
84        let path_ref = path.as_ref();
85        let path_str = path_ref.to_string_lossy().to_string();
86
87        // Check if already loaded
88        if self.bundles.contains_key(&path_str) {
89            return Ok(self.bundles.get(&path_str).unwrap());
90        }
91
92        // Read file data asynchronously
93        let data = fs::read(path_ref)
94            .await
95            .map_err(|e| BinaryError::generic(format!("Failed to read bundle file: {}", e)))?;
96
97        // Parse bundle
98        let bundle = BundleParser::from_bytes_with_options(data, self.options.clone())?;
99
100        // Cache and return
101        self.bundles.insert(path_str.clone(), bundle);
102        Ok(self.bundles.get(&path_str).unwrap())
103    }
104
105    /// Get a loaded bundle by name
106    pub fn get_bundle(&self, name: &str) -> Option<&AssetBundle> {
107        self.bundles.get(name)
108    }
109
110    /// Get a mutable reference to a loaded bundle
111    pub fn get_bundle_mut(&mut self, name: &str) -> Option<&mut AssetBundle> {
112        self.bundles.get_mut(name)
113    }
114
115    /// Unload a bundle
116    pub fn unload_bundle(&mut self, name: &str) -> bool {
117        self.bundles.remove(name).is_some()
118    }
119
120    /// Unload all bundles
121    pub fn unload_all(&mut self) {
122        self.bundles.clear();
123    }
124
125    /// Get list of loaded bundle names
126    pub fn loaded_bundles(&self) -> Vec<&str> {
127        self.bundles.keys().map(|s| s.as_str()).collect()
128    }
129
130    /// Get total memory usage of loaded bundles
131    pub fn memory_usage(&self) -> usize {
132        self.bundles
133            .values()
134            .map(|bundle| bundle.size() as usize)
135            .sum()
136    }
137
138    /// Find assets by name across all loaded bundles
139    pub fn find_assets_by_name(&self, name: &str) -> Vec<(&str, &Asset)> {
140        let mut results = Vec::new();
141
142        for (bundle_name, bundle) in &self.bundles {
143            for asset in &bundle.assets {
144                // SerializedFile doesn't have a name field, use bundle name instead
145                if bundle_name.contains(name) {
146                    results.push((bundle_name.as_str(), asset));
147                }
148            }
149        }
150
151        results
152    }
153
154    /// Find assets by type ID across all loaded bundles
155    pub fn find_assets_by_type(&self, _type_id: i32) -> Vec<(&str, &Asset)> {
156        let mut results = Vec::new();
157
158        for (bundle_name, bundle) in &self.bundles {
159            for asset in &bundle.assets {
160                // SerializedFile doesn't have a type_id field directly
161                // We'll skip this for now or implement differently
162                // TODO: Implement proper type filtering for SerializedFile
163                results.push((bundle_name.as_str(), asset));
164            }
165        }
166
167        results
168    }
169
170    /// Get bundle statistics
171    pub fn get_statistics(&self) -> LoaderStatistics {
172        let bundle_count = self.bundles.len();
173        let total_size = self.memory_usage();
174        let total_assets: usize = self.bundles.values().map(|b| b.asset_count()).sum();
175        let total_files: usize = self.bundles.values().map(|b| b.file_count()).sum();
176
177        LoaderStatistics {
178            bundle_count,
179            total_size,
180            total_assets,
181            total_files,
182            average_bundle_size: if bundle_count > 0 {
183                total_size / bundle_count
184            } else {
185                0
186            },
187        }
188    }
189
190    /// Validate all loaded bundles
191    pub fn validate_all(&self) -> Result<()> {
192        for (name, bundle) in &self.bundles {
193            bundle.validate().map_err(|e| {
194                BinaryError::generic(format!("Bundle '{}' validation failed: {}", name, e))
195            })?;
196        }
197        Ok(())
198    }
199
200    /// Set loading options
201    pub fn set_options(&mut self, options: BundleLoadOptions) {
202        self.options = options;
203    }
204
205    /// Get current loading options
206    pub fn options(&self) -> &BundleLoadOptions {
207        &self.options
208    }
209}
210
211impl Default for BundleLoader {
212    fn default() -> Self {
213        Self::new()
214    }
215}
216
217/// Bundle resource manager
218///
219/// This struct provides advanced resource management functionality,
220/// including dependency tracking and resource lifecycle management.
221pub struct BundleResourceManager {
222    loader: BundleLoader,
223    dependencies: HashMap<String, Vec<String>>,
224    reference_counts: HashMap<String, usize>,
225}
226
227impl BundleResourceManager {
228    /// Create a new resource manager
229    pub fn new() -> Self {
230        Self {
231            loader: BundleLoader::new(),
232            dependencies: HashMap::new(),
233            reference_counts: HashMap::new(),
234        }
235    }
236
237    /// Load a bundle with dependency tracking
238    pub fn load_bundle<P: AsRef<Path>>(
239        &mut self,
240        path: P,
241        dependencies: Vec<String>,
242    ) -> Result<()> {
243        let path_str = path.as_ref().to_string_lossy().to_string();
244
245        // Load dependencies first
246        for dep in &dependencies {
247            if !self.loader.bundles.contains_key(dep) {
248                return Err(BinaryError::generic(format!(
249                    "Dependency '{}' not loaded",
250                    dep
251                )));
252            }
253            // Increment reference count
254            *self.reference_counts.entry(dep.clone()).or_insert(0) += 1;
255        }
256
257        // Load the bundle
258        self.loader.load_from_file(path)?;
259
260        // Track dependencies
261        self.dependencies.insert(path_str.clone(), dependencies);
262        self.reference_counts.insert(path_str, 1);
263
264        Ok(())
265    }
266
267    /// Unload a bundle with dependency management
268    pub fn unload_bundle(&mut self, name: &str) -> Result<()> {
269        // Check if bundle exists
270        if !self.reference_counts.contains_key(name) {
271            return Err(BinaryError::generic(format!(
272                "Bundle '{}' not loaded",
273                name
274            )));
275        }
276
277        // Decrease reference count
278        let ref_count = self.reference_counts.get_mut(name).unwrap();
279        *ref_count -= 1;
280
281        // If reference count reaches zero, unload
282        if *ref_count == 0 {
283            // Unload dependencies
284            if let Some(deps) = self.dependencies.remove(name) {
285                for dep in deps {
286                    self.unload_bundle(&dep)?;
287                }
288            }
289
290            // Unload the bundle itself
291            self.loader.unload_bundle(name);
292            self.reference_counts.remove(name);
293        }
294
295        Ok(())
296    }
297
298    /// Get the underlying loader
299    pub fn loader(&self) -> &BundleLoader {
300        &self.loader
301    }
302
303    /// Get mutable access to the underlying loader
304    pub fn loader_mut(&mut self) -> &mut BundleLoader {
305        &mut self.loader
306    }
307
308    /// Get dependency information
309    pub fn get_dependencies(&self, name: &str) -> Option<&Vec<String>> {
310        self.dependencies.get(name)
311    }
312
313    /// Get reference count for a bundle
314    pub fn get_reference_count(&self, name: &str) -> usize {
315        self.reference_counts.get(name).copied().unwrap_or(0)
316    }
317}
318
319impl Default for BundleResourceManager {
320    fn default() -> Self {
321        Self::new()
322    }
323}
324
325/// Loader statistics
326#[derive(Debug, Clone)]
327pub struct LoaderStatistics {
328    pub bundle_count: usize,
329    pub total_size: usize,
330    pub total_assets: usize,
331    pub total_files: usize,
332    pub average_bundle_size: usize,
333}
334
335/// Convenience functions for quick bundle loading
336/// Load a single bundle from file
337pub fn load_bundle<P: AsRef<Path>>(path: P) -> Result<AssetBundle> {
338    let data = std::fs::read(path)
339        .map_err(|e| BinaryError::generic(format!("Failed to read bundle file: {}", e)))?;
340    BundleParser::from_bytes(data)
341}
342
343/// Load a bundle from memory
344pub fn load_bundle_from_memory(data: Vec<u8>) -> Result<AssetBundle> {
345    BundleParser::from_bytes(data)
346}
347
348/// Load a bundle with specific options
349pub fn load_bundle_with_options<P: AsRef<Path>>(
350    path: P,
351    options: BundleLoadOptions,
352) -> Result<AssetBundle> {
353    let data = std::fs::read(path)
354        .map_err(|e| BinaryError::generic(format!("Failed to read bundle file: {}", e)))?;
355    BundleParser::from_bytes_with_options(data, options)
356}
357
358#[cfg(feature = "async")]
359/// Async load a single bundle from file
360pub async fn load_bundle_async<P: AsRef<Path>>(path: P) -> Result<AssetBundle> {
361    let data = fs::read(path)
362        .await
363        .map_err(|e| BinaryError::generic(format!("Failed to read bundle file: {}", e)))?;
364    BundleParser::from_bytes(data)
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    #[test]
372    fn test_loader_creation() {
373        let loader = BundleLoader::new();
374        assert_eq!(loader.loaded_bundles().len(), 0);
375        assert_eq!(loader.memory_usage(), 0);
376    }
377
378    #[test]
379    fn test_resource_manager_creation() {
380        let manager = BundleResourceManager::new();
381        assert_eq!(manager.loader().loaded_bundles().len(), 0);
382    }
383}