From 5b6859a66f55fb4472a795e2c267dca844f760e1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Jun 2025 18:40:06 +0200 Subject: [PATCH] Add time slicing to Deserialization stage of async scenes loading to avoid hitching #3261 --- Source/Engine/Level/Level.cpp | 61 ++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 881277c5e..678eada1f 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -114,6 +114,19 @@ struct ScriptsReloadObject #endif +// Small utility for dividing the iterative work over data set that can run in equal slicer limited by time. +struct TimeSlicer +{ + int32 Index = -1; + int32 Count = 0; + double TimeBudget; + double StartTime; + + void BeginSync(float timeBudget, int32 count, int32 startIndex = 0); + bool StepSync(); + SceneResult End(); +}; + // Async map loading utility for state tracking and synchronization of various load stages. class SceneLoader { @@ -154,6 +167,7 @@ public: Array InjectedSceneChildren; SceneObjectsFactory::Context Context; SceneObjectsFactory::PrefabSyncData* PrefabSyncData = nullptr; + TimeSlicer StageSlicer; SceneLoader(bool asyncLoad = false) : AsyncLoad(asyncLoad) @@ -1002,6 +1016,38 @@ SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Value& dat return result; } +void TimeSlicer::BeginSync(float timeBudget, int32 count, int32 startIndex) +{ + if (Index == -1) + { + // Starting + Index = startIndex; + Count = count; + } + TimeBudget = (double)timeBudget; + StartTime = Platform::GetTimeSeconds(); +} + +bool TimeSlicer::StepSync() +{ + Index++; + double time = Platform::GetTimeSeconds(); + double dt = time - StartTime; + return dt >= TimeBudget; +} + +SceneResult TimeSlicer::End() +{ + if (Index >= Count) + { + // Finished + *this = TimeSlicer(); + return SceneResult::Success; + } + + return SceneResult::Wait; +} + SceneResult SceneLoader::Tick(Args& args) { switch (Stage) @@ -1216,19 +1262,24 @@ SceneResult SceneLoader::OnDeserialize(Args& args) else { Scripting::ObjectsLookupIdMapping.Set(&Modifier->IdsMapping); - for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene + StageSlicer.BeginSync(args.TimeBudget, dataCount, 1); // start from 1. at index [0] was scene + while (StageSlicer.Index < StageSlicer.Count) { - auto& objData = args.Data[i]; - auto obj = objects[i]; + auto& objData = args.Data[StageSlicer.Index]; + auto obj = objects[StageSlicer.Index]; if (obj) SceneObjectsFactory::Deserialize(Context, obj, objData); + if (StageSlicer.StepSync()) + break; } Scripting::ObjectsLookupIdMapping.Set(nullptr); } Context.Async = wasAsync; - NextStage(); - return SceneResult::Success; + auto result = StageSlicer.End(); + if (result != SceneResult::Wait) + NextStage(); + return result; } SceneResult SceneLoader::OnSyncPrefabs(Args& args)