mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
Enhance live syncback process with improved reference handling and float precision
- Implemented ObjectValue carriers for service-level references, allowing for accurate serialization of non-direct child references during syncback. - Updated `encodeService` to include explicit handling of attributes and tags, ensuring they are properly encoded and preventing data loss. - Refactored `build_dom_from_chunks` to manage ObjectValue carriers and resolve references correctly, enhancing data integrity. - Truncated float values to f32 precision in `UnresolvedValue::from_variant`, aligning with rbxl storage requirements and improving consistency. These changes address critical issues in the live syncback process, enhancing reliability and data accuracy.
This commit is contained in:
parent
7dc4f52793
commit
39c8bd41f4
8 changed files with 342 additions and 121 deletions
|
|
@ -523,11 +523,13 @@ fn get_port_number() -> usize {
|
|||
port
|
||||
}
|
||||
|
||||
/// A service entry plus its children (before serialization into the shared
|
||||
/// rbxm blob). Use `build_syncback_request` to finalize.
|
||||
/// A service entry plus its children and ObjectValue ref carriers (before
|
||||
/// serialization into the shared rbxm blob). Use `build_syncback_request`
|
||||
/// to finalize.
|
||||
pub struct ServiceEntry {
|
||||
pub chunk: librojo::web_api::ServiceChunk,
|
||||
pub children: Vec<rbx_dom_weak::InstanceBuilder>,
|
||||
pub ref_targets: Vec<rbx_dom_weak::InstanceBuilder>,
|
||||
}
|
||||
|
||||
/// Build a ServiceEntry with children and no extra properties.
|
||||
|
|
@ -535,14 +537,16 @@ pub fn make_service_chunk(
|
|||
class_name: &str,
|
||||
children: Vec<rbx_dom_weak::InstanceBuilder>,
|
||||
) -> ServiceEntry {
|
||||
make_service_chunk_full(class_name, vec![], vec![], children)
|
||||
make_service_chunk_full(class_name, vec![], vec![], vec![], children)
|
||||
}
|
||||
|
||||
/// Build a ServiceEntry with explicit service-level properties and ref hints.
|
||||
/// Build a ServiceEntry with explicit service-level properties, ObjectValue
|
||||
/// ref carriers, and refs (1-based indices into `ref_targets`).
|
||||
pub fn make_service_chunk_full(
|
||||
class_name: &str,
|
||||
properties: Vec<(&str, rbx_dom_weak::types::Variant)>,
|
||||
refs: Vec<(&str, &str, &str)>,
|
||||
refs: Vec<(&str, u32)>,
|
||||
ref_targets: Vec<rbx_dom_weak::InstanceBuilder>,
|
||||
children: Vec<rbx_dom_weak::InstanceBuilder>,
|
||||
) -> ServiceEntry {
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -551,27 +555,21 @@ pub fn make_service_chunk_full(
|
|||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v))
|
||||
.collect();
|
||||
let refs_map: HashMap<String, librojo::web_api::ServiceRef> = refs
|
||||
let refs_map: HashMap<String, u32> = refs
|
||||
.into_iter()
|
||||
.map(|(prop, name, class)| {
|
||||
(
|
||||
prop.to_string(),
|
||||
librojo::web_api::ServiceRef {
|
||||
name: name.to_string(),
|
||||
class_name: class.to_string(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.map(|(prop, idx)| (prop.to_string(), idx))
|
||||
.collect();
|
||||
|
||||
ServiceEntry {
|
||||
chunk: librojo::web_api::ServiceChunk {
|
||||
class_name: class_name.to_string(),
|
||||
child_count: children.len() as u32,
|
||||
ref_target_count: ref_targets.len() as u32,
|
||||
properties: props_map,
|
||||
refs: refs_map,
|
||||
},
|
||||
children,
|
||||
ref_targets,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -586,10 +584,14 @@ pub fn build_syncback_request(
|
|||
let root = dom.root_ref();
|
||||
|
||||
let mut chunks = Vec::new();
|
||||
for entry in entries {
|
||||
for mut entry in entries {
|
||||
for child in entry.children {
|
||||
dom.insert(root, child);
|
||||
}
|
||||
entry.chunk.ref_target_count = entry.ref_targets.len() as u32;
|
||||
for carrier in entry.ref_targets {
|
||||
dom.insert(root, carrier);
|
||||
}
|
||||
chunks.push(entry.chunk);
|
||||
}
|
||||
|
||||
|
|
@ -662,29 +664,41 @@ pub fn make_rbxl_from_chunks(
|
|||
}
|
||||
let service_ref = dom.insert(root_ref, builder);
|
||||
|
||||
let count = chunk.child_count as usize;
|
||||
let end = (cursor + count).min(cloned_children.len());
|
||||
for &child_ref in &cloned_children[cursor..end] {
|
||||
let child_count = chunk.child_count as usize;
|
||||
let ref_count = chunk.ref_target_count as usize;
|
||||
let total = child_count + ref_count;
|
||||
let end = (cursor + total).min(cloned_children.len());
|
||||
let service_range: Vec<Ref> = cloned_children[cursor..end].to_vec();
|
||||
|
||||
for &child_ref in &service_range[..child_count.min(service_range.len())] {
|
||||
dom.transfer_within(child_ref, service_ref);
|
||||
}
|
||||
cursor = end;
|
||||
|
||||
if !chunk.refs.is_empty() {
|
||||
let children: Vec<Ref> = dom.get_by_ref(service_ref).unwrap().children().to_vec();
|
||||
for (prop_name, target) in &chunk.refs {
|
||||
let found = children.iter().find(|&&child_ref| {
|
||||
let child = dom.get_by_ref(child_ref).unwrap();
|
||||
child.name.as_str() == target.name
|
||||
&& child.class.as_str() == target.class_name
|
||||
});
|
||||
if let Some(&child_ref) = found {
|
||||
let service = dom.get_by_ref_mut(service_ref).unwrap();
|
||||
service
|
||||
.properties
|
||||
.insert(prop_name.as_str().into(), Variant::Ref(child_ref));
|
||||
}
|
||||
let carrier_start = child_count;
|
||||
let carrier_end = service_range.len();
|
||||
let carriers: Vec<Ref> = service_range[carrier_start..carrier_end].to_vec();
|
||||
|
||||
for (prop_name, &idx) in &chunk.refs {
|
||||
if idx == 0 || (idx as usize) > carriers.len() {
|
||||
continue;
|
||||
}
|
||||
let carrier_ref = carriers[idx as usize - 1];
|
||||
if let Some(Variant::Ref(actual_target)) = dom
|
||||
.get_by_ref(carrier_ref)
|
||||
.and_then(|inst| inst.properties.get(&rbx_dom_weak::ustr("Value")).cloned())
|
||||
{
|
||||
let service = dom.get_by_ref_mut(service_ref).unwrap();
|
||||
service
|
||||
.properties
|
||||
.insert(prop_name.as_str().into(), Variant::Ref(actual_target));
|
||||
}
|
||||
}
|
||||
|
||||
for carrier_ref in carriers {
|
||||
dom.destroy(carrier_ref);
|
||||
}
|
||||
|
||||
cursor = end;
|
||||
}
|
||||
|
||||
let existing: std::collections::HashSet<String> =
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ fn parity_lighting_properties() {
|
|||
("ClockTime", Variant::Float32(14.5)),
|
||||
],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![InstanceBuilder::new("PointLight").with_name("TestLight")],
|
||||
)];
|
||||
assert_live_matches_cli("live_syncback", chunks, None);
|
||||
|
|
@ -224,6 +225,7 @@ fn parity_soundservice_properties() {
|
|||
],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
)];
|
||||
assert_live_matches_cli("live_syncback", chunks, None);
|
||||
}
|
||||
|
|
@ -237,6 +239,7 @@ fn parity_starterplayer_properties() {
|
|||
vec![("CameraMaxZoomDistance", Variant::Float32(200.0))],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
)];
|
||||
assert_live_matches_cli("live_syncback", chunks, None);
|
||||
}
|
||||
|
|
@ -253,6 +256,7 @@ fn parity_textchatservice_properties() {
|
|||
)],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
)];
|
||||
assert_live_matches_cli("live_syncback", chunks, None);
|
||||
}
|
||||
|
|
@ -269,6 +273,7 @@ fn parity_childless_service_with_properties() {
|
|||
)],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
)];
|
||||
assert_live_matches_cli("live_syncback", chunks, None);
|
||||
}
|
||||
|
|
@ -284,6 +289,7 @@ fn parity_service_properties_with_children() {
|
|||
("Brightness", Variant::Float32(3.0)),
|
||||
],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![
|
||||
InstanceBuilder::new("PointLight").with_name("LightA"),
|
||||
InstanceBuilder::new("ModuleScript")
|
||||
|
|
@ -304,12 +310,14 @@ fn parity_multiple_services_with_properties() {
|
|||
vec![("Ambient", Variant::Color3(Color3::new(0.2, 0.2, 0.2)))],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
),
|
||||
make_service_chunk_full(
|
||||
"SoundService",
|
||||
vec![("DistanceFactor", Variant::Float32(10.0))],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
),
|
||||
make_service_chunk(
|
||||
"ReplicatedStorage",
|
||||
|
|
@ -345,16 +353,22 @@ fn parity_cross_service_refs() {
|
|||
|
||||
#[test]
|
||||
fn parity_camera_not_synced() {
|
||||
use rbx_dom_weak::types::Variant;
|
||||
|
||||
let camera_child = InstanceBuilder::new("Camera").with_name("Camera");
|
||||
let camera_carrier = InstanceBuilder::new("ObjectValue")
|
||||
.with_name("CurrentCamera")
|
||||
.with_property("Value", Variant::Ref(camera_child.referent()));
|
||||
let entries = vec![make_service_chunk_full(
|
||||
"Workspace",
|
||||
vec![],
|
||||
vec![("CurrentCamera", "Camera", "Camera")],
|
||||
vec![("CurrentCamera", 1)],
|
||||
vec![camera_carrier],
|
||||
vec![
|
||||
camera_child,
|
||||
InstanceBuilder::new("Part")
|
||||
.with_name("Floor")
|
||||
.with_property("Anchored", rbx_dom_weak::types::Variant::Bool(true)),
|
||||
.with_property("Anchored", Variant::Bool(true)),
|
||||
],
|
||||
)];
|
||||
let (data, chunks) = build_syncback_request(entries);
|
||||
|
|
@ -654,26 +668,46 @@ fn roundtrip_build_syncback_rebuild() {
|
|||
let (_build_dir, rbxl_path_a) = run_rojo_build(session.path(), "build_a.rbxl");
|
||||
let rbxl_data_a = fs::read(&rbxl_path_a).unwrap();
|
||||
|
||||
let dom_a = rbx_binary::from_reader(Cursor::new(&rbxl_data_a)).unwrap();
|
||||
let mut dom_a = rbx_binary::from_reader(Cursor::new(&rbxl_data_a)).unwrap();
|
||||
let mut all_child_refs = Vec::new();
|
||||
let mut chunks = Vec::new();
|
||||
for &service_ref in dom_a.root().children() {
|
||||
let service_refs: Vec<rbx_dom_weak::types::Ref> =
|
||||
dom_a.root().children().to_vec();
|
||||
for &service_ref in &service_refs {
|
||||
let child_refs: Vec<rbx_dom_weak::types::Ref> = dom_a
|
||||
.get_by_ref(service_ref)
|
||||
.unwrap()
|
||||
.children()
|
||||
.to_vec();
|
||||
|
||||
let service = dom_a.get_by_ref(service_ref).unwrap();
|
||||
let child_refs: Vec<rbx_dom_weak::types::Ref> = service.children().to_vec();
|
||||
let service_props: Vec<(String, rbx_dom_weak::types::Variant)> = service
|
||||
.properties
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), v.clone()))
|
||||
.collect();
|
||||
let service_class = service.class.to_string();
|
||||
|
||||
let mut properties = std::collections::HashMap::new();
|
||||
let mut refs_map = std::collections::HashMap::new();
|
||||
for (key, value) in &service.properties {
|
||||
let mut refs_map: std::collections::HashMap<String, u32> =
|
||||
std::collections::HashMap::new();
|
||||
let mut carrier_refs: Vec<rbx_dom_weak::types::Ref> = Vec::new();
|
||||
|
||||
for (key, value) in &service_props {
|
||||
match value {
|
||||
rbx_dom_weak::types::Variant::Ref(target) => {
|
||||
if let Some(target_inst) = dom_a.get_by_ref(*target) {
|
||||
refs_map.insert(
|
||||
key.to_string(),
|
||||
librojo::web_api::ServiceRef {
|
||||
name: target_inst.name.to_string(),
|
||||
class_name: target_inst.class.to_string(),
|
||||
},
|
||||
);
|
||||
if !target.is_none() && dom_a.get_by_ref(*target).is_some() {
|
||||
let carrier = rbx_dom_weak::InstanceBuilder::new("ObjectValue")
|
||||
.with_name(key.as_str())
|
||||
.with_property(
|
||||
"Value",
|
||||
rbx_dom_weak::types::Variant::Ref(*target),
|
||||
);
|
||||
let carrier_ref =
|
||||
dom_a.insert(dom_a.root_ref(), carrier);
|
||||
carrier_refs.push(carrier_ref);
|
||||
refs_map
|
||||
.insert(key.to_string(), carrier_refs.len() as u32);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
|
@ -683,12 +717,14 @@ fn roundtrip_build_syncback_rebuild() {
|
|||
}
|
||||
|
||||
chunks.push(librojo::web_api::ServiceChunk {
|
||||
class_name: service.class.to_string(),
|
||||
class_name: service_class,
|
||||
child_count: child_refs.len() as u32,
|
||||
ref_target_count: carrier_refs.len() as u32,
|
||||
properties,
|
||||
refs: refs_map,
|
||||
});
|
||||
all_child_refs.extend(child_refs);
|
||||
all_child_refs.extend(carrier_refs);
|
||||
}
|
||||
|
||||
let mut data = Vec::new();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue