Files
FlaxEngine/Source/Engine/Level/Prefabs/PrefabManager.cpp
2021-07-07 15:15:33 +02:00

548 lines
17 KiB
C++

// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "PrefabManager.h"
#include "../Scene/Scene.h"
#include "Engine/Debug/Exceptions/ArgumentNullException.h"
#include "Engine/Level/Prefabs/Prefab.h"
#include "Engine/Level/SceneObjectsFactory.h"
#include "Engine/Level/SceneQuery.h"
#include "Engine/Level/ActorsCache.h"
#include "Engine/Content/Content.h"
#include "Engine/ContentImporters/CreateJson.h"
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Core/Collections/CollectionPoolCache.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Core/Cache.h"
#include "Engine/Debug/Exceptions/ArgumentException.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Scripting/Scripting.h"
#if USE_EDITOR
bool PrefabManager::IsCreatingPrefab = false;
bool PrefabManager::IsNotCreatingPrefab = true;
Dictionary<Guid, Array<Actor*>> PrefabManager::PrefabsReferences;
CriticalSection PrefabManager::PrefabsReferencesLocker;
#endif
class PrefabManagerService : public EngineService
{
public:
PrefabManagerService()
: EngineService(TEXT("Prefab Manager"), 110)
{
}
};
PrefabManagerService PrefabManagerServiceInstance;
Actor* PrefabManager::SpawnPrefab(Prefab* prefab)
{
auto scene = Level::GetScene(0);
if (!scene)
return nullptr;
return SpawnPrefab(prefab, scene, nullptr);
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position)
{
auto instance = SpawnPrefab(prefab);
if (instance)
{
instance->SetPosition(position);
}
return instance;
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position, const Quaternion& rotation)
{
auto instance = SpawnPrefab(prefab);
if (instance)
{
auto transform = instance->GetTransform();
transform.Translation = position;
transform.Orientation = rotation;
instance->SetTransform(transform);
}
return instance;
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position, const Quaternion& rotation, const Vector3& scale)
{
auto instance = SpawnPrefab(prefab);
if (instance)
{
Transform transform;
transform.Translation = position;
transform.Orientation = rotation;
transform.Scale = scale;
instance->SetTransform(transform);
}
return instance;
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform)
{
auto instance = SpawnPrefab(prefab);
if (instance)
{
instance->SetTransform(transform);
}
return instance;
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, const void*>* objectsCache, bool withSynchronization)
{
PROFILE_CPU_NAMED("Prefab.Spawn");
// Validate input
if (prefab == nullptr)
{
Log::ArgumentNullException();
return nullptr;
}
if (prefab->WaitForLoaded())
{
LOG(Warning, "Waiting for prefab asset be loaded failed. {0}", prefab->ToString());
return nullptr;
}
const int32 objectsCount = prefab->ObjectsCount;
if (objectsCount == 0)
{
LOG(Warning, "Prefab has no objects. {0}", prefab->ToString());
return nullptr;
}
// Note: we need to generate unique Ids for the deserialized objects (actors and scripts) to prevent Ids collisions
// Prefab asset during loading caches the object Ids stored inside the file
const Guid prefabId = prefab->GetID();
// Prepare
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
sceneObjects->Resize(objectsCount);
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache prefabDataIndexToSceneObject = ActorsCache::SceneObjectsListCache.Get();
prefabDataIndexToSceneObject->Resize(objectsCount);
CollectionPoolCache<ISerializeModifier, Cache::ISerializeModifierClearCallback>::ScopeCache modifier = Cache::ISerializeModifier.Get();
modifier->IdsMapping.EnsureCapacity(prefab->ObjectsIds.Count() * 4);
for (int32 i = 0; i < prefab->ObjectsIds.Count(); i++)
{
modifier->IdsMapping.Add(prefab->ObjectsIds[i], Guid::New());
}
if (objectsCache)
{
objectsCache->Clear();
objectsCache->SetCapacity(prefab->ObjectsDataCache.Capacity());
}
// Deserialize prefab objects
auto& data = *prefab->Data;
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
for (int32 i = 0; i < objectsCount; i++)
{
auto& stream = data[i];
SceneObject* obj = SceneObjectsFactory::Spawn(stream, modifier.Value);
prefabDataIndexToSceneObject->operator[](i) = obj;
sceneObjects->At(i) = obj;
if (obj)
{
obj->RegisterObject();
}
else
{
SceneObjectsFactory::HandleObjectDeserializationError(stream);
}
}
for (int32 i = 0; i < objectsCount; i++)
{
auto& stream = data[i];
SceneObject* obj = prefabDataIndexToSceneObject->At(i);
if (obj)
{
SceneObjectsFactory::Deserialize(obj, stream, modifier.Value);
}
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
// Assume that prefab has always only one root actor that is serialized first
if (sceneObjects.Value->IsEmpty())
{
LOG(Warning, "No valid objects in prefab.");
return nullptr;
}
auto root = (Actor*)sceneObjects.Value->At(0);
if (!root)
{
LOG(Warning, "Failed to load prefab root object.");
return nullptr;
}
// Prepare parent linkage for prefab root actor
root->_parent = parent;
if (parent)
parent->Children.Add(root);
// Link actors hierarchy
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
auto 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)
if (withSynchronization)
{
// 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;
for (int32 i = 0; i < objectsCount; i++)
{
auto& stream = data[i];
Actor* actor = dynamic_cast<Actor*>(prefabDataIndexToSceneObject->At(i));
if (!actor)
continue;
// Check for RemovedObjects listing
const auto removedObjects = stream.FindMember("RemovedObjects");
if (removedObjects != stream.MemberEnd())
{
actorToRemovedObjectsData.Add(actor, &removedObjects->value);
}
}
// TODO: consider caching actorToRemovedObjectsData per prefab
SceneObjectsFactory::SynchronizePrefabInstances(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value);
}
// Delete objects without parent or with invalid linkage to the prefab
for (int32 i = 1; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects->At(i);
if (!obj)
continue;
// Check for missing parent (eg. parent object has been deleted)
if (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();
continue;
}
#if USE_EDITOR && !BUILD_RELEASE
// Check for not being added to the parent (eg. invalid setup events fault on registration)
auto actor = dynamic_cast<Actor*>(obj);
auto script = dynamic_cast<Script*>(obj);
if (obj->GetParent() == obj || (actor && !actor->GetParent()->Children.Contains(actor)) || (script && !script->GetParent()->Scripts.Contains(script)))
{
sceneObjects->At(i) = nullptr;
LOG(Warning, "Scene object {0} {1} has invalid parent object linkage after load. Removing it.", obj->GetID(), obj->ToString());
obj->DeleteObject();
continue;
}
#endif
#if USE_EDITOR && BUILD_DEBUG
// Check for being added to parent not from spawned prefab (eg. invalid parentId linkage fault)
bool hasParentInInstance = false;
for (int32 j = 0; j < sceneObjects->Count(); j++)
{
if (sceneObjects->At(j) == obj->GetParent())
{
hasParentInInstance = true;
break;
}
}
if (!hasParentInInstance)
{
sceneObjects->At(i) = nullptr;
LOG(Warning, "Scene object {0} {1} has invalid parent object after load. Removing it.", obj->GetID(), obj->ToString());
obj->DeleteObject();
continue;
}
#endif
}
// Link objects to prefab (only deserialized from prefab data)
for (int32 i = 0; i < objectsCount; i++)
{
auto& stream = data[i];
SceneObject* obj = prefabDataIndexToSceneObject->At(i);
if (!obj)
continue;
const auto prefabObjectId = JsonTools::GetGuid(stream, "ID");
if (objectsCache)
objectsCache->Add(prefabObjectId, dynamic_cast<ScriptingObject*>(obj));
obj->LinkPrefab(prefabId, prefabObjectId);
}
// Update transformations
root->OnTransformChanged();
// Spawn if need to
if (parent && parent->IsDuringPlay())
{
// Begin play
SceneBeginData beginData;
root->BeginPlay(&beginData);
beginData.OnDone();
// Send event
Level::callActorEvent(Level::ActorEventType::OnActorSpawned, root, nullptr);
}
return root;
}
#if USE_EDITOR
bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPath, bool autoLink)
{
PROFILE_CPU_NAMED("Prefab.Create");
// Validate input
if (targetActor == nullptr)
{
Log::ArgumentNullException();
return true;
}
if (dynamic_cast<Scene*>(targetActor) != nullptr)
{
LOG(Error, "Cannot create prefab from scene actor.");
return true;
}
if ((targetActor->HideFlags & HideFlags::DontSave) != 0)
{
LOG(Error, "Cannot create prefab from actor marked with HideFlags.DontSave.");
return true;
}
// Gather all objects
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
sceneObjects->EnsureCapacity(256);
SceneQuery::GetAllSerializableSceneObjects(targetActor, *sceneObjects.Value);
// Filter actors for prefab
if (FilterPrefabInstancesToSave(Guid::Empty, *sceneObjects.Value))
return true;
LOG(Info, "Creating prefab from actor {0} (total objects count: {2}) to {1}...", targetActor->ToString(), outputPath, sceneObjects->Count());
// Serialize to json data
IsCreatingPrefab = true;
IsNotCreatingPrefab = false;
rapidjson_flax::StringBuffer actorsDataBuffer;
{
CompactJsonWriter writerObj(actorsDataBuffer);
JsonWriter& writer = writerObj;
writer.StartArray();
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects.Value->At(i);
writer.SceneObject(obj);
}
writer.EndArray();
}
IsCreatingPrefab = false;
IsNotCreatingPrefab = true;
// Randomize the objects ids (prevent overlapping of the prefab instance objects ids and the prefab objects ids)
Dictionary<Guid, Guid> objectInstanceIdToPrefabObjectId;
objectInstanceIdToPrefabObjectId.EnsureCapacity(sceneObjects->Count() * 3);
if (targetActor->HasParent())
{
// Unlink from parent actor
objectInstanceIdToPrefabObjectId.Add(targetActor->GetParent()->GetID(), Guid::Empty);
}
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
// Generate new IDs for the prefab objects (other than reference instance used to create prefab)
const SceneObject* obj = sceneObjects.Value->At(i);
objectInstanceIdToPrefabObjectId.Add(obj->GetSceneObjectId(), Guid::New());
}
{
// Parse json to DOM document
rapidjson_flax::Document doc;
{
PROFILE_CPU_NAMED("Json.Parse");
doc.Parse(actorsDataBuffer.GetString(), actorsDataBuffer.GetSize());
}
if (doc.HasParseError())
{
LOG(Warning, "Failed to parse serialized actors data.");
return true;
}
// Process json
JsonTools::ChangeIds(doc, objectInstanceIdToPrefabObjectId);
// Save back to text
actorsDataBuffer.Clear();
PrettyJsonWriter writer(actorsDataBuffer);
doc.Accept(writer.GetWriter());
}
// Save to file
#if COMPILE_WITH_ASSETS_IMPORTER
if (CreateJson::Create(outputPath, actorsDataBuffer, Prefab::TypeName))
{
LOG(Warning, "Failed to serialize prefab data to the asset.");
return true;
}
#else
#error "Cannot support prefabs creating without assets importing enabled."
#endif
// Auto link objects
if (autoLink)
{
LOG(Info, "Linking objects to prefab");
AssetInfo assetInfo;
if (!Content::GetAssetInfo(outputPath, assetInfo))
return true;
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects.Value->At(i);
Guid prefabObjectId;
if (objectInstanceIdToPrefabObjectId.TryGet(obj->GetSceneObjectId(), prefabObjectId))
{
obj->LinkPrefab(assetInfo.ID, prefabObjectId);
}
}
}
LOG(Info, "Prefab created!");
return false;
}
bool FindPrefabLink(const Guid& targetPrefabId, const Guid& prefabId)
{
// Get prefab asset
auto prefab = Content::LoadAsync<Prefab>(prefabId);
if (prefab == nullptr)
{
Log::Exception(TEXT("Missing prefab asset."));
return true;
}
if (prefab->WaitForLoaded())
{
Log::Exception(TEXT("Failed to load prefab asset."));
return true;
}
// TODO: check if prefab has any reference to targetPrefabId, then do recursive call to nested prefabs inside the prefab
return false;
}
bool PrefabManager::FilterPrefabInstancesToSave(const Guid& prefabId, Array<SceneObject*>& objects, bool showWarning)
{
#if 0 // Done by collecting actors without DontSave flag
// Remove hidden actors for saving
for (int32 i = 0; i < actors.Count() && actors.HasItems(); i++)
{
auto actor = actors[i];
if ((actor->HideFlags & HideFlags::DontSave) != 0)
{
actors.RemoveAt(i);
i--;
}
}
#endif
// Validate circular references
if (prefabId.IsValid())
{
bool hasLoopPrefabRef = false;
for (int32 i = 0; i < objects.Count() && objects.HasItems(); i++)
{
SceneObject* obj = objects[i];
if (obj->GetPrefabID().IsValid() && FindPrefabLink(prefabId, obj->GetPrefabID()))
{
hasLoopPrefabRef = true;
objects.RemoveAt(i);
i--;
}
}
if (hasLoopPrefabRef && showWarning)
{
LOG(Error, "Cannot setup prefab with circular reference to itself.");
}
}
// Check if list is empty (after validation)
if (objects.IsEmpty())
{
LOG(Warning, "Cannot create prefab. No valid objects to use.");
return true;
}
return false;
}
bool PrefabManager::ApplyAll(Actor* instance)
{
PROFILE_CPU_NAMED("Prefab.ApplyAll");
// Validate input
if (instance == nullptr)
{
Log::ArgumentNullException();
return true;
}
if (!instance->HasPrefabLink() || instance->GetPrefabID() == Guid::Empty)
{
Log::ArgumentException(TEXT("The modified actor instance has missing prefab link."));
return true;
}
// Get prefab asset
auto prefab = Content::LoadAsync<Prefab>(instance->GetPrefabID());
if (prefab == nullptr)
{
Log::Exception(TEXT("Missing prefab asset."));
return true;
}
if (prefab->WaitForLoaded())
{
Log::Exception(TEXT("Failed to load prefab asset."));
return true;
}
// Get root object of this prefab instance
// Note: given actor instance can be already part of the spawned prefab or be a new actor added to the prefab instance
// Find the actual root of the prefab instance
const auto rootObjectId = prefab->GetRootObjectId();
auto rootObjectInstance = instance;
while (rootObjectInstance && rootObjectInstance->GetPrefabObjectID() != rootObjectId)
{
rootObjectInstance = rootObjectInstance->GetParent();
}
if (rootObjectInstance == nullptr)
{
// Use the input object as fallback
rootObjectInstance = instance;
}
return prefab->ApplyAll(rootObjectInstance);
}
#endif