Files
o3de/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp
T
Danilo Aimini 80f62d0523 LYN-3708 | Optimize Prefab instance propagation to stabilize UX (#700)
* Add instanceToIgnore to calls leading to instances being added to the queue for propagation.

* Change PrefabUndoEntityUpdate to make it so that the instance triggering the prefab template change is not reloaded on propagation, since it will already be up to date due to the way we generated the patch to begin with.

* Add FindPrefabDomValue utility function for paths

* Expose the level root prefab template id in the Prefab EOS Interface

* Fix Instance Alias Path generation to work with the new FindValueInPrefabDom function

* Stop reloading ancestors on propagation, and fix instance reloading so that the level dom is used (and overrides are preserved)

* Remove commented out code, refactor FindPrefabDomValue for paths (was handling an edge case incorrectly, and it's not even triggered)

* Fix issue with PathView reference - with PathView already being a reference, this resulted in a copy and triggered a warning during automated review builds.

* Additional fix to the build warning, remove redundant error message

* Revert changes to Instance::GetAbsoluteInstanceAliasPath(), as they were impacting serialization.

* Remove the dependency to the level root prefab template in the propagation code, climb up the hierarchy instead. This allows tests to work despite not using the EOS properly.
Also use PrefabDomPaths to retrieve the instance dom from the root dom instead of iterating.

* Remove now unused PrefabDomUtils function, extend optimization to link updates.

* Trigger a full instance propagation to correctly refresh alias references.
This is an issue in the test because some operations are called from the backend API and will not trigger propagation properly. Tests will soon be rewritten to more properly represent frontend workflows.

* Fixes lingering issues with propagation:
- Restores code that fixes the selection if entityIds have changed;
- Fixes Do() function on link update. Prefab containers will propagate correctly while still being stable during editing.

* Remove GetRootPrefabInstanceTemplateId (no longer necessary after the code has been rewritten)

* Fix optimization code to account for instances being removed and propagation being run out of order in Create Prefab undo.

* Renamed variable, added comments for clarity.

* Restore asserts on instance not being found; Rename Do to Redo for clarity; Add comments.

* Fixed incomplete comment.
2021-06-08 10:44:20 -07:00

965 lines
41 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Prefab/PrefabSystemComponent.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
#include <AzToolsFramework/Prefab/Instance/InstanceSerializer.h>
#include <AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
namespace AzToolsFramework
{
namespace Prefab
{
void PrefabSystemComponent::Init()
{
}
void PrefabSystemComponent::Activate()
{
AZ::Interface<PrefabSystemComponentInterface>::Register(this);
m_prefabLoader.RegisterPrefabLoaderInterface();
m_instanceUpdateExecutor.RegisterInstanceUpdateExecutorInterface();
m_instanceToTemplatePropagator.RegisterInstanceToTemplateInterface();
m_prefabPublicHandler.RegisterPrefabPublicHandlerInterface();
AZ::SystemTickBus::Handler::BusConnect();
}
void PrefabSystemComponent::Deactivate()
{
AZ::SystemTickBus::Handler::BusDisconnect();
m_prefabPublicHandler.UnregisterPrefabPublicHandlerInterface();
m_instanceToTemplatePropagator.UnregisterInstanceToTemplateInterface();
m_instanceUpdateExecutor.UnregisterInstanceUpdateExecutorInterface();
m_prefabLoader.UnregisterPrefabLoaderInterface();
AZ::Interface<PrefabSystemComponentInterface>::Unregister(this);
}
void PrefabSystemComponent::Reflect(AZ::ReflectContext* context)
{
Instance::Reflect(context);
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabConversionPipeline::Reflect(context);
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor::Reflect(context);
AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover::Reflect(context);
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<PrefabSystemComponent, AZ::Component>()->Version(1);
}
AZ::JsonRegistrationContext* jsonRegistration = azrtti_cast<AZ::JsonRegistrationContext*>(context);
if (jsonRegistration)
{
jsonRegistration->Serializer<JsonInstanceSerializer>()->HandlesType<Instance>();
}
}
void PrefabSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("PrefabSystem"));
}
void PrefabSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
{
}
void PrefabSystemComponent::GetIncompatibleServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
}
void PrefabSystemComponent::OnSystemTick()
{
m_instanceUpdateExecutor.UpdateTemplateInstancesInQueue();
}
AZStd::unique_ptr<Instance> PrefabSystemComponent::CreatePrefab(
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume,
AZ::IO::PathView filePath, AZStd::unique_ptr<AZ::Entity> containerEntity, bool shouldCreateLinks)
{
AZ::IO::Path relativeFilePath = m_prefabLoader.GenerateRelativePath(filePath);
if (GetTemplateIdFromFilePath(relativeFilePath) != InvalidTemplateId)
{
AZ_Error("Prefab", false,
"Filepath %s has already been registered with the Prefab System Component",
relativeFilePath.c_str());
return nullptr;
}
AZStd::unique_ptr<Instance> newInstance = AZStd::make_unique<Instance>(AZStd::move(containerEntity));
for (AZ::Entity* entity : entities)
{
AZ_Assert(entity, "Prefab - Null entity passed in during Create Prefab");
newInstance->AddEntity(*entity);
}
for (AZStd::unique_ptr<Instance>& instance : instancesToConsume)
{
AZ_Assert(instance, "Prefab - Null instance passed in during Create Prefab");
newInstance->AddInstance(AZStd::move(instance));
}
newInstance->SetTemplateSourcePath(relativeFilePath);
newInstance->SetContainerEntityName(relativeFilePath.Stem().Native());
TemplateId newTemplateId = CreateTemplateFromInstance(*newInstance, shouldCreateLinks);
if (newTemplateId == InvalidTemplateId)
{
AZ_Error("Prefab", false,
"Failed to create a Template associated with file path %s during CreatePrefab.",
relativeFilePath.c_str());
newInstance = nullptr;
}
else
{
newInstance->SetTemplateId(newTemplateId);
}
return newInstance;
}
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude)
{
UpdatePrefabInstances(templateId, instanceToExclude);
auto templateIdToLinkIdsIterator = m_templateToLinkIdsMap.find(templateId);
if (templateIdToLinkIdsIterator != m_templateToLinkIdsMap.end())
{
// We need to initialize a queue here because once all linked instances of a template are updated,
// we will find all the linkIds corresponding to the updated template and add them to this queue again.
AZStd::queue<LinkIds> linkIdsToUpdateQueue;
linkIdsToUpdateQueue.push(LinkIds(templateIdToLinkIdsIterator->second.begin(),
templateIdToLinkIdsIterator->second.end()));
UpdateLinkedInstances(linkIdsToUpdateQueue);
}
}
void PrefabSystemComponent::UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom)
{
auto templateToUpdate = FindTemplate(templateId);
if (templateToUpdate)
{
PrefabDom& templateDomToUpdate = templateToUpdate->get().GetPrefabDom();
if (AZ::JsonSerialization::Compare(templateDomToUpdate, updatedDom) != AZ::JsonSerializerCompareResult::Equal)
{
templateDomToUpdate.CopyFrom(updatedDom, templateDomToUpdate.GetAllocator());
templateToUpdate->get().MarkAsDirty(true);
PropagateTemplateChanges(templateId);
}
}
}
void PrefabSystemComponent::UpdatePrefabInstances(const TemplateId& templateId, InstanceOptionalReference instanceToExclude)
{
m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId, instanceToExclude);
}
void PrefabSystemComponent::UpdateLinkedInstances(AZStd::queue<LinkIds>& linkIdsQueue)
{
while (!linkIdsQueue.empty())
{
// Fetch the list of linkIds at the head of the queue.
LinkIds& LinkIdsToUpdate = linkIdsQueue.front();
TargetTemplateIdToLinkIdMap targetTemplateIdToLinkIdMap;
BucketLinkIdsByTargetTemplateId(LinkIdsToUpdate, targetTemplateIdToLinkIdMap);
// Update all the linked instances corresponding to the LinkIds before fetching the next set of linkIds.
// This will ensure that templates are updated with changes in the same order they are received.
for (const LinkId& linkIdToUpdate : LinkIdsToUpdate)
{
UpdateLinkedInstance(linkIdToUpdate, targetTemplateIdToLinkIdMap, linkIdsQueue);
}
linkIdsQueue.pop();
}
}
void PrefabSystemComponent::BucketLinkIdsByTargetTemplateId(LinkIds& linkIdsToUpdate,
TargetTemplateIdToLinkIdMap& targetTemplateIdToLinkIdMap)
{
for (const LinkId& linkIdToUpdate : linkIdsToUpdate)
{
Link& linkToUpdate = m_linkIdMap[linkIdToUpdate];
TemplateId targetTemplateId = linkToUpdate.GetTargetTemplateId();
auto templateIdToLinkIdsIterator = targetTemplateIdToLinkIdMap.find(targetTemplateId);
if (templateIdToLinkIdsIterator == targetTemplateIdToLinkIdMap.end())
{
targetTemplateIdToLinkIdMap.emplace(targetTemplateId, AZStd::make_pair(LinkIdSet{linkIdToUpdate}, false));
}
else
{
targetTemplateIdToLinkIdMap[targetTemplateId].first.insert(linkIdToUpdate);
}
}
}
void PrefabSystemComponent::UpdateLinkedInstance(const LinkId linkIdToUpdate,
TargetTemplateIdToLinkIdMap& targetTemplateIdToLinkIdMap, AZStd::queue<LinkIds>& linkIdsQueue)
{
Link& linkToUpdate = m_linkIdMap[linkIdToUpdate];
TemplateId targetTemplateId = linkToUpdate.GetTargetTemplateId();
PrefabDomValue& linkdedInstanceDom = linkToUpdate.GetLinkedInstanceDom();
PrefabDomValue linkDomBeforeUpdate;
linkDomBeforeUpdate.CopyFrom(linkdedInstanceDom, m_templateIdMap[targetTemplateId].GetPrefabDom().GetAllocator());
linkToUpdate.UpdateTarget();
// If any of the templates links are already updated, we don't need to check whether the linkedInstance DOM differs
// in content because the template is already marked to be sent for change propagation.
bool isTemplateUpdated = targetTemplateIdToLinkIdMap[targetTemplateId].second;
if (isTemplateUpdated ||
AZ::JsonSerialization::Compare(linkDomBeforeUpdate, linkdedInstanceDom) != AZ::JsonSerializerCompareResult::Equal)
{
targetTemplateIdToLinkIdMap[targetTemplateId].second = true;
}
if (targetTemplateIdToLinkIdMap.find(targetTemplateId) != targetTemplateIdToLinkIdMap.end())
{
targetTemplateIdToLinkIdMap[targetTemplateId].first.erase(linkIdToUpdate);
UpdateTemplateChangePropagationQueue(targetTemplateIdToLinkIdMap, targetTemplateId, linkIdsQueue);
}
}
void PrefabSystemComponent::UpdateTemplateChangePropagationQueue(
TargetTemplateIdToLinkIdMap& targetTemplateIdToLinkIdMap,
const TemplateId targetTemplateId, AZStd::queue<LinkIds>& linkIdsQueue)
{
if (targetTemplateIdToLinkIdMap[targetTemplateId].first.empty() &&
targetTemplateIdToLinkIdMap[targetTemplateId].second)
{
auto templateToLinkIter = m_templateToLinkIdsMap.find(targetTemplateId);
if (templateToLinkIter != m_templateToLinkIdsMap.end())
{
linkIdsQueue.push(LinkIds(templateToLinkIter->second.begin(),
templateToLinkIter->second.end()));
}
}
}
AZStd::unique_ptr<Instance> PrefabSystemComponent::InstantiatePrefab(AZ::IO::PathView filePath)
{
// Retrieve the template id for the source prefab filepath
Prefab::TemplateId templateId = GetTemplateIdFromFilePath(filePath);
if (templateId == Prefab::InvalidTemplateId)
{
// Load the template from the file
templateId = m_prefabLoader.LoadTemplateFromFile(filePath);
}
if (templateId == Prefab::InvalidTemplateId)
{
AZ_Error("Prefab", false,
"Could not load template from path %s during InstantiatePrefab. Unable to proceed",
filePath);
return nullptr;
}
return InstantiatePrefab(templateId);
}
AZStd::unique_ptr<Instance> PrefabSystemComponent::InstantiatePrefab(const TemplateId& templateId)
{
TemplateReference instantiatingTemplate = FindTemplate(templateId);
if (!instantiatingTemplate.has_value())
{
AZ_Error("Prefab", false,
"Could not find template using Id %llu during InstantiatePrefab. Unable to proceed",
templateId);
return nullptr;
}
auto newInstance = AZStd::make_unique<Instance>();
Instance::EntityList newEntities;
if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*newInstance, newEntities, instantiatingTemplate->get().GetPrefabDom()))
{
AZ_Error("Prefab", false,
"Failed to Load Prefab Template associated with path %s. Instantiation Failed",
instantiatingTemplate->get().GetFilePath().c_str());
return nullptr;
}
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, newEntities);
return newInstance;
}
TemplateId PrefabSystemComponent::CreateTemplateFromInstance(Instance& instance, bool shouldCreateLinks)
{
// We will register the template to match the path the instance has
const AZ::IO::Path& templateSourcePath = instance.GetTemplateSourcePath();
if (templateSourcePath.empty())
{
AZ_Assert(false,
"PrefabSystemComponent::CreateTemplateFromInstance - "
"Attempted to create a prefab template from an instance without a source file path. "
"Unable to proceed.");
return InvalidTemplateId;
}
// Convert our instance into a serialized template dom
PrefabDom serializedInstance;
if (!PrefabDomUtils::StoreInstanceInPrefabDom(instance, serializedInstance))
{
return InvalidTemplateId;
}
// Generate a new template and store the dom data
const AZ::IO::Path& instanceSourcePath = instance.GetTemplateSourcePath();
TemplateId newTemplateId = AddTemplate(instanceSourcePath, AZStd::move(serializedInstance));
if (newTemplateId == InvalidTemplateId)
{
AZ_Error("Prefab", false,
"PrefabSystemComponent::CreateTemplateFromInstance - "
"Failed to create a template from instance with source file path '%s': "
"invalid template id returned.",
instanceSourcePath.c_str());
return InvalidTemplateId;
}
if (shouldCreateLinks)
{
if (!GenerateLinksForNewTemplate(newTemplateId, instance))
{
// Clear new template and any links associated with it
RemoveTemplate(newTemplateId);
return InvalidTemplateId;
}
}
return newTemplateId;
}
TemplateReference PrefabSystemComponent::FindTemplate(const TemplateId& id)
{
auto found = m_templateIdMap.find(id);
if (found != m_templateIdMap.end())
{
return found->second;
}
else
{
return AZStd::nullopt;
}
}
PrefabDom& PrefabSystemComponent::FindTemplateDom(TemplateId templateId)
{
AZStd::optional<AZStd::reference_wrapper<Template>> findTemplateResult = FindTemplate(templateId);
AZ_Assert(findTemplateResult.has_value(),
"PrefabSystemComponent::FindTemplateDom - Unable to retrieve Prefab template with id: '%llu'. "
"Template could not be found", templateId);
AZ_Assert(findTemplateResult->get().IsValid(),
"PrefabSystemComponent::FindTemplateDom - Unable to retrieve Prefab template with id: '%llu'. "
"Template is invalid", templateId);
return findTemplateResult->get().GetPrefabDom();
}
LinkReference PrefabSystemComponent::FindLink(const LinkId& id)
{
auto found = m_linkIdMap.find(id);
if (found != m_linkIdMap.end())
{
return found->second;
}
else
{
return AZStd::nullopt;
}
}
TemplateId PrefabSystemComponent::AddTemplate(const AZ::IO::Path& filePath, PrefabDom prefabDom)
{
TemplateId newTemplateId = CreateUniqueTemplateId();
Template& newTemplate = m_templateIdMap.emplace(
AZStd::make_pair(newTemplateId, AZStd::move(Template(filePath, AZStd::move(prefabDom))))).first->second;
if (!newTemplate.IsValid())
{
AZ_Assert(false,
"Prefab - PrefabSystemComponent::AddTemplate - "
"Can't add this new Template on file path '%s' since it is invalid.",
filePath.c_str());
m_templateIdMap.erase(newTemplateId);
return InvalidTemplateId;
}
if (!m_templateInstanceMapper.RegisterTemplate(newTemplateId))
{
m_templateIdMap.erase(newTemplateId);
return InvalidTemplateId;
}
m_templateFilePathToIdMap.emplace(AZStd::make_pair(filePath, newTemplateId));
newTemplate.MarkAsDirty(true);
return newTemplateId;
}
void PrefabSystemComponent::RemoveTemplate(const TemplateId& templateId)
{
auto findTemplateResult = FindTemplate(templateId);
if (!findTemplateResult.has_value())
{
AZ_Warning("Prefab", false,
"PrefabSystemComponent::RemoveTemplate - "
"Template associated by given Id '%llu' doesn't exist in PrefabSystemComponent.",
templateId);
return;
}
//Remove all Links owned by the Template from TemplateToLinkIdsMap.
Template& templateToDelete = findTemplateResult->get();
const Template::Links& linkIdsToDelete = templateToDelete.GetLinks();
bool result;
for (auto linkId : linkIdsToDelete)
{
result = RemoveLinkIdFromTemplateToLinkIdsMap(linkId);
AZ_Assert(result,
"Prefab - PrefabSystemComponent::RemoveTemplate - "
"Failed to remove Link with Id '%llu' owned by the Template with Id '%llu' on file path '%s' "
"from TemplateToLinkIdsMap.",
linkId, templateId, templateToDelete.GetFilePath().c_str());
}
//Remove all Links that depend on this source Template from other target Templates.
//Also remove this Template from TemplateToLinkIdsMap.
auto templateToLinkIterator = m_templateToLinkIdsMap.find(templateId);
if (templateToLinkIterator != m_templateToLinkIdsMap.end())
{
for (auto linkId : templateToLinkIterator->second)
{
result = RemoveLinkFromTargetTemplate(linkId);
AZ_Assert(result,
"Prefab - PrefabSystemComponent::RemoveTemplate - "
"Failed to remove Link with Id '%llu' that depend on the source Template with Id '%llu' on file path '%s'.",
linkId, templateId, templateToDelete.GetFilePath().c_str());
}
result = m_templateToLinkIdsMap.erase(templateToLinkIterator) != 0;
AZ_Assert(result,
"Prefab - PrefabSystemComponent::RemoveTemplate - "
"Failed to remove Template with Id '%llu' on file path '%s' "
"from TemplateToLinkIdsMap.",
templateId, templateToDelete.GetFilePath().c_str());
}
//Remove this Template from the rest of the maps.
result = m_templateFilePathToIdMap.erase(templateToDelete.GetFilePath()) != 0;
AZ_Assert(result,
"Prefab - PrefabSystemComponent::RemoveTemplate - "
"Failed to remove Template with Id '%llu' on file path '%s' "
"from Template File Path To Id Map.",
templateId, templateToDelete.GetFilePath().c_str());
m_templateInstanceMapper.UnregisterTemplate(templateId);
result = m_templateIdMap.erase(templateId) != 0;
AZ_Assert(result,
"Prefab - PrefabSystemComponent::RemoveTemplate - "
"Failed to remove Template with Id '%llu' on file path '%s' "
"from Template Id Map.",
templateId, templateToDelete.GetFilePath().c_str());
return;
}
void PrefabSystemComponent::RemoveAllTemplates()
{
AZStd::vector<TemplateId> templateIds;
templateIds.reserve(m_templateIdMap.size());
// Make a copy of the keys, we don't want to iterate over the map while we're removing items from it
for (const auto& [id, templateObject] : m_templateIdMap)
{
templateIds.emplace_back(id);
}
for (auto id : templateIds)
{
RemoveTemplate(id);
}
}
LinkId PrefabSystemComponent::AddLink(
const TemplateId& sourceTemplateId,
const TemplateId& targetTemplateId,
PrefabDomValue::MemberIterator& instanceIterator,
InstanceOptionalReference instance)
{
TemplateReference sourceTemplateReference = FindTemplate(sourceTemplateId);
TemplateReference targetTemplateReference = FindTemplate(targetTemplateId);
if (!sourceTemplateReference.has_value() || !targetTemplateReference.has_value())
{
AZ_Error("Prefab", false,
"PrefabSystemComponent::AddLink - "
"Can't find both source Template '%u' and target Template '%u' in Prefab System Component.",
sourceTemplateId, targetTemplateId);
return false;
}
Template& targetTemplate = targetTemplateReference->get();
#if defined(AZ_ENABLE_TRACING)
Template& sourceTemplate = sourceTemplateReference->get();
AZStd::string_view instanceName(instanceIterator->name.GetString(), instanceIterator->name.GetStringLength());
const AZStd::string& targetTemplateFilePath = targetTemplate.GetFilePath().Native();
const AZStd::string& sourceTemplateFilePath = sourceTemplate.GetFilePath().Native();
#endif
LinkId newLinkId = CreateUniqueLinkId();
Link newLink(newLinkId);
if (!ConnectTemplates(newLink, sourceTemplateId, targetTemplateId, instanceIterator))
{
AZ_Error("Prefab", false,
"PrefabSystemComponent::AddLink - "
"New Link to Nested Template Instance '%.*s' connecting source Template '%s' and target Template '%s' failed.",
aznumeric_cast<int>(instanceName.size()), instanceName.data(),
sourceTemplateFilePath.c_str(),
targetTemplateFilePath.c_str());
return InvalidLinkId;
}
if (!newLink.IsValid())
{
AZ_Error("Prefab", false,
"PrefabSystemComponent::AddLink - "
"New Link to Nested Template Instance '%.*s' connecting source Template '%s' and target Template '%s' is invalid.",
aznumeric_cast<int>(instanceName.size()), instanceName.data(),
sourceTemplateFilePath.c_str(),
targetTemplateFilePath.c_str());
return InvalidLinkId;
}
if (instance != AZStd::nullopt)
{
instance->get().SetLinkId(newLinkId);
}
m_linkIdMap.emplace(AZStd::make_pair(newLinkId, AZStd::move(newLink)));
targetTemplate.AddLink(newLinkId);
m_templateToLinkIdsMap[sourceTemplateId].emplace(newLinkId);
return newLinkId;
}
LinkId PrefabSystemComponent::CreateLink(
const TemplateId& linkTargetId,
const TemplateId& linkSourceId,
const InstanceAlias& instanceAlias,
const PrefabDomConstReference linkPatches,
const LinkId& linkId)
{
if (linkTargetId == InvalidTemplateId)
{
AZ_Error("Prefab", false, "Invalid Link Target Template Id");
}
TemplateReference targetTemplateRef = FindTemplate(linkTargetId);
if (targetTemplateRef == AZStd::nullopt)
{
AZ_Error("Prefab", false, "Link Target Template not found");
}
if (linkSourceId == InvalidTemplateId)
{
AZ_Error("Prefab", false, "Invalid Link Source Template Id");
}
TemplateReference sourceTemplateRef = FindTemplate(linkSourceId);
if (sourceTemplateRef == AZStd::nullopt)
{
AZ_Error("Prefab", false, "Link Source Template not found");
}
//use an existing link id if provided
LinkId newLinkId = linkId;
if (newLinkId == InvalidLinkId)
{
newLinkId = CreateUniqueLinkId();
}
//get owner template and add the link
Template& targetTemplate = targetTemplateRef->get();
if (!targetTemplate.AddLink(newLinkId))
{
AZ_Error("Prefab", false, "Failed to add link id '%llu' to '%s'", newLinkId, targetTemplate.GetFilePath().c_str());
}
//insert nested instance alias into the template owner dom
PrefabDom& targetTemplateDom = targetTemplate.GetPrefabDom();
auto memberFound = targetTemplateDom.FindMember(PrefabDomUtils::InstancesName);
PrefabDomValueReference instancesValue;
if (memberFound == targetTemplateDom.MemberEnd())
{
//add the instance alias to the template dom
instancesValue = targetTemplateDom.AddMember(rapidjson::StringRef(PrefabDomUtils::InstancesName),
PrefabDomValue(),
targetTemplateDom.GetAllocator());
//when AddMember returns, it returns the object that the member was added to, not the added
//member itself, so we need to move instancesValue to the correct position for the next insert
memberFound = instancesValue->get().FindMember(PrefabDomUtils::InstancesName);
instancesValue = memberFound->value;
instancesValue->get().SetObject();
}
else
{
instancesValue = memberFound->value;
}
// Only add the instance if it's not there already
if (instancesValue->get().FindMember(rapidjson::StringRef(instanceAlias.c_str())) == instancesValue->get().MemberEnd())
{
instancesValue->get().AddMember(
rapidjson::Value(instanceAlias.c_str(), targetTemplateDom.GetAllocator()), PrefabDomValue(),
targetTemplateDom.GetAllocator());
}
Template& sourceTemplate = sourceTemplateRef->get();
//setup initial link values and link dom
Link newLink(newLinkId);
newLink.SetTargetTemplateId(linkTargetId);
newLink.SetSourceTemplateId(linkSourceId);
newLink.SetInstanceName(instanceAlias.c_str());
newLink.GetLinkDom().SetObject();
newLink.GetLinkDom().AddMember(
rapidjson::StringRef(PrefabDomUtils::SourceName), rapidjson::StringRef(sourceTemplate.GetFilePath().c_str()),
newLink.GetLinkDom().GetAllocator());
if (linkPatches && linkPatches->get().IsArray() && !(linkPatches->get().Empty()))
{
m_instanceToTemplatePropagator.AddPatchesToLink(linkPatches.value(), newLink);
}
//update the target template dom to have the proper values for the source template dom
if (!newLink.UpdateTarget())
{
AZ_Error("Prefab", false, "Failed to update link with template information");
}
//add the link to the link maps
m_linkIdMap.emplace(AZStd::make_pair(newLinkId, AZStd::move(newLink)));
m_templateToLinkIdsMap[linkSourceId].emplace(newLinkId);
return newLinkId;
}
void PrefabSystemComponent::RemoveLink(const LinkId& linkId)
{
auto findLinkResult = FindLink(linkId);
if (!findLinkResult.has_value())
{
AZ_Warning("Prefab", false,
"PrefabSystemComponent::RemoveLink - "
"Link associated by given Id '%llu' doesn't exist in PrefabSystemComponent.",
linkId);
return;
}
Link& link = findLinkResult->get();
bool result;
result = RemoveLinkIdFromTemplateToLinkIdsMap(linkId, link);
AZ_Assert(result,
"Prefab - PrefabSystemComponent::RemoveLink - "
"Failed to remove Link with Id '%llu' for Instance '%s' of source Template with Id '%llu' "
"from TemplateToLinkIdsMap.",
linkId, link.GetInstanceName().c_str(), link.GetSourceTemplateId());
result = RemoveLinkFromTargetTemplate(linkId, link);
AZ_Assert(result,
"Prefab - PrefabSystemComponent::RemoveLink - "
"Failed to remove Link with Id '%llu' for Instance '%s' of source Template with Id '%llu' "
"from target Template with Id '%llu'.",
linkId, link.GetInstanceName().c_str(), link.GetSourceTemplateId(), link.GetTargetTemplateId());
m_linkIdMap.erase(linkId);
return;
}
TemplateId PrefabSystemComponent::GetTemplateIdFromFilePath(AZ::IO::PathView filePath) const
{
AZ_Assert(!filePath.IsAbsolute(), "Prefab - GetTemplateIdFromFilePath was passed an absolute path. Prefabs use paths relative to the project folder.");
auto found = m_templateFilePathToIdMap.find(filePath);
if (found != m_templateFilePathToIdMap.end())
{
return found->second;
}
else
{
return InvalidTemplateId;
}
}
bool PrefabSystemComponent::IsTemplateDirty(const TemplateId& templateId)
{
auto templateRef = FindTemplate(templateId);
if (templateRef.has_value())
{
return templateRef->get().IsDirty();
}
return false;
}
void PrefabSystemComponent::SetTemplateDirtyFlag(const TemplateId& templateId, bool dirty)
{
auto templateRef = FindTemplate(templateId);
if (templateRef.has_value())
{
templateRef->get().MarkAsDirty(dirty);
}
}
bool PrefabSystemComponent::ConnectTemplates(
Link& link,
TemplateId sourceTemplateId,
TemplateId targetTemplateId,
PrefabDomValue::MemberIterator& instanceIterator)
{
TemplateReference sourceTemplateReference = FindTemplate(sourceTemplateId);
TemplateReference targetTemplateReference = FindTemplate(targetTemplateId);
if (!sourceTemplateReference.has_value() || !targetTemplateReference.has_value())
{
AZ_Error("Prefab", false,
"PrefabSystemComponent::ConnectTemplates - "
"Can't find both source Template '%u' and target Template '%u' in Prefab System Component.",
sourceTemplateId, targetTemplateId);
return false;
}
#if defined(AZ_ENABLE_TRACING)
Template& sourceTemplate = sourceTemplateReference->get();
Template& targetTemplate = targetTemplateReference->get();
#endif
AZStd::string_view instanceName(instanceIterator->name.GetString(), instanceIterator->name.GetStringLength());
link.SetSourceTemplateId(sourceTemplateId);
link.SetTargetTemplateId(targetTemplateId);
link.SetInstanceName(instanceName.data());
PrefabDomValue& instance = instanceIterator->value;
AZ_Assert(instance.IsObject(), "Nested instance DOM provided is not a valid JSON object.");
PrefabDomValueReference sourceTemplateName = PrefabDomUtils::FindPrefabDomValue(instance, PrefabDomUtils::SourceName);
AZ_Assert(sourceTemplateName, "Couldn't find source template name in the DOM of the nested instance while creating a link.");
AZ_Assert(
sourceTemplateName->get() == sourceTemplate.GetFilePath().c_str(),
"The name of the source template in the nested instance DOM does not match the name of the source template already loaded");
PrefabDomValueReference patchesReference = PrefabDomUtils::FindPrefabDomValue(instance, PrefabDomUtils::PatchesName);
if (patchesReference.has_value())
{
AZ_Assert(patchesReference->get().IsArray(), "Patches in the nested instance DOM are not represented as an array.");
}
link.SetLinkDom(instance);
if (!link.UpdateTarget())
{
AZ_Error("Prefab", false,
"PrefabSystemComponent::ConnectTemplates - "
"Failed to update the linked instance with source Prefab file '%s' and target Prefab file '%s'.",
sourceTemplate.GetFilePath().c_str(), targetTemplate.GetFilePath().c_str());
return false;
}
link.AddLinkIdToInstanceDom(instance);
return true;
}
bool PrefabSystemComponent::GenerateLinksForNewTemplate(const TemplateId& newTemplateId, Instance& instance)
{
TemplateReference newTemplateReference = FindTemplate(newTemplateId);
if (!newTemplateReference.has_value())
{
return false;
}
Template& newTemplate = newTemplateReference->get();
// Gather the Instances member from the template DOM
PrefabDomValueReference instancesReference = newTemplate.GetInstancesValue();
// No nested instances to perform link operations on
if (instancesReference == AZStd::nullopt)
{
return true;
}
PrefabDomValue& instances = instancesReference->get();
for (PrefabDomValue::MemberIterator instanceIterator = instances.MemberBegin(); instanceIterator != instances.MemberEnd(); ++instanceIterator)
{
// Acquire the source member of the nested template so we can get its template id
// and join it with our new template via a link
PrefabDomValueReference instanceSourceReference =
PrefabDomUtils::FindPrefabDomValue(instanceIterator->value, PrefabDomUtils::SourceName);
if (!instanceSourceReference.has_value() || !instanceSourceReference->get().IsString())
{
AZ_Error("Prefab", false,
"PrefabSystemComponent::GenerateLinksForNewTemplate - "
"Failed to acquire the source path value of a nested instance during creation of a new template associated with prefab %s"
"Unable to proceed",
newTemplate.GetFilePath().c_str());
return false;
}
const PrefabDomValue& source = instanceSourceReference->get();
const TemplateId& nestedTemplateId = GetTemplateIdFromFilePath(source.GetString());
if (nestedTemplateId == InvalidTemplateId)
{
AZ_Error("Prefab", false,
"PrefabSystemComponent::GenerateLinksForNewTemplate - "
"Nested Template Instance with prefab path %s is not registered with the prefab system component"
"Unable to acquire its template id"
"Unable to complete creation of Template with prefab path %s",
newTemplate.GetFilePath().c_str());
return false;
}
// Construct and store the new link in our mapping.
AZStd::string_view nestedTemplatePath(source.GetString(), source.GetStringLength());
InstanceAlias instanceAlias = instanceIterator->name.GetString();
LinkId newLinkId = AddLink(nestedTemplateId, newTemplateId, instanceIterator, instance.FindNestedInstance(instanceAlias));
if (newLinkId == InvalidLinkId)
{
AZ_Error("Prefab", false,
"PrefabSystemComponent::GenerateLinksForNewTemplate - "
"Failed to add a new Link to Nested Template Instance '%s' which connects source Template '%.*s' and target Template '%s'.",
instanceIterator->name.GetString(),
aznumeric_cast<int>(nestedTemplatePath.size()), nestedTemplatePath.data(),
newTemplate.GetFilePath().c_str());
return false;
}
}
return true;
}
TemplateId PrefabSystemComponent::CreateUniqueTemplateId()
{
return m_templateIdCounter++;
}
LinkId PrefabSystemComponent::CreateUniqueLinkId()
{
return m_linkIdCounter++;
}
bool PrefabSystemComponent::RemoveLinkIdFromTemplateToLinkIdsMap(const LinkId& linkId)
{
auto findLinkResult = FindLink(linkId);
if (!findLinkResult.has_value())
{
return false;
}
Link& link = findLinkResult->get();
return RemoveLinkIdFromTemplateToLinkIdsMap(linkId, link);
}
bool PrefabSystemComponent::RemoveLinkIdFromTemplateToLinkIdsMap(const LinkId& linkId, const Link& link)
{
TemplateId sourceTemplateId = link.GetSourceTemplateId();
auto templateToLinkIterator = m_templateToLinkIdsMap.find(sourceTemplateId);
bool removed = false;
if (templateToLinkIterator != m_templateToLinkIdsMap.end())
{
removed = templateToLinkIterator->second.erase(linkId) != 0;
if (templateToLinkIterator->second.empty())
{
m_templateToLinkIdsMap.erase(templateToLinkIterator);
}
}
return removed;
}
bool PrefabSystemComponent::RemoveLinkFromTargetTemplate(const LinkId& linkId)
{
auto findLinkResult = FindLink(linkId);
if (!findLinkResult.has_value())
{
return false;
}
Link& link = findLinkResult->get();
return RemoveLinkFromTargetTemplate(linkId, link);
}
bool PrefabSystemComponent::RemoveLinkFromTargetTemplate(const LinkId& linkId, const Link& link)
{
TemplateId targetTemplateId = link.GetTargetTemplateId();
auto templateIterator = m_templateIdMap.find(targetTemplateId);
bool removed = false;
if (templateIterator != m_templateIdMap.end())
{
removed = templateIterator->second.RemoveLink(linkId);
//remove link
PrefabDomValueReference templateInstancesRef = templateIterator->second.GetInstancesValue();
if (templateInstancesRef == AZStd::nullopt)
{
AZ_Error("Prefab", false, "Failed to get template reference");
return false;
}
removed = templateInstancesRef->get().RemoveMember(link.GetInstanceName().c_str())
? removed : false;
}
return removed;
}
} // namespace Prefab
} // namespace AzToolsFramework