1use crate::import::{ImportType, Importer, ScanContext, ScannedImportable};
2use crate::ImporterRegistry;
3use crate::{DynEditContext, HydrateProjectConfiguration, ImportLogData, PipelineResult};
4use hydrate_data::{
5 AssetId, AssetLocation, AssetName, CanonicalPathReference, HashMap, ImporterId,
6 PathReferenceHash,
7};
8use hydrate_data::{ImportableName, PathReference};
9use hydrate_schema::SchemaRecord;
10use std::path::{Path, PathBuf};
11use std::sync::Arc;
12use uuid::Uuid;
13
14#[derive(Debug, Clone)]
15pub struct RequestedImportable {
16 pub asset_id: AssetId,
17 pub schema: SchemaRecord,
18 pub asset_name: AssetName,
19 pub asset_location: AssetLocation,
20 pub source_file: CanonicalPathReference,
21 pub canonical_path_references: HashMap<CanonicalPathReference, AssetId>,
22 pub path_references: HashMap<PathReferenceHash, CanonicalPathReference>,
23 pub replace_with_default_asset: bool,
24}
25
26#[derive(Default)]
27pub struct ImportJobToQueue {
28 pub import_job_source_files: Vec<ImportJobSourceFile>,
29 pub log_data: ImportLogData,
30}
31
32impl ImportJobToQueue {
33 pub fn is_empty(&self) -> bool {
34 self.log_data.log_events.is_empty() && self.import_job_source_files.is_empty()
35 }
36}
37
38#[derive(Debug)]
39pub struct ImportJobSourceFile {
40 pub source_file_path: PathBuf,
41 pub importer_id: ImporterId,
42 pub requested_importables: HashMap<ImportableName, RequestedImportable>,
43 pub import_type: ImportType,
44}
45
46pub fn create_asset_name(
47 source_file_path: &Path,
48 scanned_importable: &ScannedImportable,
49) -> AssetName {
50 if let Some(file_name) = source_file_path.file_name() {
51 let file_name = file_name.to_string_lossy();
52 if let Some(importable_name) = &scanned_importable.name.name() {
53 AssetName::new(format!("{}.{}", file_name, importable_name))
54 } else {
55 AssetName::new(file_name.to_string())
56 }
57 } else {
58 AssetName::empty()
59 }
60}
61
62pub fn recursively_gather_import_operations_and_create_assets(
63 project_config: &HydrateProjectConfiguration,
64 source_file_path: &Path,
65 importer: &Arc<dyn Importer>,
66 editor_context: &dyn DynEditContext,
67 importer_registry: &ImporterRegistry,
68 selected_import_location: &AssetLocation,
70
71 asset_id_assignments: Option<&HashMap<ImportableName, AssetId>>,
72
73 import_job_to_queue: &mut ImportJobToQueue,
76) -> PipelineResult<HashMap<ImportableName, AssetId>> {
77 assert!(source_file_path.is_absolute());
78 let source_file_path = dunce::canonicalize(source_file_path)?;
79
80 for import_job_source_file in &import_job_to_queue.import_job_source_files {
84 if import_job_source_file.source_file_path == source_file_path {
85 let mut imported_asset_ids = HashMap::default();
86 for (k, v) in &import_job_source_file.requested_importables {
87 imported_asset_ids.insert(k.clone(), v.asset_id);
88 }
89 return Ok(imported_asset_ids);
90 }
91 }
92
93 log::info!(
94 "recursively_gather_import_operations_and_create_assets {:?}",
95 source_file_path
96 );
97 let mut requested_importables = HashMap::<ImportableName, RequestedImportable>::default();
104 let mut imported_asset_ids = HashMap::default();
105
106 let mut scanned_importables = HashMap::default();
107
108 importer.scan_file(ScanContext::new(
109 &source_file_path,
110 editor_context.schema_set(),
111 importer_registry,
112 project_config,
113 &mut scanned_importables,
114 &mut import_job_to_queue.log_data.log_events,
115 ))?;
116
117 for (scanned_importable_name, scanned_importable) in &scanned_importables {
118 log::info!(
119 "iterating scanned importable {:?} {:?}",
120 source_file_path,
121 scanned_importable_name
122 );
123
124 let object_name = create_asset_name(&source_file_path, scanned_importable);
128
129 let mut canonical_path_references = HashMap::default();
130
131 for (referenced_source_file, importer_id) in &scanned_importable.referenced_source_file_info
133 {
134 let referenced_file_absolute = referenced_source_file
135 .canonicalized_absolute_path(project_config, &source_file_path)?;
136
137 let referenced_file_canonical =
138 referenced_file_absolute.clone().simplify(project_config);
139
140 let mut found = None;
142
143 for import_job_source_file in &import_job_to_queue.import_job_source_files {
145 for (_, requested_importable) in &import_job_source_file.requested_importables {
146 if requested_importable.source_file == referenced_file_canonical {
147 found = Some(requested_importable.asset_id);
148 }
149 }
150 }
151
152 if found.is_none() {
154 for (asset_id, _) in editor_context.data_set().assets() {
155 if let Some(import_info) = editor_context.data_set().import_info(*asset_id) {
156 if *import_info.source_file() == referenced_file_canonical {
157 found = Some(*asset_id);
158 }
159 }
160 }
161 }
162
163 if asset_id_assignments.is_some() && found.is_none() {
164 Err("Importing the asset will require importing another asset")?;
166 } else {
167 if found.is_none() {
169 let importer = importer_registry.importer(*importer_id).unwrap();
170 found = recursively_gather_import_operations_and_create_assets(
171 project_config,
172 Path::new(referenced_file_absolute.path()),
173 importer,
174 editor_context,
175 importer_registry,
176 selected_import_location,
177 asset_id_assignments,
178 import_job_to_queue,
179 )?
180 .get(referenced_source_file.importable_name())
181 .copied();
182 }
183 }
184
185 canonical_path_references.insert(referenced_source_file.clone(), found.unwrap());
187 }
189
190 let asset_id = if let Some(asset_id_assignments) = asset_id_assignments {
194 let Some(asset_id) = asset_id_assignments.get(scanned_importable_name) else {
195 continue;
196 };
197
198 *asset_id
199 } else {
200 AssetId::from_uuid(Uuid::new_v4())
201 };
202
203 let source_file = PathReference::new(
204 "".to_string(),
205 source_file_path.to_string_lossy().to_string(),
206 scanned_importable.name.clone(),
207 )
208 .simplify(project_config);
209
210 let requested_importable = RequestedImportable {
213 asset_id,
214 schema: scanned_importable.asset_type.clone(),
215 asset_name: object_name,
216 asset_location: selected_import_location.clone(),
217 source_file,
219 canonical_path_references,
220 path_references: scanned_importable.referenced_source_files.clone(),
221 replace_with_default_asset: true,
223 };
224
225 requested_importables.insert(scanned_importable.name.clone(), requested_importable);
226
227 let old = imported_asset_ids.insert(scanned_importable.name.clone(), asset_id);
228 assert!(old.is_none());
229 }
230
231 import_job_to_queue
234 .import_job_source_files
235 .push(ImportJobSourceFile {
236 source_file_path: source_file_path.to_path_buf(),
237 importer_id: importer.importer_id(),
238 requested_importables,
239 import_type: ImportType::ImportIfImportDataStale,
240 });
241
242 Ok(imported_asset_ids)
243}