Finish off bulk of metadata tracking in snapshot system

This commit is contained in:
Lucien Greathouse
2019-09-09 15:04:57 -07:00
parent 47ee8d54a8
commit 3e759b3e8e
5 changed files with 115 additions and 46 deletions

View File

@@ -121,11 +121,8 @@ pub fn build(options: &BuildOptions) -> Result<(), BuildError> {
// Place files don't contain an entry for the DataModel, but our // Place files don't contain an entry for the DataModel, but our
// RbxTree representation does. // RbxTree representation does.
let top_level_ids = tree let root_instance = tree.get_instance(root_id).unwrap();
.get_instance(root_id) let top_level_ids = root_instance.children();
.unwrap()
.instance
.get_children_ids();
rbx_xml::to_writer(&mut file, tree.inner(), top_level_ids, xml_encode_config())?; rbx_xml::to_writer(&mut file, tree.inner(), top_level_ids, xml_encode_config())?;
} }
@@ -137,11 +134,8 @@ pub fn build(options: &BuildOptions) -> Result<(), BuildError> {
log::warn!("Using the XML place format (rbxlx) is recommended instead."); log::warn!("Using the XML place format (rbxlx) is recommended instead.");
log::warn!("For more info, see https://github.com/LPGhatguy/rojo/issues/180"); log::warn!("For more info, see https://github.com/LPGhatguy/rojo/issues/180");
let top_level_ids = tree let root_instance = tree.get_instance(root_id).unwrap();
.get_instance(root_id) let top_level_ids = root_instance.children();
.unwrap()
.instance
.get_children_ids();
rbx_binary::encode(tree.inner(), top_level_ids, &mut file)?; rbx_binary::encode(tree.inner(), top_level_ids, &mut file)?;
} }

View File

@@ -11,23 +11,31 @@ pub struct InstanceMetadata {
/// manage. /// manage.
pub ignore_unknown_instances: bool, pub ignore_unknown_instances: bool,
/// A complete view of where this snapshot came from. It should contain // TODO: Make source_path a SmallVec<PathBuf> in order to support meta
/// enough information, if not None, to recreate this snapshot // files? Maybe we should use another member of the snapshot middleware to
/// deterministically assuming the source has not changed state. // support damage-painting
pub source: Option<InstanceSource>, /// The path that this file came from, if it's the top-level instance from a
/// model that came directly from disk.
///
/// This path is used to make sure that file changes update all instances
/// that may need updates..
pub source_path: Option<PathBuf>,
/// If this instance was defined in a project file, this is the name from
/// the project file and the node under it.
///
/// This information is used to make sure the instance has the correct name,
/// project-added children, and metadata when it's updated in response to a
/// file change.
pub project_node: Option<(String, ProjectNode)>,
} }
impl Default for InstanceMetadata { impl Default for InstanceMetadata {
fn default() -> Self { fn default() -> Self {
InstanceMetadata { InstanceMetadata {
ignore_unknown_instances: false, ignore_unknown_instances: false,
source: None, source_path: None,
project_node: None,
} }
} }
} }
#[derive(Debug, Clone, PartialEq)]
pub struct InstanceSource {
pub path: PathBuf,
pub project_node: Option<(String, ProjectNode)>,
}

View File

@@ -100,6 +100,10 @@ fn apply_update_child(
tree: &mut RojoTree, tree: &mut RojoTree,
patch: &PatchUpdateInstance, patch: &PatchUpdateInstance,
) { ) {
if let Some(metadata) = &patch.changed_metadata {
tree.update_metadata(patch.id, metadata.clone());
}
let mut instance = tree let mut instance = tree
.get_instance_mut(patch.id) .get_instance_mut(patch.id)
.expect("Instance referred to by patch does not exist"); .expect("Instance referred to by patch does not exist");
@@ -128,10 +132,7 @@ fn apply_update_child(
); );
} }
Some(value) => { Some(value) => {
instance instance.properties_mut().insert(key.clone(), value.clone());
.instance
.properties
.insert(key.clone(), value.clone());
} }
None => { None => {
instance.properties_mut().remove(key); instance.properties_mut().remove(key);

View File

@@ -3,11 +3,11 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use rbx_dom_weak::{RbxId, RbxInstance, RbxValue}; use rbx_dom_weak::{RbxId, RbxValue};
use super::{ use super::{
patch::{PatchAddInstance, PatchSet, PatchUpdateInstance}, patch::{PatchAddInstance, PatchSet, PatchUpdateInstance},
InstanceSnapshot, RojoTree, InstanceSnapshot, InstanceWithMeta, RojoTree,
}; };
pub fn compute_patch_set<'a>( pub fn compute_patch_set<'a>(
@@ -84,34 +84,40 @@ fn compute_patch_set_internal<'a>(
.get_instance(id) .get_instance(id)
.expect("Instance did not exist in tree"); .expect("Instance did not exist in tree");
compute_property_patches(snapshot, &instance.instance, patch_set); compute_property_patches(snapshot, &instance, patch_set);
compute_children_patches(context, snapshot, tree, id, patch_set); compute_children_patches(context, snapshot, tree, id, patch_set);
} }
fn compute_property_patches( fn compute_property_patches(
snapshot: &InstanceSnapshot, snapshot: &InstanceSnapshot,
instance: &RbxInstance, instance: &InstanceWithMeta,
patch_set: &mut PatchSet, patch_set: &mut PatchSet,
) { ) {
let mut visited_properties = HashSet::new(); let mut visited_properties = HashSet::new();
let mut changed_properties = HashMap::new(); let mut changed_properties = HashMap::new();
let changed_name = if snapshot.name == instance.name { let changed_name = if snapshot.name == instance.name() {
None None
} else { } else {
Some(snapshot.name.clone().into_owned()) Some(snapshot.name.clone().into_owned())
}; };
let changed_class_name = if snapshot.class_name == instance.class_name { let changed_class_name = if snapshot.class_name == instance.class_name() {
None None
} else { } else {
Some(snapshot.class_name.clone().into_owned()) Some(snapshot.class_name.clone().into_owned())
}; };
let changed_metadata = if &snapshot.metadata == instance.metadata() {
None
} else {
Some(snapshot.metadata.clone())
};
for (name, snapshot_value) in &snapshot.properties { for (name, snapshot_value) in &snapshot.properties {
visited_properties.insert(name.as_str()); visited_properties.insert(name.as_str());
match instance.properties.get(name) { match instance.properties().get(name) {
Some(instance_value) => { Some(instance_value) => {
if snapshot_value != instance_value { if snapshot_value != instance_value {
changed_properties.insert(name.clone(), Some(snapshot_value.clone())); changed_properties.insert(name.clone(), Some(snapshot_value.clone()));
@@ -123,7 +129,7 @@ fn compute_property_patches(
} }
} }
for name in instance.properties.keys() { for name in instance.properties().keys() {
if visited_properties.contains(name.as_str()) { if visited_properties.contains(name.as_str()) {
continue; continue;
} }
@@ -131,16 +137,20 @@ fn compute_property_patches(
changed_properties.insert(name.clone(), None); changed_properties.insert(name.clone(), None);
} }
if changed_properties.is_empty() && changed_name.is_none() { if changed_properties.is_empty()
&& changed_name.is_none()
&& changed_class_name.is_none()
&& changed_metadata.is_none()
{
return; return;
} }
patch_set.updated_instances.push(PatchUpdateInstance { patch_set.updated_instances.push(PatchUpdateInstance {
id: instance.get_id(), id: instance.id(),
changed_name, changed_name,
changed_class_name, changed_class_name,
changed_properties, changed_properties,
changed_metadata: None, changed_metadata,
}); });
} }

View File

@@ -99,9 +99,38 @@ impl RojoTree {
} }
} }
/// Replaces the metadata associated with the given instance ID.
pub fn update_metadata(&mut self, id: RbxId, metadata: InstanceMetadata) {
use std::collections::hash_map::Entry;
match self.metadata_map.entry(id) {
Entry::Occupied(mut entry) => {
let existing_metadata = entry.get();
// If this instance's source path changed, we need to update our
// path associations so that file changes will trigger updates
// to this instance correctly.
if existing_metadata.source_path != metadata.source_path {
if let Some(existing_path) = &existing_metadata.source_path {
self.path_to_ids.remove(existing_path, id);
}
if let Some(new_path) = &metadata.source_path {
self.path_to_ids.insert(new_path.clone(), id);
}
}
entry.insert(metadata);
}
Entry::Vacant(entry) => {
entry.insert(metadata);
}
}
}
fn insert_metadata(&mut self, id: RbxId, metadata: InstanceMetadata) { fn insert_metadata(&mut self, id: RbxId, metadata: InstanceMetadata) {
if let Some(source) = &metadata.source { if let Some(source_path) = &metadata.source_path {
self.path_to_ids.insert(source.path.clone(), id); self.path_to_ids.insert(source_path.clone(), id);
} }
self.metadata_map.insert(id, metadata); self.metadata_map.insert(id, metadata);
@@ -117,15 +146,16 @@ impl RojoTree {
) { ) {
let metadata = self.metadata_map.remove(&id).unwrap(); let metadata = self.metadata_map.remove(&id).unwrap();
if let Some(source) = &metadata.source { if let Some(source_path) = &metadata.source_path {
self.path_to_ids.remove(&source.path, id); self.path_to_ids.remove(source_path, id);
path_to_ids.insert(source.path.clone(), id); path_to_ids.insert(source_path.clone(), id);
} }
metadata_map.insert(id, metadata); metadata_map.insert(id, metadata);
} }
} }
/// RojoTree's equivalent of `RbxInstanceProperties`.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct InstancePropertiesWithMeta { pub struct InstancePropertiesWithMeta {
pub properties: RbxInstanceProperties, pub properties: RbxInstanceProperties,
@@ -141,13 +171,22 @@ impl InstancePropertiesWithMeta {
} }
} }
#[derive(Debug)] /// RojoTree's equivalent of `&'a RbxInstance`.
///
/// This has to be a value type for RojoTree because the instance and metadata
/// are stored in different places. The mutable equivalent is
/// `InstanceWithMetaMut`.
#[derive(Debug, Clone, Copy)]
pub struct InstanceWithMeta<'a> { pub struct InstanceWithMeta<'a> {
pub instance: &'a RbxInstance, instance: &'a RbxInstance,
pub metadata: &'a InstanceMetadata, metadata: &'a InstanceMetadata,
} }
impl InstanceWithMeta<'_> { impl InstanceWithMeta<'_> {
pub fn id(&self) -> RbxId {
self.instance.get_id()
}
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
&self.instance.name &self.instance.name
} }
@@ -163,15 +202,28 @@ impl InstanceWithMeta<'_> {
pub fn children(&self) -> &[RbxId] { pub fn children(&self) -> &[RbxId] {
self.instance.get_children_ids() self.instance.get_children_ids()
} }
pub fn metadata(&self) -> &InstanceMetadata {
&self.metadata
}
} }
/// RojoTree's equivalent of `&'a mut RbxInstance`.
///
/// This has to be a value type for RojoTree because the instance and metadata
/// are stored in different places. The immutable equivalent is
/// `InstanceWithMeta`.
#[derive(Debug)] #[derive(Debug)]
pub struct InstanceWithMetaMut<'a> { pub struct InstanceWithMetaMut<'a> {
pub instance: &'a mut RbxInstance, instance: &'a mut RbxInstance,
pub metadata: &'a mut InstanceMetadata, metadata: &'a mut InstanceMetadata,
} }
impl InstanceWithMetaMut<'_> { impl InstanceWithMetaMut<'_> {
pub fn id(&self) -> RbxId {
self.instance.get_id()
}
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
&self.instance.name &self.instance.name
} }
@@ -199,4 +251,8 @@ impl InstanceWithMetaMut<'_> {
pub fn children(&self) -> &[RbxId] { pub fn children(&self) -> &[RbxId] {
self.instance.get_children_ids() self.instance.get_children_ids()
} }
pub fn metadata(&self) -> &InstanceMetadata {
&self.metadata
}
} }