From f500fcd6fdfb03edb98666e3a12c592b3c4fd2f2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 28 Mar 2026 23:39:43 +0100 Subject: [PATCH] Optimize actors copy/paste data to use a single JSON for all objects #3945 --- Flax.flaxproj | 2 +- Source/Engine/Level/Actor.cpp | 127 ++++++++++++---------------------- 2 files changed, 44 insertions(+), 85 deletions(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index dc126772a..8692455ad 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -4,7 +4,7 @@ "Major": 1, "Minor": 11, "Revision": 0, - "Build": 6809 + "Build": 6810 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.", diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 8123142aa..70a71eee2 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1729,7 +1729,6 @@ void WriteObjectToBytes(SceneObject* obj, rapidjson_flax::StringBuffer& buffer, { // Create JSON CompactJsonWriter writer(buffer); - writer.SceneObject(obj); // Write json to output @@ -1737,28 +1736,24 @@ void WriteObjectToBytes(SceneObject* obj, rapidjson_flax::StringBuffer& buffer, output.WriteInt32((int32)buffer.GetSize()); output.WriteBytes((byte*)buffer.GetString(), (int32)buffer.GetSize()); - // Store order in parent. Makes life easier for editor to sync objects order on undo/redo actions. - output.WriteInt32(obj->GetOrderInParent()); - // Reuse string buffer buffer.Clear(); } bool Actor::ToBytes(const Array& actors, MemoryWriteStream& output) { - PROFILE_CPU(); if (actors.IsEmpty()) { // Cannot serialize empty list return true; } + PROFILE_CPU(); + PROFILE_MEM(Level); // Collect object ids that exist in the serialized data to allow references mapping later Array ids(actors.Count()); for (int32 i = 0; i < actors.Count(); i++) { - // By default we collect actors and scripts (they are ManagedObjects recognized by the id) - auto actor = actors[i]; if (!actor) continue; @@ -1766,7 +1761,8 @@ bool Actor::ToBytes(const Array& actors, MemoryWriteStream& output) for (int32 j = 0; j < actor->Scripts.Count(); j++) { const auto script = actor->Scripts[j]; - ids.Add(script->GetID()); + if (script) + ids.Add(script->GetID()); } } @@ -1776,23 +1772,28 @@ bool Actor::ToBytes(const Array& actors, MemoryWriteStream& output) // Serialized objects ids (for references mapping) output.Write(ids); - // Objects data + // Objects data (JSON) rapidjson_flax::StringBuffer buffer; + CompactJsonWriter writer(buffer); + writer.StartArray(); for (int32 i = 0; i < actors.Count(); i++) { Actor* actor = actors[i]; if (!actor) continue; - - WriteObjectToBytes(actor, buffer, output); - + writer.SceneObject(actor); for (int32 j = 0; j < actor->Scripts.Count(); j++) { Script* script = actor->Scripts[j]; - - WriteObjectToBytes(script, buffer, output); + if (!script) + continue; + writer.SceneObject(script); } } + writer.EndArray(); + // TODO: compress json (LZ4) if it's big enough + output.WriteInt32((int32)buffer.GetSize()); + output.WriteBytes((byte*)buffer.GetString(), (int32)buffer.GetSize()); return false; } @@ -1811,8 +1812,8 @@ Array Actor::ToBytes(const Array& actors) bool Actor::FromBytes(const Span& data, Array& output, ISerializeModifier* modifier) { PROFILE_CPU(); + PROFILE_MEM(Level); output.Clear(); - ASSERT(modifier); if (data.Length() <= 0) return true; @@ -1828,15 +1829,34 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM } // Serialized objects ids (for references mapping) +#if 0 Array ids; stream.Read(ids); int32 objectsCount = ids.Count(); +#else + int32 objectsCount; + stream.ReadInt32(&objectsCount); + stream.Move(objectsCount); +#endif if (objectsCount < 0) return true; + // Load objects data (JSON) + int32 bufferSize; + stream.ReadInt32(&bufferSize); + const char* buffer = (const char*)stream.Move(bufferSize); + rapidjson_flax::Document document; + { + PROFILE_CPU_NAMED("Json.Parse"); + document.Parse(buffer, bufferSize); + } + if (document.HasParseError()) + { + Log::JsonParseException(document.GetParseError(), document.GetErrorOffset()); + return true; + } + // Prepare - Array order; - order.Resize(objectsCount); modifier->EngineBuild = engineBuild; CollectionPoolCache::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); sceneObjects->Resize(objectsCount); @@ -1844,34 +1864,10 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM // Deserialize objects Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); - auto startPos = stream.GetPosition(); for (int32 i = 0; i < objectsCount; i++) { - // Buffer - int32 bufferSize; - stream.ReadInt32(&bufferSize); - const char* buffer = (const char*)stream.GetPositionHandle(); - stream.Move(bufferSize); - - // Order in parent - int32 orderInParent; - stream.ReadInt32(&orderInParent); - order[i] = orderInParent; - - // Load JSON - rapidjson_flax::Document document; - { - PROFILE_CPU_NAMED("Json.Parse"); - document.Parse(buffer, bufferSize); - } - if (document.HasParseError()) - { - Log::JsonParseException(document.GetParseError(), document.GetErrorOffset()); - return true; - } - - // Create object - auto obj = SceneObjectsFactory::Spawn(context, document); + auto& objData = document[i]; + auto obj = SceneObjectsFactory::Spawn(context, objData); sceneObjects->At(i) = obj; if (obj == nullptr) { @@ -1879,57 +1875,21 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM continue; } obj->RegisterObject(); - - // Add to results Actor* actor = dynamic_cast(obj); if (actor) - { output.Add(actor); - } } - // TODO: optimize this to call json parsing only once per-object instead of twice (spawn + load) - stream.SetPosition(startPos); for (int32 i = 0; i < objectsCount; i++) { - // Buffer - int32 bufferSize; - stream.ReadInt32(&bufferSize); - const char* buffer = (const char*)stream.GetPositionHandle(); - stream.Move(bufferSize); - - // Order in parent - int32 orderInParent; - stream.ReadInt32(&orderInParent); - - // Load JSON - rapidjson_flax::Document document; - { - PROFILE_CPU_NAMED("Json.Parse"); - document.Parse(buffer, bufferSize); - } - if (document.HasParseError()) - { - Log::JsonParseException(document.GetParseError(), document.GetErrorOffset()); - return true; - } - - // Deserialize object + auto& objData = document[i]; auto obj = sceneObjects->At(i); if (obj) - SceneObjectsFactory::Deserialize(context, obj, document); + SceneObjectsFactory::Deserialize(context, obj, objData); else - SceneObjectsFactory::HandleObjectDeserializationError(document); + SceneObjectsFactory::HandleObjectDeserializationError(objData); } Scripting::ObjectsLookupIdMapping.Set(nullptr); - // Update objects order - //for (int32 i = 0; i < objectsCount; i++) - { - //SceneObject* obj = sceneObjects->At(i); - // TODO: remove order from saved data? - //obj->SetOrderInParent(order[i]); - } - // Call events (only for parents because they will propagate events down the tree) CollectionPoolCache::ScopeCache parents = ActorsCache::ActorsListCache.Get(); parents->EnsureCapacity(output.Count()); @@ -1949,8 +1909,7 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM } for (int32 i = 0; i < parents->Count(); i++) { - Actor* actor = parents->At(i); - actor->OnTransformChanged(); + parents->At(i)->OnTransformChanged(); } // Initialize actor that are spawned to scene or create managed instanced for others