Files
FlaxEngine/Source/Engine/Level/Level.cpp

1519 lines
39 KiB
C++

// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "Level.h"
#include "ActorsCache.h"
#include "SceneQuery.h"
#include "SceneObjectsFactory.h"
#include "Scene/Scene.h"
#include "Engine/Content/Content.h"
#include "Engine/Core/Cache.h"
#include "Engine/Core/Collections/CollectionPoolCache.h"
#include "Engine/Core/ObjectsRemovalService.h"
#include "Engine/Core/Config/LayersTagsSettings.h"
#include "Engine/Core/Types/LayersMask.h"
#include "Engine/Debug/Exceptions/ArgumentException.h"
#include "Engine/Debug/Exceptions/ArgumentNullException.h"
#include "Engine/Debug/Exceptions/InvalidOperationException.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Engine/Time.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MDomain.h"
#include "Engine/Scripting/MException.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Prefabs/Prefab.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Engine/Platform/MessageBox.h"
#include "Engine/Engine/CommandLine.h"
#endif
bool LayersMask::HasLayer(const StringView& layerName) const
{
return HasLayer(Level::GetLayerIndex(layerName));
}
enum class SceneEventType
{
OnSceneSaving = 0,
OnSceneSaved = 1,
OnSceneSaveError = 2,
OnSceneLoading = 3,
OnSceneLoaded = 4,
OnSceneLoadError = 5,
OnSceneUnloading = 6,
OnSceneUnloaded = 7,
};
class SceneAction
{
public:
virtual ~SceneAction()
{
}
virtual bool CanDo() const
{
return true;
}
virtual bool Do() const
{
return true;
}
};
namespace LevelImpl
{
Array<SceneAction*> _sceneActions;
CriticalSection _sceneActionsLocker;
DateTime _lastSceneLoadTime(0);
void CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId);
void flushActions();
bool unloadScene(Scene* scene);
bool unloadScenes();
bool saveScene(Scene* scene);
bool saveScene(Scene* scene, const String& path);
bool saveScene(Scene* scene, rapidjson_flax::StringBuffer& outBuffer, bool prettyJson);
bool saveScene(Scene* scene, rapidjson_flax::StringBuffer& outBuffer, JsonWriter& writer);
bool spawnActor(Actor* actor, Actor* parent);
bool deleteActor(Actor* actor);
}
using namespace LevelImpl;
class LevelService : public EngineService
{
public:
LevelService()
: EngineService(TEXT("Scene Manager"), 30)
{
}
void Update() override;
void LateUpdate() override;
void FixedUpdate() override;
void Dispose() override;
};
LevelService LevelServiceInstanceService;
CriticalSection Level::ScenesLock;
Array<Scene*> Level::Scenes;
Delegate<Actor*> Level::ActorSpawned;
Delegate<Actor*> Level::ActorDeleted;
Delegate<Actor*, Actor*> Level::ActorParentChanged;
Delegate<Actor*> Level::ActorOrderInParentChanged;
Delegate<Actor*> Level::ActorNameChanged;
Delegate<Actor*> Level::ActorActiveChanged;
Delegate<Scene*, const Guid&> Level::SceneSaving;
Delegate<Scene*, const Guid&> Level::SceneSaved;
Delegate<Scene*, const Guid&> Level::SceneSaveError;
Delegate<Scene*, const Guid&> Level::SceneLoading;
Delegate<Scene*, const Guid&> Level::SceneLoaded;
Delegate<Scene*, const Guid&> Level::SceneLoadError;
Delegate<Scene*, const Guid&> Level::SceneUnloading;
Delegate<Scene*, const Guid&> Level::SceneUnloaded;
Action Level::ScriptsReloadStart;
Action Level::ScriptsReload;
Action Level::ScriptsReloadEnd;
Array<String> Level::Tags;
String Level::Layers[32];
bool LevelImpl::spawnActor(Actor* actor, Actor* parent)
{
if (actor == nullptr)
{
Log::ArgumentNullException(TEXT("Cannot spawn null actor."));
return true;
}
if (actor->Is<Scene>())
{
// Spawn scene
actor->PostSpawn();
actor->OnTransformChanged();
{
SceneBeginData beginData;
actor->BeginPlay(&beginData);
beginData.OnDone();
}
CallSceneEvent(SceneEventType::OnSceneLoaded, (Scene*)actor, actor->GetID());
}
else
{
// Spawn actor
if (Level::Scenes.IsEmpty())
{
Log::InvalidOperationException(TEXT("Cannot spawn actor. No scene loaded."));
return true;
}
if (parent == nullptr)
parent = Level::Scenes[0];
actor->SetParent(parent, true, true);
}
return false;
}
bool LevelImpl::deleteActor(Actor* actor)
{
if (actor == nullptr)
{
Log::ArgumentNullException(TEXT("Cannot delete null actor."));
return true;
}
actor->DeleteObject();
return false;
}
void LayersAndTagsSettings::Apply()
{
// Note: we cannot remove tags/layers at runtime so this should deserialize them in additive mode
// Tags/Layers are stored as index in actors so collection change would break the linkage
for (auto& tag : Tags)
{
if (!Level::Tags.Contains(tag))
Level::Tags.Add(tag);
}
for (int32 i = 0; i < ARRAY_COUNT(Level::Layers); i++)
{
const auto& src = Layers[i];
auto& dst = Level::Layers[i];
if (dst.IsEmpty() || !src.IsEmpty())
dst = src;
}
}
void LevelService::Update()
{
PROFILE_CPU_NAMED("Level::Update");
ScopeLock lock(Level::ScenesLock);
auto& scenes = Level::Scenes;
// Update all actors
if (!Time::GetGamePaused())
{
for (int32 i = 0; i < scenes.Count(); i++)
{
if (scenes[i]->GetIsActive())
scenes[i]->Ticking.Update.Tick();
}
}
#if USE_EDITOR
else if (!Editor::IsPlayMode)
{
// Run event for script executed in editor
for (int32 i = 0; i < scenes.Count(); i++)
{
if (scenes[i]->GetIsActive())
scenes[i]->Ticking.Update.TickExecuteInEditor();
}
}
#endif
}
void LevelService::LateUpdate()
{
PROFILE_CPU_NAMED("Level::LateUpdate");
ScopeLock lock(Level::ScenesLock);
auto& scenes = Level::Scenes;
// Update all actors
if (!Time::GetGamePaused())
{
for (int32 i = 0; i < scenes.Count(); i++)
{
if (scenes[i]->GetIsActive())
scenes[i]->Ticking.LateUpdate.Tick();
}
}
#if USE_EDITOR
else if (!Editor::IsPlayMode)
{
// Run event for script executed in editor
for (int32 i = 0; i < scenes.Count(); i++)
{
if (scenes[i]->GetIsActive())
scenes[i]->Ticking.LateUpdate.TickExecuteInEditor();
}
}
#endif
// Flush actions
flushActions();
}
void LevelService::FixedUpdate()
{
PROFILE_CPU_NAMED("Level::FixedUpdate");
ScopeLock lock(Level::ScenesLock);
auto& scenes = Level::Scenes;
// Update all actors
if (!Time::GetGamePaused())
{
for (int32 i = 0; i < scenes.Count(); i++)
{
if (scenes[i]->GetIsActive())
scenes[i]->Ticking.FixedUpdate.Tick();
}
}
#if USE_EDITOR
else if (!Editor::IsPlayMode)
{
// Run event for script executed in editor
for (int32 i = 0; i < scenes.Count(); i++)
{
if (scenes[i]->GetIsActive())
scenes[i]->Ticking.FixedUpdate.TickExecuteInEditor();
}
}
#endif
}
void LevelService::Dispose()
{
ScopeLock lock(_sceneActionsLocker);
// Unload scenes
unloadScenes();
// Ensure that all scenes and actors has been destroyed (we don't leak!)
ASSERT(Level::Scenes.IsEmpty());
}
bool Level::IsAnyActorInGame()
{
ScopeLock lock(ScenesLock);
for (int32 i = 0; i < Scenes.Count(); i++)
{
if (Scenes[i]->Children.HasItems())
return true;
}
return false;
}
bool Level::IsAnyActionPending()
{
_sceneActionsLocker.Lock();
const bool result = _sceneActions.HasItems();
_sceneActionsLocker.Unlock();
return result;
}
DateTime Level::GetLastSceneLoadTime()
{
return _lastSceneLoadTime;
}
bool Level::SpawnActor(Actor* actor, Actor* parent)
{
ASSERT(actor);
ScopeLock lock(_sceneActionsLocker);
return spawnActor(actor, parent);
}
bool Level::DeleteActor(Actor* actor)
{
ASSERT(actor);
ScopeLock lock(_sceneActionsLocker);
return deleteActor(actor);
}
void Level::CallBeginPlay(Actor* obj)
{
if (obj && !obj->IsDuringPlay())
{
SceneBeginData beginData;
obj->BeginPlay(&beginData);
beginData.OnDone();
}
}
void Level::DrawActors(RenderContext& renderContext)
{
PROFILE_CPU();
//ScopeLock lock(ScenesLock);
for (int32 i = 0; i < Scenes.Count(); i++)
{
Scenes[i]->Rendering.Draw(renderContext);
}
}
void Level::CollectPostFxVolumes(RenderContext& renderContext)
{
PROFILE_CPU();
//ScopeLock lock(ScenesLock);
for (int32 i = 0; i < Scenes.Count(); i++)
{
Scenes[i]->Rendering.CollectPostFxVolumes(renderContext);
}
}
class LoadSceneAction : public SceneAction
{
public:
Guid SceneId;
AssetReference<JsonAsset> SceneAsset;
bool AutoInitialize;
LoadSceneAction(const Guid& sceneId, JsonAsset* sceneAsset, bool autoInitialize)
{
SceneId = sceneId;
SceneAsset = sceneAsset;
AutoInitialize = autoInitialize;
}
bool CanDo() const override
{
return SceneAsset == nullptr || SceneAsset->IsLoaded();
}
bool Do() const override
{
// Now to deserialize scene in a proper way we need to load scripting
if (!Scripting::IsEveryAssemblyLoaded())
{
LOG(Error, "Scripts must be compiled without any errors in order to load a scene.");
#if USE_EDITOR
Platform::Error(TEXT("Scripts must be compiled without any errors in order to load a scene. Please fix it."));
#endif
CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
return true;
}
// Load scene
if (Level::loadScene(SceneAsset.Get(), AutoInitialize))
{
LOG(Error, "Failed to deserialize scene {0}", SceneId);
CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
return true;
}
return false;
}
};
class UnloadSceneAction : public SceneAction
{
public:
Guid TargetScene;
UnloadSceneAction(Scene* scene)
{
TargetScene = scene->GetID();
}
bool Do() const override
{
auto scene = Scripting::FindObject<Scene>(TargetScene);
if (!scene)
return true;
return unloadScene(scene);
}
};
class UnloadScenesAction : public SceneAction
{
public:
UnloadScenesAction()
{
}
bool Do() const override
{
return unloadScenes();
}
};
class SaveSceneAction : public SceneAction
{
public:
Scene* TargetScene;
bool PrettyJson;
SaveSceneAction(Scene* scene, bool prettyJson = true)
{
TargetScene = scene;
PrettyJson = prettyJson;
}
bool Do() const override
{
if (saveScene(TargetScene))
{
LOG(Error, "Failed to save scene {0}", TargetScene ? TargetScene->GetName() : String::Empty);
return true;
}
return false;
}
};
#if USE_EDITOR
class ReloadScriptsAction : public SceneAction
{
public:
ReloadScriptsAction()
{
}
bool Do() const override
{
// Reloading scripts workflow:
// - save scenes (to temporary files)
// - unload scenes
// - unload user assemblies
// - load user assemblies
// - load scenes (from temporary files)
// Note: we don't want to override original scene files
PROFILE_CPU_NAMED("Level.ReloadScripts");
LOG(Info, "Scripts reloading start");
const auto startTime = DateTime::NowUTC();
// Cache data
struct SceneData
{
Guid ID;
String Name;
rapidjson_flax::StringBuffer Data;
SceneData() = default;
SceneData(const SceneData& other)
{
CRASH;
}
const SceneData& operator=(SceneData&) const
{
CRASH;
return *this;
}
void Init(Scene* scene)
{
ID = scene->GetID();
Name = scene->GetName();
}
};
const int32 scenesCount = Level::Scenes.Count();
Array<SceneData> scenes;
scenes.Resize(scenesCount);
for (int32 i = 0; i < scenesCount; i++)
scenes[i].Init(Level::Scenes[i]);
// Fire event
Level::ScriptsReloadStart();
// Save scenes (to memory)
for (int32 i = 0; i < scenesCount; i++)
{
const auto scene = Level::Scenes[i];
LOG(Info, "Caching scene {0}", scenes[i].Name);
// Serialize to json
if (saveScene(scene, scenes[i].Data, false))
{
LOG(Error, "Failed to save scene '{0}' for scripts reload.", scenes[i].Name);
CallSceneEvent(SceneEventType::OnSceneSaveError, scene, scene->GetID());
return true;
}
CallSceneEvent(SceneEventType::OnSceneSaved, scene, scene->GetID());
}
// Unload scenes
unloadScenes();
// Reload scripting
Level::ScriptsReload();
Scripting::Reload();
// Restore scenes (from memory)
for (int32 i = 0; i < scenesCount; i++)
{
LOG(Info, "Restoring scene {0}", scenes[i].Name);
// Parse json
const auto& sceneData = scenes[i].Data;
ISerializable::SerializeDocument document;
{
PROFILE_CPU_NAMED("Json.Parse");
document.Parse(sceneData.GetString(), sceneData.GetSize());
}
if (document.HasParseError())
{
LOG(Error, "Failed to deserialize scene {0}. Result: {1}", scenes[i].Name, GetParseError_En(document.GetParseError()));
return true;
}
// Load scene
if (Level::loadScene(document, false))
{
LOG(Error, "Failed to deserialize scene {0}", scenes[i].Name);
CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, scenes[i].ID);
return true;
}
}
scenes.Resize(0);
// Initialize scenes (will link references and create managed objects using new assembly)
if (Level::Scenes.HasItems())
{
LOG(Info, "Prepare scene objects");
SceneBeginData beginData;
for (auto scene : Level::Scenes)
{
scene->BeginPlay(&beginData);
}
beginData.OnDone();
}
// Fire event
LOG(Info, "Scripts reloading end. Total time: {0}ms", static_cast<int32>((DateTime::NowUTC() - startTime).GetTotalMilliseconds()));
Level::ScriptsReloadEnd();
return false;
}
};
#endif
class SpawnActorAction : public SceneAction
{
public:
ScriptingObjectReference<Actor> TargetActor;
ScriptingObjectReference<Actor> ParentActor;
SpawnActorAction(Actor* actor, Actor* parent)
{
TargetActor = actor;
ParentActor = parent;
}
bool Do() const override
{
return spawnActor(TargetActor, ParentActor);
}
};
class DeleteActorAction : public SceneAction
{
public:
ScriptingObjectReference<Actor> TargetActor;
DeleteActorAction(Actor* actor)
{
TargetActor = actor;
}
bool Do() const override
{
return deleteActor(TargetActor);
}
};
void LevelImpl::CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId)
{
PROFILE_CPU_NAMED("Level::CallSceneEvent");
// Call event
const auto scriptsDomain = Scripting::GetScriptsDomain();
if (scriptsDomain != nullptr)
scriptsDomain->Dispatch();
switch (eventType)
{
case SceneEventType::OnSceneSaving:
Level::SceneSaving(scene, sceneId);
break;
case SceneEventType::OnSceneSaved:
Level::SceneSaved(scene, sceneId);
break;
case SceneEventType::OnSceneSaveError:
Level::SceneSaveError(scene, sceneId);
break;
case SceneEventType::OnSceneLoading:
Level::SceneLoading(scene, sceneId);
break;
case SceneEventType::OnSceneLoaded:
Level::SceneLoaded(scene, sceneId);
break;
case SceneEventType::OnSceneLoadError:
Level::SceneLoadError(scene, sceneId);
break;
case SceneEventType::OnSceneUnloading:
Level::SceneUnloading(scene, sceneId);
break;
case SceneEventType::OnSceneUnloaded:
Level::SceneUnloaded(scene, sceneId);
break;
}
}
int32 Level::GetOrAddTag(const StringView& tag)
{
int32 index = Tags.Find(tag);
if (index == INVALID_INDEX)
{
index = Tags.Count();
Tags.AddOne() = tag;
}
return index;
}
int32 Level::GetNonEmptyLayerNamesCount()
{
int32 result = 31;
while (result >= 0 && Layers[result].IsEmpty())
result--;
return result + 1;
}
int32 Level::GetLayerIndex(const StringView& layer)
{
int32 result = -1;
for (int32 i = 0; i < 32; i++)
{
if (Layers[i] == layer)
{
result = i;
break;
}
}
return result;
}
void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b)
{
PROFILE_CPU();
ASSERT(a);
// Call event
const auto scriptsDomain = Scripting::GetScriptsDomain();
if (scriptsDomain != nullptr)
scriptsDomain->Dispatch();
switch (eventType)
{
case ActorEventType::OnActorSpawned:
ActorSpawned(a);
break;
case ActorEventType::OnActorDeleted:
ActorDeleted(a);
break;
case ActorEventType::OnActorParentChanged:
ActorParentChanged(a, b);
break;
case ActorEventType::OnActorOrderInParentChanged:
ActorOrderInParentChanged(a);
break;
case ActorEventType::OnActorNameChanged:
ActorNameChanged(a);
break;
case ActorEventType::OnActorActiveChanged:
ActorActiveChanged(a);
break;
}
}
void LevelImpl::flushActions()
{
ScopeLock lock(_sceneActionsLocker);
while (_sceneActions.HasItems() && _sceneActions.First()->CanDo())
{
const auto action = _sceneActions.Dequeue();
action->Do();
Delete(action);
}
}
bool LevelImpl::unloadScene(Scene* scene)
{
if (scene == nullptr)
{
Log::ArgumentNullException();
return true;
}
const auto sceneId = scene->GetID();
PROFILE_CPU_NAMED("Level.UnloadScene");
// Fire event
CallSceneEvent(SceneEventType::OnSceneUnloading, scene, sceneId);
// Call end play
if (scene->IsDuringPlay())
scene->EndPlay();
// Simple enqueue scene root object to be deleted
Level::Scenes.Remove(scene);
scene->DeleteObject();
// Fire event
CallSceneEvent(SceneEventType::OnSceneUnloaded, nullptr, sceneId);
// Force flush deleted objects so we actually delete unloaded scene objects (prevent from reloading their managed objects, etc.)
ObjectsRemovalService::Flush();
return false;
}
bool LevelImpl::unloadScenes()
{
auto scenes = Level::Scenes;
for (int32 i = 0; i < scenes.Count(); i++)
{
if (unloadScene(scenes[i]))
return true;
}
return false;
}
bool Level::loadScene(const Guid& sceneId, bool autoInitialize)
{
const auto sceneAsset = Content::LoadAsync<JsonAsset>(sceneId);
return loadScene(sceneAsset, autoInitialize);
}
bool Level::loadScene(const String& scenePath, bool autoInitialize)
{
LOG(Info, "Loading scene from file. Path: \'{0}\'", scenePath);
// Check for missing file
if (!FileSystem::FileExists(scenePath))
{
LOG(Error, "Missing scene file.");
return true;
}
// Load file
BytesContainer sceneData;
if (File::ReadAllBytes(scenePath, sceneData))
{
LOG(Error, "Cannot load data from file.");
return true;
}
return loadScene(sceneData, autoInitialize);
}
bool Level::loadScene(JsonAsset* sceneAsset, bool autoInitialize)
{
// Keep reference to the asset (prevent unloading during action)
AssetReference<JsonAsset> ref = sceneAsset;
// Wait for loaded
if (sceneAsset == nullptr || sceneAsset->WaitForLoaded())
{
LOG(Error, "Cannot load scene asset.");
return true;
}
return loadScene(*sceneAsset->Data, sceneAsset->DataEngineBuild, autoInitialize);
}
bool Level::loadScene(const BytesContainer& sceneData, bool autoInitialize, Scene** outScene)
{
if (sceneData.IsInvalid())
{
LOG(Error, "Missing scene data.");
return true;
}
// Parse scene JSON file
rapidjson_flax::Document document;
{
PROFILE_CPU_NAMED("Json.Parse");
document.Parse(sceneData.Get<char>(), sceneData.Length());
}
if (document.HasParseError())
{
Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
return true;
}
return loadScene(document, autoInitialize, outScene);
}
bool Level::loadScene(rapidjson_flax::Document& document, bool autoInitialize, Scene** outScene)
{
auto data = document.FindMember("Data");
if (data == document.MemberEnd())
{
LOG(Error, "Missing Data member.");
return true;
}
const int32 saveEngineBuild = JsonTools::GetInt(document, "EngineBuild", 0);
return loadScene(data->value, saveEngineBuild, autoInitialize, outScene);
}
bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, bool autoInitialize, Scene** outScene)
{
PROFILE_CPU_NAMED("Level.LoadScene");
if (outScene)
*outScene = nullptr;
LOG(Info, "Loading scene...");
const DateTime startTime = DateTime::NowUTC();
_lastSceneLoadTime = startTime;
// Here whole scripting backend should be loaded for current project
// Later scripts will setup attached scripts and restore initial vars
if (!Scripting::HasGameModulesLoaded())
{
LOG(Error, "Cannot load scene without game modules loaded.");
#if USE_EDITOR
if (!CommandLine::Options.Headless.IsTrue())
MessageBox::Show(TEXT("Cannot load scene without game script modules. Please fix the compilation issues."), TEXT("Missing game modules"), MessageBoxButtons::OK, MessageBoxIcon::Error);
#endif
return true;
}
// Peek meta
if (engineBuild < 6000)
{
LOG(Error, "Invalid serialized engine build.");
return true;
}
if (!data.IsArray())
{
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");
if (!sceneId.IsValid())
{
LOG(Error, "Invalid scene id.");
return true;
}
auto modifier = Cache::ISerializeModifier.Get();
modifier->EngineBuild = engineBuild;
// Skip is that scene is already loaded
if (FindScene(sceneId) != nullptr)
{
LOG(Info, "Scene {0} is already loaded.", sceneId);
return false;
}
// 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->LoadTime = startTime;
scene->RegisterObject();
scene->Deserialize(sceneValue, modifier.Value);
// Fire event
CallSceneEvent(SceneEventType::OnSceneLoading, scene, sceneId);
// Maps the loaded actor object to the json data with the RemovedObjects array (used to skip restoring objects removed per prefab instance)
SceneObjectsFactory::ActorToRemovedObjectsDataLookup actorToRemovedObjectsData;
// Loaded scene objects list
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
sceneObjects->Resize(objectsCount);
sceneObjects->At(0) = scene;
{
PROFILE_CPU_NAMED("Spawn");
// Spawn all scene objects
for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene
{
auto& objData = data[i];
auto obj = SceneObjectsFactory::Spawn(objData, modifier.Value);
sceneObjects->At(i) = obj;
if (obj)
{
// Register object so it can be later referenced during deserialization (FindObject will work to link references between objects)
obj->RegisterObject();
// Special case for actors
if (auto actor = dynamic_cast<Actor*>(obj))
{
// Check for RemovedObjects listing
const auto removedObjects = SERIALIZE_FIND_MEMBER(objData, "RemovedObjects");
if (removedObjects != objData.MemberEnd())
{
actorToRemovedObjectsData.Add(actor, &removedObjects->value);
}
}
}
}
}
// /\ all above this has to be done on an any thread
// \/ all below this has to be done on multiple threads at once
{
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);
for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene
{
auto& objData = data[i];
auto obj = sceneObjects->At(i);
if (obj)
SceneObjectsFactory::Deserialize(obj, objData, modifier.Value);
else
SceneObjectsFactory::HandleObjectDeserializationError(objData);
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
}
// /\ all above this has to be done on multiple threads at once
// \/ all below this has to be done on an any thread
// Call post load event to connect all scene actors
{
PROFILE_CPU_NAMED("Post Load");
for (int32 i = 0; i < objectsCount; i++)
{
SceneObject* obj = sceneObjects->At(i);
if (obj)
obj->PostLoad();
}
}
// Synchronize prefab instances (prefab may have new objects added or some removed 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(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value);
// Delete objects without parent
for (int32 i = 1; i < objectsCount; i++)
{
SceneObject* obj = sceneObjects->At(i);
if (obj && obj->GetParent() == nullptr)
{
sceneObjects->At(i) = nullptr;
LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString());
obj->DeleteObject();
}
}
// Cache transformations
{
PROFILE_CPU_NAMED("Cache Transform");
scene->OnTransformChanged();
}
// /\ all above this has to be done on an any thread
// \/ all below this has to be done on a main thread
// Link scene and call init
{
PROFILE_CPU_NAMED("BeginPlay");
Scenes.Add(scene);
if (autoInitialize)
{
SceneBeginData beginData;
scene->BeginPlay(&beginData);
beginData.OnDone();
}
else
{
// Send warning to log just in case (easier to track if scene will be loaded without init)
// Why? Because we can load collection of scenes and then call for all of them init so references between objects in a different scenes will be resolved without leaks.
//LOG(Warning, "Scene \'{0}:{1}\', has been loaded but not initialized. Remember to call OnBeginPlay().", scene->GetName(), scene->GetID());
}
}
// Fire event
CallSceneEvent(SceneEventType::OnSceneLoaded, scene, sceneId);
LOG(Info, "Scene loaded in {0} ms", (int32)(DateTime::NowUTC() - startTime).GetTotalMilliseconds());
if (outScene)
*outScene = scene;
return false;
}
bool LevelImpl::saveScene(Scene* scene)
{
#if USE_EDITOR
const auto path = scene->GetPath();
if (path.IsEmpty())
{
LOG(Error, "Missing scene path.");
return true;
}
return saveScene(scene, path);
#else
LOG(Error, "Cannot save data to the cooked content.");
return false;
#endif
}
bool LevelImpl::saveScene(Scene* scene, const String& path)
{
ASSERT(scene);
auto sceneId = scene->GetID();
LOG(Info, "Saving scene {0} to \'{1}\'", scene->GetName(), path);
const DateTime startTime = DateTime::NowUTC();
scene->SaveTime = startTime;
// Serialize to json
rapidjson_flax::StringBuffer buffer;
if (saveScene(scene, buffer, true) && buffer.GetSize() > 0)
{
CallSceneEvent(SceneEventType::OnSceneSaveError, scene, sceneId);
return true;
}
// Save json to file
if (File::WriteAllBytes(path, (byte*)buffer.GetString(), (int32)buffer.GetSize()))
{
LOG(Error, "Cannot save scene file");
CallSceneEvent(SceneEventType::OnSceneSaveError, scene, sceneId);
return true;
}
LOG(Info, "Scene saved! Time {0} ms", Math::CeilToInt((float)(DateTime::NowUTC() - startTime).GetTotalMilliseconds()));
// Fire event
CallSceneEvent(SceneEventType::OnSceneSaved, scene, sceneId);
return false;
}
bool LevelImpl::saveScene(Scene* scene, rapidjson_flax::StringBuffer& outBuffer, bool prettyJson)
{
PROFILE_CPU_NAMED("Level.SaveScene");
if (prettyJson)
{
PrettyJsonWriter writerObj(outBuffer);
return saveScene(scene, outBuffer, writerObj);
}
else
{
CompactJsonWriter writerObj(outBuffer);
return saveScene(scene, outBuffer, writerObj);
}
}
bool LevelImpl::saveScene(Scene* scene, rapidjson_flax::StringBuffer& outBuffer, JsonWriter& writer)
{
ASSERT(scene);
const auto sceneId = scene->GetID();
// Fire event
CallSceneEvent(SceneEventType::OnSceneSaving, scene, sceneId);
// Get all objects in the scene
Array<SceneObject*> allObjects;
SceneQuery::GetAllSerializableSceneObjects(scene, allObjects);
// Serialize to json
writer.StartObject();
{
PROFILE_CPU_NAMED("Serialize");
// Json resource header
writer.JKEY("ID");
writer.Guid(sceneId);
writer.JKEY("TypeName");
writer.String("FlaxEngine.SceneAsset");
writer.JKEY("EngineBuild");
writer.Int(FLAXENGINE_VERSION_BUILD);
// Json resource data
writer.JKEY("Data");
writer.StartArray();
for (int32 i = 0; i < allObjects.Count(); i++)
{
writer.SceneObject(allObjects[i]);
}
writer.EndArray();
}
writer.EndObject();
return false;
}
bool Level::SaveScene(Scene* scene, bool prettyJson)
{
ScopeLock lock(_sceneActionsLocker);
return SaveSceneAction(scene, prettyJson).Do();
}
bool Level::SaveSceneToBytes(Scene* scene, rapidjson_flax::StringBuffer& outData, bool prettyJson)
{
ASSERT(scene);
ScopeLock lock(_sceneActionsLocker);
LOG(Info, "Saving scene {0} to bytes", scene->GetName());
const DateTime startTime = DateTime::NowUTC();
scene->SaveTime = startTime;
// Serialize to json
if (saveScene(scene, outData, prettyJson))
{
CallSceneEvent(SceneEventType::OnSceneSaveError, scene, scene->GetID());
return true;
}
// Info
LOG(Info, "Scene saved! Time {0} ms", Math::CeilToInt(static_cast<float>((DateTime::NowUTC()- startTime).GetTotalMilliseconds())));
// Fire event
CallSceneEvent(SceneEventType::OnSceneSaved, scene, scene->GetID());
return false;
}
Array<byte> Level::SaveSceneToBytes(Scene* scene, bool prettyJson)
{
Array<byte> data;
rapidjson_flax::StringBuffer sceneData;
if (!SaveSceneToBytes(scene, sceneData, prettyJson))
{
data.Set((const byte*)sceneData.GetString(), (int32)sceneData.GetSize() * sizeof(rapidjson_flax::StringBuffer::Ch));
}
return data;
}
void Level::SaveSceneAsync(Scene* scene)
{
ScopeLock lock(_sceneActionsLocker);
_sceneActions.Enqueue(New<SaveSceneAction>(scene));
}
bool Level::SaveAllScenes()
{
ScopeLock lock(_sceneActionsLocker);
for (int32 i = 0; i < Scenes.Count(); i++)
{
if (SaveSceneAction(Scenes[i]).Do())
return true;
}
return false;
}
void Level::SaveAllScenesAsync()
{
ScopeLock lock(_sceneActionsLocker);
for (int32 i = 0; i < Scenes.Count(); i++)
_sceneActions.Enqueue(New<SaveSceneAction>(Scenes[i]));
}
bool Level::LoadScene(const Guid& id, bool autoInitialize)
{
// Check ID
if (!id.IsValid())
{
Log::ArgumentException();
return true;
}
// Skip is that scene is already loaded
if (FindScene(id) != nullptr)
{
LOG(Info, "Scene {0} is already loaded.", id);
return false;
}
// Now to deserialize scene in a proper way we need to load scripting
if (!Scripting::IsEveryAssemblyLoaded())
{
#if USE_EDITOR
LOG(Error, "Scripts must be compiled without any errors in order to load a scene. Please fix it.");
#else
LOG(Warning, "Scripts must be compiled without any errors in order to load a scene.");
#endif
return true;
}
// Preload scene asset
const auto sceneAsset = Content::LoadAsync<JsonAsset>(id);
if (sceneAsset == nullptr)
{
LOG(Error, "Cannot load scene asset.");
return true;
}
// Load scene
if (loadScene(sceneAsset, autoInitialize))
{
LOG(Error, "Failed to deserialize scene {0}", id);
CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, id);
return true;
}
return false;
}
Scene* Level::LoadSceneFromBytes(const BytesContainer& data, bool autoInitialize)
{
Scene* scene = nullptr;
if (loadScene(data, autoInitialize, &scene))
{
LOG(Error, "Failed to deserialize scene from bytes");
CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, Guid::Empty);
}
return scene;
}
bool Level::LoadSceneAsync(const Guid& id)
{
// Check ID
if (!id.IsValid())
{
Log::ArgumentException();
return true;
}
// Preload scene asset
const auto sceneAsset = Content::LoadAsync<JsonAsset>(id);
if (sceneAsset == nullptr)
{
LOG(Error, "Cannot load scene asset.");
return true;
}
ScopeLock lock(_sceneActionsLocker);
_sceneActions.Enqueue(New<LoadSceneAction>(id, sceneAsset, true));
return false;
}
bool Level::UnloadScene(Scene* scene)
{
return unloadScene(scene);
}
void Level::UnloadSceneAsync(Scene* scene)
{
CHECK(scene);
ScopeLock lock(_sceneActionsLocker);
_sceneActions.Enqueue(New<UnloadSceneAction>(scene));
}
bool Level::UnloadAllScenes()
{
ScopeLock lock(_sceneActionsLocker);
return unloadScenes();
}
void Level::UnloadAllScenesAsync()
{
ScopeLock lock(_sceneActionsLocker);
_sceneActions.Enqueue(New<UnloadScenesAction>());
}
#if USE_EDITOR
void Level::ReloadScriptsAsync()
{
ScopeLock lock(_sceneActionsLocker);
_sceneActions.Enqueue(New<ReloadScriptsAction>());
}
#endif
Actor* Level::FindActor(const Guid& id)
{
return Scripting::FindObject<Actor>(id);
}
Actor* Level::FindActor(const StringView& name)
{
Actor* result = nullptr;
ScopeLock lock(ScenesLock);
for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
{
result = Scenes[i]->FindActor(name);
}
return result;
}
Actor* Level::FindActor(const MClass* type)
{
CHECK_RETURN(type, nullptr);
Actor* result = nullptr;
ScopeLock lock(ScenesLock);
for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
result = Scenes[i]->FindActor(type);
return result;
}
Script* Level::FindScript(const MClass* type)
{
CHECK_RETURN(type, nullptr);
Script* result = nullptr;
ScopeLock lock(ScenesLock);
for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
result = Scenes[i]->FindScript(type);
return result;
}
namespace
{
void GetActors(const MClass* type, Actor* actor, Array<Actor*>& result)
{
if (actor->GetClass()->IsSubClassOf(type))
result.Add(actor);
for (auto child : actor->Children)
GetActors(type, child, result);
}
void GetScripts(const MClass* type, Actor* actor, Array<Script*>& result)
{
for (auto script : actor->Scripts)
if (script->GetClass()->IsSubClassOf(type))
result.Add(script);
for (auto child : actor->Children)
GetScripts(type, child, result);
}
}
Array<Actor*> Level::GetActors(const MClass* type)
{
Array<Actor*> result;
CHECK_RETURN(type, result);
ScopeLock lock(ScenesLock);
for (int32 i = 0; i < Scenes.Count(); i++)
::GetActors(type, Scenes[i], result);
return result;
}
Array<Script*> Level::GetScripts(const MClass* type)
{
Array<Script*> result;
CHECK_RETURN(type, result);
ScopeLock lock(ScenesLock);
for (int32 i = 0; i < Scenes.Count(); i++)
::GetScripts(type, Scenes[i], result);
return result;
}
Scene* Level::FindScene(const Guid& id)
{
ScopeLock lock(ScenesLock);
for (int32 i = 0; i < Scenes.Count(); i++)
if (Scenes[i]->GetID() == id)
return Scenes[i];
return nullptr;
}
void Level::GetScenes(Array<Scene*>& scenes)
{
ScopeLock lock(ScenesLock);
scenes = Scenes;
}
void Level::GetScenes(Array<Actor*>& scenes)
{
ScopeLock lock(ScenesLock);
scenes.Clear();
scenes.EnsureCapacity(Scenes.Count());
for (int32 i = 0; i < Scenes.Count(); i++)
scenes.Add(Scenes[i]);
}
void Level::GetScenes(Array<Guid>& scenes)
{
ScopeLock lock(ScenesLock);
scenes.Clear();
scenes.EnsureCapacity(Scenes.Count());
for (int32 i = 0; i < Scenes.Count(); i++)
scenes.Add(Scenes[i]->GetID());
}
void FillTree(Actor* node, Array<Actor*>& result)
{
result.Add(node->Children);
for (int i = 0; i < node->Children.Count(); i++)
{
FillTree(node->Children[i], result);
}
}
void Level::ConstructSolidActorsTreeList(const Array<Actor*>& input, Array<Actor*>& output)
{
for (int32 i = 0; i < input.Count(); i++)
{
auto target = input[i];
// Check if has been already added
if (output.Contains(target))
continue;
// Add whole child tree to the results
output.Add(target);
FillTree(target, output);
}
}
void Level::ConstructParentActorsTreeList(const Array<Actor*>& input, Array<Actor*>& output)
{
// Build solid part of the tree
Array<Actor*> fullTree;
ConstructSolidActorsTreeList(input, fullTree);
for (int32 i = 0; i < input.Count(); i++)
{
Actor* target = input[i];
// If there is no target node parent in the solid tree list,
// then it means it's a local root node and can be added to the results.
if (!fullTree.Contains(target->GetParent()))
output.Add(target);
}
}