diff --git a/src/snapshot/instance_snapshot.rs b/src/snapshot/instance_snapshot.rs index 8182ad5f..83ad5055 100644 --- a/src/snapshot/instance_snapshot.rs +++ b/src/snapshot/instance_snapshot.rs @@ -1,10 +1,10 @@ //! Defines the structure of an instance snapshot. -use std::{borrow::Cow, collections::HashMap, path::PathBuf}; +use std::{borrow::Cow, collections::HashMap}; use rbx_dom_weak::{RbxId, RbxTree, RbxValue}; -use crate::project::ProjectNode; +use super::InstanceMetadata; /// A lightweight description of what an instance should look like. Attempts to /// be somewhat memory efficient by borrowing from its source data, indicated by @@ -18,14 +18,21 @@ pub struct InstanceSnapshot<'source> { /// A temporary ID applied to the snapshot that's used for Ref properties. pub snapshot_id: Option, - /// A complete view of where this snapshot came from. It should contain - /// enough information, if not None, to recreate this snapshot - /// deterministically assuming the source has not changed state. - pub source: Option, + /// Rojo-specific metadata associated with the instance. + pub metadata: InstanceMetadata, + /// Correpsonds to the Name property of the instance. pub name: Cow<'source, str>, + + /// Corresponds to the ClassName property of the instance. pub class_name: Cow<'source, str>, + + /// All other properties of the instance, weakly-typed. pub properties: HashMap, + + /// The children of the instance represented as more snapshots. + /// + /// Order is relevant for Roblox instances! pub children: Vec>, } @@ -39,7 +46,7 @@ impl<'source> InstanceSnapshot<'source> { InstanceSnapshot { snapshot_id: None, - source: self.source.clone(), + metadata: self.metadata.clone(), name: Cow::Owned(self.name.clone().into_owned()), class_name: Cow::Owned(self.class_name.clone().into_owned()), properties: self.properties.clone(), @@ -61,7 +68,7 @@ impl<'source> InstanceSnapshot<'source> { InstanceSnapshot { snapshot_id: Some(id), - source: None, + metadata: InstanceMetadata::default(), name: Cow::Owned(instance.name.clone()), class_name: Cow::Owned(instance.class_name.clone()), properties: instance.properties.clone(), @@ -70,14 +77,15 @@ impl<'source> InstanceSnapshot<'source> { } } -#[derive(Debug, Clone, PartialEq)] -pub enum SnapshotSource { - File { - path: PathBuf, - }, - ProjectFile { - path: PathBuf, - name: String, - node: ProjectNode, - }, +impl<'source> Default for InstanceSnapshot<'source> { + fn default() -> InstanceSnapshot<'source> { + InstanceSnapshot { + snapshot_id: None, + metadata: InstanceMetadata::default(), + name: Cow::Borrowed("DEFAULT"), + class_name: Cow::Borrowed("DEFAULT"), + properties: HashMap::new(), + children: Vec::new(), + } + } } diff --git a/src/snapshot/metadata.rs b/src/snapshot/metadata.rs new file mode 100644 index 00000000..279e4e81 --- /dev/null +++ b/src/snapshot/metadata.rs @@ -0,0 +1,127 @@ +use std::{collections::HashMap, path::PathBuf}; + +use rbx_dom_weak::{RbxId, RbxInstance, RbxInstanceProperties, RbxTree}; + +use crate::project::ProjectNode; + +/// Rojo-specific metadata that can be associated with an instance or a snapshot +/// of an instance. +#[derive(Debug, Clone, PartialEq)] +pub struct InstanceMetadata { + /// Whether instances not present in the source should be ignored when + /// live-syncing. This is useful when there are instances that Rojo does not + /// manage. + pub ignore_unknown_instances: bool, + + /// A complete view of where this snapshot came from. It should contain + /// enough information, if not None, to recreate this snapshot + /// deterministically assuming the source has not changed state. + pub source: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum InstanceSource { + File { + path: PathBuf, + }, + ProjectFile { + path: PathBuf, + name: String, + node: ProjectNode, + }, +} + +impl Default for InstanceMetadata { + fn default() -> Self { + InstanceMetadata { + ignore_unknown_instances: false, + source: None, + } + } +} + +#[derive(Debug, Clone)] +pub struct InstancePropertiesWithMeta { + pub inner: RbxInstanceProperties, + pub metadata: InstanceMetadata, +} + +#[derive(Debug)] +pub struct InstanceWithMeta<'a> { + pub inner: &'a RbxInstance, + pub metadata: &'a InstanceMetadata, +} + +#[derive(Debug)] +pub struct InstanceWithMetaMut<'a> { + pub inner: &'a mut RbxInstance, + pub metadata: &'a mut InstanceMetadata, +} + +#[derive(Debug)] +pub struct TreeWithMetadata { + inner: RbxTree, + metadata: HashMap, +} + +impl TreeWithMetadata { + pub fn new(root: InstancePropertiesWithMeta) -> TreeWithMetadata { + let inner = RbxTree::new(root.inner); + let mut metadata = HashMap::new(); + metadata.insert(inner.get_root_id(), root.metadata); + + TreeWithMetadata { inner, metadata } + } + + pub fn get_root_id(&self) -> RbxId { + self.inner.get_root_id() + } + + pub fn get_instance(&self, id: RbxId) -> Option { + if let Some(inner) = self.inner.get_instance(id) { + let metadata = self.metadata.get(&id).unwrap(); + + Some(InstanceWithMeta { inner, metadata }) + } else { + None + } + } + + pub fn get_instance_mut(&mut self, id: RbxId) -> Option { + if let Some(inner) = self.inner.get_instance_mut(id) { + let metadata = self.metadata.get_mut(&id).unwrap(); + + Some(InstanceWithMetaMut { inner, metadata }) + } else { + None + } + } + + pub fn insert_instance( + &mut self, + properties: InstancePropertiesWithMeta, + parent_id: RbxId, + ) -> RbxId { + let id = self.inner.insert_instance(properties.inner, parent_id); + self.metadata.insert(id, properties.metadata); + id + } + + pub fn remove_instance(&mut self, id: RbxId) -> Option { + if let Some(inner) = self.inner.remove_instance(id) { + let mut metadata = HashMap::new(); + + let root_meta = self.metadata.remove(&id).unwrap(); + metadata.insert(id, root_meta); + + for instance in inner.descendants(id) { + let instance_meta = self.metadata.remove(&instance.get_id()).unwrap(); + metadata.insert(instance.get_id(), instance_meta); + } + + Some(TreeWithMetadata { inner, metadata }) + } else { + None + } + } +} diff --git a/src/snapshot/mod.rs b/src/snapshot/mod.rs index f6cc3c3f..8b3e1864 100644 --- a/src/snapshot/mod.rs +++ b/src/snapshot/mod.rs @@ -19,11 +19,13 @@ #![allow(dead_code)] mod instance_snapshot; +mod metadata; mod patch; mod patch_apply; mod patch_compute; pub use instance_snapshot::InstanceSnapshot; +pub use metadata::*; pub use patch::*; pub use patch_apply::apply_patch_set; pub use patch_compute::compute_patch_set; diff --git a/src/snapshot/patch.rs b/src/snapshot/patch.rs index 15fdc6ba..c89ed885 100644 --- a/src/snapshot/patch.rs +++ b/src/snapshot/patch.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use rbx_dom_weak::{RbxId, RbxValue}; -use super::InstanceSnapshot; +use super::{InstanceMetadata, InstanceSnapshot}; /// A set of different kinds of patches that can be applied to an RbxTree. #[derive(Debug, Default, Clone, PartialEq)] @@ -41,4 +41,7 @@ pub struct PatchUpdateInstance { /// Contains all changed properties. If a property is assigned to `None`, /// then that property has been removed. pub changed_properties: HashMap>, + + /// Changed Rojo-specific metadata, if any of it changed. + pub changed_metadata: Option, } diff --git a/src/snapshot/patch_apply.rs b/src/snapshot/patch_apply.rs index dfd5c523..3c52da95 100644 --- a/src/snapshot/patch_apply.rs +++ b/src/snapshot/patch_apply.rs @@ -143,7 +143,7 @@ mod test { use maplit::hashmap; use rbx_dom_weak::RbxValue; - use super::super::patch::PatchAddInstance; + use super::super::PatchAddInstance; #[test] fn add_from_empty() { @@ -159,7 +159,7 @@ mod test { let snapshot = InstanceSnapshot { snapshot_id: None, - source: None, + metadata: Default::default(), name: Cow::Borrowed("Foo"), class_name: Cow::Borrowed("Bar"), properties: hashmap! { @@ -218,6 +218,7 @@ mod test { // Baz has been added "Baz".to_owned() => Some(RbxValue::Int32 { value: 10 }), }, + changed_metadata: None, }; let patch_set = PatchSet { diff --git a/src/snapshot/patch_compute.rs b/src/snapshot/patch_compute.rs index 83acbed3..a5227c57 100644 --- a/src/snapshot/patch_compute.rs +++ b/src/snapshot/patch_compute.rs @@ -140,6 +140,7 @@ fn compute_property_patches( changed_name, changed_class_name, changed_properties, + changed_metadata: None, }); } @@ -244,7 +245,7 @@ mod test { } }, - source: None, + metadata: Default::default(), name: Cow::Borrowed("foo"), class_name: Cow::Borrowed("foo"), children: Vec::new(), @@ -262,6 +263,7 @@ mod test { value: Some(root_id), }), }, + changed_metadata: None, }], added_instances: Vec::new(), removed_instances: Vec::new(), @@ -295,13 +297,13 @@ mod test { }, snapshot_id: None, - source: None, + metadata: Default::default(), name: Cow::Borrowed("child"), class_name: Cow::Borrowed("child"), children: Vec::new(), }], - source: None, + metadata: Default::default(), properties: HashMap::new(), name: Cow::Borrowed("foo"), class_name: Cow::Borrowed("foo"), @@ -314,7 +316,7 @@ mod test { parent_id: root_id, instance: InstanceSnapshot { snapshot_id: None, - source: None, + metadata: Default::default(), properties: hashmap! { "Self".to_owned() => RbxValue::Ref { value: Some(root_id), diff --git a/src/snapshot_middleware/csv.rs b/src/snapshot_middleware/csv.rs index 4be88d2b..ba647114 100644 --- a/src/snapshot_middleware/csv.rs +++ b/src/snapshot_middleware/csv.rs @@ -39,7 +39,7 @@ impl SnapshotMiddleware for SnapshotCsv { Ok(Some(InstanceSnapshot { snapshot_id: None, - source: None, // TODO + metadata: Default::default(), // TODO name: Cow::Owned(instance_name), class_name: Cow::Borrowed("LocalizationTable"), properties: hashmap! { diff --git a/src/snapshot_middleware/dir.rs b/src/snapshot_middleware/dir.rs index 2d6b60ef..36170297 100644 --- a/src/snapshot_middleware/dir.rs +++ b/src/snapshot_middleware/dir.rs @@ -43,7 +43,7 @@ impl SnapshotMiddleware for SnapshotDir { Ok(Some(InstanceSnapshot { snapshot_id: None, - source: None, // TODO + metadata: Default::default(), // TODO name: Cow::Owned(instance_name), class_name: Cow::Borrowed("Folder"), properties: HashMap::new(), diff --git a/src/snapshot_middleware/json_model.rs b/src/snapshot_middleware/json_model.rs index ec5148a8..8420372e 100644 --- a/src/snapshot_middleware/json_model.rs +++ b/src/snapshot_middleware/json_model.rs @@ -120,7 +120,7 @@ impl JsonModelCore { InstanceSnapshot { snapshot_id: None, - source: None, // TODO + metadata: Default::default(), // TODO name: Cow::Owned(name), class_name: Cow::Owned(class_name), properties, @@ -170,7 +170,7 @@ mod test { instance_snapshot, InstanceSnapshot { snapshot_id: None, - source: None, // TODO + metadata: Default::default(), // TODO name: Cow::Borrowed("foo"), class_name: Cow::Borrowed("IntValue"), properties: hashmap! { @@ -180,7 +180,7 @@ mod test { }, children: vec![InstanceSnapshot { snapshot_id: None, - source: None, // TODO + metadata: Default::default(), // TODO name: Cow::Borrowed("The Child"), class_name: Cow::Borrowed("StringValue"), properties: HashMap::new(), diff --git a/src/snapshot_middleware/lua.rs b/src/snapshot_middleware/lua.rs index 0aa61080..91b5d77d 100644 --- a/src/snapshot_middleware/lua.rs +++ b/src/snapshot_middleware/lua.rs @@ -90,7 +90,7 @@ fn snapshot_lua_file( Ok(Some(InstanceSnapshot { snapshot_id: None, - source: None, // TODO + metadata: Default::default(), // TODO name: Cow::Owned(instance_name.to_owned()), class_name: Cow::Borrowed(class_name), properties, diff --git a/src/snapshot_middleware/project.rs b/src/snapshot_middleware/project.rs index 2d431978..471a91d8 100644 --- a/src/snapshot_middleware/project.rs +++ b/src/snapshot_middleware/project.rs @@ -131,7 +131,7 @@ fn snapshot_project_node( Ok(Some(InstanceSnapshot { snapshot_id: None, - source: None, // TODO + metadata: Default::default(), // TODO name, class_name, properties, diff --git a/src/snapshot_middleware/txt.rs b/src/snapshot_middleware/txt.rs index 058ae408..98155c10 100644 --- a/src/snapshot_middleware/txt.rs +++ b/src/snapshot_middleware/txt.rs @@ -51,7 +51,7 @@ impl SnapshotMiddleware for SnapshotTxt { Ok(Some(InstanceSnapshot { snapshot_id: None, - source: None, // TODO + metadata: Default::default(), // TODO name: Cow::Owned(instance_name), class_name: Cow::Borrowed("StringValue"), properties,