unity_asset/environment/imp/
loader.rs

1use super::*;
2
3impl Environment {
4    /// Load assets from a path (file or directory).
5    pub fn load<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
6        let path = path.as_ref();
7
8        if path.is_file() {
9            self.load_file(path)?;
10        } else if path.is_dir() {
11            self.load_directory(path)?;
12        }
13
14        Ok(())
15    }
16
17    /// Load a single file.
18    pub fn load_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
19        let path = path.as_ref();
20
21        // Check file extension to determine type
22        if let Some(ext) = path.extension() {
23            match ext.to_str() {
24                Some("asset") | Some("prefab") | Some("unity") | Some("meta") => {
25                    match YamlDocument::load_yaml_with_warnings(path, false) {
26                        Ok((doc, warnings)) => {
27                            for w in warnings {
28                                self.push_warning(EnvironmentWarning::YamlDocumentSkipped {
29                                    path: path.to_path_buf(),
30                                    doc_index: w.doc_index,
31                                    error: w.error,
32                                });
33                            }
34                            self.yaml_documents.insert(path.to_path_buf(), doc);
35                        }
36                        Err(_) => {
37                            // Some Unity projects can store `.asset`-like files in binary form.
38                            // If YAML parsing fails, fall back to binary detection.
39                            self.try_load_binary(path)?;
40                        }
41                    }
42                }
43                _ => {
44                    // Best-effort binary detection for common build outputs.
45                    self.try_load_binary(path)?;
46                }
47            }
48        } else {
49            // Some Unity outputs (especially streamed resources and certain build artifacts)
50            // can be extension-less. Attempt binary detection anyway.
51            self.try_load_binary(path)?;
52        }
53
54        Ok(())
55    }
56
57    fn try_load_binary(&mut self, path: &Path) -> Result<()> {
58        match load_unity_file(path) {
59            Ok(UnityFile::AssetBundle(bundle)) => {
60                let mut bundle = bundle;
61                if let Some(registry) = self.type_tree_registry.clone() {
62                    for file in bundle.assets.iter_mut() {
63                        file.set_type_tree_registry(Some(registry.clone()));
64                    }
65                }
66                let source = BinarySource::path(path);
67                self.bundles.insert(source.clone(), bundle);
68                match self.bundle_container_cache.write() {
69                    Ok(mut cache) => {
70                        cache.remove(&source);
71                    }
72                    Err(e) => {
73                        let mut cache = e.into_inner();
74                        cache.remove(&source);
75                    }
76                }
77            }
78            Ok(UnityFile::SerializedFile(asset)) => {
79                let mut asset = asset;
80                if let Some(registry) = self.type_tree_registry.clone() {
81                    asset.set_type_tree_registry(Some(registry));
82                }
83                let source = BinarySource::path(path);
84                self.binary_assets.insert(source, asset);
85                match self.bundle_container_cache.write() {
86                    Ok(mut cache) => cache.clear(),
87                    Err(e) => e.into_inner().clear(),
88                }
89            }
90            Ok(UnityFile::WebFile(web)) => {
91                let web_path = path.to_path_buf();
92                self.webfiles.insert(web_path.clone(), web);
93                self.load_webfile_entries(&web_path)?;
94            }
95            Err(_) => {}
96        }
97
98        Ok(())
99    }
100
101    fn load_webfile_entries(&mut self, web_path: &PathBuf) -> Result<()> {
102        let web = self.webfiles.get(web_path).ok_or_else(|| {
103            UnityAssetError::format(format!("WebFile not loaded: {:?}", web_path))
104        })?;
105
106        let mut entry_names: Vec<String> = web.files.iter().map(|f| f.name.clone()).collect();
107        entry_names.sort();
108        entry_names.dedup();
109
110        for entry_name in entry_names {
111            let view = match web.extract_file_view(&entry_name) {
112                Ok(v) => v,
113                Err(_) => continue,
114            };
115
116            let Ok(parsed) =
117                load_unity_file_from_shared_range(view.backing_shared(), view.absolute_range())
118            else {
119                continue;
120            };
121
122            match parsed {
123                UnityFile::AssetBundle(bundle) => {
124                    let mut bundle = bundle;
125                    if let Some(registry) = self.type_tree_registry.clone() {
126                        for file in bundle.assets.iter_mut() {
127                            file.set_type_tree_registry(Some(registry.clone()));
128                        }
129                    }
130                    let source = BinarySource::WebEntry {
131                        web_path: web_path.clone(),
132                        entry_name: entry_name.clone(),
133                    };
134                    self.bundles.insert(source.clone(), bundle);
135                    match self.bundle_container_cache.write() {
136                        Ok(mut cache) => {
137                            cache.remove(&source);
138                        }
139                        Err(e) => {
140                            let mut cache = e.into_inner();
141                            cache.remove(&source);
142                        }
143                    }
144                }
145                UnityFile::SerializedFile(asset) => {
146                    let mut asset = asset;
147                    if let Some(registry) = self.type_tree_registry.clone() {
148                        asset.set_type_tree_registry(Some(registry));
149                    }
150                    let source = BinarySource::WebEntry {
151                        web_path: web_path.clone(),
152                        entry_name,
153                    };
154                    self.binary_assets.insert(source, asset);
155                    match self.bundle_container_cache.write() {
156                        Ok(mut cache) => cache.clear(),
157                        Err(e) => e.into_inner().clear(),
158                    }
159                }
160                UnityFile::WebFile(_) => {
161                    // Nested WebFiles are uncommon; ignore for now.
162                }
163            }
164        }
165
166        Ok(())
167    }
168
169    /// Load all supported files from a directory.
170    pub fn load_directory<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
171        let path = path.as_ref();
172
173        if !path.exists() {
174            return Err(UnityAssetError::format(format!(
175                "Directory does not exist: {:?}",
176                path
177            )));
178        }
179
180        if !path.is_dir() {
181            return Err(UnityAssetError::format(format!(
182                "Path is not a directory: {:?}",
183                path
184            )));
185        }
186
187        // Recursively traverse directory
188        self.traverse_directory(path)?;
189
190        Ok(())
191    }
192
193    /// Recursively traverse directory and load Unity files.
194    fn traverse_directory(&mut self, dir: &Path) -> Result<()> {
195        let entries = std::fs::read_dir(dir).map_err(|e| {
196            UnityAssetError::with_source(format!("Failed to read directory {:?}", dir), e)
197        })?;
198
199        for entry in entries {
200            let entry = entry
201                .map_err(|e| UnityAssetError::with_source("Failed to read directory entry", e))?;
202            let path = entry.path();
203
204            if path.is_dir() {
205                // Skip common Unity directories that don't contain assets
206                if let Some(dir_name) = path.file_name().and_then(|n| n.to_str()) {
207                    match dir_name {
208                        "Library" | "Temp" | "Logs" | ".git" | ".vs" | "obj" | "bin" => {
209                            continue; // Skip these directories
210                        }
211                        _ => {
212                            // Recursively process subdirectory
213                            self.traverse_directory(&path)?;
214                        }
215                    }
216                }
217            } else if path.is_file() {
218                // Try to load the file
219                if let Err(e) = self.load_file(&path) {
220                    // Record warning but continue processing other files
221                    self.push_warning(EnvironmentWarning::LoadFailed {
222                        path,
223                        error: e.to_string(),
224                    });
225                }
226            }
227        }
228
229        Ok(())
230    }
231}