hydrate_pipeline/
lib.rs

1use std::sync::Arc;
2
3pub use hydrate_schema::*;
4
5pub use hydrate_data::*;
6
7mod pipeline_error;
8
9mod build;
10mod import;
11mod project;
12mod thumbnails;
13pub use thumbnails::*;
14
15pub use import::{
16    import_util::create_asset_name,
17    import_util::recursively_gather_import_operations_and_create_assets, ImportContext,
18    ImportJobSourceFile, ImportJobToQueue, ImportJobs, ImportStatus, ImportStatusImporting,
19    ImportType, Importer, ImporterRegistry, ImporterRegistryBuilder, RequestedImportable,
20    ScanContext, ScannedImportable,
21};
22
23pub use project::{HydrateProjectConfiguration, NamePathPair};
24
25pub use crate::build::{
26    AssetArtifactIdPair, BuildJobs, BuildStatus, BuildStatusBuilding, Builder, BuilderContext,
27    BuilderRegistry, BuilderRegistryBuilder, EnumerateDependenciesContext, HandleFactory,
28    JobEnumeratedDependencies, JobId, JobInput, JobOutput, JobProcessor, JobProcessorRegistry,
29    JobProcessorRegistryBuilder, RunContext,
30};
31pub use pipeline_error::*;
32
33mod log_events;
34mod uuid_newtype;
35pub use log_events::*;
36
37pub struct AssetPluginRegistries {
38    pub importer_registry: ImporterRegistry,
39    pub builder_registry: BuilderRegistry,
40    pub job_processor_registry: JobProcessorRegistry,
41    pub thumbnail_provider_registry: ThumbnailProviderRegistry,
42}
43
44pub struct AssetPluginSetupContext<'a> {
45    pub importer_registry: &'a mut ImporterRegistryBuilder,
46    pub builder_registry: &'a mut BuilderRegistryBuilder,
47    pub job_processor_registry: &'a mut JobProcessorRegistryBuilder,
48    pub thumbnail_provider_registry: &'a mut ThumbnailProviderRegistryBuilder,
49}
50
51pub trait AssetPlugin {
52    fn setup(context: AssetPluginSetupContext);
53}
54
55pub struct AssetPluginRegistryBuilders {
56    importer_registry: ImporterRegistryBuilder,
57    builder_registry: BuilderRegistryBuilder,
58    job_processor_registry: JobProcessorRegistryBuilder,
59    thumbnail_provider_registry: ThumbnailProviderRegistryBuilder,
60}
61
62impl AssetPluginRegistryBuilders {
63    pub fn new() -> Self {
64        AssetPluginRegistryBuilders {
65            importer_registry: Default::default(),
66            builder_registry: Default::default(),
67            job_processor_registry: Default::default(),
68            thumbnail_provider_registry: Default::default(),
69        }
70    }
71
72    pub fn register_plugin<T: AssetPlugin>(mut self) -> Self {
73        T::setup(AssetPluginSetupContext {
74            importer_registry: &mut self.importer_registry,
75            builder_registry: &mut self.builder_registry,
76            job_processor_registry: &mut self.job_processor_registry,
77            thumbnail_provider_registry: &mut self.thumbnail_provider_registry,
78        });
79        self
80    }
81
82    pub fn finish(
83        self,
84        schema_set: &SchemaSet,
85    ) -> AssetPluginRegistries {
86        let importer_registry = self.importer_registry.build();
87        let builder_registry = self.builder_registry.build(schema_set);
88        let job_processor_registry = self.job_processor_registry.build();
89        let thumbnail_provider_registry = self.thumbnail_provider_registry.build(schema_set);
90
91        AssetPluginRegistries {
92            importer_registry,
93            builder_registry,
94            job_processor_registry,
95            thumbnail_provider_registry,
96        }
97    }
98}
99
100pub trait DynEditorModel {
101    fn schema_set(&self) -> &SchemaSet;
102
103    fn handle_import_complete(
104        &mut self,
105        asset_id: AssetId,
106        asset_name: AssetName,
107        asset_location: AssetLocation,
108        default_asset: &SingleObject,
109        replace_with_default_asset: bool,
110        import_info: ImportInfo,
111        canonical_path_references: &HashMap<CanonicalPathReference, AssetId>,
112        path_references: &HashMap<PathReferenceHash, CanonicalPathReference>,
113    ) -> DataSetResult<()>;
114
115    fn data_set(&self) -> &DataSet;
116
117    fn is_path_node_or_root(
118        &self,
119        schema_record: &SchemaRecord,
120    ) -> bool;
121
122    fn asset_display_name_long(
123        &self,
124        asset_id: AssetId,
125    ) -> String;
126}
127
128pub trait DynEditContext {
129    fn data_set(&self) -> &DataSet;
130
131    fn schema_set(&self) -> &SchemaSet;
132}
133
134pub enum AssetEngineState {
135    Idle,
136    Importing(ImportStatusImporting),
137    Building(BuildStatusBuilding),
138    ImportCompleted(Arc<ImportLogData>),
139    BuildCompleted(Arc<BuildLogData>),
140}
141
142pub struct AssetEngine {
143    importer_registry: ImporterRegistry,
144    import_jobs: ImportJobs,
145    builder_registry: BuilderRegistry,
146    build_jobs: BuildJobs,
147    thumbnail_system: ThumbnailSystem,
148}
149
150impl AssetEngine {
151    pub fn new(
152        schema_set: &SchemaSet,
153        registries: AssetPluginRegistries,
154        editor_model: &dyn DynEditorModel,
155        project_configuration: &HydrateProjectConfiguration,
156    ) -> Self {
157        let import_jobs = ImportJobs::new(
158            project_configuration,
159            &registries.importer_registry,
160            editor_model,
161            &project_configuration.import_data_path,
162        );
163
164        let build_jobs = BuildJobs::new(
165            schema_set,
166            &registries.job_processor_registry,
167            project_configuration.import_data_path.clone(),
168            project_configuration.job_data_path.clone(),
169            project_configuration.build_data_path.clone(),
170        );
171
172        let thumbnail_system = ThumbnailSystem::new(
173            project_configuration,
174            registries.thumbnail_provider_registry,
175            schema_set,
176        );
177
178        //TODO: Consider looking at disk to determine previous manifest build hash so we don't for a rebuild every time we open
179
180        AssetEngine {
181            importer_registry: registries.importer_registry,
182            import_jobs,
183            builder_registry: registries.builder_registry,
184            build_jobs,
185            thumbnail_system,
186        }
187    }
188
189    pub fn current_task_log_data(&self) -> LogDataRef {
190        if let Some(build_log) = self.build_jobs.current_build_log() {
191            LogDataRef::Build(build_log)
192        } else if let Some(import_log) = self.import_jobs.current_import_log() {
193            LogDataRef::Import(import_log)
194        } else {
195            LogDataRef::None
196        }
197    }
198
199    pub fn thumbnail_provider_registry(&self) -> &ThumbnailProviderRegistry {
200        self.thumbnail_system.thumbnail_provider_registry()
201    }
202
203    pub fn importer_registry(&self) -> &ImporterRegistry {
204        &self.importer_registry
205    }
206
207    #[profiling::function]
208    pub fn update(
209        &mut self,
210        editor_model: &mut dyn DynEditorModel,
211    ) -> PipelineResult<AssetEngineState> {
212        //
213        // If user changes any asset data, cancel the in-flight build
214        // If user initiates any import jobs, cancel the in-flight build
215        // If file changes are detected on asset, import, or build data, cancel the in-flight build
216        //
217
218        //
219        // If there are import jobs pending, cancel the in-flight build and execute them
220        //
221        if !self.build_jobs.is_building() {
222            let import_state = self
223                .import_jobs
224                .update(&self.importer_registry, editor_model)?;
225
226            match import_state {
227                ImportStatus::Idle => {
228                    // We can go to the next step
229                }
230                ImportStatus::Importing(importing_state) => {
231                    return Ok(AssetEngineState::Importing(importing_state))
232                }
233                ImportStatus::Completed(import_log_data) => {
234                    return Ok(AssetEngineState::ImportCompleted(import_log_data))
235                }
236            }
237        }
238
239        if !self.build_jobs.is_building() {
240            assert!(!self.import_jobs.is_importing());
241            self.thumbnail_system
242                .update(editor_model.data_set(), editor_model.schema_set());
243        }
244
245        //
246        // Process the in-flight build. It will be cancelled and restarted if any data is detected
247        // as changing during the build.
248        //
249
250        // Check if our import state is consistent, if it is we save expected hashes and run builds
251        let build_state =
252            self.build_jobs
253                .update(&self.builder_registry, editor_model, &self.import_jobs)?;
254
255        match build_state {
256            BuildStatus::Idle => Ok(AssetEngineState::Idle),
257            BuildStatus::Building(building_state) => {
258                return Ok(AssetEngineState::Building(building_state))
259            }
260            BuildStatus::Completed(build_log_data) => {
261                return Ok(AssetEngineState::BuildCompleted(build_log_data))
262            }
263        }
264    }
265
266    pub fn thumbnail_system_state(&self) -> &ThumbnailSystemState {
267        self.thumbnail_system.system_state()
268    }
269
270    pub fn importers_for_file_extension(
271        &self,
272        extension: &str,
273    ) -> &[ImporterId] {
274        self.importer_registry
275            .importers_for_file_extension(extension)
276    }
277
278    pub fn importer(
279        &self,
280        importer_id: ImporterId,
281    ) -> Option<&Arc<dyn Importer>> {
282        self.importer_registry.importer(importer_id)
283    }
284
285    pub fn builder_for_asset(
286        &self,
287        fingerprint: SchemaFingerprint,
288    ) -> Option<&Box<dyn Builder>> {
289        self.builder_registry.builder_for_asset(fingerprint)
290    }
291
292    pub fn queue_import_operation(
293        &mut self,
294        import_job_to_queue: ImportJobToQueue,
295    ) {
296        self.import_jobs.queue_import_operation(import_job_to_queue);
297    }
298
299    pub fn queue_build_asset(
300        &mut self,
301        asset_id: AssetId,
302    ) {
303        self.build_jobs.queue_build_operation(asset_id);
304    }
305
306    pub fn needs_build(&self) -> bool {
307        self.build_jobs.needs_build()
308    }
309
310    pub fn queue_build_all(&mut self) {
311        self.build_jobs.build();
312    }
313
314    pub fn duplicate_import_data(
315        &self,
316        old_asset_id: AssetId,
317        new_asset_id: AssetId,
318    ) -> PipelineResult<()> {
319        self.import_jobs
320            .duplicate_import_data(old_asset_id, new_asset_id)
321    }
322}