1use crate::edit_context::EditContext;
2use crate::editor::undo::UndoStack;
3use crate::{
4 AssetId, AssetPath, AssetPathCache, AssetSourceId, DataSet, DataSource,
5 FileSystemIdBasedDataSource, FileSystemPathBasedDataSource, HashMap, PathNode, PathNodeRoot,
6 PendingFileOperations, SchemaNamedType, SchemaSet,
7};
8use hydrate_data::{
9 AssetLocation, AssetName, CanonicalPathReference, DataSetError, DataSetResult, ImportInfo,
10 PathReferenceHash, SingleObject,
11};
12use hydrate_pipeline::{
13 DynEditorModel, HydrateProjectConfiguration, ImportJobToQueue, ImporterRegistry,
14};
15use hydrate_schema::{SchemaFingerprint, SchemaRecord};
16use slotmap::DenseSlotMap;
17use std::path::PathBuf;
18slotmap::new_key_type! { pub struct EditContextKey; }
19
20pub struct EditorModel {
21 project_config: HydrateProjectConfiguration,
22 schema_set: SchemaSet,
23 undo_stack: UndoStack,
24 root_edit_context_key: EditContextKey,
25 edit_contexts: DenseSlotMap<EditContextKey, EditContext>,
26 data_sources: HashMap<AssetSourceId, Box<dyn DataSource>>,
28
29 path_node_schema: SchemaNamedType,
30 path_node_root_schema: SchemaNamedType,
31}
32
33pub struct EditorModelWithCache<'a> {
34 pub asset_path_cache: &'a AssetPathCache,
35 pub editor_model: &'a mut EditorModel,
36}
37
38impl<'a> DynEditorModel for EditorModelWithCache<'a> {
39 fn schema_set(&self) -> &SchemaSet {
40 self.editor_model.schema_set()
41 }
42
43 fn handle_import_complete(
44 &mut self,
45 asset_id: AssetId,
46 asset_name: AssetName,
47 asset_location: AssetLocation,
48 default_asset: &SingleObject,
49 replace_with_default_asset: bool,
50 import_info: ImportInfo,
51 canonical_path_references: &HashMap<CanonicalPathReference, AssetId>,
52 _path_references: &HashMap<PathReferenceHash, CanonicalPathReference>,
53 ) -> DataSetResult<()> {
54 let edit_context = self.editor_model.root_edit_context_mut();
58 if replace_with_default_asset {
59 if edit_context.has_asset(asset_id) {
60 edit_context.delete_asset(asset_id)?;
61 }
62
63 edit_context.init_from_single_object(
64 asset_id,
65 asset_name,
66 asset_location,
67 default_asset,
68 )?;
69 }
70
71 edit_context.set_import_info(asset_id, import_info)?;
75 for (path_reference, referenced_asset_id) in canonical_path_references {
76 edit_context.set_path_reference_override(
77 asset_id,
78 path_reference.clone(),
79 *referenced_asset_id,
80 )?;
81 }
82
83 Ok(())
84 }
85
86 fn data_set(&self) -> &DataSet {
87 self.editor_model.root_edit_context().data_set()
88 }
89
90 fn is_path_node_or_root(
91 &self,
92 schema_record: &SchemaRecord,
93 ) -> bool {
94 self.editor_model
95 .is_path_node_or_root(schema_record.fingerprint())
96 }
97
98 fn asset_display_name_long(
99 &self,
100 asset_id: AssetId,
101 ) -> String {
102 self.editor_model
103 .asset_display_name_long(asset_id, &self.asset_path_cache)
104 }
105}
106
107impl EditorModel {
108 pub fn new(
109 project_config: HydrateProjectConfiguration,
110 schema_set: SchemaSet,
111 ) -> Self {
112 let undo_stack = UndoStack::default();
113 let mut edit_contexts: DenseSlotMap<EditContextKey, EditContext> = Default::default();
114
115 let root_edit_context_key = edit_contexts.insert_with_key(|key| {
116 EditContext::new(&project_config, key, schema_set.clone(), &undo_stack)
117 });
118
119 let path_node_root_schema = schema_set
120 .find_named_type(PathNodeRoot::schema_name())
121 .unwrap()
122 .clone();
123
124 let path_node_schema = schema_set
125 .find_named_type(PathNode::schema_name())
126 .unwrap()
127 .clone();
128
129 EditorModel {
130 project_config,
131 schema_set,
132 undo_stack,
133 root_edit_context_key,
134 edit_contexts,
135 data_sources: Default::default(),
136 path_node_root_schema,
139 path_node_schema,
140 }
141 }
142
143 pub fn path_node_schema(&self) -> &SchemaNamedType {
144 &self.path_node_schema
145 }
146
147 pub fn path_node_root_schema(&self) -> &SchemaNamedType {
148 &self.path_node_root_schema
149 }
150
151 pub fn data_sources(&self) -> &HashMap<AssetSourceId, Box<dyn DataSource>> {
152 &self.data_sources
153 }
154
155 pub fn is_path_node_or_root(
156 &self,
157 fingerprint: SchemaFingerprint,
158 ) -> bool {
159 self.path_node_schema.fingerprint() == fingerprint
160 || self.path_node_root_schema.fingerprint() == fingerprint
161 }
162
163 pub fn is_generated_asset(
164 &self,
165 asset_id: AssetId,
166 ) -> bool {
167 for data_source in self.data_sources.values() {
168 if data_source.is_generated_asset(asset_id) {
169 return true;
170 }
171 }
172
173 false
174 }
175
176 pub fn persist_generated_asset(
177 &mut self,
178 asset_id: AssetId,
179 ) {
180 for (_, data_source) in &mut self.data_sources {
181 let root_edit_context = self
182 .edit_contexts
183 .get_mut(self.root_edit_context_key)
184 .unwrap();
185
186 data_source.persist_generated_asset(root_edit_context, asset_id);
187 }
188 }
189
190 pub fn commit_all_pending_undo_contexts(&mut self) {
191 for (_, context) in &mut self.edit_contexts {
192 context.commit_pending_undo_context();
193 }
194 }
195
196 pub fn cancel_all_pending_undo_contexts(&mut self) {
197 for (_, context) in &mut self.edit_contexts {
198 context.commit_pending_undo_context();
199 }
200 }
201
202 pub fn any_edit_context_has_unsaved_changes(&self) -> bool {
203 for (_, data_source) in &self.data_sources {
204 for (_, edit_context) in &self.edit_contexts {
205 if data_source.edit_context_has_unsaved_changes(edit_context) {
206 return true;
207 }
208 }
209 }
210
211 false
212 }
213
214 pub fn pending_file_operations(&self) -> PendingFileOperations {
215 let mut pending_file_operations = PendingFileOperations::default();
216
217 for (_, data_source) in &self.data_sources {
218 for (_, edit_context) in &self.edit_contexts {
219 data_source
220 .append_pending_file_operations(edit_context, &mut pending_file_operations);
221 }
222 }
223
224 pending_file_operations
225 }
226
227 pub fn schema_set(&self) -> &SchemaSet {
228 &self.schema_set
229 }
230
231 pub fn clone_schema_set(&self) -> SchemaSet {
232 self.schema_set.clone()
233 }
234
235 pub fn root_edit_context(&self) -> &EditContext {
236 self.edit_contexts.get(self.root_edit_context_key).unwrap()
237 }
238
239 pub fn root_edit_context_mut(&mut self) -> &mut EditContext {
240 self.edit_contexts
241 .get_mut(self.root_edit_context_key)
242 .unwrap()
243 }
244
245 pub fn asset_path(
246 &self,
247 asset_id: AssetId,
248 asset_path_cache: &AssetPathCache,
249 ) -> Option<AssetPath> {
250 let root_data_set = &self.root_edit_context().data_set;
251 let location = root_data_set.asset_location(asset_id);
252
253 let path = location
256 .map(|x| asset_path_cache.path_to_id_lookup().get(&x.path_node_id()))
257 .flatten()
258 .cloned()?;
259
260 let name = root_data_set.asset_name(asset_id).unwrap().as_string();
261 if let Some(name) = name {
262 Some(path.join(name))
263 } else {
264 Some(path.join(&format!("{}", asset_id.as_uuid())))
265 }
266 }
267
268 pub fn asset_display_name_long(
269 &self,
270 asset_id: AssetId,
271 asset_path_cache: &AssetPathCache,
272 ) -> String {
273 self.asset_path(asset_id, asset_path_cache)
274 .map(|x| x.as_str().to_string())
275 .unwrap_or_else(|| format!("{}", asset_id.as_uuid()))
276 }
277
278 pub fn data_source(
279 &mut self,
280 asset_source_id: AssetSourceId,
281 ) -> Option<&dyn DataSource> {
282 self.data_sources.get(&asset_source_id).map(|x| &**x)
283 }
284
285 pub fn is_a_root_asset(
286 &self,
287 asset_id: AssetId,
288 ) -> bool {
289 for source in self.data_sources.keys() {
290 if *source.uuid() == asset_id.as_uuid() {
291 return true;
292 }
293 }
294
295 false
296 }
297
298 pub fn add_file_system_id_based_asset_source<RootPathT: Into<PathBuf>>(
299 &mut self,
300 project_config: &HydrateProjectConfiguration,
301 data_source_name: &str,
302 file_system_root_path: RootPathT,
303 import_job_to_queue: &mut ImportJobToQueue,
304 ) -> AssetSourceId {
305 let file_system_root_path = dunce::canonicalize(&file_system_root_path.into()).unwrap();
306 let path_node_root_schema = self.path_node_root_schema.as_record().unwrap().clone();
307 let root_edit_context = self.root_edit_context_mut();
308
309 root_edit_context.commit_pending_undo_context();
311
312 let asset_source_id = AssetSourceId::new();
316 let root_asset_id = AssetId::from_uuid(*asset_source_id.uuid());
317 root_edit_context
318 .new_asset_with_id(
319 root_asset_id,
320 &AssetName::new(data_source_name),
321 &AssetLocation::null(),
322 &path_node_root_schema,
323 )
324 .unwrap();
325
326 let mut fs = FileSystemIdBasedDataSource::new(
330 file_system_root_path.clone(),
331 root_edit_context,
332 asset_source_id,
333 );
334 fs.load_from_storage(project_config, root_edit_context, import_job_to_queue);
335
336 self.data_sources.insert(asset_source_id, Box::new(fs));
337
338 asset_source_id
339 }
340
341 pub fn add_file_system_path_based_data_source<RootPathT: Into<PathBuf>>(
342 &mut self,
343 project_config: &HydrateProjectConfiguration,
344 data_source_name: &str,
345 file_system_root_path: RootPathT,
346 importer_registry: &ImporterRegistry,
347 import_jobs_to_queue: &mut ImportJobToQueue,
348 ) -> AssetSourceId {
349 let file_system_root_path = dunce::canonicalize(&file_system_root_path.into()).unwrap();
350 let path_node_root_schema = self.path_node_root_schema.as_record().unwrap().clone();
351 let root_edit_context = self.root_edit_context_mut();
352
353 root_edit_context.commit_pending_undo_context();
355
356 let asset_source_id = AssetSourceId::new();
360 let root_asset_id = AssetId::from_uuid(*asset_source_id.uuid());
361 root_edit_context
362 .new_asset_with_id(
363 root_asset_id,
364 &AssetName::new(data_source_name),
365 &AssetLocation::null(),
366 &path_node_root_schema,
367 )
368 .unwrap();
369
370 let mut fs = FileSystemPathBasedDataSource::new(
374 file_system_root_path.clone(),
375 root_edit_context,
376 asset_source_id,
377 importer_registry,
378 );
379 fs.load_from_storage(project_config, root_edit_context, import_jobs_to_queue);
380
381 self.data_sources.insert(asset_source_id, Box::new(fs));
382
383 asset_source_id
384 }
385
386 pub fn save_root_edit_context(&mut self) {
387 let root_edit_context = self
391 .edit_contexts
392 .get_mut(self.root_edit_context_key)
393 .unwrap();
394 root_edit_context.commit_pending_undo_context();
395
396 for (_id, data_source) in &mut self.data_sources {
397 data_source.flush_to_storage(root_edit_context);
398 }
399 }
400
401 pub fn revert_root_edit_context(
402 &mut self,
403 project_config: &HydrateProjectConfiguration,
404 import_job_to_queue: &mut ImportJobToQueue,
405 ) {
406 let root_edit_context = self
410 .edit_contexts
411 .get_mut(self.root_edit_context_key)
412 .unwrap();
413 root_edit_context.cancel_pending_undo_context().unwrap();
414
415 for (_id, data_source) in &mut self.data_sources {
419 data_source.load_from_storage(project_config, root_edit_context, import_job_to_queue);
420 }
421
422 }
430
431 pub fn close_file_system_source(
432 &mut self,
433 _asset_source_id: AssetSourceId,
434 ) {
435 unimplemented!();
436 }
444
445 pub fn open_edit_context(
450 &mut self,
451 assets: &[AssetId],
452 ) -> DataSetResult<EditContextKey> {
453 let new_edit_context_key = self.edit_contexts.insert_with_key(|key| {
454 EditContext::new_with_data(
455 &self.project_config,
456 key,
457 self.schema_set.clone(),
458 &self.undo_stack,
459 )
460 });
461
462 let [root_edit_context, new_edit_context] = self
463 .edit_contexts
464 .get_disjoint_mut([self.root_edit_context_key, new_edit_context_key])
465 .unwrap();
466
467 for asset in assets {
468 if !root_edit_context.assets().contains_key(asset) {
469 return Err(DataSetError::AssetNotFound)?;
470 }
471 }
472
473 for &asset_id in assets {
474 new_edit_context
475 .data_set
476 .copy_from(root_edit_context.data_set(), asset_id)
477 .expect("Could not copy asset to newly created edit context");
478 }
479
480 Ok(new_edit_context_key)
481 }
482
483 pub fn flush_edit_context_to_root(
484 &mut self,
485 edit_context: EditContextKey,
486 ) -> DataSetResult<()> {
487 assert_ne!(edit_context, self.root_edit_context_key);
488 let [root_context, context_to_flush] = self
489 .edit_contexts
490 .get_disjoint_mut([self.root_edit_context_key, edit_context])
491 .unwrap();
492
493 let mut first_error = None;
496 for &asset_id in context_to_flush.assets().keys() {
497 if let Err(e) = root_context
498 .data_set
499 .copy_from(&context_to_flush.data_set, asset_id)
500 {
501 if first_error.is_none() {
502 first_error = Some(Err(e));
503 }
504 }
505 }
506
507 first_error.unwrap_or(Ok(()))
508 }
509
510 pub fn close_edit_context(
511 &mut self,
512 edit_context: EditContextKey,
513 ) {
514 assert_ne!(edit_context, self.root_edit_context_key);
515 self.edit_contexts.remove(edit_context);
516 }
517
518 pub fn undo(&mut self) -> DataSetResult<()> {
519 self.undo_stack.undo(&mut self.edit_contexts)
520 }
521
522 pub fn redo(&mut self) -> DataSetResult<()> {
523 self.undo_stack.redo(&mut self.edit_contexts)
524 }
525}