You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp

1138 lines
48 KiB
C++

/*
* 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/PrefabSystemComponent.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/RTTI/BehaviorContext.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();
m_prefabPublicRequestHandler.Connect();
m_prefabSystemScriptingHandler.Connect(this);
AZ::SystemTickBus::Handler::BusConnect();
}
void PrefabSystemComponent::Deactivate()
{
AZ::SystemTickBus::Handler::BusDisconnect();
m_prefabSystemScriptingHandler.Disconnect();
m_prefabPublicRequestHandler.Disconnect();
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);
PrefabPublicRequestHandler::Reflect(context);
PrefabFocusHandler::Reflect(context);
PrefabLoader::Reflect(context);
PrefabSystemScriptingHandler::Reflect(context);
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<PrefabSystemComponent, AZ::Component>()->Version(1);
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<PrefabLoaderScriptingBus>("PrefabLoaderScriptingBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "prefab")
->Attribute(AZ::Script::Attributes::Category, "Prefab")
->Event("SaveTemplateToString", &PrefabLoaderScriptingBus::Events::SaveTemplateToString);
;
}
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, InstanceOptionalReference parent,
bool shouldCreateLinks)
{
AZStd::unique_ptr<Instance> newInstance = AZStd::make_unique<Instance>(AZStd::move(containerEntity), parent);
CreatePrefab(entities, AZStd::move(instancesToConsume), filePath, newInstance, shouldCreateLinks);
return newInstance;
}
void PrefabSystemComponent::CreatePrefab(
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume,
AZ::IO::PathView filePath, AZStd::unique_ptr<Instance>& newInstance, 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;
}
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);
}
}
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, InstanceOptionalConstReference instanceToExclude)
{
TemplateReference findTemplateResult = FindTemplate(templateId);
if (findTemplateResult.has_value())
{
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);
}
UpdatePrefabInstances(templateId, instanceToExclude);
}
}
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());
SetTemplateDirtyFlag(templateId, true);
PropagateTemplateChanges(templateId);
}
}
}
void PrefabSystemComponent::UpdatePrefabInstances(TemplateId templateId, InstanceOptionalConstReference 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, InstanceOptionalReference parent, const InstantiatedEntitiesCallback& instantiatedEntitiesCallback)
{
// 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, parent, instantiatedEntitiesCallback);
}
AZStd::unique_ptr<Instance> PrefabSystemComponent::InstantiatePrefab(
TemplateId templateId, InstanceOptionalReference parent, const InstantiatedEntitiesCallback& instantiatedEntitiesCallback)
{
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>(parent);
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;
}
if (instantiatedEntitiesCallback)
{
instantiatedEntitiesCallback(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(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));
return newTemplateId;
}
void PrefabSystemComponent::UpdateTemplateFilePath(TemplateId templateId, const AZ::IO::PathView& filePath)
{
auto findTemplateResult = FindTemplate(templateId);
if (!findTemplateResult.has_value())
{
AZ_Error(
"Prefab", false,
"Template associated by given Id '%llu' doesn't exist in PrefabSystemComponent.",
templateId);
return;
}
if (!filePath.IsRelative())
{
AZ_Error("Prefab", false, "Provided filePath '%.*s' must be relative.", AZ_STRING_ARG(filePath.Native()));
return;
}
Template& templateToChange = findTemplateResult->get();
if (templateToChange.GetFilePath() == filePath)
{
return;
}
m_templateFilePathToIdMap.erase(templateToChange.GetFilePath());
if (!m_templateFilePathToIdMap.try_emplace(filePath, templateId).second)
{
AZ_Error("Prefab", false, "Provided filePath '%.*s' already exists.", AZ_STRING_ARG(filePath.Native()));
return;
}
PrefabDom& prefabDom = templateToChange.GetPrefabDom();
PrefabDomValueReference pathReference = Prefab::PrefabDomUtils::FindPrefabDomValue(prefabDom, "Source");
if (pathReference)
{
const AZStd::string_view pathStr = filePath.Native();
pathReference->get().SetString(pathStr.data(), aznumeric_caster(pathStr.length()), prefabDom.GetAllocator());
}
templateToChange.SetFilePath(filePath);
}
void PrefabSystemComponent::RemoveTemplate(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();
[[maybe_unused]] 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) != nullptr;
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());
if (!m_removingAllTemplates)
{
PrefabPublicNotificationBus::Broadcast(&PrefabPublicNotificationBus::Events::OnTemplateRemoved, templateId);
}
}
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);
}
m_removingAllTemplates = true;
for (auto id : templateIds)
{
RemoveTemplate(id);
}
m_removingAllTemplates = false;
PrefabPublicNotificationBus::Broadcast(&PrefabPublicNotificationBus::Events::OnAllTemplatesRemoved);
}
LinkId PrefabSystemComponent::AddLink(
TemplateId sourceTemplateId,
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(
TemplateId linkTargetId,
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;
}
else
{
instancesValue = memberFound->value;
}
if (!instancesValue->get().IsObject())
{
instancesValue->get().SetObject();
}
// 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();
[[maybe_unused]] 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(TemplateId templateId)
{
auto templateRef = FindTemplate(templateId);
if (templateRef.has_value())
{
return !templateRef->get().IsProcedural() && // all procedural prefabs are read-only
templateRef->get().IsDirty();
}
return false;
}
void PrefabSystemComponent::SetTemplateDirtyFlag(TemplateId templateId, bool dirty)
{
if (auto templateReference = FindTemplate(templateId); templateReference.has_value())
{
templateReference->get().MarkAsDirty(dirty);
PrefabPublicNotificationBus::Broadcast(
&PrefabPublicNotificationBus::Events::OnPrefabTemplateDirtyFlagUpdated, templateId, dirty);
}
}
bool PrefabSystemComponent::AreDirtyTemplatesPresent(TemplateId rootTemplateId)
{
TemplateReference prefabTemplate = FindTemplate(rootTemplateId);
if (!prefabTemplate.has_value())
{
AZ_Assert(false, "Template with id %llu is not found", rootTemplateId);
return false;
}
if (IsTemplateDirty(rootTemplateId))
{
return true;
}
const Template::Links& linkIds = prefabTemplate->get().GetLinks();
for (LinkId linkId : linkIds)
{
auto linkIterator = m_linkIdMap.find(linkId);
if (linkIterator != m_linkIdMap.end())
{
if (AreDirtyTemplatesPresent(linkIterator->second.GetSourceTemplateId()))
{
return true;
}
else
{
continue;
}
}
}
return false;
}
void PrefabSystemComponent::SaveAllDirtyTemplates(TemplateId rootTemplateId)
{
AZStd::set<AZ::IO::PathView> dirtyTemplatePaths = GetDirtyTemplatePaths(rootTemplateId);
for (AZ::IO::PathView dirtyTemplatePath : dirtyTemplatePaths)
{
auto dirtyTemplateIterator = m_templateFilePathToIdMap.find(dirtyTemplatePath);
if (dirtyTemplateIterator == m_templateFilePathToIdMap.end())
{
AZ_Assert(false, "Template id for template with path '%s' is not found.", dirtyTemplatePath);
}
else
{
m_prefabLoader.SaveTemplate(dirtyTemplateIterator->second);
}
}
}
AZStd::set<AZ::IO::PathView> PrefabSystemComponent::GetDirtyTemplatePaths(TemplateId rootTemplateId)
{
AZStd::vector<AZ::IO::PathView> dirtyTemplatePathVector;
GetDirtyTemplatePathsHelper(rootTemplateId, dirtyTemplatePathVector);
AZStd::set<AZ::IO::PathView> dirtyTemplatePaths;
dirtyTemplatePaths.insert(dirtyTemplatePathVector.begin(), dirtyTemplatePathVector.end());
return AZStd::move(dirtyTemplatePaths);
}
void PrefabSystemComponent::GetDirtyTemplatePathsHelper(
TemplateId rootTemplateId, AZStd::vector<AZ::IO::PathView>& dirtyTemplatePaths)
{
TemplateReference prefabTemplate = FindTemplate(rootTemplateId);
if (!prefabTemplate.has_value())
{
AZ_Assert(false, "Template with id %llu is not found", rootTemplateId);
return;
}
if (IsTemplateDirty(rootTemplateId))
{
dirtyTemplatePaths.emplace_back(prefabTemplate->get().GetFilePath());
}
const Template::Links& linkIds = prefabTemplate->get().GetLinks();
for (LinkId linkId : linkIds)
{
auto linkIterator = m_linkIdMap.find(linkId);
if (linkIterator != m_linkIdMap.end())
{
GetDirtyTemplatePathsHelper(linkIterator->second.GetSourceTemplateId(), dirtyTemplatePaths);
}
}
}
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.");
[[maybe_unused]] 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(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();
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