diff --git a/Source/Engine/Core/Cache.cpp b/Source/Engine/Core/Cache.cpp index b562465c0..315a93775 100644 --- a/Source/Engine/Core/Cache.cpp +++ b/Source/Engine/Core/Cache.cpp @@ -3,7 +3,7 @@ #include "Cache.h" #include "FlaxEngine.Gen.h" -CollectionPoolCache Cache::ISerializeModifier; +Cache::ISerializeModifierCache Cache::ISerializeModifier; void Cache::ISerializeModifierClearCallback(::ISerializeModifier* obj) { diff --git a/Source/Engine/Core/Cache.h b/Source/Engine/Core/Cache.h index c0a8e3ac4..1b8d95910 100644 --- a/Source/Engine/Core/Cache.h +++ b/Source/Engine/Core/Cache.h @@ -15,11 +15,12 @@ public: static void ISerializeModifierClearCallback(ISerializeModifier* obj); public: + typedef CollectionPoolCache ISerializeModifierCache; /// /// Gets the ISerializeModifier lookup cache. Safe allocation, per thread, uses caching. /// - static CollectionPoolCache ISerializeModifier; + static ISerializeModifierCache ISerializeModifier; public: diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index d3f1ccba3..881277c5e 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -118,27 +118,79 @@ struct ScriptsReloadObject class SceneLoader { public: - enum Stages + struct Args { - Init, + rapidjson_flax::Value& Data; + const String* AssetPath; + int32 EngineBuild; + float TimeBudget; + }; + + enum class Stages + { + Begin, Spawn, SetupPrefabs, + SyncNewPrefabs, Deserialize, + SyncPrefabs, SetupTransforms, + Initialize, BeginPlay, + End, Loaded, - } Stage = Init; + } Stage = Stages::Begin; + bool AsyncLoad; bool AsyncJobs; + Guid SceneId = Guid::Empty; + Scene* Scene = nullptr; float TotalTime = 0.0f; + uint64 StartFrame; + + // Cache data + ISerializeModifier* Modifier = nullptr; + ActorsCache::SceneObjectsListType* SceneObjects = nullptr; + Array InjectedSceneChildren; + SceneObjectsFactory::Context Context; + SceneObjectsFactory::PrefabSyncData* PrefabSyncData = nullptr; SceneLoader(bool asyncLoad = false) - : AsyncLoad(true) + : AsyncLoad(asyncLoad) , AsyncJobs(JobSystem::GetThreadsCount() > 1) + , Modifier(Cache::ISerializeModifier.GetUnscoped()) + , Context(Modifier) { } - SceneResult Tick(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath, float* timeBudget); + ~SceneLoader() + { + if (PrefabSyncData) + Delete(PrefabSyncData); + if (SceneObjects) + ActorsCache::SceneObjectsListCache.Put(SceneObjects); + if (Modifier) + Cache::ISerializeModifier.Put(Modifier); + } + + NON_COPYABLE(SceneLoader); + + FORCE_INLINE void NextStage() + { + Stage = (Stages)((uint8)Stage + 1); + } + + SceneResult Tick(Args& args); + SceneResult OnBegin(Args& args); + SceneResult OnSpawn(Args& args); + SceneResult OnSetupPrefabs(Args& args); + SceneResult OnSyncNewPrefabs(Args& args); + SceneResult OnDeserialize(Args& args); + SceneResult OnSyncPrefabs(Args& args); + SceneResult OnSetupTransforms(Args& args); + SceneResult OnInitialize(Args& args); + SceneResult OnBeginPlay(Args& args); + SceneResult OnEnd(Args& args); }; namespace LevelImpl @@ -153,9 +205,9 @@ namespace LevelImpl void CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId); void flushActions(); - SceneResult loadScene(SceneLoader& loader, JsonAsset* sceneAsset); - SceneResult loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene = nullptr); - SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene = nullptr); + SceneResult loadScene(SceneLoader& loader, JsonAsset* sceneAsset, float* timeBudget = nullptr); + SceneResult loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene = nullptr, float* timeBudget = nullptr); + SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene = nullptr, float* timeBudget = nullptr); SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene = nullptr, const String* assetPath = nullptr, float* timeBudget = nullptr); bool unloadScene(Scene* scene); bool unloadScenes(); @@ -448,7 +500,7 @@ public: return SceneResult::Failed; if (!SceneAsset->IsLoaded()) return SceneResult::Wait; - return LevelImpl::loadScene(Loader, SceneAsset); + return LevelImpl::loadScene(Loader, SceneAsset, &context.TimeBudget); } }; @@ -797,6 +849,16 @@ void LevelImpl::flushActions() else if (Engine::GetFramesPerSecond() > 0) targetFps = (float)Engine::GetFramesPerSecond(); context.TimeBudget = Level::StreamingFrameBudget / targetFps; +#if USE_EDITOR + // Throttle up in Editor + context.TimeBudget *= Editor::IsPlayMode ? 1.2f : 2.0f; +#endif +#if BUILD_DEBUG + // Throttle up in Debug + context.TimeBudget *= 1.2f; +#endif + if (context.TimeBudget <= ZeroTolerance) + context.TimeBudget = MAX_float; // Runs actions in order ScopeLock lock(_sceneActionsLocker); @@ -861,7 +923,7 @@ bool LevelImpl::unloadScenes() return false; } -SceneResult LevelImpl::loadScene(SceneLoader& loader, JsonAsset* sceneAsset) +SceneResult LevelImpl::loadScene(SceneLoader& loader, JsonAsset* sceneAsset, float* timeBudget) { // Keep reference to the asset (prevent unloading during action) AssetReference ref = sceneAsset; @@ -871,10 +933,10 @@ SceneResult LevelImpl::loadScene(SceneLoader& loader, JsonAsset* sceneAsset) return SceneResult::Failed; } - return loadScene(loader, *sceneAsset->Data, sceneAsset->DataEngineBuild, nullptr, &sceneAsset->GetPath()); + return loadScene(loader, *sceneAsset->Data, sceneAsset->DataEngineBuild, nullptr, &sceneAsset->GetPath(), timeBudget); } -SceneResult LevelImpl::loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene) +SceneResult LevelImpl::loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene, float* timeBudget) { if (sceneData.IsInvalid()) { @@ -896,10 +958,10 @@ SceneResult LevelImpl::loadScene(SceneLoader& loader, const BytesContainer& scen } ScopeLock lock(Level::ScenesLock); - return loadScene(loader, document, outScene); + return loadScene(loader, document, outScene, timeBudget); } -SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene) +SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene, float* timeBudget) { auto data = document.FindMember("Data"); if (data == document.MemberEnd()) @@ -908,7 +970,7 @@ SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Document& return SceneResult::Failed; } const int32 saveEngineBuild = JsonTools::GetInt(document, "EngineBuild", 0); - return loadScene(loader, data->value, saveEngineBuild, outScene); + return loadScene(loader, data->value, saveEngineBuild, outScene, nullptr, timeBudget); } SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath, float* timeBudget) @@ -919,27 +981,64 @@ SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Value& dat ContentDeprecated::Clear(); #endif SceneResult result = SceneResult::Success; - while ((!timeBudget || *timeBudget > 0.0f) && loader.Stage != SceneLoader::Loaded && result == SceneResult::Success) + float timeLeft = timeBudget ? *timeBudget : MAX_float; + SceneLoader::Args args = { data, assetPath, engineBuild, timeLeft }; + while (timeLeft > 0.0f && loader.Stage != SceneLoader::Stages::Loaded) { Stopwatch time; - result = loader.Tick(data, engineBuild, outScene, assetPath, timeBudget); + result = loader.Tick(args); time.Stop(); const float delta = time.GetTotalSeconds(); loader.TotalTime += delta; - if (timeBudget) - *timeBudget -= delta; + timeLeft -= delta; + if (timeLeft < 0.0f && result == SceneResult::Success) + { + result = SceneResult::Wait; + break; + } } + if (outScene) + *outScene = loader.Scene; return result; } -SceneResult SceneLoader::Tick(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath, float* timeBudget) +SceneResult SceneLoader::Tick(Args& args) { - LOG(Info, "Loading scene..."); - Stopwatch stopwatch; - _lastSceneLoadTime = DateTime::Now(); + switch (Stage) + { + case Stages::Begin: + return OnBegin(args); + case Stages::Spawn: + return OnSpawn(args); + case Stages::SetupPrefabs: + return OnSetupPrefabs(args); + case Stages::SyncNewPrefabs: + return OnSyncNewPrefabs(args); + case Stages::Deserialize: + return OnDeserialize(args); + case Stages::SyncPrefabs: + return OnSyncPrefabs(args); + case Stages::Initialize: + return OnInitialize(args); + case Stages::SetupTransforms: + return OnSetupTransforms(args); + case Stages::BeginPlay: + return OnBeginPlay(args); + case Stages::End: + return OnEnd(args); + default: + return SceneResult::Failed; + } +} - // Here whole scripting backend should be loaded for current project - // Later scripts will setup attached scripts and restore initial vars +SceneResult SceneLoader::OnBegin(Args& args) +{ + PROFILE_CPU_NAMED("Begin"); + LOG(Info, "Loading scene..."); + _lastSceneLoadTime = DateTime::Now(); + StartFrame = Engine::UpdateCount; + + // Scripting backend should be loaded for the current project before loading scene if (!Scripting::HasGameModulesLoaded()) { LOG(Error, "Cannot load scene without game modules loaded."); @@ -956,163 +1055,186 @@ SceneResult SceneLoader::Tick(rapidjson_flax::Value& data, int32 engineBuild, Sc } // Peek meta - if (engineBuild < 6000) + if (args.EngineBuild < 6000) { LOG(Error, "Invalid serialized engine build."); return SceneResult::Failed; } - if (!data.IsArray()) + if (!args.Data.IsArray()) { LOG(Error, "Invalid Data member."); return SceneResult::Failed; } + Modifier->EngineBuild = args.EngineBuild; // Peek scene node value (it's the first actor serialized) - auto sceneId = JsonTools::GetGuid(data[0], "ID"); - if (!sceneId.IsValid()) + SceneId = JsonTools::GetGuid(args.Data[0], "ID"); + if (!SceneId.IsValid()) { LOG(Error, "Invalid scene id."); return SceneResult::Failed; } - auto modifier = Cache::ISerializeModifier.Get(); - modifier->EngineBuild = engineBuild; // Skip is that scene is already loaded - if (Level::FindScene(sceneId) != nullptr) + if (Level::FindScene(SceneId) != nullptr) { - LOG(Info, "Scene {0} is already loaded.", sceneId); + LOG(Info, "Scene {0} is already loaded.", SceneId); return SceneResult::Failed; } // Create scene actor - // Note: the first object in the scene file data is a Scene Actor - auto scene = New(ScriptingObjectSpawnParams(sceneId, Scene::TypeInitializer)); - scene->RegisterObject(); - scene->Deserialize(data[0], modifier.Value); + Scene = New<::Scene>(ScriptingObjectSpawnParams(SceneId, Scene::TypeInitializer)); + Scene->RegisterObject(); + Scene->Deserialize(args.Data[0], Modifier); // Fire event - CallSceneEvent(SceneEventType::OnSceneLoading, scene, sceneId); + CallSceneEvent(SceneEventType::OnSceneLoading, Scene, SceneId); + + NextStage(); + return SceneResult::Success; +} + +SceneResult SceneLoader::OnSpawn(Args& args) +{ + PROFILE_CPU_NAMED("Spawn"); // Get any injected children of the scene. - Array injectedSceneChildren = scene->Children; + InjectedSceneChildren = Scene->Children; - // Loaded scene objects list - CollectionPoolCache::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); - const int32 dataCount = (int32)data.Size(); - sceneObjects->Resize(dataCount); - sceneObjects->At(0) = scene; + // Allocate scene objects list + SceneObjects = ActorsCache::SceneObjectsListCache.GetUnscoped(); + const int32 dataCount = (int32)args.Data.Size(); + SceneObjects->Resize(dataCount); + SceneObjects->At(0) = Scene; + AsyncJobs &= dataCount > 10; // Spawn all scene objects - SceneObjectsFactory::Context context(modifier.Value); - context.Async = JobSystem::GetThreadsCount() > 1 && dataCount > 10; + Context.Async = AsyncJobs; + SceneObject** objects = SceneObjects->Get(); + if (Context.Async) { - PROFILE_CPU_NAMED("Spawn"); - SceneObject** objects = sceneObjects->Get(); - if (context.Async) + Level::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) { - Level::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) + PROFILE_MEM(Level); + i++; // Start from 1. at index [0] was scene + auto& stream = args.Data[i]; + auto obj = SceneObjectsFactory::Spawn(Context, stream); + objects[i] = obj; + if (obj) { - PROFILE_MEM(Level); - i++; // Start from 1. at index [0] was scene - auto& stream = data[i]; - auto obj = SceneObjectsFactory::Spawn(context, stream); - objects[i] = obj; - if (obj) - { - if (!obj->IsRegistered()) - 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) - if (!obj->GetManagedInstance()) - obj->CreateManaged(); -#endif - } - else - SceneObjectsFactory::HandleObjectDeserializationError(stream); - }, dataCount - 1); - Level::ScenesLock.Lock(); - } - else - { - for (int32 i = 1; i < dataCount; 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) + if (!obj->IsRegistered()) obj->RegisterObject(); - else - SceneObjectsFactory::HandleObjectDeserializationError(stream); +#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) + if (!obj->GetManagedInstance()) + obj->CreateManaged(); +#endif } + else + SceneObjectsFactory::HandleObjectDeserializationError(stream); + }, dataCount - 1); + Level::ScenesLock.Lock(); + } + else + { + for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene + { + auto& stream = args.Data[i]; + auto obj = SceneObjectsFactory::Spawn(Context, stream); + objects[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); + NextStage(); + return SceneResult::Success; +} - // /\ all above this has to be done on an any thread - // \/ all below this has to be done on multiple threads at once +SceneResult SceneLoader::OnSetupPrefabs(Args& args) +{ + // Capture prefab instances in a scene to restore any missing objects (eg. newly added objects to prefab that are missing in scene file) + PrefabSyncData = New(*SceneObjects, args.Data, Modifier); + SceneObjectsFactory::SetupPrefabInstances(Context, *PrefabSyncData); + + NextStage(); + return SceneResult::Success; +} + +SceneResult SceneLoader::OnSyncNewPrefabs(Args& args) +{ + // Sync the new prefab instances by spawning missing objects that were added to prefab but were not saved in a scene + // TODO: resave and force sync scenes during game cooking so this step could be skipped in game + SceneObjectsFactory::SynchronizeNewPrefabInstances(Context, *PrefabSyncData); + + NextStage(); + return SceneResult::Success; +} + +SceneResult SceneLoader::OnDeserialize(Args& args) +{ + PROFILE_CPU_NAMED("Deserialize"); + const int32 dataCount = (int32)args.Data.Size(); + SceneObject** objects = SceneObjects->Get(); + bool wasAsync = Context.Async; + Context.Async = false; // TODO: before doing full async for scene objects fix: + // TODO: - fix Actor's Scripts and Children order when loading objects data out of order via async jobs + // TODO: - add _loadNoAsync flag to SceneObject or Actor to handle non-async loading for those types (eg. UIControl/UICanvas) // Load all scene objects + if (Context.Async) { - PROFILE_CPU_NAMED("Deserialize"); - SceneObject** objects = sceneObjects->Get(); - bool wasAsync = context.Async; - context.Async = false; // TODO: before doing full async for scene objects fix: - // TODO: - fix Actor's Scripts and Children order when loading objects data out of order via async jobs - // TODO: - add _loadNoAsync flag to SceneObject or Actor to handle non-async loading for those types (eg. UIControl/UICanvas) - if (context.Async) + Level::ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize) +#if USE_EDITOR + volatile int64 deprecated = 0; +#endif + JobSystem::Execute([&](int32 i) { - Level::ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize) -#if USE_EDITOR - volatile int64 deprecated = 0; -#endif - JobSystem::Execute([&](int32 i) + i++; // Start from 1. at index [0] was scene + auto obj = objects[i]; + if (obj) { - 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]); + auto& idMapping = Scripting::ObjectsLookupIdMapping.Get(); + idMapping = &Context.GetModifier()->IdsMapping; + SceneObjectsFactory::Deserialize(Context, obj, args.Data[i]); #if USE_EDITOR - if (ContentDeprecated::Clear()) - Platform::InterlockedIncrement(&deprecated); + if (ContentDeprecated::Clear()) + Platform::InterlockedIncrement(&deprecated); #endif - idMapping = nullptr; - } - }, dataCount - 1); -#if USE_EDITOR - if (deprecated != 0) - ContentDeprecated::Mark(); -#endif - Level::ScenesLock.Lock(); - } - else - { - Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); - for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene - { - auto& objData = data[i]; - auto obj = objects[i]; - if (obj) - SceneObjectsFactory::Deserialize(context, obj, objData); + idMapping = nullptr; } - Scripting::ObjectsLookupIdMapping.Set(nullptr); - } - context.Async = wasAsync; + }, dataCount - 1); +#if USE_EDITOR + if (deprecated != 0) + ContentDeprecated::Mark(); +#endif + Level::ScenesLock.Lock(); } + else + { + Scripting::ObjectsLookupIdMapping.Set(&Modifier->IdsMapping); + for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene + { + auto& objData = args.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 - // \/ all below this has to be done on an any thread + NextStage(); + return SceneResult::Success; +} +SceneResult SceneLoader::OnSyncPrefabs(Args& args) +{ // Add injected children of scene (via OnSceneLoading) into sceneObjects to be initialized - for (auto child : injectedSceneChildren) + for (auto child : InjectedSceneChildren) { Array injectedSceneObjects; injectedSceneObjects.Add(child); @@ -1121,71 +1243,93 @@ SceneResult SceneLoader::Tick(rapidjson_flax::Value& data, int32 engineBuild, Sc { if (!o->IsRegistered()) o->RegisterObject(); - sceneObjects->Add(o); + SceneObjects->Add(o); } } // Synchronize prefab instances (prefab may have objects removed or reordered so deserialized instances need to synchronize with it) // TODO: resave and force sync scenes during game cooking so this step could be skipped in game - SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData); + SceneObjectsFactory::SynchronizePrefabInstances(Context, *PrefabSyncData); - // Cache transformations - { - PROFILE_CPU_NAMED("Cache Transform"); + NextStage(); + return SceneResult::Success; +} - scene->OnTransformChanged(); - } +SceneResult SceneLoader::OnSetupTransforms(Args& args) +{ + // Cache actor transformations + PROFILE_CPU_NAMED("SetupTransforms"); + Scene->OnTransformChanged(); + NextStage(); + return SceneResult::Success; +} + +SceneResult SceneLoader::OnInitialize(Args& args) +{ // Initialize scene objects + PROFILE_CPU_NAMED("Initialize"); + ASSERT_LOW_LAYER(IsInMainThread()); + SceneObject** objects = SceneObjects->Get(); + for (int32 i = 0; i < SceneObjects->Count(); i++) { - PROFILE_CPU_NAMED("Initialize"); - - SceneObject** objects = sceneObjects->Get(); - for (int32 i = 0; i < sceneObjects->Count(); i++) + SceneObject* obj = objects[i]; + if (obj) { - SceneObject* obj = objects[i]; - if (obj) - { - obj->Initialize(); + obj->Initialize(); - // Delete objects without parent - if (i != 0 && obj->GetParent() == nullptr) - { - LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString()); - obj->DeleteObject(); - } + // Delete objects without parent + if (i != 0 && obj->GetParent() == nullptr) + { + LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString()); + obj->DeleteObject(); } } - prefabSyncData.InitNewObjects(); } + PrefabSyncData->InitNewObjects(); - // /\ all above this has to be done on an any thread - // \/ all below this has to be done on a main thread + NextStage(); + return SceneResult::Success; +} - // Link scene and call init - { - PROFILE_CPU_NAMED("BeginPlay"); +SceneResult SceneLoader::OnBeginPlay(Args& args) +{ + PROFILE_CPU_NAMED("BeginPlay"); + ASSERT_LOW_LAYER(IsInMainThread()); - ScopeLock lock(Level::ScenesLock); - Level::Scenes.Add(scene); - SceneBeginData beginData; - scene->BeginPlay(&beginData); - beginData.OnDone(); - } + // Link scene + ScopeLock lock(Level::ScenesLock); + Level::Scenes.Add(Scene); - Stage = Loaded; + // TODO: prototype time-slicing with load-balancing for Begin Play: + // TODO: - collect all actors to enable + // TODO: - invoke in order OnBeginPlay -> Child Actors Begin -> Child Scripts Begin -> OnEnable for each actor + // TODO: - consider not drawing level until it's fully loaded (other engine systems should respect this too?) + // TODO: - consider refactoring Joints creation maybe? to get rid of SceneBeginData + + // Start the game for scene objects + SceneBeginData beginData; + Scene->BeginPlay(&beginData); + beginData.OnDone(); + + NextStage(); + return SceneResult::Success; +} + +SceneResult SceneLoader::OnEnd(Args& args) +{ + PROFILE_CPU_NAMED("End"); + Stopwatch time; // Fire event - CallSceneEvent(SceneEventType::OnSceneLoaded, scene, sceneId); + CallSceneEvent(SceneEventType::OnSceneLoaded, Scene, SceneId); - stopwatch.Stop(); - LOG(Info, "Scene loaded in {0}ms", stopwatch.GetMilliseconds()); - if (outScene) - *outScene = scene; + time.Stop(); + LOG(Info, "Scene loaded in {}ms ({} frames)", (int32)((TotalTime + time.GetTotalSeconds()) * 1000.0), Engine::UpdateCount - StartFrame); #if USE_EDITOR // Resave assets that use deprecated data format - for (auto& e : context.DeprecatedPrefabs) + for (auto& e : Context.DeprecatedPrefabs) { AssetReference prefab = e.Item; LOG(Info, "Resaving asset '{}' that uses deprecated data format", prefab->GetPath()); @@ -1194,16 +1338,17 @@ SceneResult SceneLoader::Tick(rapidjson_flax::Value& data, int32 engineBuild, Sc LOG(Error, "Failed to resave asset '{}'", prefab->GetPath()); } } - if (ContentDeprecated::Clear() && assetPath) + if (ContentDeprecated::Clear() && args.AssetPath) { - LOG(Info, "Resaving asset '{}' that uses deprecated data format", *assetPath); - if (saveScene(scene, *assetPath)) + LOG(Info, "Resaving asset '{}' that uses deprecated data format", *args.AssetPath); + if (saveScene(Scene, *args.AssetPath)) { - LOG(Error, "Failed to resave asset '{}'", *assetPath); + LOG(Error, "Failed to resave asset '{}'", *args.AssetPath); } } #endif + NextStage(); return SceneResult::Success; } @@ -1732,8 +1877,9 @@ Array Level::GetScripts(const MClass* type, Actor* root) const bool isInterface = type->IsInterface(); if (root) ::GetScripts(type, isInterface, root, result); - else for (int32 i = 0; i < Scenes.Count(); i++) - ::GetScripts(type, isInterface, Scenes[i], result); + else + for (int32 i = 0; i < Scenes.Count(); i++) + ::GetScripts(type, isInterface, Scenes[i], result); return result; }