hydrate_pipeline/import/
import_util.rs

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    //asset_engine: &AssetEngine,
69    selected_import_location: &AssetLocation,
70
71    asset_id_assignments: Option<&HashMap<ImportableName, AssetId>>,
72
73    // In addition to being the imports that need to be queued, this is also the assets that were
74    // created. Pre-existing but referenced assets won't be in this list
75    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    //
81    // If we request to import a file we already processed, just return the name/id pairs again
82    //
83    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    //
98    // We now build a list of things we will be importing from the file.
99    // 1. Scan the file to see what's available
100    // 2. Create/Find assets for all the things we want to import
101    // 3. Enqueue the import operation
102    //
103    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        //
125        // Pick name for the asset for this file
126        //
127        let object_name = create_asset_name(&source_file_path, scanned_importable);
128
129        let mut canonical_path_references = HashMap::default();
130
131        //TODO: Check referenced source files to find existing imported assets or import referenced files
132        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            // Does it already exist?
141            let mut found = None;
142
143            // Have we already iterated over it and will be creating it later?
144            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            // Have we imported it previously?
153            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                // fail
165                Err("Importing the asset will require importing another asset")?;
166            } else {
167                // If we didn't find it, try to import it
168                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            //if let Some(found) = found {
186            canonical_path_references.insert(referenced_source_file.clone(), found.unwrap());
187            //}
188        }
189
190        // At this point all referenced files have either been found or scanned
191
192        // We create a random asset ID now so that other imported files can reference this asset later
193        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        // This is everything we will need to create the asset, set the import info, and init
211        // the build info with path overrides
212        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            //importer_id: importer.importer_id(),
218            source_file,
219            canonical_path_references,
220            path_references: scanned_importable.referenced_source_files.clone(),
221            //TODO: A re-import of data from the source file might not want to do this
222            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    //asset_engine.queue_import_operation(asset_ids, importer.importer_id(), file.to_path_buf());
232    //(asset_ids, importer.importer_id(), file.to_path_buf())
233    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}