diff --git a/src/lib.rs b/src/lib.rs index 805c7890..6e64cb1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ pub mod project; mod auth_cookie; mod imfs; +mod mapset; mod message_queue; mod path_map; mod path_serializer; diff --git a/src/mapset.rs b/src/mapset.rs new file mode 100644 index 00000000..aeaf4ebd --- /dev/null +++ b/src/mapset.rs @@ -0,0 +1,69 @@ +use std::{ + borrow::Borrow, + collections::HashMap, + fmt::{self, Debug}, + hash::Hash, +}; + +/// A map whose value contains a set of multiple values. +#[derive(Clone)] +pub struct MapSet { + inner: HashMap>, +} + +#[allow(dead_code)] // This is a core library-ish struct, unused stuff is ok +impl MapSet { + pub fn new() -> Self { + MapSet { + inner: HashMap::new(), + } + } + + pub fn get>(&mut self, k: Q) -> &[V] { + self.inner.get(k.borrow()).map(Vec::as_slice).unwrap_or(&[]) + } + + pub fn insert(&mut self, k: K, v: V) { + let bucket = self.inner.entry(k).or_default(); + + for value in &*bucket { + if &*value == &v { + return; + } + } + + bucket.push(v); + } + + pub fn remove, U: Borrow>(&mut self, k: Q, v: U) -> Option { + let b = v.borrow(); + + if let Some(bucket) = self.inner.get_mut(k.borrow()) { + let mut removed_value = None; + + if let Some(index) = bucket.iter().position(|value| value == b) { + removed_value = Some(bucket.swap_remove(index)); + } + + if bucket.len() == 0 { + self.inner.remove(k.borrow()); + } + + removed_value + } else { + None + } + } +} + +impl Debug for MapSet { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(formatter) + } +} + +impl PartialEq for MapSet { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} diff --git a/src/snapshot/metadata.rs b/src/snapshot/metadata.rs index 279e4e81..678b1e9c 100644 --- a/src/snapshot/metadata.rs +++ b/src/snapshot/metadata.rs @@ -1,6 +1,4 @@ -use std::{collections::HashMap, path::PathBuf}; - -use rbx_dom_weak::{RbxId, RbxInstance, RbxInstanceProperties, RbxTree}; +use std::path::PathBuf; use crate::project::ProjectNode; @@ -39,89 +37,3 @@ impl Default for InstanceMetadata { } } } - -#[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 8b3e1864..08cb6b31 100644 --- a/src/snapshot/mod.rs +++ b/src/snapshot/mod.rs @@ -23,9 +23,11 @@ mod metadata; mod patch; mod patch_apply; mod patch_compute; +mod tree; 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; +pub use tree::*; diff --git a/src/snapshot/tree.rs b/src/snapshot/tree.rs new file mode 100644 index 00000000..c21bb4ef --- /dev/null +++ b/src/snapshot/tree.rs @@ -0,0 +1,120 @@ +use std::{collections::HashMap, path::PathBuf}; + +use rbx_dom_weak::{RbxId, RbxInstance, RbxInstanceProperties, RbxTree}; + +use crate::mapset::MapSet; + +use super::InstanceMetadata; + +/// An expanded variant of rbx_dom_weak's `RbxTree` that tracks additional +/// metadata per instance that's Rojo-specific. +/// +/// This tree is also optimized for doing fast incremental updates and patches. +#[derive(Debug)] +pub struct RojoTree { + /// Contains the instances without their Rojo-specific metadata. + inner: RbxTree, + + /// Metadata associated with each instance that is kept up-to-date with the + /// set of actual instances. + metadata: HashMap, + + /// A multimap from source paths to all of the root instances that were + /// constructed from that path. + /// + /// Descendants of those instances should not be contained in the set, the + /// value portion of the map is also a set in order to support the same path + /// appearing multiple times in the same Rojo project. This is sometimes + /// called "path aliasing" in various Rojo documentation. + path_to_id: MapSet, +} + +impl RojoTree { + pub fn new(root: InstancePropertiesWithMeta) -> RojoTree { + let inner = RbxTree::new(root.inner); + let mut metadata = HashMap::new(); + metadata.insert(inner.get_root_id(), root.metadata); + + RojoTree { + inner, + metadata, + path_to_id: MapSet::new(), + } + } + + 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 mut path_to_id = MapSet::new(); // TODO + + 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(RojoTree { + inner, + metadata, + path_to_id, + }) + } else { + 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, +}