mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-25 04:54:07 +00:00
Enhance syncback process by integrating serialization of service children
- Introduced `SerializationService` to serialize child instances during syncback, improving data handling. - Updated `performSyncback` to include serialized children data in the syncback request. - Refactored `ServiceChunk` structure to store child count instead of serialized data, optimizing memory usage. - Adjusted related functions and tests to accommodate the new data structure, ensuring consistency and accuracy in syncback operations. These changes significantly enhance the efficiency and clarity of the syncback process, allowing for better management of service data.
This commit is contained in:
parent
a823db0297
commit
a75a1443c0
7 changed files with 259 additions and 161 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
local ChangeHistoryService = game:GetService("ChangeHistoryService")
|
local ChangeHistoryService = game:GetService("ChangeHistoryService")
|
||||||
local Players = game:GetService("Players")
|
local Players = game:GetService("Players")
|
||||||
|
local SerializationService = game:GetService("SerializationService")
|
||||||
local ServerStorage = game:GetService("ServerStorage")
|
local ServerStorage = game:GetService("ServerStorage")
|
||||||
local RunService = game:GetService("RunService")
|
local RunService = game:GetService("RunService")
|
||||||
|
|
||||||
|
|
@ -640,11 +641,21 @@ function App:performSyncback()
|
||||||
end
|
end
|
||||||
|
|
||||||
local services = {}
|
local services = {}
|
||||||
|
local allChildren = {}
|
||||||
for _, className in SYNCBACK_SERVICES do
|
for _, className in SYNCBACK_SERVICES do
|
||||||
local ok, service = pcall(game.FindService, game, className)
|
local ok, service = pcall(game.FindService, game, className)
|
||||||
if ok and service then
|
if ok and service then
|
||||||
table.insert(services, encodeService(service))
|
local chunk = encodeService(service)
|
||||||
|
for _, child in service:GetChildren() do
|
||||||
|
table.insert(allChildren, child)
|
||||||
end
|
end
|
||||||
|
table.insert(services, chunk)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local data = buffer.create(0)
|
||||||
|
if #allChildren > 0 then
|
||||||
|
data = SerializationService:SerializeInstancesAsync(allChildren)
|
||||||
end
|
end
|
||||||
|
|
||||||
local url = ("http://%s:%s/api/syncback"):format(host, port)
|
local url = ("http://%s:%s/api/syncback"):format(host, port)
|
||||||
|
|
@ -652,6 +663,7 @@ function App:performSyncback()
|
||||||
protocolVersion = Config.protocolVersion,
|
protocolVersion = Config.protocolVersion,
|
||||||
serverVersion = Config.expectedServerVersionString,
|
serverVersion = Config.expectedServerVersionString,
|
||||||
placeId = game.PlaceId,
|
placeId = game.PlaceId,
|
||||||
|
data = data,
|
||||||
services = services,
|
services = services,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
local SerializationService = game:GetService("SerializationService")
|
|
||||||
|
|
||||||
local Packages = script.Parent.Parent.Parent.Packages
|
local Packages = script.Parent.Parent.Parent.Packages
|
||||||
local Log = require(Packages.Log)
|
local Log = require(Packages.Log)
|
||||||
local RbxDom = require(Packages.RbxDom)
|
local RbxDom = require(Packages.RbxDom)
|
||||||
|
|
@ -24,7 +22,8 @@ return function(service: Instance)
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
local isReadable = propertyMeta.scriptability == "ReadWrite" or propertyMeta.scriptability == "Read"
|
local isReadable = propertyMeta.scriptability == "ReadWrite"
|
||||||
|
or propertyMeta.scriptability == "Read"
|
||||||
local doesSerialize = propertyMeta.serialization ~= "DoesNotSerialize"
|
local doesSerialize = propertyMeta.serialization ~= "DoesNotSerialize"
|
||||||
|
|
||||||
if isReadable and doesSerialize then
|
if isReadable and doesSerialize then
|
||||||
|
|
@ -54,20 +53,9 @@ return function(service: Instance)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local children = service:GetChildren()
|
|
||||||
local data = buffer.create(0)
|
|
||||||
if #children > 0 then
|
|
||||||
local serializeOk, result = pcall(SerializationService.SerializeInstancesAsync, SerializationService, children)
|
|
||||||
if serializeOk then
|
|
||||||
data = result
|
|
||||||
else
|
|
||||||
Log.warn("Failed to serialize children of {}: {}", service.ClassName, tostring(result))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local chunk = {
|
local chunk = {
|
||||||
className = service.ClassName,
|
className = service.ClassName,
|
||||||
data = data,
|
childCount = #service:GetChildren(),
|
||||||
}
|
}
|
||||||
if next(properties) then
|
if next(properties) then
|
||||||
chunk.properties = properties
|
chunk.properties = properties
|
||||||
|
|
|
||||||
|
|
@ -142,43 +142,47 @@ fn build_dom_from_chunks(payload: SyncbackPayload) -> anyhow::Result<WeakDom> {
|
||||||
|
|
||||||
let mut dom = WeakDom::new(InstanceBuilder::new("DataModel"));
|
let mut dom = WeakDom::new(InstanceBuilder::new("DataModel"));
|
||||||
let root_ref = dom.root_ref();
|
let root_ref = dom.root_ref();
|
||||||
|
|
||||||
let mut global_ref_map: HashMap<Ref, Ref> = HashMap::new();
|
let mut global_ref_map: HashMap<Ref, Ref> = HashMap::new();
|
||||||
let mut created_services: std::collections::HashSet<String> = std::collections::HashSet::new();
|
let mut created_services: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||||
|
|
||||||
|
let cloned_children: Vec<Ref> = if !payload.data.is_empty() {
|
||||||
|
let chunk_dom = rbx_binary::from_reader(Cursor::new(&payload.data))
|
||||||
|
.context("Failed to parse rbxm data blob")?;
|
||||||
|
|
||||||
|
let mut cloned = Vec::new();
|
||||||
|
for &child_ref in chunk_dom.root().children() {
|
||||||
|
deep_clone_into(&chunk_dom, &mut dom, child_ref, root_ref, &mut global_ref_map);
|
||||||
|
let new_ref = *global_ref_map.get(&child_ref).unwrap();
|
||||||
|
cloned.push(new_ref);
|
||||||
|
}
|
||||||
|
cloned
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cursor = 0usize;
|
||||||
for chunk in &payload.services {
|
for chunk in &payload.services {
|
||||||
let mut builder = InstanceBuilder::new(&chunk.class_name);
|
let mut builder = InstanceBuilder::new(&chunk.class_name);
|
||||||
|
|
||||||
for (key, value) in &chunk.properties {
|
for (key, value) in &chunk.properties {
|
||||||
builder = builder.with_property(key.as_str(), value.clone());
|
builder = builder.with_property(key.as_str(), value.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let service_ref = dom.insert(root_ref, builder);
|
let service_ref = dom.insert(root_ref, builder);
|
||||||
created_services.insert(chunk.class_name.clone());
|
created_services.insert(chunk.class_name.clone());
|
||||||
|
|
||||||
if !chunk.data.is_empty() {
|
let count = chunk.child_count as usize;
|
||||||
let chunk_dom =
|
let end = (cursor + count).min(cloned_children.len());
|
||||||
rbx_binary::from_reader(Cursor::new(&chunk.data)).with_context(|| {
|
for &child_ref in &cloned_children[cursor..end] {
|
||||||
format!("Failed to parse rbxm for service {}", chunk.class_name)
|
dom.transfer_within(child_ref, service_ref);
|
||||||
})?;
|
|
||||||
|
|
||||||
for &child_ref in chunk_dom.root().children() {
|
|
||||||
deep_clone_into(
|
|
||||||
&chunk_dom,
|
|
||||||
&mut dom,
|
|
||||||
child_ref,
|
|
||||||
service_ref,
|
|
||||||
&mut global_ref_map,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
cursor = end;
|
||||||
|
|
||||||
if !chunk.refs.is_empty() {
|
if !chunk.refs.is_empty() {
|
||||||
let children: Vec<Ref> = dom.get_by_ref(service_ref).unwrap().children().to_vec();
|
let children: Vec<Ref> = dom.get_by_ref(service_ref).unwrap().children().to_vec();
|
||||||
for (prop_name, target) in &chunk.refs {
|
for (prop_name, target) in &chunk.refs {
|
||||||
let found = children.iter().find(|&&child_ref| {
|
let found = children.iter().find(|&&child_ref| {
|
||||||
let child = dom.get_by_ref(child_ref).unwrap();
|
let child = dom.get_by_ref(child_ref).unwrap();
|
||||||
child.name.as_str() == target.name && child.class.as_str() == target.class_name
|
child.name.as_str() == target.name
|
||||||
|
&& child.class.as_str() == target.class_name
|
||||||
});
|
});
|
||||||
if let Some(&child_ref) = found {
|
if let Some(&child_ref) = found {
|
||||||
let service = dom.get_by_ref_mut(service_ref).unwrap();
|
let service = dom.get_by_ref_mut(service_ref).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -247,11 +247,13 @@ async fn handle_api_syncback(
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"Live syncback requested with {} service chunks",
|
"Live syncback requested with {} service chunks, {} bytes of rbxm data",
|
||||||
syncback_request.services.len()
|
syncback_request.services.len(),
|
||||||
|
syncback_request.data.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
let payload = SyncbackPayload {
|
let payload = SyncbackPayload {
|
||||||
|
data: syncback_request.data,
|
||||||
services: syncback_request.services,
|
services: syncback_request.services,
|
||||||
};
|
};
|
||||||
syncback_signal.fire(payload);
|
syncback_signal.fire(payload);
|
||||||
|
|
|
||||||
|
|
@ -27,18 +27,21 @@ pub(crate) const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
/// Current protocol version, which is required to match.
|
/// Current protocol version, which is required to match.
|
||||||
pub const PROTOCOL_VERSION: u64 = 6;
|
pub const PROTOCOL_VERSION: u64 = 6;
|
||||||
|
|
||||||
/// A single service's children serialized as rbxm binary data, plus the
|
/// Metadata for a single service in the live syncback request.
|
||||||
/// service's own properties and Ref property hints.
|
|
||||||
///
|
///
|
||||||
/// Attributes and Tags are included in the `properties` map as
|
/// Children are NOT stored here — they live in the top-level `data` blob
|
||||||
/// `Variant::Attributes(...)` and `Variant::Tags(...)` respectively,
|
/// (a single rbxm containing all children from all services). `child_count`
|
||||||
/// matching the encoding used by the existing `/api/write` path.
|
/// tells the server how many consecutive children in that blob belong to
|
||||||
|
/// this service.
|
||||||
|
///
|
||||||
|
/// Attributes and Tags are included in `properties` as
|
||||||
|
/// `Variant::Attributes(...)` / `Variant::Tags(...)`.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ServiceChunk {
|
pub struct ServiceChunk {
|
||||||
pub class_name: String,
|
pub class_name: String,
|
||||||
#[serde(with = "serde_bytes")]
|
#[serde(default)]
|
||||||
pub data: Vec<u8>,
|
pub child_count: u32,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub properties: HashMap<String, Variant>,
|
pub properties: HashMap<String, Variant>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -64,12 +67,15 @@ pub struct SyncbackRequest {
|
||||||
pub protocol_version: f64,
|
pub protocol_version: f64,
|
||||||
pub server_version: String,
|
pub server_version: String,
|
||||||
pub place_id: Option<f64>,
|
pub place_id: Option<f64>,
|
||||||
|
#[serde(with = "serde_bytes", default)]
|
||||||
|
pub data: Vec<u8>,
|
||||||
pub services: Vec<ServiceChunk>,
|
pub services: Vec<ServiceChunk>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Payload passed from the API handler to the serve loop after validation.
|
/// Payload passed from the API handler to the serve loop after validation.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SyncbackPayload {
|
pub struct SyncbackPayload {
|
||||||
|
pub data: Vec<u8>,
|
||||||
pub services: Vec<ServiceChunk>,
|
pub services: Vec<ServiceChunk>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -523,37 +523,30 @@ fn get_port_number() -> usize {
|
||||||
port
|
port
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a ServiceChunk by serializing child instances into rbxm format.
|
/// A service entry plus its children (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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a ServiceEntry with children and no extra properties.
|
||||||
pub fn make_service_chunk(
|
pub fn make_service_chunk(
|
||||||
class_name: &str,
|
class_name: &str,
|
||||||
children: Vec<rbx_dom_weak::InstanceBuilder>,
|
children: Vec<rbx_dom_weak::InstanceBuilder>,
|
||||||
) -> librojo::web_api::ServiceChunk {
|
) -> ServiceEntry {
|
||||||
make_service_chunk_full(class_name, vec![], vec![], children)
|
make_service_chunk_full(class_name, vec![], vec![], children)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a ServiceChunk with explicit service-level properties and ref hints
|
/// Build a ServiceEntry with explicit service-level properties and ref hints.
|
||||||
/// alongside the serialized children. Attributes and Tags should be included
|
|
||||||
/// in the `properties` vec as `Variant::Attributes(...)` / `Variant::Tags(...)`.
|
|
||||||
pub fn make_service_chunk_full(
|
pub fn make_service_chunk_full(
|
||||||
class_name: &str,
|
class_name: &str,
|
||||||
properties: Vec<(&str, rbx_dom_weak::types::Variant)>,
|
properties: Vec<(&str, rbx_dom_weak::types::Variant)>,
|
||||||
refs: Vec<(&str, &str, &str)>,
|
refs: Vec<(&str, &str, &str)>,
|
||||||
children: Vec<rbx_dom_weak::InstanceBuilder>,
|
children: Vec<rbx_dom_weak::InstanceBuilder>,
|
||||||
) -> librojo::web_api::ServiceChunk {
|
) -> ServiceEntry {
|
||||||
use rbx_dom_weak::WeakDom;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
let mut dom = WeakDom::new(rbx_dom_weak::InstanceBuilder::new("DataModel"));
|
|
||||||
let root = dom.root_ref();
|
|
||||||
for child in children {
|
|
||||||
dom.insert(root, child);
|
|
||||||
}
|
|
||||||
let child_refs: Vec<Ref> = dom.root().children().to_vec();
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
if !child_refs.is_empty() {
|
|
||||||
rbx_binary::to_writer(&mut buf, &dom, &child_refs).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let props_map: HashMap<String, rbx_dom_weak::types::Variant> = properties
|
let props_map: HashMap<String, rbx_dom_weak::types::Variant> = properties
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| (k.to_string(), v))
|
.map(|(k, v)| (k.to_string(), v))
|
||||||
|
|
@ -571,17 +564,50 @@ pub fn make_service_chunk_full(
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
librojo::web_api::ServiceChunk {
|
ServiceEntry {
|
||||||
|
chunk: librojo::web_api::ServiceChunk {
|
||||||
class_name: class_name.to_string(),
|
class_name: class_name.to_string(),
|
||||||
data: buf,
|
child_count: children.len() as u32,
|
||||||
properties: props_map,
|
properties: props_map,
|
||||||
refs: refs_map,
|
refs: refs_map,
|
||||||
|
},
|
||||||
|
children,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a complete rbxl file from service chunks, mirroring the same data
|
/// Serialize all ServiceEntries into the final wire format: a single rbxm
|
||||||
/// that live syncback receives. Used for CLI parity testing.
|
/// data blob and a Vec of ServiceChunks (metadata only).
|
||||||
pub fn make_rbxl_from_chunks(chunks: &[librojo::web_api::ServiceChunk]) -> Vec<u8> {
|
pub fn build_syncback_request(
|
||||||
|
entries: Vec<ServiceEntry>,
|
||||||
|
) -> (Vec<u8>, Vec<librojo::web_api::ServiceChunk>) {
|
||||||
|
use rbx_dom_weak::WeakDom;
|
||||||
|
|
||||||
|
let mut dom = WeakDom::new(rbx_dom_weak::InstanceBuilder::new("DataModel"));
|
||||||
|
let root = dom.root_ref();
|
||||||
|
|
||||||
|
let mut chunks = Vec::new();
|
||||||
|
for entry in entries {
|
||||||
|
for child in entry.children {
|
||||||
|
dom.insert(root, child);
|
||||||
|
}
|
||||||
|
chunks.push(entry.chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
let all_refs: Vec<Ref> = dom.root().children().to_vec();
|
||||||
|
let mut data = Vec::new();
|
||||||
|
if !all_refs.is_empty() {
|
||||||
|
rbx_binary::to_writer(&mut data, &dom, &all_refs).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
(data, chunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a complete rbxl file from the single-rbxm data blob + service
|
||||||
|
/// chunks, mirroring the same data that live syncback receives.
|
||||||
|
pub fn make_rbxl_from_chunks(
|
||||||
|
data: &[u8],
|
||||||
|
chunks: &[librojo::web_api::ServiceChunk],
|
||||||
|
) -> Vec<u8> {
|
||||||
use rbx_dom_weak::types::Variant;
|
use rbx_dom_weak::types::Variant;
|
||||||
use rbx_dom_weak::WeakDom;
|
use rbx_dom_weak::WeakDom;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -590,22 +616,15 @@ pub fn make_rbxl_from_chunks(chunks: &[librojo::web_api::ServiceChunk]) -> Vec<u
|
||||||
let mut dom = WeakDom::new(rbx_dom_weak::InstanceBuilder::new("DataModel"));
|
let mut dom = WeakDom::new(rbx_dom_weak::InstanceBuilder::new("DataModel"));
|
||||||
let root_ref = dom.root_ref();
|
let root_ref = dom.root_ref();
|
||||||
|
|
||||||
for chunk in chunks {
|
let cloned_children: Vec<Ref> = if !data.is_empty() {
|
||||||
let mut builder = rbx_dom_weak::InstanceBuilder::new(&chunk.class_name);
|
let chunk_dom = rbx_binary::from_reader(Cursor::new(data))
|
||||||
|
.unwrap_or_else(|e| panic!("Failed to parse rbxm data blob: {e}"));
|
||||||
for (key, value) in &chunk.properties {
|
|
||||||
builder = builder.with_property(key.as_str(), value.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let service_ref = dom.insert(root_ref, builder);
|
|
||||||
|
|
||||||
if !chunk.data.is_empty() {
|
|
||||||
let chunk_dom = rbx_binary::from_reader(Cursor::new(&chunk.data))
|
|
||||||
.unwrap_or_else(|e| panic!("Failed to parse rbxm for {}: {e}", chunk.class_name));
|
|
||||||
|
|
||||||
let mut ref_map: HashMap<Ref, Ref> = HashMap::new();
|
let mut ref_map: HashMap<Ref, Ref> = HashMap::new();
|
||||||
|
let mut cloned = Vec::new();
|
||||||
for &child_ref in chunk_dom.root().children() {
|
for &child_ref in chunk_dom.root().children() {
|
||||||
deep_clone_into_test(&chunk_dom, &mut dom, child_ref, service_ref, &mut ref_map);
|
deep_clone_into_test(&chunk_dom, &mut dom, child_ref, root_ref, &mut ref_map);
|
||||||
|
cloned.push(*ref_map.get(&child_ref).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
let all_refs: Vec<Ref> = ref_map.values().copied().collect();
|
let all_refs: Vec<Ref> = ref_map.values().copied().collect();
|
||||||
|
|
@ -630,14 +649,33 @@ pub fn make_rbxl_from_chunks(chunks: &[librojo::web_api::ServiceChunk]) -> Vec<u
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cloned
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cursor = 0usize;
|
||||||
|
for chunk in chunks {
|
||||||
|
let mut builder = rbx_dom_weak::InstanceBuilder::new(&chunk.class_name);
|
||||||
|
for (key, value) in &chunk.properties {
|
||||||
|
builder = builder.with_property(key.as_str(), value.clone());
|
||||||
}
|
}
|
||||||
|
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] {
|
||||||
|
dom.transfer_within(child_ref, service_ref);
|
||||||
|
}
|
||||||
|
cursor = end;
|
||||||
|
|
||||||
if !chunk.refs.is_empty() {
|
if !chunk.refs.is_empty() {
|
||||||
let children: Vec<Ref> = dom.get_by_ref(service_ref).unwrap().children().to_vec();
|
let children: Vec<Ref> = dom.get_by_ref(service_ref).unwrap().children().to_vec();
|
||||||
for (prop_name, target) in &chunk.refs {
|
for (prop_name, target) in &chunk.refs {
|
||||||
let found = children.iter().find(|&&child_ref| {
|
let found = children.iter().find(|&&child_ref| {
|
||||||
let child = dom.get_by_ref(child_ref).unwrap();
|
let child = dom.get_by_ref(child_ref).unwrap();
|
||||||
child.name.as_str() == target.name && child.class.as_str() == target.class_name
|
child.name.as_str() == target.name
|
||||||
|
&& child.class.as_str() == target.class_name
|
||||||
});
|
});
|
||||||
if let Some(&child_ref) = found {
|
if let Some(&child_ref) = found {
|
||||||
let service = dom.get_by_ref_mut(service_ref).unwrap();
|
let service = dom.get_by_ref_mut(service_ref).unwrap();
|
||||||
|
|
@ -691,6 +729,7 @@ fn deep_clone_into_test(
|
||||||
/// project path.
|
/// project path.
|
||||||
pub fn run_cli_syncback_on_chunks(
|
pub fn run_cli_syncback_on_chunks(
|
||||||
fixture_name: &str,
|
fixture_name: &str,
|
||||||
|
data: &[u8],
|
||||||
chunks: &[librojo::web_api::ServiceChunk],
|
chunks: &[librojo::web_api::ServiceChunk],
|
||||||
) -> (TempDir, PathBuf) {
|
) -> (TempDir, PathBuf) {
|
||||||
use crate::rojo_test::io_util::{copy_recursive, ROJO_PATH, SERVE_TESTS_PATH};
|
use crate::rojo_test::io_util::{copy_recursive, ROJO_PATH, SERVE_TESTS_PATH};
|
||||||
|
|
@ -701,7 +740,7 @@ pub fn run_cli_syncback_on_chunks(
|
||||||
fs::create_dir(&project_path).expect("Couldn't create project subdirectory");
|
fs::create_dir(&project_path).expect("Couldn't create project subdirectory");
|
||||||
copy_recursive(&source_path, &project_path).expect("Couldn't copy fixture");
|
copy_recursive(&source_path, &project_path).expect("Couldn't copy fixture");
|
||||||
|
|
||||||
let rbxl_data = make_rbxl_from_chunks(chunks);
|
let rbxl_data = make_rbxl_from_chunks(data, chunks);
|
||||||
let rbxl_path = dir.path().join("input.rbxl");
|
let rbxl_path = dir.path().join("input.rbxl");
|
||||||
fs::write(&rbxl_path, rbxl_data).expect("Failed to write rbxl");
|
fs::write(&rbxl_path, rbxl_data).expect("Failed to write rbxl");
|
||||||
|
|
||||||
|
|
@ -743,20 +782,34 @@ impl TestServeSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Post to /api/syncback with correct version/protocol info.
|
/// Post to /api/syncback with correct version/protocol info.
|
||||||
|
/// `data` is the single rbxm blob; `services` are the metadata chunks.
|
||||||
/// Panics if the response is not 200.
|
/// Panics if the response is not 200.
|
||||||
pub fn post_api_syncback(
|
pub fn post_api_syncback(
|
||||||
&self,
|
&self,
|
||||||
place_id: Option<u64>,
|
place_id: Option<u64>,
|
||||||
|
data: Vec<u8>,
|
||||||
services: Vec<librojo::web_api::ServiceChunk>,
|
services: Vec<librojo::web_api::ServiceChunk>,
|
||||||
) {
|
) {
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
let request = serde_json::json!({
|
#[derive(Serialize)]
|
||||||
"protocolVersion": librojo::web_api::PROTOCOL_VERSION,
|
#[serde(rename_all = "camelCase")]
|
||||||
"serverVersion": env!("CARGO_PKG_VERSION"),
|
struct Req {
|
||||||
"placeId": place_id,
|
protocol_version: u64,
|
||||||
"services": services,
|
server_version: String,
|
||||||
});
|
place_id: Option<u64>,
|
||||||
|
#[serde(with = "serde_bytes")]
|
||||||
|
data: Vec<u8>,
|
||||||
|
services: Vec<librojo::web_api::ServiceChunk>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = Req {
|
||||||
|
protocol_version: librojo::web_api::PROTOCOL_VERSION,
|
||||||
|
server_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
|
place_id,
|
||||||
|
data,
|
||||||
|
services,
|
||||||
|
};
|
||||||
|
|
||||||
let mut body = Vec::new();
|
let mut body = Vec::new();
|
||||||
let mut serializer = rmp_serde::Serializer::new(&mut body)
|
let mut serializer = rmp_serde::Serializer::new(&mut body)
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,24 @@ use rbx_dom_weak::InstanceBuilder;
|
||||||
use crate::rojo_test::{
|
use crate::rojo_test::{
|
||||||
roundtrip_util::assert_dirs_equal,
|
roundtrip_util::assert_dirs_equal,
|
||||||
serve_util::{
|
serve_util::{
|
||||||
make_service_chunk, make_service_chunk_full, run_cli_syncback_on_chunks, run_serve_test,
|
build_syncback_request, make_service_chunk, make_service_chunk_full,
|
||||||
|
run_cli_syncback_on_chunks, run_serve_test, ServiceEntry,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use librojo::web_api::{ServiceChunk, SocketPacketType, PROTOCOL_VERSION};
|
use librojo::web_api::{SocketPacketType, PROTOCOL_VERSION};
|
||||||
|
|
||||||
fn assert_live_matches_cli(fixture_name: &str, chunks: &[ServiceChunk], place_id: Option<u64>) {
|
fn assert_live_matches_cli(
|
||||||
|
fixture_name: &str,
|
||||||
|
entries: Vec<ServiceEntry>,
|
||||||
|
place_id: Option<u64>,
|
||||||
|
) {
|
||||||
|
let (data, chunks) = build_syncback_request(entries);
|
||||||
run_serve_test(fixture_name, |session, _| {
|
run_serve_test(fixture_name, |session, _| {
|
||||||
session.post_api_syncback(place_id, chunks.to_vec());
|
session.post_api_syncback(place_id, data.clone(), chunks.clone());
|
||||||
session.wait_to_come_back_online();
|
session.wait_to_come_back_online();
|
||||||
|
|
||||||
let (_cli_dir, cli_path) = run_cli_syncback_on_chunks(fixture_name, chunks);
|
let (_cli_dir, cli_path) = run_cli_syncback_on_chunks(fixture_name, &data, &chunks);
|
||||||
assert_dirs_equal(session.path(), &cli_path);
|
assert_dirs_equal(session.path(), &cli_path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +52,7 @@ fn parity_scripts() {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)];
|
)];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -63,7 +69,7 @@ fn parity_models_with_properties() {
|
||||||
)),
|
)),
|
||||||
)],
|
)],
|
||||||
)];
|
)];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -76,7 +82,7 @@ fn parity_special_names() {
|
||||||
InstanceBuilder::new("Folder").with_name("A/B"),
|
InstanceBuilder::new("Folder").with_name("A/B"),
|
||||||
],
|
],
|
||||||
)];
|
)];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -89,7 +95,7 @@ fn parity_duplicate_names() {
|
||||||
InstanceBuilder::new("Folder").with_name("Data"),
|
InstanceBuilder::new("Folder").with_name("Data"),
|
||||||
],
|
],
|
||||||
)];
|
)];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -111,7 +117,7 @@ fn parity_deep_hierarchy() {
|
||||||
rbx_dom_weak::types::Variant::String("return true".into()),
|
rbx_dom_weak::types::Variant::String("return true".into()),
|
||||||
)])])])])],
|
)])])])])],
|
||||||
)];
|
)];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -134,7 +140,7 @@ fn parity_mixed_file_types() {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)];
|
)];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -169,7 +175,7 @@ fn parity_multi_service() {
|
||||||
.with_property("Anchored", rbx_dom_weak::types::Variant::Bool(true))],
|
.with_property("Anchored", rbx_dom_weak::types::Variant::Bool(true))],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -181,7 +187,7 @@ fn parity_empty_services_mixed() {
|
||||||
),
|
),
|
||||||
make_service_chunk("ServerScriptService", vec![]),
|
make_service_chunk("ServerScriptService", vec![]),
|
||||||
];
|
];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Service property parity tests ────────────────────────────────
|
// ── Service property parity tests ────────────────────────────────
|
||||||
|
|
@ -200,7 +206,7 @@ fn parity_lighting_properties() {
|
||||||
vec![],
|
vec![],
|
||||||
vec![InstanceBuilder::new("PointLight").with_name("TestLight")],
|
vec![InstanceBuilder::new("PointLight").with_name("TestLight")],
|
||||||
)];
|
)];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -219,7 +225,7 @@ fn parity_soundservice_properties() {
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
)];
|
)];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -232,7 +238,7 @@ fn parity_starterplayer_properties() {
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
)];
|
)];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -248,7 +254,7 @@ fn parity_textchatservice_properties() {
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
)];
|
)];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -264,7 +270,7 @@ fn parity_childless_service_with_properties() {
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
)];
|
)];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -285,7 +291,7 @@ fn parity_service_properties_with_children() {
|
||||||
.with_property("Source", Variant::String("return {}".into())),
|
.with_property("Source", Variant::String("return {}".into())),
|
||||||
],
|
],
|
||||||
)];
|
)];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -310,13 +316,37 @@ fn parity_multiple_services_with_properties() {
|
||||||
vec![InstanceBuilder::new("Folder").with_name("Assets")],
|
vec![InstanceBuilder::new("Folder").with_name("Assets")],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
assert_live_matches_cli("live_syncback", &chunks, None);
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parity_cross_service_refs() {
|
||||||
|
use rbx_dom_weak::types::Variant;
|
||||||
|
|
||||||
|
let target_part = InstanceBuilder::new("Part")
|
||||||
|
.with_name("TargetPart")
|
||||||
|
.with_property("Anchored", Variant::Bool(true));
|
||||||
|
|
||||||
|
let obj_value = InstanceBuilder::new("ObjectValue")
|
||||||
|
.with_name("Pointer")
|
||||||
|
.with_property("Value", Variant::Ref(target_part.referent()));
|
||||||
|
|
||||||
|
let chunks = vec![
|
||||||
|
make_service_chunk("Workspace", vec![target_part]),
|
||||||
|
make_service_chunk(
|
||||||
|
"ReplicatedStorage",
|
||||||
|
vec![InstanceBuilder::new("Folder")
|
||||||
|
.with_name("Refs")
|
||||||
|
.with_child(obj_value)],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
assert_live_matches_cli("live_syncback", chunks, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parity_camera_not_synced() {
|
fn parity_camera_not_synced() {
|
||||||
let camera_child = InstanceBuilder::new("Camera").with_name("Camera");
|
let camera_child = InstanceBuilder::new("Camera").with_name("Camera");
|
||||||
let chunks = [make_service_chunk_full(
|
let entries = vec![make_service_chunk_full(
|
||||||
"Workspace",
|
"Workspace",
|
||||||
vec![],
|
vec![],
|
||||||
vec![("CurrentCamera", "Camera", "Camera")],
|
vec![("CurrentCamera", "Camera", "Camera")],
|
||||||
|
|
@ -327,9 +357,10 @@ fn parity_camera_not_synced() {
|
||||||
.with_property("Anchored", rbx_dom_weak::types::Variant::Bool(true)),
|
.with_property("Anchored", rbx_dom_weak::types::Variant::Bool(true)),
|
||||||
],
|
],
|
||||||
)];
|
)];
|
||||||
|
let (data, chunks) = build_syncback_request(entries);
|
||||||
|
|
||||||
run_serve_test("live_syncback", |session, _| {
|
run_serve_test("live_syncback", |session, _| {
|
||||||
session.post_api_syncback(None, chunks.to_vec());
|
session.post_api_syncback(None, data.clone(), chunks.clone());
|
||||||
session.wait_to_come_back_online();
|
session.wait_to_come_back_online();
|
||||||
|
|
||||||
let workspace_dir = session.path().join("src/workspace");
|
let workspace_dir = session.path().join("src/workspace");
|
||||||
|
|
@ -485,12 +516,12 @@ fn rejects_unlisted_place() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn allows_whitelisted_place() {
|
fn allows_whitelisted_place() {
|
||||||
let chunks = vec![make_service_chunk(
|
let (data, chunks) = build_syncback_request(vec![make_service_chunk(
|
||||||
"ReplicatedStorage",
|
"ReplicatedStorage",
|
||||||
vec![InstanceBuilder::new("Folder").with_name("Allowed")],
|
vec![InstanceBuilder::new("Folder").with_name("Allowed")],
|
||||||
)];
|
)]);
|
||||||
run_serve_test("live_syncback_place_ids", |session, _| {
|
run_serve_test("live_syncback_place_ids", |session, _| {
|
||||||
session.post_api_syncback(Some(123), chunks);
|
session.post_api_syncback(Some(123), data, chunks);
|
||||||
let new_info = session.wait_to_come_back_online();
|
let new_info = session.wait_to_come_back_online();
|
||||||
assert!(
|
assert!(
|
||||||
!new_info.project_name.is_empty(),
|
!new_info.project_name.is_empty(),
|
||||||
|
|
@ -501,12 +532,12 @@ fn allows_whitelisted_place() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn allows_any_place_when_unrestricted() {
|
fn allows_any_place_when_unrestricted() {
|
||||||
let chunks = vec![make_service_chunk(
|
let (data, chunks) = build_syncback_request(vec![make_service_chunk(
|
||||||
"ReplicatedStorage",
|
"ReplicatedStorage",
|
||||||
vec![InstanceBuilder::new("Folder").with_name("Any")],
|
vec![InstanceBuilder::new("Folder").with_name("Any")],
|
||||||
)];
|
)]);
|
||||||
run_serve_test("live_syncback", |session, _| {
|
run_serve_test("live_syncback", |session, _| {
|
||||||
session.post_api_syncback(Some(99999), chunks);
|
session.post_api_syncback(Some(99999), data, chunks);
|
||||||
session.wait_to_come_back_online();
|
session.wait_to_come_back_online();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -515,7 +546,7 @@ fn allows_any_place_when_unrestricted() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn server_comes_back_functional() {
|
fn server_comes_back_functional() {
|
||||||
let chunks = vec![make_service_chunk(
|
let (data, chunks) = build_syncback_request(vec![make_service_chunk(
|
||||||
"ReplicatedStorage",
|
"ReplicatedStorage",
|
||||||
vec![InstanceBuilder::new("ModuleScript")
|
vec![InstanceBuilder::new("ModuleScript")
|
||||||
.with_name("TestModule")
|
.with_name("TestModule")
|
||||||
|
|
@ -523,10 +554,10 @@ fn server_comes_back_functional() {
|
||||||
"Source",
|
"Source",
|
||||||
rbx_dom_weak::types::Variant::String("return 42".into()),
|
rbx_dom_weak::types::Variant::String("return 42".into()),
|
||||||
)],
|
)],
|
||||||
)];
|
)]);
|
||||||
|
|
||||||
run_serve_test("live_syncback", |session, _| {
|
run_serve_test("live_syncback", |session, _| {
|
||||||
session.post_api_syncback(None, chunks);
|
session.post_api_syncback(None, data, chunks);
|
||||||
let new_info = session.wait_to_come_back_online();
|
let new_info = session.wait_to_come_back_online();
|
||||||
|
|
||||||
let read = session.get_api_read(new_info.root_instance_id).unwrap();
|
let read = session.get_api_read(new_info.root_instance_id).unwrap();
|
||||||
|
|
@ -548,17 +579,17 @@ fn server_comes_back_functional() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn syncback_twice_different_data() {
|
fn syncback_twice_different_data() {
|
||||||
let chunks_a = vec![make_service_chunk(
|
let (data_a, chunks_a) = build_syncback_request(vec![make_service_chunk(
|
||||||
"ReplicatedStorage",
|
"ReplicatedStorage",
|
||||||
vec![InstanceBuilder::new("Folder").with_name("DataA")],
|
vec![InstanceBuilder::new("Folder").with_name("DataA")],
|
||||||
)];
|
)]);
|
||||||
let chunks_b = vec![make_service_chunk(
|
let (data_b, chunks_b) = build_syncback_request(vec![make_service_chunk(
|
||||||
"ReplicatedStorage",
|
"ReplicatedStorage",
|
||||||
vec![InstanceBuilder::new("Folder").with_name("DataB")],
|
vec![InstanceBuilder::new("Folder").with_name("DataB")],
|
||||||
)];
|
)]);
|
||||||
|
|
||||||
run_serve_test("live_syncback", |session, _| {
|
run_serve_test("live_syncback", |session, _| {
|
||||||
session.post_api_syncback(None, chunks_a);
|
session.post_api_syncback(None, data_a, chunks_a);
|
||||||
session.wait_to_come_back_online();
|
session.wait_to_come_back_online();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -566,7 +597,7 @@ fn syncback_twice_different_data() {
|
||||||
"First syncback should create DataA"
|
"First syncback should create DataA"
|
||||||
);
|
);
|
||||||
|
|
||||||
session.post_api_syncback(None, chunks_b);
|
session.post_api_syncback(None, data_b, chunks_b);
|
||||||
session.wait_to_come_back_online();
|
session.wait_to_come_back_online();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -582,7 +613,7 @@ fn syncback_twice_different_data() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn syncback_replaces_all_old_files() {
|
fn syncback_replaces_all_old_files() {
|
||||||
let chunks = vec![make_service_chunk(
|
let (data, chunks) = build_syncback_request(vec![make_service_chunk(
|
||||||
"ReplicatedStorage",
|
"ReplicatedStorage",
|
||||||
vec![InstanceBuilder::new("ModuleScript")
|
vec![InstanceBuilder::new("ModuleScript")
|
||||||
.with_name("NewFile")
|
.with_name("NewFile")
|
||||||
|
|
@ -590,7 +621,7 @@ fn syncback_replaces_all_old_files() {
|
||||||
"Source",
|
"Source",
|
||||||
rbx_dom_weak::types::Variant::String("return 'new'".into()),
|
rbx_dom_weak::types::Variant::String("return 'new'".into()),
|
||||||
)],
|
)],
|
||||||
)];
|
)]);
|
||||||
|
|
||||||
run_serve_test("live_syncback", |session, _| {
|
run_serve_test("live_syncback", |session, _| {
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -598,7 +629,7 @@ fn syncback_replaces_all_old_files() {
|
||||||
"OldModule should exist before syncback"
|
"OldModule should exist before syncback"
|
||||||
);
|
);
|
||||||
|
|
||||||
session.post_api_syncback(None, chunks);
|
session.post_api_syncback(None, data, chunks);
|
||||||
session.wait_to_come_back_online();
|
session.wait_to_come_back_online();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -624,16 +655,12 @@ fn roundtrip_build_syncback_rebuild() {
|
||||||
let rbxl_data_a = fs::read(&rbxl_path_a).unwrap();
|
let rbxl_data_a = fs::read(&rbxl_path_a).unwrap();
|
||||||
|
|
||||||
let dom_a = rbx_binary::from_reader(Cursor::new(&rbxl_data_a)).unwrap();
|
let dom_a = rbx_binary::from_reader(Cursor::new(&rbxl_data_a)).unwrap();
|
||||||
|
let mut all_child_refs = Vec::new();
|
||||||
let mut chunks = Vec::new();
|
let mut chunks = Vec::new();
|
||||||
for &service_ref in dom_a.root().children() {
|
for &service_ref in dom_a.root().children() {
|
||||||
let service = dom_a.get_by_ref(service_ref).unwrap();
|
let service = dom_a.get_by_ref(service_ref).unwrap();
|
||||||
let child_refs: Vec<rbx_dom_weak::types::Ref> = service.children().to_vec();
|
let child_refs: Vec<rbx_dom_weak::types::Ref> = service.children().to_vec();
|
||||||
|
|
||||||
let mut data = Vec::new();
|
|
||||||
if !child_refs.is_empty() {
|
|
||||||
rbx_binary::to_writer(&mut data, &dom_a, &child_refs).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut properties = std::collections::HashMap::new();
|
let mut properties = std::collections::HashMap::new();
|
||||||
let mut refs_map = std::collections::HashMap::new();
|
let mut refs_map = std::collections::HashMap::new();
|
||||||
for (key, value) in &service.properties {
|
for (key, value) in &service.properties {
|
||||||
|
|
@ -655,15 +682,21 @@ fn roundtrip_build_syncback_rebuild() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chunks.push(ServiceChunk {
|
chunks.push(librojo::web_api::ServiceChunk {
|
||||||
class_name: service.class.to_string(),
|
class_name: service.class.to_string(),
|
||||||
data,
|
child_count: child_refs.len() as u32,
|
||||||
properties,
|
properties,
|
||||||
refs: refs_map,
|
refs: refs_map,
|
||||||
});
|
});
|
||||||
|
all_child_refs.extend(child_refs);
|
||||||
}
|
}
|
||||||
|
|
||||||
session.post_api_syncback(None, chunks);
|
let mut data = Vec::new();
|
||||||
|
if !all_child_refs.is_empty() {
|
||||||
|
rbx_binary::to_writer(&mut data, &dom_a, &all_child_refs).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
session.post_api_syncback(None, data, chunks);
|
||||||
session.wait_to_come_back_online();
|
session.wait_to_come_back_online();
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(500));
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue