Optimize scenes loading with Job System

This commit is contained in:
Wojtek Figat
2023-10-01 10:55:01 +02:00
parent f77198c7ca
commit b960600102
11 changed files with 162 additions and 69 deletions

View File

@@ -18,6 +18,7 @@
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Threading/JobSystem.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Profiler/ProfilerCPU.h"
@@ -932,11 +933,9 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
LOG(Error, "Invalid Data member.");
return true;
}
int32 objectsCount = data.Size();
// Peek scene node value (it's the first actor serialized)
auto& sceneValue = data[0];
auto sceneId = JsonTools::GetGuid(sceneValue, "ID");
auto sceneId = JsonTools::GetGuid(data[0], "ID");
if (!sceneId.IsValid())
{
LOG(Error, "Invalid scene id.");
@@ -957,59 +956,103 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
auto scene = New<Scene>(ScriptingObjectSpawnParams(sceneId, Scene::TypeInitializer));
scene->LoadTime = startTime;
scene->RegisterObject();
scene->Deserialize(sceneValue, modifier.Value);
scene->Deserialize(data[0], modifier.Value);
// Fire event
CallSceneEvent(SceneEventType::OnSceneLoading, scene, sceneId);
// Loaded scene objects list
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
const int32 objectsCount = (int32)data.Size();
sceneObjects->Resize(objectsCount);
sceneObjects->At(0) = scene;
// Spawn all scene objects
SceneObjectsFactory::Context context(modifier.Value);
context.Async = JobSystem::GetThreadsCount() > 1 && objectsCount > 10;
{
PROFILE_CPU_NAMED("Spawn");
// Spawn all scene objects
for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene
SceneObject** objects = sceneObjects->Get();
if (context.Async)
{
auto& stream = data[i];
auto obj = SceneObjectsFactory::Spawn(context, stream);
sceneObjects->At(i) = obj;
if (obj)
obj->RegisterObject();
else
SceneObjectsFactory::HandleObjectDeserializationError(stream);
JobSystem::Execute([&](int32 i)
{
i++; // Start from 1. at index [0] was scene
auto& stream = data[i];
auto obj = SceneObjectsFactory::Spawn(context, stream);
objects[i] = obj;
if (obj)
{
obj->RegisterObject();
#if USE_EDITOR
// Auto-create C# objects for all actors in Editor during scene load when running in async (so main thread already has all of them)
obj->CreateManaged();
#endif
}
else
SceneObjectsFactory::HandleObjectDeserializationError(stream);
}, objectsCount - 1);
}
else
{
for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene
{
auto& stream = data[i];
auto obj = SceneObjectsFactory::Spawn(context, stream);
sceneObjects->At(i) = obj;
if (obj)
obj->RegisterObject();
else
SceneObjectsFactory::HandleObjectDeserializationError(stream);
}
}
}
// Capture prefab instances in a scene to restore any missing objects (eg. newly added objects to prefab that are missing in scene file)
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, data, modifier.Value);
SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData);
// TODO: resave and force sync scenes during game cooking so this step could be skipped in game
SceneObjectsFactory::SynchronizeNewPrefabInstances(context, prefabSyncData);
// /\ all above this has to be done on an any thread
// \/ all below this has to be done on multiple threads at once
// Load all scene objects
{
PROFILE_CPU_NAMED("Deserialize");
// TODO: at this point we would probably spawn a few thread pool tasks which will load deserialize scene object but only if scene is big enough
// Load all scene objects
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
SceneObject** objects = sceneObjects->Get();
for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene
bool wasAsync = context.Async;
context.Async = false; // TODO: fix Actor's Scripts and Children order when loading objects data out of order via async jobs
if (context.Async)
{
auto& objData = data[i];
auto obj = objects[i];
if (obj)
SceneObjectsFactory::Deserialize(context, obj, objData);
ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize)
JobSystem::Execute([&](int32 i)
{
i++; // Start from 1. at index [0] was scene
auto obj = objects[i];
if (obj)
{
auto& idMapping = Scripting::ObjectsLookupIdMapping.Get();
idMapping = &context.GetModifier()->IdsMapping;
SceneObjectsFactory::Deserialize(context, obj, data[i]);
idMapping = nullptr;
}
}, objectsCount - 1);
ScenesLock.Lock();
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
else
{
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene
{
auto& objData = data[i];
auto obj = objects[i];
if (obj)
SceneObjectsFactory::Deserialize(context, obj, objData);
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
}
context.Async = wasAsync;
}
// /\ all above this has to be done on multiple threads at once
@@ -1031,7 +1074,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
PROFILE_CPU_NAMED("Initialize");
SceneObject** objects = sceneObjects->Get();
for (int32 i = 0; i < sceneObjects->Count(); i++)
for (int32 i = 0; i < objectsCount; i++)
{
SceneObject* obj = objects[i];
if (obj)