Optimize async scene loading to run in separate stages with time-slicing

This commit is contained in:
Wojtek Figat
2025-06-11 14:33:47 +02:00
parent b50f3fcb64
commit d6eb647d59
3 changed files with 328 additions and 181 deletions

View File

@@ -3,7 +3,7 @@
#include "Cache.h"
#include "FlaxEngine.Gen.h"
CollectionPoolCache<ISerializeModifier, Cache::ISerializeModifierClearCallback> Cache::ISerializeModifier;
Cache::ISerializeModifierCache Cache::ISerializeModifier;
void Cache::ISerializeModifierClearCallback(::ISerializeModifier* obj)
{

View File

@@ -15,11 +15,12 @@ public:
static void ISerializeModifierClearCallback(ISerializeModifier* obj);
public:
typedef CollectionPoolCache<ISerializeModifier, ISerializeModifierClearCallback> ISerializeModifierCache;
/// <summary>
/// Gets the ISerializeModifier lookup cache. Safe allocation, per thread, uses caching.
/// </summary>
static CollectionPoolCache<ISerializeModifier, ISerializeModifierClearCallback> ISerializeModifier;
static ISerializeModifierCache ISerializeModifier;
public:

View File

@@ -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<Actor*> 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<JsonAsset> 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<Scene>(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<Actor*> injectedSceneChildren = scene->Children;
InjectedSceneChildren = Scene->Children;
// Loaded scene objects list
CollectionPoolCache<ActorsCache::SceneObjectsListType>::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<SceneObjectsFactory::PrefabSyncData>(*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<SceneObject*> 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> 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<Script*> 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;
}