/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include namespace AzToolsFramework { namespace Prefab { void PrefabSystemComponent::Init() { } void PrefabSystemComponent::Activate() { AZ::Interface::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::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(context)) { serialize->Class()->Version(1); } if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { behaviorContext->EBus("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(context); if (jsonRegistration) { jsonRegistration->Serializer()->HandlesType(); } } 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 PrefabSystemComponent::CreatePrefab( const AZStd::vector& entities, AZStd::vector>&& instancesToConsume, AZ::IO::PathView filePath, AZStd::unique_ptr containerEntity, InstanceOptionalReference parent, bool shouldCreateLinks) { AZStd::unique_ptr newInstance = AZStd::make_unique(AZStd::move(containerEntity), parent); CreatePrefab(entities, AZStd::move(instancesToConsume), filePath, newInstance, shouldCreateLinks); return newInstance; } void PrefabSystemComponent::CreatePrefab( const AZStd::vector& entities, AZStd::vector>&& instancesToConsume, AZ::IO::PathView filePath, AZStd::unique_ptr& 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 : 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 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& 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& 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& 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 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 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(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> 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 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(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(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 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 PrefabSystemComponent::GetDirtyTemplatePaths(TemplateId rootTemplateId) { AZStd::vector dirtyTemplatePathVector; GetDirtyTemplatePathsHelper(rootTemplateId, dirtyTemplatePathVector); AZStd::set dirtyTemplatePaths; dirtyTemplatePaths.insert(dirtyTemplatePathVector.begin(), dirtyTemplatePathVector.end()); return AZStd::move(dirtyTemplatePaths); } void PrefabSystemComponent::GetDirtyTemplatePathsHelper( TemplateId rootTemplateId, AZStd::vector& 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(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