Files
FlaxEngine/Source/Engine/Level/Level.cpp
NoriteSC 6ab2e540a3 Bug Fixes
script and actors not having connect transform in on awake and in on start

join auto anchor having wrong anchor because was including scale

fixed selection do to changes to transform accessibility ?
2023-08-25 14:51:49 +02:00

1665 lines
43 KiB
C++

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "Level.h"
#include "ActorsCache.h"
#include "LargeWorlds.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/ManagedCLR/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"
#include "Engine/Serialization/JsonSerializer.h"
#include "Editor/Scripting/ScriptsBuilder.h"
#endif
#if USE_LARGE_WORLDS
bool LargeWorlds::Enable = true;
#else
bool LargeWorlds::Enable = false;
#endif
void LargeWorlds::UpdateOrigin(Vector3& origin, const Vector3& position)
{
if (Enable)
{
constexpr Real chunkSizeInv = 1.0 / ChunkSize;
constexpr Real chunkSizeHalf = ChunkSize * 0.5;
origin = Vector3(Int3((position - chunkSizeHalf) * chunkSizeInv)) * ChunkSize;
}
}
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;
}
};
#if USE_EDITOR
struct ScriptsReloadObject
{
StringAnsi TypeName;
ScriptingObject** Object;
Array<byte> Data;
};
#endif
namespace LevelImpl
{
Array<SceneAction*> _sceneActions;
CriticalSection _sceneActionsLocker;
DateTime _lastSceneLoadTime(0);
#if USE_EDITOR
Array<ScriptsReloadObject> ScriptsReloadObjects;
#endif
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 LateFixedUpdate() override;
void Dispose() override;
};
LevelService LevelServiceInstanceService;
CriticalSection Level::ScenesLock;
Array<Scene*> Level::Scenes;
bool Level::TickEnabled = true;
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;
#if USE_EDITOR
Action Level::ScriptsReloadStart;
Action Level::ScriptsReload;
Action Level::ScriptsReloaded;
Action Level::ScriptsReloadEnd;
#endif
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->GetType().ManagedClass->IsAbstract())
{
Log::Exception(TEXT("Cannot spawn abstract actor type."));
return true;
}
if (actor->Is<Scene>())
{
// Spawn scene
actor->InitializeHierarchy();
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->SetPhysicsScene(parent->GetPhysicsScene());
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)
{
Tags::Get(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;
}
}
#define TICK_LEVEL(tickingStage, name) \
PROFILE_CPU_NAMED(name); \
ScopeLock lock(Level::ScenesLock); \
auto& scenes = Level::Scenes; \
if (!Time::GetGamePaused() && Level::TickEnabled) \
{ \
for (int32 i = 0; i < scenes.Count(); i++) \
{ \
if (scenes[i]->GetIsActive()) \
scenes[i]->Ticking.tickingStage.Tick(); \
} \
}
#if USE_EDITOR
#define TICK_LEVEL_EDITOR(tickingStage) \
else if (!Editor::IsPlayMode) \
{ \
for (int32 i = 0; i < scenes.Count(); i++) \
{ \
if (scenes[i]->GetIsActive()) \
scenes[i]->Ticking.tickingStage.TickExecuteInEditor(); \
} \
}
#else
#define TICK_LEVEL_EDITOR(tickingStage)
#endif
void LevelService::Update()
{
TICK_LEVEL(Update, "Level::Update")
TICK_LEVEL_EDITOR(Update)
}
void LevelService::LateUpdate()
{
TICK_LEVEL(LateUpdate, "Level::LateUpdate")
TICK_LEVEL_EDITOR(LateUpdate)
flushActions();
}
void LevelService::FixedUpdate()
{
TICK_LEVEL(FixedUpdate, "Level::FixedUpdate")
TICK_LEVEL_EDITOR(FixedUpdate)
}
void LevelService::LateFixedUpdate()
{
TICK_LEVEL(LateFixedUpdate, "Level::LateFixedUpdate")
TICK_LEVEL_EDITOR(LateFixedUpdate)
}
#undef TICK_LEVEL
#undef TICK_LEVEL_EDITOR
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(RenderContextBatch& renderContextBatch, byte category)
{
PROFILE_CPU();
//ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
{
if (scene->IsActiveInHierarchy())
scene->Rendering.Draw(renderContextBatch, (SceneRendering::DrawCategory)category);
}
}
void Level::CollectPostFxVolumes(RenderContext& renderContext)
{
PROFILE_CPU();
//ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
{
if (scene->IsActiveInHierarchy())
scene->Rendering.CollectPostFxVolumes(renderContext);
}
}
class LoadSceneAction : public SceneAction
{
public:
Guid SceneId;
AssetReference<JsonAsset> SceneAsset;
LoadSceneAction(const Guid& sceneId, JsonAsset* sceneAsset)
{
SceneId = sceneId;
SceneAsset = sceneAsset;
}
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()))
{
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();
Level::ScriptsReloaded();
// Restore objects
for (auto& e : ScriptsReloadObjects)
{
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(e.TypeName);
*e.Object = ScriptingObject::NewObject(typeHandle);
if (!*e.Object)
{
LOG(Warning, "Failed to restore hot-reloaded object of type {0}.", String(e.TypeName));
continue;
}
auto* serializable = ScriptingObject::ToInterface<ISerializable>(*e.Object);
if (serializable && e.Data.HasItems())
{
JsonSerializer::LoadFromBytes(serializable, e.Data, FLAXENGINE_VERSION_BUILD);
}
}
ScriptsReloadObjects.Clear();
// 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))
{
LOG(Error, "Failed to deserialize scene {0}", scenes[i].Name);
CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, scenes[i].ID);
return true;
}
}
scenes.Resize(0);
// Fire event
LOG(Info, "Scripts reloading end. Total time: {0}ms", static_cast<int32>((DateTime::NowUTC() - startTime).GetTotalMilliseconds()));
Level::ScriptsReloadEnd();
return false;
}
};
void Level::ScriptsReloadRegisterObject(ScriptingObject*& obj)
{
if (!obj)
return;
auto& e = ScriptsReloadObjects.AddOne();
e.Object = &obj;
e.TypeName = obj->GetType().Fullname;
if (auto* serializable = ScriptingObject::ToInterface<ISerializable>(obj))
e.Data = JsonSerializer::SaveToBytes(serializable);
ScriptingObject* o = obj;
obj = nullptr;
o->DeleteObjectNow();
}
#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::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();
// Remove from scenes list
Level::Scenes.Remove(scene);
// Fire event
CallSceneEvent(SceneEventType::OnSceneUnloaded, scene, sceneId);
// Simple enqueue scene root object to be deleted
scene->DeleteObject();
// 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 = scenes.Count() - 1; i >= 0; i--)
{
if (unloadScene(scenes[i]))
return true;
}
return false;
}
bool Level::loadScene(const Guid& sceneId)
{
const auto sceneAsset = Content::LoadAsync<JsonAsset>(sceneId);
return loadScene(sceneAsset);
}
bool Level::loadScene(const String& scenePath)
{
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);
}
bool Level::loadScene(JsonAsset* sceneAsset)
{
// 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);
}
bool Level::loadScene(const BytesContainer& sceneData, 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, outScene);
}
bool Level::loadScene(rapidjson_flax::Document& document, 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, outScene);
}
bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, 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())
{
if (ScriptsBuilder::LastCompilationFailed())
MessageBox::Show(TEXT("Scripts compilation failed. Cannot load scene without game script modules. Please fix the compilation issues. See logs for more info."), TEXT("Failed to compile scripts"), MessageBoxButtons::OK, MessageBoxIcon::Error);
else
MessageBox::Show(TEXT("Failed to load scripts. Cannot load scene without game script modules. See logs for more info."), 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);
// Loaded scene objects list
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
sceneObjects->Resize(objectsCount);
sceneObjects->At(0) = scene;
SceneObjectsFactory::Context context(modifier.Value);
{
PROFILE_CPU_NAMED("Spawn");
// Spawn all scene objects
for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene
{
auto& stream = data[i];
auto obj = SceneObjectsFactory::Spawn(context, stream);
sceneObjects->At(i) = obj;
if (obj)
obj->RegisterObject();
else
SceneObjectsFactory::HandleObjectDeserializationError(stream);
}
}
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, data, modifier.Value);
SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData);
// TODO: resave and force sync scenes during game cooking so this step could be skipped in game
SceneObjectsFactory::SynchronizeNewPrefabInstances(context, prefabSyncData);
// /\ all above this has to be done on an any thread
// \/ all below this has to be done on multiple threads at once
{
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(context, obj, 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
// 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);
// Cache transformations
{
PROFILE_CPU_NAMED("Cache Transform");
scene->OnTransformChanged();
}
// Initialize scene objects
{
PROFILE_CPU_NAMED("Initialize");
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects->At(i);
if (obj)
{
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();
}
}
}
}
// /\ 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");
ScopeLock lock(ScenesLock);
Scenes.Add(scene);
SceneBeginData beginData;
scene->BeginPlay(&beginData);
beginData.OnDone();
}
// 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()));
#if USE_EDITOR
// Reload asset at the target location if is loaded
Asset* asset = Content::GetAsset(sceneId);
if (!asset)
asset = Content::GetAsset(path);
if (asset)
asset->Reload();
#endif
// 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)
{
// 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))
{
LOG(Error, "Failed to deserialize scene {0}", id);
CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, id);
return true;
}
return false;
}
Scene* Level::LoadSceneFromBytes(const BytesContainer& data)
{
Scene* scene = nullptr;
if (loadScene(data, &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));
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::TryFindObject<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;
}
Actor* Level::FindActor(const MClass* type, const StringView& name)
{
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, name);
return result;
}
Actor* FindActorRecursive(Actor* node, const Tag& tag)
{
if (node->HasTag(tag))
return node;
Actor* result = nullptr;
for (Actor* child : node->Children)
{
result = FindActorRecursive(child, tag);
if (result)
break;
}
return result;
}
Actor* FindActorRecursiveByType(Actor* node, const MClass* type, const Tag& tag)
{
CHECK_RETURN(type, nullptr);
if (node->HasTag(tag) && node->GetClass()->IsSubClassOf(type))
return node;
Actor* result = nullptr;
for (Actor* child : node->Children)
{
result = FindActorRecursiveByType(child, type, tag);
if (result)
break;
}
return result;
}
void FindActorsRecursive(Actor* node, const Tag& tag, const bool activeOnly, Array<Actor*>& result)
{
if (activeOnly && !node->GetIsActive())
return;
if (node->HasTag(tag))
result.Add(node);
for (Actor* child : node->Children)
FindActorsRecursive(child, tag, activeOnly, result);
}
void FindActorsRecursiveByParentTags(Actor* node, const Array<Tag>& tags, const bool activeOnly, Array<Actor*>& result)
{
if (activeOnly && !node->GetIsActive())
return;
for (Tag tag : tags)
{
if (node->HasTag(tag))
{
result.Add(node);
break;
}
}
for (Actor* child : node->Children)
FindActorsRecursiveByParentTags(child, tags, activeOnly, result);
}
Actor* Level::FindActor(const Tag& tag, Actor* root)
{
PROFILE_CPU();
if (root)
return FindActorRecursive(root, tag);
Actor* result = nullptr;
for (Scene* scene : Scenes)
{
result = FindActorRecursive(scene, tag);
if (result)
break;
}
return result;
}
Actor* Level::FindActor(const MClass* type, const Tag& tag, Actor* root)
{
CHECK_RETURN(type, nullptr);
if (root)
return FindActorRecursiveByType(root, type, tag);
Actor* result = nullptr;
ScopeLock lock(ScenesLock);
for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
result = Scenes[i]->FindActor(type, tag);
return result;
}
void FindActorRecursive(Actor* node, const Tag& tag, Array<Actor*>& result)
{
if (node->HasTag(tag))
result.Add(node);
for (Actor* child : node->Children)
FindActorRecursive(child, tag, result);
}
Array<Actor*> Level::FindActors(const Tag& tag, const bool activeOnly, Actor* root)
{
PROFILE_CPU();
Array<Actor*> result;
if (root)
{
FindActorsRecursive(root, tag, activeOnly, result);
}
else
{
ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursive(scene, tag, activeOnly, result);
}
return result;
}
Array<Actor*> Level::FindActorsByParentTag(const Tag& parentTag, const bool activeOnly, Actor* root)
{
PROFILE_CPU();
Array<Actor*> result;
const Array<Tag> subTags = Tags::GetSubTags(parentTag);
if (subTags.Count() == 0)
{
return result;
}
if (subTags.Count() == 1)
{
result = FindActors(subTags[0], activeOnly, root);
return result;
}
if (root)
{
FindActorsRecursiveByParentTags(root, subTags, activeOnly, result);
}
else
{
ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursiveByParentTags(scene, subTags, activeOnly, result);
}
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);
}
}