Optimize async scene loading to run in separate stages with time-slicing
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user