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
|
|
@ -14,52 +14,182 @@ todos:
|
|||
- id: fix-float-rounding
|
||||
content: Truncate all float values to f32 in UnresolvedValue::from_variant for rbxl parity
|
||||
status: completed
|
||||
- id: fix-index-refs
|
||||
content: Change service-level refs to index-based with ObjectValue carriers (childCount + refTargetCount, 1-based index, 0=nil)
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Live Syncback Fixes
|
||||
|
||||
## Issue 1: Workspace Attributes/Properties Lost
|
||||
## Issues 1-4: DONE
|
||||
|
||||
**Root cause:** `encodeService.lua` iterates `classDescriptor.properties` but the Luau RbxDom database may not list `Attributes` and `Tags` as enumerable properties. `encodeInstance.lua` has explicit `encodeAttributes()` / `encodeTags()` helpers using `instance:GetAttributes()` / `instance:GetTags()` directly.
|
||||
## Issue 5: Index-Based Service Refs via ObjectValue Carriers
|
||||
|
||||
**Fix:** Add explicit Attributes and Tags encoding to `[plugin/src/ChangeBatcher/encodeService.lua](plugin/src/ChangeBatcher/encodeService.lua)` after the property loop. Add `if propertyName == "Attributes" or propertyName == "Tags" then continue end` to the main loop to avoid double-encoding.
|
||||
**Problem:** Service Ref targets aren't always direct children. Can't include arbitrary instances directly in the rbxm without duplicating or breaking serialization.
|
||||
|
||||
## Issue 2: Lighting.ClockTime Appearing
|
||||
**Solution:** Create temporary ObjectValue instances as Ref carriers. The ObjectValue's `Value` property IS a Ref that gets preserved in the rbxm because both the ObjectValue and the target are in the same blob.
|
||||
|
||||
**Root cause:** Luau DB marks ClockTime as serializable; Rust DB marks it as DoesNotSerialize. Plugin sends it, CLI syncback filters it.
|
||||
**Layout:**
|
||||
|
||||
**Fix:** In `[src/cli/serve.rs](src/cli/serve.rs)` `build_dom_from_chunks`, filter each plugin-sent property through `should_property_serialize` from the Rust reflection DB before applying it to the builder.
|
||||
```
|
||||
allChildren = [
|
||||
...svc1_children, ...svc1_objectValues,
|
||||
...svc2_children, ...svc2_objectValues,
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
## Issue 3: Rojo_Ref_CurrentCamera Leaking (pre-existing bug)
|
||||
### Plugin -- `[plugin/src/ChangeBatcher/encodeService.lua](plugin/src/ChangeBatcher/encodeService.lua)`
|
||||
|
||||
**Root cause:** `collect_referents` captures `Workspace.CurrentCamera` -> Camera ref before camera is destroyed. `link_referents` writes `Rojo_Ref_CurrentCamera` without checking target still exists.
|
||||
Return metadata + list of ObjectValue carrier instances to append:
|
||||
|
||||
**Fix:** In `[src/syncback/mod.rs](src/syncback/mod.rs)`, after camera destruction (line 222), filter `deferred_referents.path_links` to remove entries whose target ref no longer exists.
|
||||
```lua
|
||||
local children = service:GetChildren()
|
||||
local refTargets = {} -- ObjectValue carriers
|
||||
|
||||
## Issue 4: Float Precision
|
||||
for propertyName ... do
|
||||
if descriptor.dataType == "Ref" then
|
||||
local readOk, target = descriptor:read(service)
|
||||
if readOk and target then
|
||||
-- Check if target is a direct child
|
||||
for i, child in children do
|
||||
if child == target then
|
||||
refs[propertyName] = i -- 1-based into children range
|
||||
goto found
|
||||
end
|
||||
end
|
||||
-- Not a child: create ObjectValue carrier
|
||||
local carrier = Instance.new("ObjectValue")
|
||||
carrier.Name = propertyName
|
||||
carrier.Value = target
|
||||
table.insert(refTargets, carrier)
|
||||
refs[propertyName] = #children + #refTargets
|
||||
::found::
|
||||
end
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
**Root cause:** Live syncback sends Float64 from Lua; rbxl stores Float32. Tiny diffs cause properties to fail default comparison.
|
||||
chunk.childCount = #children
|
||||
chunk.refTargetCount = #refTargets
|
||||
return chunk, refTargets
|
||||
```
|
||||
|
||||
**Fix:** In `[src/resolution.rs](src/resolution.rs)` `UnresolvedValue::from_variant`, truncate ALL float values to f32 precision. This is the single chokepoint for json5 writes:
|
||||
### Plugin -- `[plugin/src/App/init.lua](plugin/src/App/init.lua)` `performSyncback`
|
||||
|
||||
- **Float32(n)** (line 67): already f32, `n as f64` is fine (no change needed)
|
||||
- **Float64(n)** (line 68): change to `(n as f32) as f64` to truncate
|
||||
- **Vector2** (line 82): `(vector.x as f32) as f64` etc
|
||||
- **Vector3** (line 85): same
|
||||
- **CFrame** (lines 90-103): all 12 components
|
||||
- **Color3** (line 88): already f32, fine
|
||||
- **UDim/UDim2**: fall through to `FullyQualified` -- add a `round_variant` helper that truncates float components to f32 before wrapping
|
||||
Append children + ObjectValue carriers, clean up after serialization:
|
||||
|
||||
Helper:
|
||||
```lua
|
||||
local allRefTargets = {}
|
||||
for _, className in SYNCBACK_SERVICES do
|
||||
local ok, service = pcall(game.FindService, game, className)
|
||||
if ok and service then
|
||||
local chunk, refTargets = encodeService(service)
|
||||
for _, child in service:GetChildren() do
|
||||
table.insert(allChildren, child)
|
||||
end
|
||||
for _, carrier in refTargets do
|
||||
table.insert(allChildren, carrier)
|
||||
table.insert(allRefTargets, carrier)
|
||||
end
|
||||
table.insert(services, chunk)
|
||||
end
|
||||
end
|
||||
|
||||
local data = buffer.create(0)
|
||||
if #allChildren > 0 then
|
||||
data = SerializationService:SerializeInstancesAsync(allChildren)
|
||||
end
|
||||
|
||||
-- Clean up temporary ObjectValue carriers
|
||||
for _, carrier in allRefTargets do
|
||||
carrier:Destroy()
|
||||
end
|
||||
```
|
||||
|
||||
### Server -- `[src/web/interface.rs](src/web/interface.rs)`
|
||||
|
||||
```rust
|
||||
fn truncate_to_f32(v: f64) -> f64 {
|
||||
(v as f32) as f64
|
||||
pub struct ServiceChunk {
|
||||
pub class_name: String,
|
||||
#[serde(default)]
|
||||
pub child_count: u32,
|
||||
#[serde(default)]
|
||||
pub ref_target_count: u32,
|
||||
#[serde(default)]
|
||||
pub properties: HashMap<String, Variant>,
|
||||
#[serde(default)]
|
||||
pub refs: HashMap<String, u32>, // prop_name -> 1-based index, 0=nil
|
||||
}
|
||||
```
|
||||
|
||||
Remove `ServiceRef` struct.
|
||||
|
||||
### Server -- `[src/cli/serve.rs](src/cli/serve.rs)` `build_dom_from_chunks`
|
||||
|
||||
```rust
|
||||
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 = &cloned_children[cursor..end];
|
||||
|
||||
// Parent only the real children
|
||||
for &child_ref in &service_range[..child_count.min(service_range.len())] {
|
||||
dom.transfer_within(child_ref, service_ref);
|
||||
}
|
||||
|
||||
// Resolve refs using the full range
|
||||
let mut carriers_to_destroy = Vec::new();
|
||||
if !chunk.refs.is_empty() {
|
||||
for (prop_name, &idx) in &chunk.refs {
|
||||
if idx == 0 || (idx as usize - 1) >= service_range.len() {
|
||||
continue;
|
||||
}
|
||||
let carrier_ref = service_range[idx as usize - 1];
|
||||
// If in the ref target range, it's an ObjectValue carrier
|
||||
if (idx as usize) > child_count {
|
||||
let carrier = dom.get_by_ref(carrier_ref).unwrap();
|
||||
if let Some(Variant::Ref(actual_target)) = carrier.properties.get("Value") {
|
||||
let service = dom.get_by_ref_mut(service_ref).unwrap();
|
||||
service.properties.insert(
|
||||
prop_name.as_str().into(),
|
||||
Variant::Ref(*actual_target),
|
||||
);
|
||||
}
|
||||
carriers_to_destroy.push(carrier_ref);
|
||||
} else {
|
||||
// Direct child ref
|
||||
let service = dom.get_by_ref_mut(service_ref).unwrap();
|
||||
service.properties.insert(
|
||||
prop_name.as_str().into(),
|
||||
Variant::Ref(carrier_ref),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy ObjectValue carriers
|
||||
for carrier_ref in carriers_to_destroy {
|
||||
dom.destroy(carrier_ref);
|
||||
}
|
||||
|
||||
cursor = end;
|
||||
```
|
||||
|
||||
### Test helpers -- `[tests/rojo_test/serve_util.rs](tests/rojo_test/serve_util.rs)`
|
||||
|
||||
- `ServiceEntry` gets `ref_targets: Vec<InstanceBuilder>` for ObjectValue carriers
|
||||
- `make_service_chunk_full` refs param: `Vec<(&str, u32)>` (prop_name, 1-based index)
|
||||
- `build_syncback_request` appends ref_targets after children per service
|
||||
- `make_rbxl_from_chunks` resolves refs: direct children by index, carriers by reading `Value` property
|
||||
|
||||
### Tests
|
||||
|
||||
- Update `parity_camera_not_synced` to use index-based ref with ObjectValue carrier
|
||||
- Existing tests that don't use refs continue to work (empty refs map, refTargetCount=0)
|
||||
|
||||
## Deferred: Matching Parity
|
||||
|
||||
Matching differences between live and CLI syncback are likely downstream of the above fixes (extra properties inflating scores, missing attributes, float diffs). Revisit after these four fixes land. If matching is still off, consider reusing the previously-served tree instead of creating a fresh `ServeSession::new_oneshot` in `run_live_syncback`.
|
||||
|
||||
Revisit after these fixes land.
|
||||
|
|
@ -642,13 +642,18 @@ function App:performSyncback()
|
|||
|
||||
local services = {}
|
||||
local allChildren = {}
|
||||
local allCarriers = {}
|
||||
for _, className in SYNCBACK_SERVICES do
|
||||
local ok, service = pcall(game.FindService, game, className)
|
||||
if ok and service then
|
||||
local chunk = encodeService(service)
|
||||
local chunk, refTargets = encodeService(service)
|
||||
for _, child in service:GetChildren() do
|
||||
table.insert(allChildren, child)
|
||||
end
|
||||
for _, carrier in refTargets do
|
||||
table.insert(allChildren, carrier)
|
||||
table.insert(allCarriers, carrier)
|
||||
end
|
||||
table.insert(services, chunk)
|
||||
end
|
||||
end
|
||||
|
|
@ -658,6 +663,10 @@ function App:performSyncback()
|
|||
data = SerializationService:SerializeInstancesAsync(allChildren)
|
||||
end
|
||||
|
||||
for _, carrier in allCarriers do
|
||||
carrier:Destroy()
|
||||
end
|
||||
|
||||
local url = ("http://%s:%s/api/syncback"):format(host, port)
|
||||
local body = Http.msgpackEncode({
|
||||
protocolVersion = Config.protocolVersion,
|
||||
|
|
|
|||
|
|
@ -11,16 +11,26 @@ local SKIP_PROPERTIES = {
|
|||
Archivable = true,
|
||||
}
|
||||
|
||||
local EXCLUDE_PROPERTIES = {
|
||||
Lighting = { ClockTime = true },
|
||||
}
|
||||
|
||||
return function(service: Instance)
|
||||
local properties = {}
|
||||
local refs = {}
|
||||
local children = service:GetChildren()
|
||||
local refTargets = {}
|
||||
|
||||
local classDescriptor = RbxDom.findClassDescriptor(service.ClassName)
|
||||
if classDescriptor then
|
||||
local classExcludes = EXCLUDE_PROPERTIES[service.ClassName]
|
||||
for propertyName, propertyMeta in pairs(classDescriptor.properties) do
|
||||
if SKIP_PROPERTIES[propertyName] then
|
||||
continue
|
||||
end
|
||||
if classExcludes and classExcludes[propertyName] then
|
||||
continue
|
||||
end
|
||||
if propertyName == "Attributes" or propertyName == "Tags" then
|
||||
continue
|
||||
end
|
||||
|
|
@ -38,11 +48,12 @@ return function(service: Instance)
|
|||
|
||||
if descriptor.dataType == "Ref" then
|
||||
local readOk, target = descriptor:read(service)
|
||||
if readOk and target and target.Parent == service then
|
||||
refs[propertyName] = {
|
||||
name = target.Name,
|
||||
className = target.ClassName,
|
||||
}
|
||||
if readOk and target then
|
||||
local carrier = Instance.new("ObjectValue")
|
||||
carrier.Name = propertyName
|
||||
carrier.Value = target
|
||||
table.insert(refTargets, carrier)
|
||||
refs[propertyName] = #refTargets
|
||||
end
|
||||
continue
|
||||
end
|
||||
|
|
@ -84,7 +95,8 @@ return function(service: Instance)
|
|||
|
||||
local chunk = {
|
||||
className = service.ClassName,
|
||||
childCount = #service:GetChildren(),
|
||||
childCount = #children,
|
||||
refTargetCount = #refTargets,
|
||||
}
|
||||
if next(properties) then
|
||||
chunk.properties = properties
|
||||
|
|
@ -92,5 +104,5 @@ return function(service: Instance)
|
|||
if next(refs) then
|
||||
chunk.refs = refs
|
||||
end
|
||||
return chunk
|
||||
return chunk, refTargets
|
||||
end
|
||||
|
|
|
|||
|
|
@ -161,6 +161,9 @@ fn build_dom_from_chunks(payload: SyncbackPayload) -> anyhow::Result<WeakDom> {
|
|||
};
|
||||
|
||||
let mut cursor = 0usize;
|
||||
let mut all_carriers: Vec<Ref> = Vec::new();
|
||||
let mut deferred_refs: Vec<(Ref, Vec<(String, Ref)>)> = Vec::new();
|
||||
|
||||
for chunk in &payload.services {
|
||||
let mut builder = InstanceBuilder::new(&chunk.class_name);
|
||||
for (key, value) in &chunk.properties {
|
||||
|
|
@ -172,29 +175,33 @@ fn build_dom_from_chunks(payload: SyncbackPayload) -> anyhow::Result<WeakDom> {
|
|||
let service_ref = dom.insert(root_ref, builder);
|
||||
created_services.insert(chunk.class_name.clone());
|
||||
|
||||
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 = &service_range[carrier_start..carrier_end];
|
||||
|
||||
let mut ref_entries: Vec<(String, Ref)> = Vec::new();
|
||||
for (prop_name, &idx) in &chunk.refs {
|
||||
if idx == 0 || (idx as usize) > carriers.len() {
|
||||
continue;
|
||||
}
|
||||
ref_entries.push((prop_name.clone(), carriers[idx as usize - 1]));
|
||||
}
|
||||
if !ref_entries.is_empty() {
|
||||
deferred_refs.push((service_ref, ref_entries));
|
||||
}
|
||||
|
||||
all_carriers.extend_from_slice(carriers);
|
||||
cursor = end;
|
||||
}
|
||||
|
||||
for &service_name in VISIBLE_SERVICES {
|
||||
|
|
@ -205,6 +212,25 @@ fn build_dom_from_chunks(payload: SyncbackPayload) -> anyhow::Result<WeakDom> {
|
|||
|
||||
fixup_ref_properties(&mut dom, &global_ref_map);
|
||||
|
||||
for (service_ref, ref_entries) in deferred_refs {
|
||||
for (prop_name, carrier_ref) in ref_entries {
|
||||
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 all_carriers {
|
||||
dom.destroy(carrier_ref);
|
||||
}
|
||||
|
||||
Ok(dom)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,11 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::REF_PATH_ATTRIBUTE_PREFIX;
|
||||
|
||||
/// Truncates float components of Variant types to f32 precision, matching the
|
||||
/// rbxl binary format which stores most float properties as Float32.
|
||||
/// Rounds float components of Variant types to 6 decimal places.
|
||||
/// Uses format-and-reparse for exact decimal representation in json5 output.
|
||||
fn truncate_variant(v: Variant) -> Variant {
|
||||
#[inline]
|
||||
fn t(v: f32) -> f32 {
|
||||
v as f64 as f32
|
||||
format!("{:.6}", v).parse::<f32>().unwrap_or(v)
|
||||
}
|
||||
|
||||
match v {
|
||||
|
|
@ -66,8 +65,6 @@ fn truncate_variant(v: Variant) -> Variant {
|
|||
rbx_dom_weak::types::UDim::new(t(u.x.scale), u.x.offset),
|
||||
rbx_dom_weak::types::UDim::new(t(u.y.scale), u.y.offset),
|
||||
)),
|
||||
Variant::Float32(n) => Variant::Float32(n as f64 as f32),
|
||||
Variant::Float64(n) => Variant::Float64(n as f32 as f64),
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,12 +30,16 @@ pub const PROTOCOL_VERSION: u64 = 6;
|
|||
/// Metadata for a single service in the live syncback request.
|
||||
///
|
||||
/// Children are NOT stored here — they live in the top-level `data` blob
|
||||
/// (a single rbxm containing all children from all services). `child_count`
|
||||
/// tells the server how many consecutive children in that blob belong to
|
||||
/// this service.
|
||||
/// (a single rbxm containing all children from all services).
|
||||
///
|
||||
/// Attributes and Tags are included in `properties` as
|
||||
/// `Variant::Attributes(...)` / `Variant::Tags(...)`.
|
||||
/// Each service's range in the blob is `child_count + ref_target_count`
|
||||
/// consecutive entries. The first `child_count` are real children (parented
|
||||
/// to the service). The next `ref_target_count` are ObjectValue carriers
|
||||
/// whose `Value` property holds a Ref to the actual target.
|
||||
///
|
||||
/// `refs` maps property names to 1-based indices into this combined range
|
||||
/// (0 = nil). Indices <= child_count point directly to a child instance.
|
||||
/// Indices > child_count point to an ObjectValue carrier.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ServiceChunk {
|
||||
|
|
@ -43,18 +47,11 @@ pub struct ServiceChunk {
|
|||
#[serde(default)]
|
||||
pub child_count: u32,
|
||||
#[serde(default)]
|
||||
pub ref_target_count: u32,
|
||||
#[serde(default)]
|
||||
pub properties: HashMap<String, Variant>,
|
||||
#[serde(default)]
|
||||
pub refs: HashMap<String, ServiceRef>,
|
||||
}
|
||||
|
||||
/// Describes a Ref property target by name and class so the server can resolve
|
||||
/// it among the service's cloned children.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ServiceRef {
|
||||
pub name: String,
|
||||
pub class_name: String,
|
||||
pub refs: HashMap<String, u32>,
|
||||
}
|
||||
|
||||
/// Incoming request from the plugin for live syncback.
|
||||
|
|
|
|||
|
|
@ -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 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(child_ref));
|
||||
.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