Added a PrefabDocument for Prefab to simplify working with Prefabs during conversion to spawnables.

The new PrefabDocument handles the Prefab and the Instance. This reduces the number of times the Instance has to be reloaded from the Prefab and keeps the entity ids stable between steps. The intention is for all the PrefabDocument to conceptually manipulate the Prefab DOM, although behind the scenes it will manipulate the Instance for now. The Instance should only be directly used in case the PrefabDocument doesn't provide the functionality yet.

Signed-off-by: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com>
This commit is contained in:
AMZN-koppersr
2021-12-14 15:02:04 -08:00
parent 9def902e1c
commit b8e3b27ea9
12 changed files with 230 additions and 116 deletions
@@ -417,6 +417,7 @@ namespace AzToolsFramework
bool readyToCreateRootSpawnable = m_playInEditorData.m_assetsCache.IsActivated();
if (!readyToCreateRootSpawnable &&
!m_playInEditorData.m_assetsCache.Activate(Prefab::PrefabConversionUtils::PlayInEditor))
{
AZ_Error("Prefab", false, "Failed to create a prefab processing stack from key '%.*s'.", AZ_STRING_ARG(Prefab::PrefabConversionUtils::PlayInEditor));
return;
@@ -37,7 +37,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
}
prefabProcessorContext.ListPrefabs(
[this, &serializeContext, &prefabProcessorContext]([[maybe_unused]] AZStd::string_view prefabName, PrefabDom& prefab)
[this, &serializeContext, &prefabProcessorContext]([[maybe_unused]] AZStd::string_view prefabName, PrefabDocument& prefab)
{
auto result = RemoveEditorInfo(prefab, serializeContext, prefabProcessorContext);
if (!result)
@@ -58,10 +58,9 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
}
}
void EditorInfoRemover::GetEntitiesFromInstance(
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>& instance, EntityList& hierarchyEntities)
void EditorInfoRemover::GetEntitiesFromInstance(AzToolsFramework::Prefab::Instance& instance, EntityList& hierarchyEntities)
{
instance->GetAllEntitiesInHierarchy(
instance.GetAllEntitiesInHierarchy(
[&hierarchyEntities](const AZStd::unique_ptr<AZ::Entity>& entity)
{
hierarchyEntities.emplace_back(entity.get());
@@ -498,7 +497,7 @@ exportComponent, prefabProcessorContext);
}
EditorInfoRemover::RemoveEditorInfoResult EditorInfoRemover::RemoveEditorInfo(
PrefabDom& prefab,
PrefabDocument& prefab,
AZ::SerializeContext* serializeContext,
PrefabProcessorContext& prefabProcessorContext)
{
@@ -510,28 +509,10 @@ exportComponent, prefabProcessorContext);
m_componentRequirementsValidator.SetPlatformTags(prefabProcessorContext.GetPlatformTags());
// convert Prefab DOM into Prefab Instance.
AZStd::unique_ptr<Instance> instance(aznew Instance());
if (!Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*instance, prefab,
Prefab::PrefabDomUtils::LoadFlags::AssignRandomEntityId))
{
PrefabDomValueReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName);
AZStd::string errorMessage("Failed to Load Prefab Instance from given Prefab Dom during Removal of Editor Info.");
if (sourceReference.has_value() &&
sourceReference->get().IsString() &&
sourceReference->get().GetStringLength() != 0)
{
AZStd::string_view source(sourceReference->get().GetString(), sourceReference->get().GetStringLength());
errorMessage += AZStd::string::format("Prefab Source: %.*s", AZ_STRING_ARG(source));
}
return AZ::Failure(errorMessage);
}
// grab all nested entities from the Instance as source entities.
Instance& sourceInstance = prefab.GetInstance();
EntityList sourceEntities;
GetEntitiesFromInstance(instance, sourceEntities);
GetEntitiesFromInstance(sourceInstance, sourceEntities);
EntityList exportEntities;
@@ -597,7 +578,7 @@ exportComponent, prefabProcessorContext);
exportEntitiesMap.emplace(entity->GetId(), entity);
}
);
instance->RemoveNestedEntities(
sourceInstance.RemoveNestedEntities(
[&exportEntitiesMap](const AZStd::unique_ptr<AZ::Entity>& entity)
{
return exportEntitiesMap.find(entity->GetId()) == exportEntitiesMap.end();
@@ -605,7 +586,7 @@ exportComponent, prefabProcessorContext);
);
// replace entities of instance with exported ones.
instance->GetAllEntitiesInHierarchy(
sourceInstance.GetAllEntitiesInHierarchy(
[&exportEntitiesMap](AZStd::unique_ptr<AZ::Entity>& entity)
{
auto entityId = entity->GetId();
@@ -614,16 +595,6 @@ exportComponent, prefabProcessorContext);
}
);
// save the final result in the target Prefab DOM.
PrefabDom filteredPrefab;
if (!PrefabDomUtils::StoreInstanceInPrefabDom(*instance, filteredPrefab))
{
return AZ::Failure(AZStd::string::format(
"Saving exported Prefab Instance within a Prefab Dom failed.")
);
}
prefab.Swap(filteredPrefab);
return AZ::Success();
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -43,7 +43,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
using RemoveEditorInfoResult = AZ::Outcome<void, AZStd::string>;
RemoveEditorInfoResult RemoveEditorInfo(
PrefabDom& prefab,
PrefabDocument& prefab,
AZ::SerializeContext* serializeContext,
PrefabProcessorContext& prefabProcessorContext);
@@ -51,8 +51,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
protected:
using EntityList = AZStd::vector<AZ::Entity*>;
static void GetEntitiesFromInstance(
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>& instance, EntityList& hierarchyEntities);
static void GetEntitiesFromInstance(AzToolsFramework::Prefab::Instance& instance, EntityList& hierarchyEntities);
static bool ReadComponentAttribute(
AZ::Component* component,
@@ -25,7 +25,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
AZ::DataStream::StreamType serializationFormat = m_serializationFormat == SerializationFormats::Binary ?
AZ::DataStream::StreamType::ST_BINARY : AZ::DataStream::StreamType::ST_XML;
context.ListPrefabs([&context, serializationFormat](AZStd::string_view prefabName, PrefabDom& prefab)
context.ListPrefabs([&context, serializationFormat](AZStd::string_view prefabName, PrefabDocument& prefab)
{
ProcessPrefab(context, prefabName, prefab, serializationFormat);
});
@@ -45,7 +45,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
}
}
void PrefabCatchmentProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab,
void PrefabCatchmentProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDocument& prefab,
AZ::DataStream::StreamType serializationFormat)
{
using namespace AzToolsFramework::Prefab::SpawnableUtils;
@@ -64,45 +64,34 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
AZStd::move(uniqueName), context.GetSourceUuid(), AZStd::move(serializer));
AZ_Assert(spawnable, "Failed to create a new spawnable.");
Instance instance;
if (Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(
instance, prefab, object.GetReferencedAssets(),
Prefab::PrefabDomUtils::LoadFlags::AssignRandomEntityId)) // Always assign random entity ids because the spawnable is
// going to be used to create clones of the entities.
{
// Resolve entity aliases that store PrefabDOM information to use the spawnable instead. This is done before the entities are
// moved from the instance as they'd otherwise can't be found.
context.ResolveSpawnableEntityAliases(prefabName, *spawnable, instance);
Instance& instance = prefab.GetInstance();
// Resolve entity aliases that store PrefabDOM information to use the spawnable instead. This is done before the entities are
// moved from the instance as they'd otherwise can't be found.
context.ResolveSpawnableEntityAliases(prefabName, *spawnable, instance);
AzFramework::Spawnable::EntityList& entities = spawnable->GetEntities();
instance.DetachAllEntitiesInHierarchy(
[&entities, &context](AZStd::unique_ptr<AZ::Entity> entity)
AzFramework::Spawnable::EntityList& entities = spawnable->GetEntities();
instance.DetachAllEntitiesInHierarchy(
[&entities, &context](AZStd::unique_ptr<AZ::Entity> entity)
{
if (entity)
{
if (entity)
entity->InvalidateDependencies();
AZ::Entity::DependencySortOutcome evaluation = entity->EvaluateDependenciesGetDetails();
if (evaluation.IsSuccess())
{
entity->InvalidateDependencies();
AZ::Entity::DependencySortOutcome evaluation = entity->EvaluateDependenciesGetDetails();
if (evaluation.IsSuccess())
{
entities.emplace_back(AZStd::move(entity));
}
else
{
AZ_Error(
"Prefabs", false, "Entity '%s' %s cannot be activated for the following reason: %s",
entity->GetName().c_str(), entity->GetId().ToString().c_str(), evaluation.GetError().m_message.c_str());
context.ErrorEncountered();
}
entities.emplace_back(AZStd::move(entity));
}
});
else
{
AZ_Error(
"Prefabs", false, "Entity '%s' %s cannot be activated for the following reason: %s",
entity->GetName().c_str(), entity->GetId().ToString().c_str(), evaluation.GetError().m_message.c_str());
context.ErrorEncountered();
}
}
});
SpawnableUtils::SortEntitiesByTransformHierarchy(*spawnable);
context.GetProcessedObjects().push_back(AZStd::move(object));
}
else
{
AZ_Error("Prefabs", false, "Failed to convert prefab '%.*s' to a spawnable.", AZ_STRING_ARG(prefabName));
context.ErrorEncountered();
}
SpawnableUtils::SortEntitiesByTransformHierarchy(*spawnable);
context.GetProcessedObjects().push_back(AZStd::move(object));
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -40,7 +40,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
static void Reflect(AZ::ReflectContext* context);
protected:
static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab,
static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDocument& prefab,
AZ::DataStream::StreamType serializationFormat);
SerializationFormats m_serializationFormat{ SerializationFormats::Binary };
@@ -0,0 +1,109 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabDocument.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
PrefabDocument::PrefabDocument(AZStd::string name)
: m_name(AZStd::move(name))
, m_instance(AZStd::make_unique<AzToolsFramework::Prefab::Instance>())
{
m_instance->SetTemplateSourcePath(AZ::IO::Path("InMemory") / name);
}
bool PrefabDocument::SetPrefabDom(const PrefabDom& prefab)
{
if (ConstructInstanceFromPrefabDom(prefab))
{
constexpr bool copyConstStrings = true;
m_dom.CopyFrom(prefab, m_dom.GetAllocator(), copyConstStrings);
return true;
}
else
{
return false;
}
}
bool PrefabDocument::SetPrefabDom(PrefabDom&& prefab)
{
if (ConstructInstanceFromPrefabDom(prefab))
{
m_dom = AZStd::move(prefab);
return true;
}
else
{
return false;
}
}
const AZStd::string& PrefabDocument::GetName() const
{
return m_name;
}
const PrefabDom& PrefabDocument::GetDom() const
{
if (m_isDirty)
{
m_isDirty = !PrefabDomUtils::StoreInstanceInPrefabDom(*m_instance, m_dom);
}
return m_dom;
}
PrefabDom&& PrefabDocument::TakeDom()
{
if (m_isDirty)
{
m_isDirty = !PrefabDomUtils::StoreInstanceInPrefabDom(*m_instance, m_dom);
}
return AZStd::move(m_dom);
}
AzToolsFramework::Prefab::Instance& PrefabDocument::GetInstance()
{
// Assume that changes will be made to the instance.
m_isDirty = true;
return *m_instance;
}
const AzToolsFramework::Prefab::Instance& PrefabDocument::GetInstance() const
{
return *m_instance;
}
bool PrefabDocument::ConstructInstanceFromPrefabDom(const PrefabDom& prefab)
{
using namespace AzToolsFramework::Prefab;
m_instance->Reset();
if (PrefabDomUtils::LoadInstanceFromPrefabDom(*m_instance, prefab, PrefabDomUtils::LoadFlags::AssignRandomEntityId))
{
return true;
}
else
{
AZStd::string errorMessage("Failed to construct Prefab instance from given PrefabDOM");
PrefabDomValueConstReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName);
if (sourceReference.has_value() && sourceReference->get().IsString() && sourceReference->get().GetStringLength() != 0)
{
errorMessage += " (Source: ";
errorMessage += AZStd::string_view(sourceReference->get().GetString(), sourceReference->get().GetStringLength());
errorMessage += ')';
}
errorMessage += '.';
AZ_Error("PrefabDocument", false, errorMessage.c_str());
return false;
}
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,47 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class PrefabDocument final
{
public:
explicit PrefabDocument(AZStd::string name);
PrefabDocument(const PrefabDocument&) = delete;
PrefabDocument(PrefabDocument&&) = default;
PrefabDocument& operator=(const PrefabDocument&) = delete;
PrefabDocument& operator=(PrefabDocument&&) = default;
bool SetPrefabDom(const PrefabDom& prefab);
bool SetPrefabDom(PrefabDom&& prefab);
const AZStd::string& GetName() const;
const PrefabDom& GetDom() const;
PrefabDom&& TakeDom();
// Where possible, prefer functions directly on the PrefabDocument Instead of using the Instance.
AzToolsFramework::Prefab::Instance& GetInstance();
const AzToolsFramework::Prefab::Instance& GetInstance() const;
private:
bool ConstructInstanceFromPrefabDom(const PrefabDom& prefab);
mutable PrefabDom m_dom;
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> m_instance;
AZStd::string m_name;
mutable bool m_isDirty{ false };
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -10,6 +10,7 @@
#include <AzCore/Component/EntityUtils.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabDocument.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
#include <AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h>
@@ -31,49 +32,39 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
: m_sourceUuid(sourceUuid)
{}
bool PrefabProcessorContext::AddPrefab(AZStd::string prefabName, PrefabDom prefab)
bool PrefabProcessorContext::AddPrefab(PrefabDocument&& document)
{
if (!m_isIterating)
AZStd::string name = document.GetName();
if (!m_prefabNames.contains(name))
{
auto result = m_prefabs.emplace(AZStd::move(prefabName), AZStd::move(prefab));
return result.second;
}
else
{
auto it = m_prefabs.find(prefabName);
if (it == m_prefabs.end())
{
auto result = m_pendingPrefabAdditions.emplace(AZStd::move(prefabName), AZStd::move(prefab));
return result.second;
}
else
{
return false;
}
m_prefabNames.emplace(AZStd::move(name));
PrefabContainer& container = m_isIterating ? m_pendingPrefabAdditions : m_prefabs;
container.push_back(AZStd::move(document));
return true;
}
return false;
}
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(AZStd::string_view, PrefabDom&)>& callback)
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(AZStd::string_view, PrefabDocument&)>& callback)
{
m_isIterating = true;
for (auto& it : m_prefabs)
for (PrefabDocument& document : m_prefabs)
{
callback(it.first, it.second);
callback(document.GetName(), document);
}
m_isIterating = false;
for (auto& prefab : m_pendingPrefabAdditions)
{
m_prefabs.emplace(AZStd::move(prefab.first), AZStd::move(prefab.second));
}
m_prefabs.insert(
m_prefabs.end(), AZStd::make_move_iterator(m_pendingPrefabAdditions.begin()),
AZStd::make_move_iterator(m_pendingPrefabAdditions.end()));
m_pendingPrefabAdditions.clear();
}
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDom&)>& callback) const
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDocument&)>& callback) const
{
for (const auto& it : m_prefabs)
for (const PrefabDocument& document : m_prefabs)
{
callback(it.first, it.second);
callback(document.GetName(), document);
}
}
@@ -21,6 +21,7 @@
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabDocument.h>
#include <AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -93,9 +94,9 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
explicit PrefabProcessorContext(const AZ::Uuid& sourceUuid);
virtual ~PrefabProcessorContext() = default;
virtual bool AddPrefab(AZStd::string prefabName, PrefabDom prefab);
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, PrefabDom&)>& callback);
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDom&)>& callback) const;
virtual bool AddPrefab(PrefabDocument&& document);
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, PrefabDocument&)>& callback);
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDocument&)>& callback) const;
virtual bool HasPrefabs() const;
virtual bool RegisterSpawnableProductAssetDependency(
@@ -128,13 +129,15 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
virtual void ErrorEncountered();
protected:
using NamedPrefabContainer = AZStd::unordered_map<AZStd::string, PrefabDom>;
using PrefabNames = AZStd::unordered_set<AZStd::string>;
using PrefabContainer = AZStd::vector<PrefabDocument>;
using SpawnableEntityAliasStore = AZStd::vector<EntityAliasStore>;
AZ::Data::AssetLoadBehavior ToAssetLoadBehavior(EntityAliasSpawnableLoadBehavior loadBehavior) const;
NamedPrefabContainer m_prefabs;
NamedPrefabContainer m_pendingPrefabAdditions;
PrefabContainer m_prefabs;
PrefabContainer m_pendingPrefabAdditions;
PrefabNames m_prefabNames;
SpawnableEntityAliasStore m_entityAliases;
ProcessedObjectStoreContainer m_products;
ProductAssetDependencyContainer m_registeredProductAssetDependencies;
@@ -730,6 +730,8 @@ set(FILES
Prefab/Spawnable/PrefabConversionPipeline.h
Prefab/Spawnable/PrefabConversionPipeline.cpp
Prefab/Spawnable/PrefabConverterStackProfileNames.h
Prefab/Spawnable/PrefabDocument.h
Prefab/Spawnable/PrefabDocument.cpp
Prefab/Spawnable/ProcesedObjectStore.h
Prefab/Spawnable/ProcesedObjectStore.cpp
Prefab/Spawnable/PrefabProcessor.h
@@ -237,7 +237,7 @@ namespace AZ::Prefab
bool PrefabBuilderComponent::ProcessPrefab(
const AZ::PlatformTagSet& platformTags, const char* filePath, AZ::IO::PathView tempDirPath, const AZ::Uuid& sourceFileUuid,
AzToolsFramework::Prefab::PrefabDom& mutableRootDom, AZStd::vector<AssetBuilderSDK::JobProduct>& jobProducts)
AzToolsFramework::Prefab::PrefabDom&& rootDom, AZStd::vector<AssetBuilderSDK::JobProduct>& jobProducts)
{
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext context(sourceFileUuid);
AZStd::string rootPrefabName;
@@ -247,7 +247,9 @@ namespace AZ::Prefab
filePath);
return false;
}
context.AddPrefab(AZStd::move(rootPrefabName), AZStd::move(mutableRootDom));
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument rootDocument(AZStd::move(rootPrefabName));
rootDocument.SetPrefabDom(AZStd::move(rootDom));
context.AddPrefab(AZStd::move(rootDocument));
context.SetPlatformTags(AZStd::move(platformTags));
@@ -319,8 +321,8 @@ namespace AZ::Prefab
});
if (ProcessPrefab(
platformTags, request.m_fullPath.c_str(), request.m_tempDirPath.c_str(), request.m_sourceFileUUID, mutableRootDom,
response.m_outputProducts))
platformTags, request.m_fullPath.c_str(), request.m_tempDirPath.c_str(), request.m_sourceFileUUID,
AZStd::move(mutableRootDom), response.m_outputProducts))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
@@ -53,7 +53,7 @@ namespace AZ::Prefab
const AzToolsFramework::Prefab::PrefabDom& genericDocument);
bool ProcessPrefab(
const AZ::PlatformTagSet& platformTags, const char* filePath, AZ::IO::PathView tempDirPath, const AZ::Uuid& sourceFileUuid,
AzToolsFramework::Prefab::PrefabDom& mutableRootDom,
AzToolsFramework::Prefab::PrefabDom&& rootDom,
AZStd::vector<AssetBuilderSDK::JobProduct>& jobProducts);
protected: