mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-23 06:05:24 +00:00
Move Rojo server into root of the repository
This commit is contained in:
62
src/snapshot/instance_snapshot.rs
Normal file
62
src/snapshot/instance_snapshot.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
//! Defines the structure of an instance snapshot.
|
||||
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
use rbx_dom_weak::{RbxId, RbxTree, RbxValue};
|
||||
|
||||
/// A lightweight description of what an instance should look like. Attempts to
|
||||
/// be somewhat memory efficient by borrowing from its source data, indicated by
|
||||
/// the lifetime parameter, `'source`.
|
||||
///
|
||||
// Possible future improvements:
|
||||
// - Use refcounted/interned strings
|
||||
// - Replace use of RbxValue with a sum of RbxValue + borrowed value
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct InstanceSnapshot<'source> {
|
||||
pub snapshot_id: Option<RbxId>,
|
||||
|
||||
pub name: Cow<'source, str>,
|
||||
pub class_name: Cow<'source, str>,
|
||||
pub properties: HashMap<String, RbxValue>,
|
||||
pub children: Vec<InstanceSnapshot<'source>>,
|
||||
// TODO: Snapshot source, like a file or a project node?
|
||||
}
|
||||
|
||||
impl<'source> InstanceSnapshot<'source> {
|
||||
pub fn get_owned(&'source self) -> InstanceSnapshot<'static> {
|
||||
let children: Vec<InstanceSnapshot<'static>> = self
|
||||
.children
|
||||
.iter()
|
||||
.map(InstanceSnapshot::get_owned)
|
||||
.collect();
|
||||
|
||||
InstanceSnapshot {
|
||||
snapshot_id: None,
|
||||
name: Cow::Owned(self.name.clone().into_owned()),
|
||||
class_name: Cow::Owned(self.class_name.clone().into_owned()),
|
||||
properties: self.properties.clone(),
|
||||
children,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_tree(tree: &RbxTree, id: RbxId) -> InstanceSnapshot<'static> {
|
||||
let instance = tree
|
||||
.get_instance(id)
|
||||
.expect("instance did not exist in tree");
|
||||
|
||||
let children = instance
|
||||
.get_children_ids()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|id| InstanceSnapshot::from_tree(tree, id))
|
||||
.collect();
|
||||
|
||||
InstanceSnapshot {
|
||||
snapshot_id: Some(id),
|
||||
name: Cow::Owned(instance.name.clone()),
|
||||
class_name: Cow::Owned(instance.class_name.clone()),
|
||||
properties: instance.properties.clone(),
|
||||
children,
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/snapshot/mod.rs
Normal file
29
src/snapshot/mod.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
//! This module defines the instance snapshot subsystem of Rojo.
|
||||
//!
|
||||
//! It defines a way to define the instance tree of a project as a pure function
|
||||
//! of the filesystem by providing a lightweight instance 'snapshot' type, a
|
||||
//! method to generate minimal patches, and a method that applies those patches.
|
||||
//!
|
||||
//! The aim with this approach is to reduce the number of bugs that arise from
|
||||
//! attempting to manually update instances in response to filesystem updates.
|
||||
//! Instead of surgically identifying what needs to change, we can do rough
|
||||
//! "damage-painting", running our relatively fast snapshot function over
|
||||
//! anything that could have changed and running it through a diffing function
|
||||
//! to minimize the set of real changes.
|
||||
//!
|
||||
//! Building out a snapshot reconciler is mostly overkill for scripts, since
|
||||
//! their relationships are mostly simple and well-defined. It becomes very
|
||||
//! important, however, when dealing with large opaque model files and
|
||||
//! user-defined plugins.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod instance_snapshot;
|
||||
mod patch;
|
||||
mod patch_apply;
|
||||
mod patch_compute;
|
||||
|
||||
pub use instance_snapshot::InstanceSnapshot;
|
||||
pub use patch::*;
|
||||
pub use patch_apply::apply_patch_set;
|
||||
pub use patch_compute::compute_patch_set;
|
||||
44
src/snapshot/patch.rs
Normal file
44
src/snapshot/patch.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
//! Defines the data structures used for describing instance patches.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rbx_dom_weak::{RbxId, RbxValue};
|
||||
|
||||
use super::InstanceSnapshot;
|
||||
|
||||
/// A set of different kinds of patches that can be applied to an RbxTree.
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct PatchSet<'a> {
|
||||
pub removed_instances: Vec<RbxId>,
|
||||
pub added_instances: Vec<PatchAddInstance<'a>>,
|
||||
pub updated_instances: Vec<PatchUpdateInstance>,
|
||||
}
|
||||
|
||||
impl<'a> PatchSet<'a> {
|
||||
pub fn new() -> PatchSet<'a> {
|
||||
PatchSet {
|
||||
removed_instances: Vec::new(),
|
||||
added_instances: Vec::new(),
|
||||
updated_instances: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A patch containing an instance that was added to the tree.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PatchAddInstance<'a> {
|
||||
pub parent_id: RbxId,
|
||||
pub instance: InstanceSnapshot<'a>,
|
||||
}
|
||||
|
||||
/// A patch indicating that properties (or the name) of an instance changed.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PatchUpdateInstance {
|
||||
pub id: RbxId,
|
||||
pub changed_name: Option<String>,
|
||||
pub changed_class_name: Option<String>,
|
||||
|
||||
/// Contains all changed properties. If a property is assigned to `None`,
|
||||
/// then that property has been removed.
|
||||
pub changed_properties: HashMap<String, Option<RbxValue>>,
|
||||
}
|
||||
240
src/snapshot/patch_apply.rs
Normal file
240
src/snapshot/patch_apply.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
//! Defines the algorithm for applying generated patches.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rbx_dom_weak::{RbxId, RbxInstanceProperties, RbxTree, RbxValue};
|
||||
|
||||
use super::{
|
||||
patch::{PatchSet, PatchUpdateInstance},
|
||||
InstanceSnapshot,
|
||||
};
|
||||
|
||||
pub fn apply_patch_set(tree: &mut RbxTree, patch_set: &PatchSet) {
|
||||
let mut context = PatchApplyContext::default();
|
||||
|
||||
for removed_id in &patch_set.removed_instances {
|
||||
tree.remove_instance(*removed_id);
|
||||
}
|
||||
|
||||
for add_patch in &patch_set.added_instances {
|
||||
apply_add_child(&mut context, tree, add_patch.parent_id, &add_patch.instance);
|
||||
}
|
||||
|
||||
for update_patch in &patch_set.updated_instances {
|
||||
apply_update_child(&context, tree, update_patch);
|
||||
}
|
||||
|
||||
apply_deferred_properties(context, tree);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PatchApplyContext {
|
||||
snapshot_id_to_instance_id: HashMap<RbxId, RbxId>,
|
||||
properties_to_apply: HashMap<RbxId, HashMap<String, RbxValue>>,
|
||||
}
|
||||
|
||||
/// Apply properties that were deferred in order to get more information.
|
||||
///
|
||||
/// Ref properties from snapshots refer to eachother via snapshot ID. Some of
|
||||
/// these properties are transformed when the patch is computed, notably the
|
||||
/// instances that the patch computing method is able to pair up.
|
||||
///
|
||||
/// The remaining Ref properties need to be handled during patch application,
|
||||
/// where we build up a map of snapshot IDs to instance IDs as they're created,
|
||||
/// then apply properties all at once at the end.
|
||||
fn apply_deferred_properties(context: PatchApplyContext, tree: &mut RbxTree) {
|
||||
for (id, mut properties) in context.properties_to_apply {
|
||||
let instance = tree
|
||||
.get_instance_mut(id)
|
||||
.expect("Invalid instance ID in deferred property map");
|
||||
|
||||
for property_value in properties.values_mut() {
|
||||
if let RbxValue::Ref { value: Some(id) } = property_value {
|
||||
if let Some(&instance_id) = context.snapshot_id_to_instance_id.get(id) {
|
||||
*property_value = RbxValue::Ref {
|
||||
value: Some(instance_id),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance.properties = properties;
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_add_child(
|
||||
context: &mut PatchApplyContext,
|
||||
tree: &mut RbxTree,
|
||||
parent_id: RbxId,
|
||||
snapshot: &InstanceSnapshot,
|
||||
) {
|
||||
let properties = RbxInstanceProperties {
|
||||
name: snapshot.name.clone().into_owned(),
|
||||
class_name: snapshot.class_name.clone().into_owned(),
|
||||
|
||||
// Property assignment is deferred until after we know about all
|
||||
// instances in this patch.
|
||||
properties: HashMap::new(),
|
||||
};
|
||||
|
||||
let id = tree.insert_instance(properties, parent_id);
|
||||
|
||||
context
|
||||
.properties_to_apply
|
||||
.insert(id, snapshot.properties.clone());
|
||||
|
||||
if let Some(snapshot_id) = snapshot.snapshot_id {
|
||||
context.snapshot_id_to_instance_id.insert(snapshot_id, id);
|
||||
}
|
||||
|
||||
for child_snapshot in &snapshot.children {
|
||||
apply_add_child(context, tree, id, child_snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_update_child(
|
||||
context: &PatchApplyContext,
|
||||
tree: &mut RbxTree,
|
||||
patch: &PatchUpdateInstance,
|
||||
) {
|
||||
let instance = tree
|
||||
.get_instance_mut(patch.id)
|
||||
.expect("Instance referred to by patch does not exist");
|
||||
|
||||
if let Some(name) = &patch.changed_name {
|
||||
instance.name = name.clone();
|
||||
}
|
||||
|
||||
if let Some(class_name) = &patch.changed_class_name {
|
||||
instance.class_name = class_name.clone();
|
||||
}
|
||||
|
||||
for (key, property_entry) in &patch.changed_properties {
|
||||
match property_entry {
|
||||
// Ref values need to be potentially rewritten from snapshot IDs to
|
||||
// instance IDs if they referred to an instance that was created as
|
||||
// part of this patch.
|
||||
Some(RbxValue::Ref { value: Some(id) }) => {
|
||||
let new_id = context.snapshot_id_to_instance_id.get(id).unwrap_or(id);
|
||||
|
||||
instance.properties.insert(
|
||||
key.clone(),
|
||||
RbxValue::Ref {
|
||||
value: Some(*new_id),
|
||||
},
|
||||
);
|
||||
}
|
||||
Some(value) => {
|
||||
instance.properties.insert(key.clone(), value.clone());
|
||||
}
|
||||
None => {
|
||||
instance.properties.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
use maplit::hashmap;
|
||||
use rbx_dom_weak::RbxValue;
|
||||
|
||||
use super::super::patch::PatchAddInstance;
|
||||
|
||||
#[test]
|
||||
fn add_from_empty() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let mut tree = RbxTree::new(RbxInstanceProperties {
|
||||
name: "Folder".to_owned(),
|
||||
class_name: "Folder".to_owned(),
|
||||
properties: HashMap::new(),
|
||||
});
|
||||
|
||||
let root_id = tree.get_root_id();
|
||||
|
||||
let snapshot = InstanceSnapshot {
|
||||
snapshot_id: None,
|
||||
name: Cow::Borrowed("Foo"),
|
||||
class_name: Cow::Borrowed("Bar"),
|
||||
properties: hashmap! {
|
||||
"Baz".to_owned() => RbxValue::Int32 { value: 5 },
|
||||
},
|
||||
children: Vec::new(),
|
||||
};
|
||||
|
||||
let patch_set = PatchSet {
|
||||
added_instances: vec![PatchAddInstance {
|
||||
parent_id: root_id,
|
||||
instance: snapshot.clone(),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
apply_patch_set(&mut tree, &patch_set);
|
||||
|
||||
let root_instance = tree.get_instance(root_id).unwrap();
|
||||
let child_id = root_instance.get_children_ids()[0];
|
||||
let child_instance = tree.get_instance(child_id).unwrap();
|
||||
|
||||
assert_eq!(child_instance.name.as_str(), &snapshot.name);
|
||||
assert_eq!(child_instance.class_name.as_str(), &snapshot.class_name);
|
||||
assert_eq!(&child_instance.properties, &snapshot.properties);
|
||||
assert!(child_instance.get_children_ids().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_existing() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let mut tree = RbxTree::new(RbxInstanceProperties {
|
||||
name: "OldName".to_owned(),
|
||||
class_name: "OldClassName".to_owned(),
|
||||
properties: hashmap! {
|
||||
"Foo".to_owned() => RbxValue::Int32 { value: 7 },
|
||||
"Bar".to_owned() => RbxValue::Int32 { value: 3 },
|
||||
"Unchanged".to_owned() => RbxValue::Int32 { value: -5 },
|
||||
},
|
||||
});
|
||||
|
||||
let root_id = tree.get_root_id();
|
||||
|
||||
let patch = PatchUpdateInstance {
|
||||
id: root_id,
|
||||
changed_name: Some("Foo".to_owned()),
|
||||
changed_class_name: Some("NewClassName".to_owned()),
|
||||
changed_properties: hashmap! {
|
||||
// The value of Foo has changed
|
||||
"Foo".to_owned() => Some(RbxValue::Int32 { value: 8 }),
|
||||
|
||||
// Bar has been deleted
|
||||
"Bar".to_owned() => None,
|
||||
|
||||
// Baz has been added
|
||||
"Baz".to_owned() => Some(RbxValue::Int32 { value: 10 }),
|
||||
},
|
||||
};
|
||||
|
||||
let patch_set = PatchSet {
|
||||
updated_instances: vec![patch],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
apply_patch_set(&mut tree, &patch_set);
|
||||
|
||||
let expected_properties = hashmap! {
|
||||
"Foo".to_owned() => RbxValue::Int32 { value: 8 },
|
||||
"Baz".to_owned() => RbxValue::Int32 { value: 10 },
|
||||
"Unchanged".to_owned() => RbxValue::Int32 { value: -5 },
|
||||
};
|
||||
|
||||
let root_instance = tree.get_instance(root_id).unwrap();
|
||||
assert_eq!(root_instance.name, "Foo");
|
||||
assert_eq!(root_instance.class_name, "NewClassName");
|
||||
assert_eq!(root_instance.properties, expected_properties);
|
||||
}
|
||||
}
|
||||
330
src/snapshot/patch_compute.rs
Normal file
330
src/snapshot/patch_compute.rs
Normal file
@@ -0,0 +1,330 @@
|
||||
//! Defines the algorithm for computing a roughly-minimal patch set given an
|
||||
//! existing instance tree and an instance snapshot.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use rbx_dom_weak::{RbxId, RbxInstance, RbxTree, RbxValue};
|
||||
|
||||
use super::{
|
||||
patch::{PatchAddInstance, PatchSet, PatchUpdateInstance},
|
||||
InstanceSnapshot,
|
||||
};
|
||||
|
||||
pub fn compute_patch_set<'a>(
|
||||
snapshot: &'a InstanceSnapshot,
|
||||
tree: &RbxTree,
|
||||
id: RbxId,
|
||||
) -> PatchSet<'a> {
|
||||
let mut patch_set = PatchSet::new();
|
||||
let mut context = ComputePatchContext::default();
|
||||
|
||||
compute_patch_set_internal(&mut context, snapshot, tree, id, &mut patch_set);
|
||||
|
||||
// Rewrite Ref properties to refer to instance IDs instead of snapshot IDs
|
||||
// for all of the IDs that we know about so far.
|
||||
rewrite_refs_in_updates(&context, &mut patch_set.updated_instances);
|
||||
rewrite_refs_in_additions(&context, &mut patch_set.added_instances);
|
||||
|
||||
patch_set
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ComputePatchContext {
|
||||
snapshot_id_to_instance_id: HashMap<RbxId, RbxId>,
|
||||
}
|
||||
|
||||
fn rewrite_refs_in_updates(context: &ComputePatchContext, updates: &mut [PatchUpdateInstance]) {
|
||||
for update in updates {
|
||||
for property_value in update.changed_properties.values_mut() {
|
||||
if let Some(RbxValue::Ref { value: Some(id) }) = property_value {
|
||||
if let Some(&instance_id) = context.snapshot_id_to_instance_id.get(id) {
|
||||
*property_value = Some(RbxValue::Ref {
|
||||
value: Some(instance_id),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rewrite_refs_in_additions(context: &ComputePatchContext, additions: &mut [PatchAddInstance]) {
|
||||
for addition in additions {
|
||||
rewrite_refs_in_snapshot(context, &mut addition.instance);
|
||||
}
|
||||
}
|
||||
|
||||
fn rewrite_refs_in_snapshot(context: &ComputePatchContext, snapshot: &mut InstanceSnapshot) {
|
||||
for property_value in snapshot.properties.values_mut() {
|
||||
if let RbxValue::Ref { value: Some(id) } = property_value {
|
||||
if let Some(&instance_id) = context.snapshot_id_to_instance_id.get(id) {
|
||||
*property_value = RbxValue::Ref {
|
||||
value: Some(instance_id),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for child in &mut snapshot.children {
|
||||
rewrite_refs_in_snapshot(context, child);
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_patch_set_internal<'a>(
|
||||
context: &mut ComputePatchContext,
|
||||
snapshot: &'a InstanceSnapshot,
|
||||
tree: &RbxTree,
|
||||
id: RbxId,
|
||||
patch_set: &mut PatchSet<'a>,
|
||||
) {
|
||||
if let Some(snapshot_id) = snapshot.snapshot_id {
|
||||
context.snapshot_id_to_instance_id.insert(snapshot_id, id);
|
||||
}
|
||||
|
||||
let instance = tree
|
||||
.get_instance(id)
|
||||
.expect("Instance did not exist in tree");
|
||||
|
||||
compute_property_patches(snapshot, instance, patch_set);
|
||||
compute_children_patches(context, snapshot, tree, id, patch_set);
|
||||
}
|
||||
|
||||
fn compute_property_patches(
|
||||
snapshot: &InstanceSnapshot,
|
||||
instance: &RbxInstance,
|
||||
patch_set: &mut PatchSet,
|
||||
) {
|
||||
let mut visited_properties = HashSet::new();
|
||||
let mut changed_properties = HashMap::new();
|
||||
|
||||
let changed_name = if snapshot.name == instance.name {
|
||||
None
|
||||
} else {
|
||||
Some(snapshot.name.clone().into_owned())
|
||||
};
|
||||
|
||||
let changed_class_name = if snapshot.class_name == instance.class_name {
|
||||
None
|
||||
} else {
|
||||
Some(snapshot.class_name.clone().into_owned())
|
||||
};
|
||||
|
||||
for (name, snapshot_value) in &snapshot.properties {
|
||||
visited_properties.insert(name.as_str());
|
||||
|
||||
match instance.properties.get(name) {
|
||||
Some(instance_value) => {
|
||||
if snapshot_value != instance_value {
|
||||
changed_properties.insert(name.clone(), Some(snapshot_value.clone()));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
changed_properties.insert(name.clone(), Some(snapshot_value.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name in instance.properties.keys() {
|
||||
if visited_properties.contains(name.as_str()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
changed_properties.insert(name.clone(), None);
|
||||
}
|
||||
|
||||
if changed_properties.is_empty() && changed_name.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
patch_set.updated_instances.push(PatchUpdateInstance {
|
||||
id: instance.get_id(),
|
||||
changed_name,
|
||||
changed_class_name,
|
||||
changed_properties,
|
||||
});
|
||||
}
|
||||
|
||||
fn compute_children_patches<'a>(
|
||||
context: &mut ComputePatchContext,
|
||||
snapshot: &'a InstanceSnapshot,
|
||||
tree: &RbxTree,
|
||||
id: RbxId,
|
||||
patch_set: &mut PatchSet<'a>,
|
||||
) {
|
||||
let instance = tree
|
||||
.get_instance(id)
|
||||
.expect("Instance did not exist in tree");
|
||||
|
||||
let instance_children = instance.get_children_ids();
|
||||
|
||||
let mut paired_instances = vec![false; instance_children.len()];
|
||||
|
||||
for snapshot_child in snapshot.children.iter() {
|
||||
let matching_instance =
|
||||
instance_children
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(instance_index, instance_child_id)| {
|
||||
if paired_instances[*instance_index] {
|
||||
return false;
|
||||
}
|
||||
|
||||
let instance_child = tree
|
||||
.get_instance(**instance_child_id)
|
||||
.expect("Instance did not exist in tree");
|
||||
|
||||
if snapshot_child.name == instance_child.name
|
||||
&& snapshot_child.class_name == instance_child.class_name
|
||||
{
|
||||
paired_instances[*instance_index] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
});
|
||||
|
||||
match matching_instance {
|
||||
Some((_, instance_child_id)) => {
|
||||
compute_patch_set_internal(
|
||||
context,
|
||||
snapshot_child,
|
||||
tree,
|
||||
*instance_child_id,
|
||||
patch_set,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
patch_set.added_instances.push(PatchAddInstance {
|
||||
parent_id: id,
|
||||
instance: snapshot_child.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (instance_index, instance_child_id) in instance_children.iter().enumerate() {
|
||||
if paired_instances[instance_index] {
|
||||
continue;
|
||||
}
|
||||
|
||||
patch_set.removed_instances.push(*instance_child_id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use maplit::hashmap;
|
||||
use rbx_dom_weak::RbxInstanceProperties;
|
||||
|
||||
/// This test makes sure that rewriting refs in instance update patches to
|
||||
/// instances that already exists works. We should be able to correlate the
|
||||
/// snapshot ID and instance ID during patch computation and replace the
|
||||
/// value before returning from compute_patch_set.
|
||||
#[test]
|
||||
fn rewrite_ref_existing_instance_update() {
|
||||
let tree = RbxTree::new(RbxInstanceProperties {
|
||||
name: "foo".to_owned(),
|
||||
class_name: "foo".to_owned(),
|
||||
properties: HashMap::new(),
|
||||
});
|
||||
|
||||
let root_id = tree.get_root_id();
|
||||
|
||||
// This snapshot should be identical to the existing tree except for the
|
||||
// addition of a prop named Self, which is a self-referential Ref.
|
||||
let snapshot_id = RbxId::new();
|
||||
let snapshot = InstanceSnapshot {
|
||||
snapshot_id: Some(snapshot_id),
|
||||
properties: hashmap! {
|
||||
"Self".to_owned() => RbxValue::Ref {
|
||||
value: Some(snapshot_id),
|
||||
}
|
||||
},
|
||||
|
||||
name: Cow::Borrowed("foo"),
|
||||
class_name: Cow::Borrowed("foo"),
|
||||
children: Vec::new(),
|
||||
};
|
||||
|
||||
let patch_set = compute_patch_set(&snapshot, &tree, root_id);
|
||||
|
||||
let expected_patch_set = PatchSet {
|
||||
updated_instances: vec![PatchUpdateInstance {
|
||||
id: root_id,
|
||||
changed_name: None,
|
||||
changed_class_name: None,
|
||||
changed_properties: hashmap! {
|
||||
"Self".to_owned() => Some(RbxValue::Ref {
|
||||
value: Some(root_id),
|
||||
}),
|
||||
},
|
||||
}],
|
||||
added_instances: Vec::new(),
|
||||
removed_instances: Vec::new(),
|
||||
};
|
||||
|
||||
assert_eq!(patch_set, expected_patch_set);
|
||||
}
|
||||
|
||||
/// The same as rewrite_ref_existing_instance_update, except that the
|
||||
/// property is added in a new instance instead of modifying an existing
|
||||
/// one.
|
||||
#[test]
|
||||
fn rewrite_ref_existing_instance_addition() {
|
||||
let tree = RbxTree::new(RbxInstanceProperties {
|
||||
name: "foo".to_owned(),
|
||||
class_name: "foo".to_owned(),
|
||||
properties: HashMap::new(),
|
||||
});
|
||||
|
||||
let root_id = tree.get_root_id();
|
||||
|
||||
// This patch describes the existing instance with a new child added.
|
||||
let snapshot_id = RbxId::new();
|
||||
let snapshot = InstanceSnapshot {
|
||||
snapshot_id: Some(snapshot_id),
|
||||
children: vec![InstanceSnapshot {
|
||||
properties: hashmap! {
|
||||
"Self".to_owned() => RbxValue::Ref {
|
||||
value: Some(snapshot_id),
|
||||
},
|
||||
},
|
||||
|
||||
snapshot_id: None,
|
||||
name: Cow::Borrowed("child"),
|
||||
class_name: Cow::Borrowed("child"),
|
||||
children: Vec::new(),
|
||||
}],
|
||||
|
||||
properties: HashMap::new(),
|
||||
name: Cow::Borrowed("foo"),
|
||||
class_name: Cow::Borrowed("foo"),
|
||||
};
|
||||
|
||||
let patch_set = compute_patch_set(&snapshot, &tree, root_id);
|
||||
|
||||
let expected_patch_set = PatchSet {
|
||||
added_instances: vec![PatchAddInstance {
|
||||
parent_id: root_id,
|
||||
instance: InstanceSnapshot {
|
||||
snapshot_id: None,
|
||||
properties: hashmap! {
|
||||
"Self".to_owned() => RbxValue::Ref {
|
||||
value: Some(root_id),
|
||||
},
|
||||
},
|
||||
name: Cow::Borrowed("child"),
|
||||
class_name: Cow::Borrowed("child"),
|
||||
children: Vec::new(),
|
||||
},
|
||||
}],
|
||||
updated_instances: Vec::new(),
|
||||
removed_instances: Vec::new(),
|
||||
};
|
||||
|
||||
assert_eq!(patch_set, expected_patch_set);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user