diff --git a/Source/Engine/Core/Cache.cpp b/Source/Engine/Core/Cache.cpp index 25f4066a8..fa7c63e9c 100644 --- a/Source/Engine/Core/Cache.cpp +++ b/Source/Engine/Core/Cache.cpp @@ -8,6 +8,7 @@ CollectionPoolCache void Cache::ISerializeModifierClearCallback(::ISerializeModifier* obj) { obj->EngineBuild = FLAXENGINE_VERSION_BUILD; + obj->CurrentInstance = -1; obj->IdsMapping.Clear(); } diff --git a/Source/Engine/Core/Collections/CollectionPoolCache.h b/Source/Engine/Core/Collections/CollectionPoolCache.h index 815955028..f0274e667 100644 --- a/Source/Engine/Core/Collections/CollectionPoolCache.h +++ b/Source/Engine/Core/Collections/CollectionPoolCache.h @@ -49,7 +49,7 @@ public: private: CollectionPoolCache* _pool; - ScopeCache(CollectionPoolCache* pool, T* value) + FORCE_INLINE ScopeCache(CollectionPoolCache* pool, T* value) { _pool = pool; Value = value; @@ -71,7 +71,7 @@ public: ~ScopeCache() { - _pool->Release(Value); + _pool->Put(Value); } T* operator->() @@ -99,13 +99,6 @@ private: CriticalSection _locker; Array> _pool; - void Release(T* value) - { - _locker.Lock(); - _pool.Add(value); - _locker.Unlock(); - } - public: /// /// Finalizes an instance of the class. @@ -120,7 +113,16 @@ public: /// Gets the collection instance from the pool. Can reuse the object from the pool or create a new one. Returns collection is always cleared and ready to use. /// /// The collection (cleared). - ScopeCache Get() + FORCE_INLINE ScopeCache Get() + { + return ScopeCache(this, GetUnscoped()); + } + + /// + /// Gets the collection instance from the pool. Can reuse the object from the pool or create a new one. Returns collection is always cleared and ready to use. + /// + /// The collection (cleared). + T* GetUnscoped() { T* result; _locker.Lock(); @@ -129,10 +131,18 @@ public: else result = CreateCallback(); _locker.Unlock(); - ClearCallback(result); + return result; + } - return ScopeCache(this, result); + /// + /// Puts the collection value back to the pool. + /// + void Put(T* value) + { + _locker.Lock(); + _pool.Add(value); + _locker.Unlock(); } /// diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index f5ffb1bfa..c454f02d1 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -785,12 +785,11 @@ public: /// The other collection to clone. void Clone(const Dictionary& other) { + // TODO: if both key and value are POD types then use raw memory copy for buckets Clear(); - SetCapacity(other.Capacity(), false); + EnsureCapacity(other.Capacity(), false); for (Iterator i = other.Begin(); i != other.End(); ++i) Add(i); - ASSERT(Count() == other.Count()); - ASSERT(Capacity() == other.Capacity()); } /// diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 394d5c9dc..94e829991 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1013,6 +1013,7 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) const auto parent = Scripting::FindObject(parentId); if (_parent != parent) { + ScopeLock lock(Level::ScenesLock); if (IsDuringPlay()) { SetParent(parent, false, false); diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 07de023a3..99d54efe7 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -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(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::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) diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index f8193c257..9ec66f069 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -4,6 +4,7 @@ #include "Engine/Level/Actor.h" #include "Engine/Level/Prefabs/Prefab.h" #include "Engine/Content/Content.h" +#include "Engine/Core/Cache.h" #include "Engine/Core/Log.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Serialization/JsonTools.h" @@ -18,16 +19,49 @@ SceneObjectsFactory::Context::Context(ISerializeModifier* modifier) { } -void SceneObjectsFactory::Context::SetupIdsMapping(const SceneObject* obj) +SceneObjectsFactory::Context::~Context() +{ + if (Async) + { + Array> modifiers; + Modifiers.GetValues(modifiers); + for (ISerializeModifier* e : modifiers) + Cache::ISerializeModifier.Put(e); + } +} + +ISerializeModifier* SceneObjectsFactory::Context::GetModifier() +{ + ISerializeModifier* modifier = Modifier; + if (Async) + { + // When using context in async then use one ISerializeModifier per-thread + ISerializeModifier*& modifierThread = Modifiers.Get(); + if (!modifierThread) + { + modifierThread = Cache::ISerializeModifier.GetUnscoped(); + Modifiers.Set(modifierThread); + Locker.Lock(); + modifierThread->EngineBuild = modifier->EngineBuild; + modifierThread->CurrentInstance = modifier->CurrentInstance; + modifierThread->IdsMapping = modifier->IdsMapping; + Locker.Unlock(); + } + modifier = modifierThread; + } + return modifier; +} + +void SceneObjectsFactory::Context::SetupIdsMapping(const SceneObject* obj, ISerializeModifier* modifier) { int32 instanceIndex; - if (ObjectToInstance.TryGet(obj->GetID(), instanceIndex) && instanceIndex != CurrentInstance) + if (ObjectToInstance.TryGet(obj->GetID(), instanceIndex) && instanceIndex != modifier->CurrentInstance) { // Apply the current prefab instance objects ids table to resolve references inside a prefab properly - CurrentInstance = instanceIndex; + modifier->CurrentInstance = instanceIndex; auto& instance = Instances[instanceIndex]; for (auto& e : instance.IdsMapping) - Modifier->IdsMapping[e.Key] = e.Value; + modifier->IdsMapping[e.Key] = e.Value; } } @@ -35,13 +69,13 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, const ISerializable::D { // Get object id Guid id = JsonTools::GetGuid(stream, "ID"); - context.Modifier->IdsMapping.TryGet(id, id); + ISerializeModifier* modifier = context.GetModifier(); + modifier->IdsMapping.TryGet(id, id); if (!id.IsValid()) { LOG(Warning, "Invalid object id."); return nullptr; } - SceneObject* obj = nullptr; // Check for prefab instance @@ -78,7 +112,7 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, const ISerializable::D } // Map prefab object ID to the deserialized instance ID - context.Modifier->IdsMapping[prefabObjectId] = id; + modifier->IdsMapping[prefabObjectId] = id; // Create prefab instance (recursive prefab loading to support nested prefabs) obj = Spawn(context, *prefabData); @@ -169,6 +203,7 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria #if ENABLE_ASSERTION CHECK(obj); #endif + ISerializeModifier* modifier = context.GetModifier(); // Check for prefab instance Guid prefabObjectId; @@ -204,24 +239,16 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria } // Deserialize prefab data (recursive prefab loading to support nested prefabs) - const auto prevVersion = context.Modifier->EngineBuild; - context.Modifier->EngineBuild = prefab->DataEngineBuild; + const auto prevVersion = modifier->EngineBuild; + modifier->EngineBuild = prefab->DataEngineBuild; Deserialize(context, obj, *(ISerializable::DeserializeStream*)prefabData); - context.Modifier->EngineBuild = prevVersion; + modifier->EngineBuild = prevVersion; } - int32 instanceIndex; - if (context.ObjectToInstance.TryGet(obj->GetID(), instanceIndex) && instanceIndex != context.CurrentInstance) - { - // Apply the current prefab instance objects ids table to resolve references inside a prefab properly - context.CurrentInstance = instanceIndex; - auto& instance = context.Instances[instanceIndex]; - for (auto& e : instance.IdsMapping) - context.Modifier->IdsMapping[e.Key] = e.Value; - } + context.SetupIdsMapping(obj, modifier); // Load data - obj->Deserialize(stream, context.Modifier); + obj->Deserialize(stream, modifier); } void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::DeserializeStream& value) @@ -518,7 +545,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn LOG(Info, "Object {0} has invalid parent object {4} -> {5} (PrefabObjectID: {1}, PrefabID: {2}, Path: {3})", obj->GetSceneObjectId(), prefabObjectId, prefab->GetID(), prefab->GetPath(), parentPrefabObjectId, actualParentPrefabId); // Map actual prefab object id to the current scene objects collection - context.SetupIdsMapping(obj); + context.SetupIdsMapping(obj, data.Modifier); data.Modifier->IdsMapping.TryGet(actualParentPrefabId, actualParentPrefabId); // Find parent diff --git a/Source/Engine/Level/SceneObjectsFactory.h b/Source/Engine/Level/SceneObjectsFactory.h index d3f5b3dd7..f7f495449 100644 --- a/Source/Engine/Level/SceneObjectsFactory.h +++ b/Source/Engine/Level/SceneObjectsFactory.h @@ -4,6 +4,8 @@ #include "SceneObject.h" #include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Platform/CriticalSection.h" +#include "Engine/Threading/ThreadLocal.h" /// /// Helper class for scene objects creation and deserialization utilities. @@ -21,13 +23,17 @@ public: struct Context { ISerializeModifier* Modifier; - int32 CurrentInstance = -1; + bool Async = false; Array Instances; Dictionary ObjectToInstance; + CriticalSection Locker; + ThreadLocal Modifiers; Context(ISerializeModifier* modifier); + ~Context(); - void SetupIdsMapping(const SceneObject* obj); + ISerializeModifier* GetModifier(); + void SetupIdsMapping(const SceneObject* obj, ISerializeModifier* modifier); }; /// diff --git a/Source/Engine/Scripting/Script.cpp b/Source/Engine/Scripting/Script.cpp index 8eaff42de..8ac233f0b 100644 --- a/Source/Engine/Scripting/Script.cpp +++ b/Source/Engine/Scripting/Script.cpp @@ -327,6 +327,7 @@ void Script::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier const auto parent = Scripting::FindObject(parentId); if (_parent != parent) { + ScopeLock lock(Level::ScenesLock); if (IsDuringPlay()) { SetParent(parent, false); diff --git a/Source/Engine/Serialization/ISerializeModifier.h b/Source/Engine/Serialization/ISerializeModifier.h index c11be5503..a57b3f00c 100644 --- a/Source/Engine/Serialization/ISerializeModifier.h +++ b/Source/Engine/Serialization/ISerializeModifier.h @@ -18,6 +18,9 @@ public: /// uint32 EngineBuild = FLAXENGINE_VERSION_BUILD; + // Utility for scene deserialization to track currently mapped in Prefab Instance object IDs into IdsMapping. + int32 CurrentInstance = -1; + /// /// The object IDs mapping. Key is a serialized object id, value is mapped value to use. /// diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index 1e9e55b33..0dd16b3d3 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -228,6 +228,7 @@ int32 JobSystemThread::Run() void JobSystem::Execute(const Function& job, int32 jobCount) { +#if JOB_SYSTEM_ENABLED // TODO: disable async if called on job thread? or maybe Wait should handle waiting in job thread to do the processing? if (jobCount > 1) { @@ -235,7 +236,8 @@ void JobSystem::Execute(const Function& job, int32 jobCount) const int64 jobWaitHandle = Dispatch(job, jobCount); Wait(jobWaitHandle); } - else if (jobCount > 0) + else +#endif { // Sync for (int32 i = 0; i < jobCount; i++) diff --git a/Source/Engine/Threading/ThreadLocal.h b/Source/Engine/Threading/ThreadLocal.h index 40f9096cd..4de8ced57 100644 --- a/Source/Engine/Threading/ThreadLocal.h +++ b/Source/Engine/Threading/ThreadLocal.h @@ -65,10 +65,10 @@ public: template void GetValues(Array& result) const { - result.EnsureCapacity(MaxThreads); for (int32 i = 0; i < MaxThreads; i++) { - result.Add(_buckets[i].Value); + if (Platform::AtomicRead((int64 volatile*)&_buckets[i].ThreadID) != 0) + result.Add(_buckets[i].Value); } }