Integrating latest 47acbe8

This commit is contained in:
alexpete
2021-03-25 13:57:57 -07:00
parent 448c549698
commit 75dc720198
10312 changed files with 2711566 additions and 671451 deletions
@@ -0,0 +1,58 @@
/*
* 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/Spawnable/ComponentRequirementsValidator.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/algorithm.h>
#include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
void ComponentRequirementsValidator::SetPlatformTags(AZ::PlatformTagSet platformTags)
{
m_platformTags = AZStd::move(platformTags);
}
void ComponentRequirementsValidator::SetEntities(const AZStd::vector<AZ::Entity*>& entities)
{
m_immutableEntities.clear();
m_immutableEntities.assign(entities.cbegin(), entities.cend());
}
ComponentRequirementsValidator::ValidationResult ComponentRequirementsValidator::Validate(
const AZ::Component* component)
{
AZ::ComponentValidationResult result = component->ValidateComponentRequirements(m_immutableEntities, m_platformTags);
if (!result.IsSuccess())
{
// Try to cast to GenericComponentWrapper, and if we can, get the internal template.
const char* componentName = component->RTTI_GetTypeName();
const auto* asEditorComponent = azrtti_cast<const Components::EditorComponentBase*>(component);
const Components::GenericComponentWrapper* wrapper = azrtti_cast<const Components::GenericComponentWrapper*>(asEditorComponent);
if (wrapper && wrapper->GetTemplate())
{
componentName = wrapper->GetTemplate()->RTTI_GetTypeName();
}
return AZ::Failure(AZStd::string::format(
"Editor Component '%s' could not pass validation due to the error - %s",
componentName,
result.GetError().c_str())
);
}
return AZ::Success();
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,45 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/ComponentExport.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/optional.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class ComponentRequirementsValidator
{
public:
AZ_CLASS_ALLOCATOR(ComponentRequirementsValidator, AZ::SystemAllocator, 0);
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::ComponentRequirementsValidator, "{1E9CD55D-FFEA-4E71-A316-731E25E6C981}");
virtual ~ComponentRequirementsValidator() = default;
void SetPlatformTags(AZ::PlatformTagSet platformTags);
void SetEntities(const AZStd::vector<AZ::Entity*>& entities);
using ValidationResult = AZ::Outcome<void, AZStd::string>;
ValidationResult Validate(const AZ::Component* component);
private:
AZ::ImmutableEntityVector m_immutableEntities;
AZ::PlatformTagSet m_platformTags;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,632 @@
/*
* 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 <AzCore/Component/ComponentExport.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/string/string_view.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/ToolsComponents/EditorOnlyEntityComponentBus.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
EditorInfoRemover::~EditorInfoRemover()
{
for (auto* handler : m_editorOnlyEntityHandlerCandidates)
{
delete handler;
}
}
void EditorInfoRemover::Process(PrefabProcessorContext& prefabProcessorContext)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (!serializeContext)
{
AZ_Assert(serializeContext, "Failed to retrieve serialize context.");
return;
}
prefabProcessorContext.ListPrefabs([this, &serializeContext, &prefabProcessorContext](AZStd::string_view prefabName, PrefabDom& prefab)
{
auto result = RemoveEditorInfo(prefab, serializeContext, prefabProcessorContext);
if (!result)
{
AZ_Assert(false,
"Converting to runtime Prefab '%.*s' failed, Error: %s .",
AZ_STRING_ARG(prefabName),
result.GetError().c_str());
return;
}
});
}
void EditorInfoRemover::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<EditorInfoRemover, PrefabProcessor>()->Version(1);
}
}
AZStd::vector<AZ::Entity*> EditorInfoRemover::GetEntitiesFromInstance(AZStd::unique_ptr<Instance>& instance)
{
AZStd::vector<AZ::Entity*> result;
instance->GetNestedEntities(
[&result](const AZStd::unique_ptr<AZ::Entity>& entity)
{
result.emplace_back(entity.get());
return true;
}
);
return result;
}
void EditorInfoRemover::SetEditorOnlyEntityHandlerFromCandidates(const EntityList& entities)
{
ClearEditorOnlyEntityIds();
m_editorOnlyEntityHandler = nullptr;
for (auto& handlerCandidate : m_editorOnlyEntityHandlerCandidates)
{
// See if this handler can handle at least one of the entities.
for (auto entity : entities)
{
if (handlerCandidate->IsEntityUniquelyForThisHandler(entity))
{
m_editorOnlyEntityHandler = handlerCandidate;
break;
}
}
if (HasValidEditorOnlyHandler())
{
break;
}
}
}
bool EditorInfoRemover::HasValidEditorOnlyHandler() const
{
return m_editorOnlyEntityHandler != nullptr;
}
void EditorInfoRemover::ClearEditorOnlyEntityIds()
{
m_editorOnlyEntityIds.clear();
}
void EditorInfoRemover::AddEntityIdIfEditorOnly(AZ::Entity* entity)
{
bool isEditorOnly = false;
EditorOnlyEntityComponentRequestBus::EventResult(isEditorOnly, entity->GetId(), &EditorOnlyEntityComponentRequests::IsEditorOnlyEntity);
if (isEditorOnly && HasValidEditorOnlyHandler())
{
m_editorOnlyEntityHandler->AddEditorOnlyEntity(entity, m_editorOnlyEntityIds);
}
}
/**
* Identify and remove any entities marked as editor-only.
* If any are discovered, adjust descendants' transforms to retain spatial relationships.
* Note we cannot use EBuses for this purpose, since we're crunching data, and can't assume any entities are active.
*/
EditorInfoRemover::RemoveEditorOnlyEntitiesResult EditorInfoRemover::RemoveEditorOnlyEntities(EntityList& entities)
{
if (HasValidEditorOnlyHandler())
{
const auto handlerResult =
m_editorOnlyEntityHandler->HandleEditorOnlyEntities(entities, m_editorOnlyEntityIds, *m_serializeContext);
if (!handlerResult)
{
return AZ::Failure(AZStd::string::format(
"Error occurred when handle editor-only entities. Error: %s",
handlerResult.GetError().c_str())
);
}
}
// Remove editor-only entities from the given entity list.
AZStd::erase_if(
entities,
[this](auto entity)
{
return m_editorOnlyEntityIds.find(entity->GetId()) != m_editorOnlyEntityIds.end();
}
);
return AZ::Success();
}
EditorInfoRemover::ExportEntityResult EditorInfoRemover::ExportEntity(AZ::Entity* sourceEntity, PrefabProcessorContext& context)
{
// For export, components can assume they're initialized, but not activated.
if (sourceEntity->GetState() == AZ::Entity::State::Constructed)
{
sourceEntity->Init();
}
AZ::Entity* exportEntity = aznew AZ::Entity(sourceEntity->GetId(), sourceEntity->GetName().c_str());
exportEntity->SetRuntimeActiveByDefault(sourceEntity->IsRuntimeActiveByDefault());
AddEntityIdIfEditorOnly(sourceEntity);
const AZ::Entity::ComponentArrayType& editorComponents = sourceEntity->GetComponents();
EntityList exportedEntities;
for (AZ::Component* component : editorComponents)
{
auto result = ExportComponent(component, context, sourceEntity, exportEntity);
if (!result)
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - export component '%s' failed. Error: %s",
exportEntity->GetName().c_str(),
exportEntity->GetId().ToString().c_str(),
component->RTTI_GetTypeName(),
result.GetError().c_str())
);
}
}
// Pre-sort prior to exporting so it isn't required at instantiation time.
const auto sortResult = exportEntity->EvaluateDependenciesGetDetails();
/* :CBR_TODO: verify AZ::Entity::DependencySortResult::HasIncompatibleServices and
AZ::Entity::DependencySortResult::DescriptorNotRegistered are still covered here*/
if (!sortResult.IsSuccess())
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - dependency evaluation failed. Error: %s",
exportEntity->GetName().c_str(),
exportEntity->GetId().ToString().c_str(),
sortResult.GetError().m_message.c_str()));
}
return AZ::Success(exportEntity);
}
bool EditorInfoRemover::ReadComponentAttribute(
AZ::Component* component,
AZ::Edit::Attribute* attribute,
AZStd::vector<AZ::Crc32>& attributeTags)
{
attributeTags.clear();
PropertyAttributeReader reader(component, attribute);
return reader.Read<AZStd::vector<AZ::Crc32>>(attributeTags);
}
EditorInfoRemover::ShouldExportResult EditorInfoRemover::ShouldExportComponent(
AZ::Component* component,
PrefabProcessorContext& context) const
{
const AZ::SerializeContext::ClassData* classData = m_serializeContext->FindClassData(component->RTTI_GetType());
if (!classData || !classData->m_editData)
{
return AZ::Success(true);
}
const AZ::Edit::ElementData* editorDataElement = classData->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData);
if (!editorDataElement)
{
return AZ::Success(true);
}
AZStd::vector<AZ::Crc32> attributeTags;
const auto& platformTags = context.GetPlatformTags();
// If the component has declared the 'ExportIfAllPlatforms' attribute, skip export if any of the flags are not present.
AZ::Edit::Attribute* allTagsAttribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::ExportIfAllPlatformTags);
if (allTagsAttribute)
{
if (!ReadComponentAttribute(component, allTagsAttribute, attributeTags))
{
return AZ::Failure(
AZStd::string("'ExportIfAllPlatforms' attribute is not bound to the correct return type. Expects AZStd::vector<AZ::Crc32>.")
);
}
for (AZ::Crc32 tag : attributeTags)
{
if (platformTags.find(tag) == platformTags.end())
{
// Export platform tags does not contain all tags specified in 'ExportIfAllPlatforms' attribute.
return AZ::Success(false);
}
}
}
// If the component has declared the 'ExportIfAnyPlatforms' attribute, skip export if none of the flags are present.
AZ::Edit::Attribute* anyTagsAttribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::ExportIfAnyPlatformTags);
if (anyTagsAttribute)
{
if (!ReadComponentAttribute(component, anyTagsAttribute, attributeTags))
{
return AZ::Failure(
AZStd::string("'ExportIfAnyPlatforms' attribute is not bound to the correct return type. Expects AZStd::vector<AZ::Crc32>.")
);
}
bool anyFlagSet = false;
for (AZ::Crc32 tag : attributeTags)
{
if (platformTags.find(tag) != platformTags.end())
{
anyFlagSet = true;
break;
}
}
if (!anyFlagSet)
{
// None of the flags in 'ExportIfAnyPlatforms' was present in the export platform tags.
return AZ::Success(false);
}
}
return AZ::Success(true);
}
EditorInfoRemover::ResolveExportedComponentResult EditorInfoRemover::ResolveExportedComponent(
AZ::ExportedComponent& component,
PrefabProcessorContext& prefabProcessorContext)
{
AZ::Component* inputComponent = component.m_component;
if (!inputComponent)
{
return AZ::Success(component);
}
// Don't export the component if it has unmet platform tag requirements.
ShouldExportResult shouldExportResult = ShouldExportComponent(inputComponent, prefabProcessorContext);
if (!shouldExportResult)
{
return AZ::Failure(shouldExportResult.TakeError());
}
if (!shouldExportResult.GetValue())
{
// If the platform tag requirements aren't met, return a null component that's been flagged as exported,
// so that we know not to try and process it any further.
return AZ::Success(AZ::ExportedComponent());
}
// Determine if the component has a custom export callback, and invoke it if so.
// If there's no custom export callback, just return what we were given.
const AZ::SerializeContext::ClassData* classData = m_serializeContext->FindClassData(inputComponent->RTTI_GetType());
if (!classData || !classData->m_editData)
{
return AZ::Success(component);
}
const AZ::Edit::ElementData* editorDataElement = classData->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData);
if (!editorDataElement)
{
return AZ::Success(component);
}
AZ::Edit::Attribute* exportCallbackAttribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::RuntimeExportCallback);
if (!exportCallbackAttribute)
{
return AZ::Success(component);
}
PropertyAttributeReader reader(inputComponent, exportCallbackAttribute);
AZ::ExportedComponent exportedComponent;
if (reader.Read<AZ::ExportedComponent>(exportedComponent, inputComponent, prefabProcessorContext.GetPlatformTags()))
{
// If the callback handled the export and provided a different component instance, continue to resolve recursively.
if (exportedComponent.m_componentExportHandled && (exportedComponent.m_component != inputComponent))
{
return ResolveExportedComponent(exportedComponent, prefabProcessorContext);
}
else
{
// It provided the *same* component back (or didn't handle the export at all), so we're done.
return AZ::Success(exportedComponent);
}
}
else
{
return AZ::Failure(AZStd::string("Bound 'CustomExportCallback' does not have the required return type/signature."));
}
}
EditorInfoRemover::BuildGameEntityResult EditorInfoRemover::BuildGameEntity(
AzToolsFramework::Components::EditorComponentBase* editorComponent,
AZ::Entity* sourceEntity,
AZ::Entity* exportEntity)
{
const size_t oldComponentCount = exportEntity->GetComponents().size();
editorComponent->BuildGameEntity(exportEntity);
AZ::ComponentId newID = editorComponent->GetId();
for (auto i = oldComponentCount; i < exportEntity->GetComponents().size(); ++i)
{
AZ::Component* exportComponent = exportEntity->GetComponents()[i];
// Verify that the result of BuildGameEntity() wasn't an editor component.
auto* exportAsEditorComponent = azrtti_cast<Components::EditorComponentBase*>(exportComponent);
if (exportAsEditorComponent)
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - component '%s' exported an editor component from BuildGameEntity() for runtime use.",
sourceEntity->GetName().c_str(),
sourceEntity->GetId().ToString().c_str(),
editorComponent->RTTI_GetType().ToString<AZStd::string>().c_str()));
}
else if (editorComponent->GetId() == AZ::InvalidComponentId)
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - component '%s' doesn't have a valid component Id.",
sourceEntity->GetName().c_str(),
sourceEntity->GetId().ToString().c_str(),
editorComponent->RTTI_GetType().ToString<AZStd::string>().c_str()));
}
exportComponent->SetId(newID++);
// The first time round set the new component the same as the editor one. This will change in a separate ticket
// when 8 bit runtime Ids are implemented.
// Make sure the newID isn't already on the source Entity. If it is increment the ID and try again.
while (sourceEntity->FindComponent(newID))
{
++newID;
}
}
return AZ::Success();
}
EditorInfoRemover::ExportComponentResult EditorInfoRemover::ExportComponent(
AZ::Component* component,
PrefabProcessorContext& prefabProcessorContext,
AZ::Entity* sourceEntity,
AZ::Entity* exportEntity)
{
auto validationResult = m_componentRequirementsValidator.Validate(component);
if (!validationResult.IsSuccess())
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - validation of component '%s' failed: %s",
sourceEntity->GetName().c_str(),
sourceEntity->GetId().ToString().c_str(),
component->RTTI_GetTypeName(),
validationResult.GetError().c_str())
);
}
AZ::ExportedComponent exportComponent(component, false, false);
auto exportResult = ResolveExportedComponent(
exportComponent, prefabProcessorContext);
if (!exportResult)
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - component '%s' could not be exported due to export attributes: %s.",
sourceEntity->GetName().c_str(),
sourceEntity->GetId().ToString().c_str(),
component->RTTI_GetTypeName(),
exportResult.GetError().c_str()));
}
AZ::ExportedComponent& exportedComponent = exportResult.GetValue();
// If ResolveExportedComponent didn't handle the component export, then we'll do the following:
// - For editor components, fall back on the legacy BuildGameEntity() path for handling component exports.
// - For runtime components, provide a default behavior of "clone / add" to export the component.
if (!exportedComponent.m_componentExportHandled)
{
auto* asEditorComponent = azrtti_cast<Components::EditorComponentBase*>(component);
// Editor components: Try to use BuildGameEntity()
if (asEditorComponent) // BEGIN BuildGameEntity compatibility path for editor components not using the newer RuntimeExportCallback functionality.
{
auto buildGameEntityResult = BuildGameEntity(asEditorComponent, sourceEntity, exportEntity);
if (!buildGameEntityResult.IsSuccess())
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - component '%s' to build game entity failed. Error: %s.",
sourceEntity->GetName().c_str(),
sourceEntity->GetId().ToString().c_str(),
component->RTTI_GetTypeName(),
buildGameEntityResult.GetError().c_str()));
}
// Since this is an editor component, we very specifically do *not* want to clone and add it as a runtime
// component by default, so regardless of whether or not the BuildGameEntity() call did anything,
// null out the editor component and mark it handled.
return AZ::Success();
} // END BuildGameEntity compatibility path for editor components not using the newer RuntimeExportCallback functionality.
else
{
// Nothing else has handled the component export, so fall back on the default behavior
// for runtime components: clone and add the runtime component that already exists.
exportedComponent = AZ::ExportedComponent(component, false);
}
}
// At this point, either ResolveExportedComponent or the default logic above should have set the component export
// as being handled. If not, there is likely a new code path that requires a default export behavior.
AZ_Assert(exportedComponent.m_componentExportHandled,
"Entity '%s' %s - component '%s' had no export handlers and could not be added to the entity.",
exportEntity->GetName().c_str(),
exportEntity->GetId().ToString().c_str(),
component->RTTI_GetTypeName());
// If we have an exported component, we add it to the exported entity.
// If we don't (m_component == nullptr), this component chose not to be exported, so we skip it.
if (exportedComponent.m_componentExportHandled && exportedComponent.m_component)
{
AZ::Component* runtimeComponent = exportedComponent.m_component;
// Verify that we aren't trying to export an editor component.
auto* exportAsEditorComponent = azrtti_cast<Components::EditorComponentBase*>(runtimeComponent);
if (exportAsEditorComponent)
{
auto* asEditorComponent =
azrtti_cast<Components::EditorComponentBase*>(component);
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - component '%s' is trying to export an Editor component for runtime use.",
sourceEntity->GetName().c_str(),
sourceEntity->GetId().ToString().c_str(),
asEditorComponent->RTTI_GetType().ToString<AZStd::string>().c_str()));
}
// If the final component is not owned by us, make our own copy.
if (!exportedComponent.m_deleteAfterExport)
{
runtimeComponent = m_serializeContext->CloneObject(runtimeComponent);
}
// Synchronize to source component Id, and add to the export entity.
runtimeComponent->SetId(component->GetId());
if (!exportEntity->AddComponent(runtimeComponent))
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - component '%s' could not be added to this entity.",
exportEntity->GetName().c_str(),
exportEntity->GetId().ToString().c_str(),
runtimeComponent->RTTI_GetTypeName())
);
}
}
return AZ::Success();
}
EditorInfoRemover::RemoveEditorInfoResult EditorInfoRemover::RemoveEditorInfo(
PrefabDom& prefab,
AZ::SerializeContext* serializeContext,
PrefabProcessorContext& prefabProcessorContext)
{
if (!serializeContext)
{
return AZ::Failure(AZStd::string("Invalid Serialize Context used."));
}
m_serializeContext = serializeContext;
m_componentRequirementsValidator.SetPlatformTags(prefabProcessorContext.GetPlatformTags());
// convert Prefab DOM into Prefab Instance.
AZStd::unique_ptr<Instance> instance(aznew Instance());
if (!Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*instance, prefab, false))
{
PrefabDomValueReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName);
AZStd::string errorMessage("Failed to Load Prefab Instance from given Prefab Dom during Removal of Editor Info.");
if (sourceReference.has_value() &&
sourceReference->get().IsString() &&
sourceReference->get().GetStringLength() != 0)
{
AZStd::string_view source(sourceReference->get().GetString(), sourceReference->get().GetStringLength());
errorMessage += AZStd::string::format("Prefab Source: %.*s", AZ_STRING_ARG(source));
}
return AZ::Failure(errorMessage);
}
// grab all nested entities from the Instance as source entities.
EntityList sourceEntities = GetEntitiesFromInstance(instance);
EntityList exportEntities;
// prepare for validation of component requirements.
m_componentRequirementsValidator.SetEntities(sourceEntities);
// find valid editor-only entity handler for removing editor-only entities later.
SetEditorOnlyEntityHandlerFromCandidates(sourceEntities);
// export entities.
for (AZ::Entity* entity : sourceEntities)
{
const auto result = ExportEntity(entity, prefabProcessorContext);
if (!result)
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - export entity failed. Error: %s",
entity->GetName().c_str(),
entity->GetId().ToString().c_str(),
result.GetError().c_str())
);
}
exportEntities.emplace_back(result.GetValue());
}
// remove editor-only entities with valid editor-only entity handler.
const auto removeEditorOnlyEntitiesResult = RemoveEditorOnlyEntities(exportEntities);
if (!removeEditorOnlyEntitiesResult)
{
return AZ::Failure(AZStd::string::format(
"Remove Editor-Only Entities failed. Error: '%s'",
removeEditorOnlyEntitiesResult.GetError().c_str())
);
}
// validate component requirements for exported entities.
m_componentRequirementsValidator.SetEntities(exportEntities);
for (AZ::Entity* exportEntity : exportEntities)
{
const AZ::Entity::ComponentArrayType& gameComponents = exportEntity->GetComponents();
for (const AZ::Component* component : gameComponents)
{
const auto result = m_componentRequirementsValidator.Validate(component);
if (!result)
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - validation of export component '%s' failed: %s",
exportEntity->GetName().c_str(),
exportEntity->GetId().ToString().c_str(),
component->RTTI_GetTypeName(),
result.GetError().c_str())
);
}
}
}
// remove editor-only entities from instance.
AZStd::unordered_map<AZ::EntityId, AZ::Entity*> exportEntitiesMap;
AZStd::for_each(exportEntities.begin(), exportEntities.end(),
[&exportEntitiesMap](auto& entity)
{
exportEntitiesMap.emplace(entity->GetId(), entity);
}
);
instance->RemoveNestedEntities(
[&exportEntitiesMap](const AZStd::unique_ptr<AZ::Entity>& entity)
{
return exportEntitiesMap.find(entity->GetId()) == exportEntitiesMap.end();
}
);
// replace entities of instance with exported ones.
instance->GetNestedEntities(
[&exportEntitiesMap](AZStd::unique_ptr<AZ::Entity>& entity)
{
auto entityId = entity->GetId();
entity.release();
entity.reset(exportEntitiesMap[entityId]);
return true;
}
);
// save the final result in the target Prefab DOM.
if (!PrefabDomUtils::StoreInstanceInPrefabDom(*instance, prefab))
{
return AZ::Failure(AZStd::string::format(
"Saving exported Prefab Instance within a Prefab Dom failed.")
);
}
return AZ::Success();
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,111 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Spawnable/ComponentRequirementsValidator.h>
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/EditorOnlyEntityHandler.h>
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/UiEditorOnlyEntityHandler.h>
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/WorldEditorOnlyEntityHandler.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessor.h>
namespace AZ
{
class ReflectContext;
}
namespace AzToolsFramework::Components
{
class EditorComponentBase;
}
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class EditorInfoRemover
: public PrefabProcessor
{
public:
AZ_CLASS_ALLOCATOR(EditorInfoRemover, AZ::SystemAllocator, 0);
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover,
"{50B48C7E-C9DE-48DE-8438-1A186A8EEAC8}", PrefabProcessor);
~EditorInfoRemover() override;
void Process(PrefabProcessorContext& prefabProcessorContext) override;
using RemoveEditorInfoResult = AZ::Outcome<void, AZStd::string>;
RemoveEditorInfoResult RemoveEditorInfo(
PrefabDom& prefab,
AZ::SerializeContext* serializeContext,
PrefabProcessorContext& prefabProcessorContext);
static void Reflect(AZ::ReflectContext* context);
protected:
using EntityList = AZStd::vector<AZ::Entity*>;
static EntityList GetEntitiesFromInstance(
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>& instance);
static bool ReadComponentAttribute(
AZ::Component* component,
AZ::Edit::Attribute* attribute,
AZStd::vector<AZ::Crc32>& attributeTags);
void SetEditorOnlyEntityHandlerFromCandidates(const EntityList& entities);
bool HasValidEditorOnlyHandler() const;
void ClearEditorOnlyEntityIds();
void AddEntityIdIfEditorOnly(AZ::Entity* entity);
using RemoveEditorOnlyEntitiesResult = AZ::Outcome<void, AZStd::string>;
RemoveEditorOnlyEntitiesResult RemoveEditorOnlyEntities(EntityList& entities);
using ExportEntityResult = AZ::Outcome<AZ::Entity*, AZStd::string>;
ExportEntityResult ExportEntity(AZ::Entity* sourceEntity, PrefabProcessorContext& context);
using ResolveExportedComponentResult = AZ::Outcome<AZ::ExportedComponent, AZStd::string>;
ResolveExportedComponentResult ResolveExportedComponent(
AZ::ExportedComponent& component, PrefabProcessorContext& prefabProcessorContext);
using ShouldExportResult = AZ::Outcome<bool, AZStd::string>;
ShouldExportResult ShouldExportComponent(
AZ::Component* component,
PrefabProcessorContext& prefabProcessorContext) const;
using BuildGameEntityResult = AZ::Outcome<void, AZStd::string>;
BuildGameEntityResult BuildGameEntity(
AzToolsFramework::Components::EditorComponentBase* editorComponent,
AZ::Entity* sourceEntity,
AZ::Entity* exportEntity
);
using ExportComponentResult = AZ::Outcome<void, AZStd::string>;
ExportComponentResult ExportComponent(
AZ::Component* component,
PrefabProcessorContext& prefabProcessorContext,
AZ::Entity* sourceEntity,
AZ::Entity* exportEntity);
AZ::SerializeContext* m_serializeContext{ nullptr };
EditorOnlyEntityHandler* m_editorOnlyEntityHandler{ nullptr };
EditorOnlyEntityHandlers m_editorOnlyEntityHandlerCandidates{
aznew WorldEditorOnlyEntityHandler(),
aznew UiEditorOnlyEntityHandler() };
ComponentRequirementsValidator m_componentRequirementsValidator;
EntityIdSet m_editorOnlyEntityIds;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,76 @@
/*
* 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/Spawnable/EditorOnlyEntityHandler/EditorOnlyEntityHandler.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
void EditorOnlyEntityHandler::AddEditorOnlyEntity(
AZ::Entity* editorOnlyEntity,
AZStd::unordered_set<AZ::EntityId>& editorOnlyEntities)
{
editorOnlyEntities.insert(editorOnlyEntity->GetId());
}
EditorOnlyEntityHandler::Result EditorOnlyEntityHandler::HandleEditorOnlyEntities(
const EntityList& /*entities*/,
const EntityIdSet& /*editorOnlyEntityIds*/,
AZ::SerializeContext& /*serializeContext*/)
{
return AZ::Success();
}
EditorOnlyEntityHandler::Result EditorOnlyEntityHandler::ValidateReferences(
const EntityList& entities,
const EntityIdSet& editorOnlyEntityIds,
AZ::SerializeContext& serializeContext)
{
EditorOnlyEntityHandler::Result result = AZ::Success();
// Inspect all runtime entities via the serialize context and identify any references to editor-only entity Ids.
for (AZ::Entity* runtimeEntity : entities)
{
if (editorOnlyEntityIds.end() != editorOnlyEntityIds.find(runtimeEntity->GetId()))
{
continue; // This is not a runtime entity, so no need to validate its references as it's going away.
}
AZ::EntityUtils::EnumerateEntityIds<AZ::Entity>(
runtimeEntity,
[&editorOnlyEntityIds, &result, runtimeEntity](const AZ::EntityId& id, bool /*isEntityId*/, const AZ::SerializeContext::ClassElement* /*elementData*/)
{
if (editorOnlyEntityIds.end() != editorOnlyEntityIds.find(id))
{
result = AZ::Failure(
AZStd::string::format(
"A runtime entity (%s) contains references to an entity marked as editor-only.",
runtimeEntity->GetName().c_str()
)
);
return false;
}
return true;
},
&serializeContext
);
if (!result)
{
break;
}
}
return result;
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,68 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Entity.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
/**
* Callback handler interface for processing prefab prior to stripping of editor-only entities.
*/
class EditorOnlyEntityHandler
{
public:
AZ_CLASS_ALLOCATOR(EditorOnlyEntityHandler, AZ::SystemAllocator, 0);
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::EditorOnlyEntityHandler, "{C420F65D-18AE-4CAF-BB18-70FA4FE73243}");
virtual ~EditorOnlyEntityHandler() = default;
virtual bool IsEntityUniquelyForThisHandler(AZ::Entity* entity) const = 0;
/**
* Adds the given entity ID to the set of editor only entities.
*
* Handlers can customize this behavior, such as additionally adding child entities
* when a parent is marked as editor-only.
*/
virtual void AddEditorOnlyEntity(
AZ::Entity* editorOnlyEntity,
AZStd::unordered_set<AZ::EntityId>& editorOnlyEntities);
using Result = AZ::Outcome<void, AZStd::string>;
/**
* This handler is responsible for making any necessary modifications to other entities in the Prefab prior to the removal
* of all editor-only entities.
* After this callback returns, editor-only entities will be removed from the Prefab.
* See \ref WorldEditorOnlyEntityHandler below for an example of processing and validation that occurs for standard world entities.
* @param entities a list of all entities in the Prefab, including those marked as editor-only.
* @param editorOnlyEntityIds a precomputed set containing Ids for all entities within the 'entities' list that were marked as editor-only.
* @param serializeContext useful to inspect entity data for validation purposes.
*/
virtual Result HandleEditorOnlyEntities(
const EntityList& /*entities*/,
const EntityIdSet& /*editorOnlyEntityIds*/,
AZ::SerializeContext& /*serializeContext*/);
// Verify that none of the runtime entities reference editor-only entities. Fail w/ details if so.
static Result ValidateReferences(
const EntityList& entities,
const EntityIdSet& editorOnlyEntityIds,
AZ::SerializeContext& serializeContext);
};
using EditorOnlyEntityHandlers = AZStd::vector<EditorOnlyEntityHandler*>;
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,45 @@
/*
* 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/Spawnable/EditorOnlyEntityHandler/UiEditorOnlyEntityHandler.h>
#include <AzFramework/InGameUI/UiFrameworkBus.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
bool UiEditorOnlyEntityHandler::IsEntityUniquelyForThisHandler(AZ::Entity* entity) const
{
// Assume that an entity is a UI element if it has a UI element component.
bool uniqueForThisHandler = false;
UiFrameworkBus::BroadcastResult(uniqueForThisHandler, &UiFrameworkInterface::HasUiElementComponent, entity);
return uniqueForThisHandler;
}
void UiEditorOnlyEntityHandler::AddEditorOnlyEntity(AZ::Entity* editorOnlyEntity, EntityIdSet& editorOnlyEntities)
{
UiFrameworkBus::Broadcast(&UiFrameworkInterface::AddEditorOnlyEntity, editorOnlyEntity, editorOnlyEntities);
}
EditorOnlyEntityHandler::Result UiEditorOnlyEntityHandler::HandleEditorOnlyEntities(
const AzToolsFramework::EntityList& exportEntities,
const AzToolsFramework::EntityIdSet& editorOnlyEntityIds,
AZ::SerializeContext& serializeContext)
{
UiFrameworkBus::Broadcast(&UiFrameworkInterface::HandleEditorOnlyEntities, exportEntities, editorOnlyEntityIds);
// Perform a final check to verify that all editor-only entities have been removed
auto result = ValidateReferences(exportEntities, editorOnlyEntityIds, serializeContext);
return result;
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,43 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/EditorOnlyEntityHandler.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
/**
* EditorOnlyEntity handler for UI entities.
* - Removes editor-only entities and their descedent hierarchy entirely.
* -- This differs from the world-entity handler where editor-only entities
* are removed "in-place".
* - Validates that no editor entities are referenced by non-editor entities.
*/
class UiEditorOnlyEntityHandler
: public EditorOnlyEntityHandler
{
public:
AZ_CLASS_ALLOCATOR(UiEditorOnlyEntityHandler, AZ::SystemAllocator, 0);
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::UiEditorOnlyEntityHandler, "{949CF813-4A8E-4D55-B323-0ED2A967CDCC}", EditorOnlyEntityHandler);
bool IsEntityUniquelyForThisHandler(AZ::Entity* entity) const override;
void AddEditorOnlyEntity(AZ::Entity* editorOnlyEntity, EntityIdSet& editorOnlyEntities) override;
Result HandleEditorOnlyEntities(
const AzToolsFramework::EntityList& entities,
const AzToolsFramework::EntityIdSet& editorOnlyEntityIds,
AZ::SerializeContext& serializeContext) override;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,83 @@
/*
* 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/Spawnable/EditorOnlyEntityHandler/WorldEditorOnlyEntityHandler.h>
#include <AzCore/Component/TransformBus.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
bool WorldEditorOnlyEntityHandler::IsEntityUniquelyForThisHandler(AZ::Entity* entity) const
{
return AZ::EntityUtils::FindFirstDerivedComponent<AZ::TransformInterface>(entity) != nullptr;
}
EditorOnlyEntityHandler::Result WorldEditorOnlyEntityHandler::HandleEditorOnlyEntities(const AzToolsFramework::EntityList& entities, const AzToolsFramework::EntityIdSet& editorOnlyEntityIds, AZ::SerializeContext& serializeContext)
{
FixTransformRelationships(entities, editorOnlyEntityIds);
return ValidateReferences(entities, editorOnlyEntityIds, serializeContext);
}
void WorldEditorOnlyEntityHandler::FixTransformRelationships(const AzToolsFramework::EntityList& entities, const AzToolsFramework::EntityIdSet& editorOnlyEntityIds)
{
AZStd::unordered_map<AZ::EntityId, AZStd::vector<AZ::Entity*>> parentToChildren;
// Build a map of entity Ids to their parent Ids, for faster lookup during processing.
for (AZ::Entity* entity : entities)
{
AZ::TransformInterface* transformComponent = AZ::EntityUtils::FindFirstDerivedComponent<AZ::TransformInterface>(entity);
if (transformComponent)
{
const AZ::EntityId parentId = transformComponent->GetParentId();
if (parentId.IsValid())
{
parentToChildren[parentId].push_back(entity);
}
}
}
// Identify any editor-only entities. If we encounter one, adjust transform relationships
// for all of its children to ensure relative transforms are maintained and respected at
// runtime.
// This works regardless of entity ordering in the Prefab because we add reassigned children to
// parentToChildren cache during the operation.
for (AZ::Entity* entity : entities)
{
if (editorOnlyEntityIds.end() == editorOnlyEntityIds.find(entity->GetId()))
{
continue; // This is not an editor-only entity.
}
AZ::TransformInterface* transformComponent = AZ::EntityUtils::FindFirstDerivedComponent<AZ::TransformInterface>(entity);
if (transformComponent)
{
const AZ::Transform& parentLocalTm = transformComponent->GetLocalTM();
// Identify all transform children and adjust them to be children of the removed entity's parent.
for (AZ::Entity* childEntity : parentToChildren[entity->GetId()])
{
AZ::TransformInterface* childTransformComponent = AZ::EntityUtils::FindFirstDerivedComponent<AZ::TransformInterface>(childEntity);
if (childTransformComponent && childTransformComponent->GetParentId() == entity->GetId())
{
const AZ::Transform localTm = childTransformComponent->GetLocalTM();
childTransformComponent->SetParent(transformComponent->GetParentId());
childTransformComponent->SetLocalTM(parentLocalTm * localTm);
parentToChildren[transformComponent->GetParentId()].push_back(childEntity);
}
}
}
}
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,46 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/EditorOnlyEntityHandler.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
/**
* EditorOnlyEntity handler for world entities.
* - Fixes up transform relationships so entities removed mid-hierarchy still result in valid runtime transform relationships
* and correct relative transforms.
* - Validates that no editor entities are referenced by non-editor entities.
*/
class WorldEditorOnlyEntityHandler
: public EditorOnlyEntityHandler
{
public:
AZ_CLASS_ALLOCATOR(WorldEditorOnlyEntityHandler, AZ::SystemAllocator, 0);
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::WorldEditorOnlyEntityHandler, "{55587AE2-B583-48E4-9634-6BFACF6CBF04}", EditorOnlyEntityHandler);
bool IsEntityUniquelyForThisHandler(AZ::Entity* entity) const override;
Result HandleEditorOnlyEntities(
const AzToolsFramework::EntityList& entities,
const AzToolsFramework::EntityIdSet& editorOnlyEntityIds,
AZ::SerializeContext& serializeContext) override;
// Adjust transform relationships to maintain integrity of the transform hierarchy at runtime, even if editor-only
// entities were positioned within the transform hierarchy.
static void FixTransformRelationships(
const AzToolsFramework::EntityList& entities,
const AzToolsFramework::EntityIdSet& editorOnlyEntityIds);
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,63 @@
/*
* 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 <AzCore/IO/ByteContainerStream.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.h>
#include <AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
void PrefabCatchmentProcessor::Process(PrefabProcessorContext& context)
{
context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab)
{
ProcessPrefab(context, prefabName, prefab);
});
}
void PrefabCatchmentProcessor::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<PrefabCatchmentProcessor, PrefabProcessor>()->Version(1);
}
}
void PrefabCatchmentProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab)
{
AZStd::string uniqueName = prefabName;
uniqueName += '.';
uniqueName += AzFramework::Spawnable::FileExtension;
auto serializer = [](AZStd::vector<uint8_t>& output, const ProcessedObjectStore& object) -> bool
{
AZ::IO::ByteContainerStream stream(&output);
return AZ::Utils::SaveObjectToStream(stream, AZ::DataStream::StreamType::ST_BINARY,
AZStd::any_cast<void>(&object.GetObject()), object.GetObject().type());
};
auto spawnable = SpawnableUtils::CreateSpawnable(prefab);
SpawnableUtils::SortEntitiesByTransformHierarchy(spawnable);
AZStd::any spawnableAny(AZStd::move(spawnable));
context.GetProcessedObjects().emplace_back(AZStd::move(uniqueName), AZStd::move(spawnableAny),
AZStd::move(serializer), AZ::AzTypeInfo<AzFramework::Spawnable>::Uuid());
context.RemovePrefab(prefabName);
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,42 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessor.h>
namespace AZ
{
class ReflectContext;
}
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class PrefabCatchmentProcessor
: public PrefabProcessor
{
public:
AZ_CLASS_ALLOCATOR(PrefabCatchmentProcessor, AZ::SystemAllocator, 0);
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor,
"{F71E2FBA-22ED-44C7-B4C8-D2CF4B2C7B97}", PrefabProcessor);
~PrefabCatchmentProcessor() override = default;
void Process(PrefabProcessorContext& context) override;
static void Reflect(AZ::ReflectContext* context);
protected:
static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab);
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,48 @@
/*
* 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 <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
bool PrefabConversionPipeline::LoadStackProfile(AZStd::string_view stackProfile)
{
m_processors.clear();
AZStd::string registryKey = "/Amazon/Tools/Prefab/Processing/Stack/";
registryKey += stackProfile;
auto registry = AZ::SettingsRegistry::Get();
AZ_Assert(registry, "PrefabConversionPipeline is created before the Settings Registry is available.");
return registry->GetObject(m_processors, registryKey);
}
void PrefabConversionPipeline::ProcessPrefab(PrefabProcessorContext& context)
{
for (auto& processor : m_processors)
{
processor->Process(context);
}
}
void PrefabConversionPipeline::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<PrefabProcessor>()->Version(1);
serializeContext->RegisterGenericType<PrefabProcessorList>();
serializeContext->RegisterGenericType<PrefabProcessorListEntry>();
}
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,42 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string_view.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessor.h>
#include <AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class PrefabConversionPipeline final
{
public:
AZ_CLASS_ALLOCATOR(PrefabConversionPipeline, AZ::SystemAllocator, 0);
using PrefabProcessorListEntry = AZStd::unique_ptr<PrefabProcessor>;
using PrefabProcessorList = AZStd::vector<PrefabProcessorListEntry>;
bool LoadStackProfile(AZStd::string_view stackProfile);
void ProcessPrefab(PrefabProcessorContext& context);
static void Reflect(AZ::ReflectContext* context);
private:
PrefabProcessorList m_processors;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,31 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class PrefabProcessor
{
public:
AZ_CLASS_ALLOCATOR(PrefabProcessor, AZ::SystemAllocator, 0);
AZ_RTTI(PrefabProcessor, "{393C95DF-C0DA-4EF0-A081-9CA899649DDD}");
virtual ~PrefabProcessor() = default;
virtual void Process(PrefabProcessorContext& context) = 0;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,84 @@
/*
* 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/Spawnable/PrefabProcessorContext.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
bool PrefabProcessorContext::AddPrefab(AZStd::string prefabName, PrefabDom prefab)
{
auto result = m_prefabs.emplace(AZStd::move(prefabName), AZStd::move(prefab));
return result.second;
}
bool PrefabProcessorContext::RemovePrefab(AZStd::string_view prefabName)
{
if (!m_isIterating)
{
return m_prefabs.erase(prefabName) > 0;
}
else
{
m_delayedDelete.emplace_back(prefabName);
}
return false;
}
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(AZStd::string_view, PrefabDom&)>& callback)
{
m_isIterating = true;
for (auto& it : m_prefabs)
{
if (AZStd::find(m_delayedDelete.begin(), m_delayedDelete.end(), it.first) == m_delayedDelete.end())
{
callback(it.first, it.second);
}
}
m_isIterating = false;
// Clear out any prefabs that have been deleted.
for (AZStd::string& deleted : m_delayedDelete)
{
m_prefabs.erase(deleted);
}
m_delayedDelete.clear();
}
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDom&)>& callback) const
{
for (const auto& it : m_prefabs)
{
callback(it.first, it.second);
}
}
bool PrefabProcessorContext::HasPrefabs() const
{
return !m_prefabs.empty();
}
PrefabProcessorContext::ProcessedObjectStoreContainer& PrefabProcessorContext::GetProcessedObjects()
{
return m_products;
}
const PrefabProcessorContext::ProcessedObjectStoreContainer& PrefabProcessorContext::GetProcessedObjects() const
{
return m_products;
}
const AZ::PlatformTagSet& PrefabProcessorContext::GetPlatformTags() const
{
return m_platformTags;
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,58 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/ComponentExport.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/string_view.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class PrefabProcessorContext
{
public:
using ProcessedObjectStoreContainer = AZStd::vector<ProcessedObjectStore>;
AZ_CLASS_ALLOCATOR(PrefabProcessorContext, AZ::SystemAllocator, 0);
AZ_RTTI(PrefabProcessorContext, "{C7D77E3A-C544-486B-B774-7C82C38FE22F}");
virtual ~PrefabProcessorContext() = default;
virtual bool AddPrefab(AZStd::string prefabName, PrefabDom prefab);
virtual bool RemovePrefab(AZStd::string_view prefabName);
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, PrefabDom&)>& callback);
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDom&)>& callback) const;
virtual bool HasPrefabs() const;
virtual ProcessedObjectStoreContainer& GetProcessedObjects();
virtual const ProcessedObjectStoreContainer& GetProcessedObjects() const;
virtual const AZ::PlatformTagSet& GetPlatformTags() const;
protected:
using NamedPrefabContainer = AZStd::unordered_map<AZStd::string, PrefabDom>;
NamedPrefabContainer m_prefabs;
ProcessedObjectStoreContainer m_products;
AZStd::vector<AZStd::string> m_delayedDelete;
AZ::PlatformTagSet m_platformTags;
bool m_isIterating{ false };
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,65 @@
/*
* 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 <AzCore/Casting/lossy_cast.h>
#include <AzCore/Math/Uuid.h>
#include <AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
ProcessedObjectStore::ProcessedObjectStore(AZStd::string uniqueId, AZStd::any object, SerializerFunction objectSerializer,
AZ::Data::AssetType assetType)
: m_uniqueId(AZStd::move(uniqueId))
, m_object(AZStd::move(object))
, m_objectSerializer(AZStd::move(objectSerializer))
, m_assetType(AZStd::move(assetType))
{
}
bool ProcessedObjectStore::Serialize(AZStd::vector<uint8_t>& output) const
{
if (m_objectSerializer)
{
return m_objectSerializer(output, *this);
}
else
{
return false;
}
}
const AZStd::any& ProcessedObjectStore::GetObject() const
{
return m_object;
}
AZStd::any ProcessedObjectStore::ReleaseObject()
{
return AZStd::move(m_object);
}
uint32_t ProcessedObjectStore::BuildSubId() const
{
AZ::Uuid subIdHash = AZ::Uuid::CreateData(m_uniqueId.data(), m_uniqueId.size());
return azlossy_caster(subIdHash.GetHash());
}
const AZ::Data::AssetType& ProcessedObjectStore::GetAssetType() const
{
return m_assetType;
}
const AZStd::string& ProcessedObjectStore::GetId() const
{
return m_uniqueId;
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,56 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/any.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/functional.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
//! Storage for objects created through the Prefab processing pipeline.
//! These typically store the created object for immediate use in the editor plus additional information
//! to allow the Prefab Builder to convert the object into a serialized form and register it with the
//! Asset Database.
class ProcessedObjectStore
{
public:
using SerializerFunction = AZStd::function<bool(AZStd::vector<uint8_t>&, const ProcessedObjectStore&)>;
//! Constructs a new instance.
//! @param uniqueId A name for the object that's unique within the scope of the Prefab. This name will be used to generate a sub id for the product
//! which requires that the name is stable between runs.
//! @param object The object that generated during processing of a Prefab.
//! @param objectSerializer The callback used to convert the provided object into a binary stream.
//! @param assetType The asset type of the asset.
//! @param storagePath The relative path where the asset will be stored if/when committed to disk.
ProcessedObjectStore(AZStd::string uniqueId, AZStd::any object, SerializerFunction objectSerializer, AZ::Data::AssetType assetType);
bool Serialize(AZStd::vector<uint8_t>& output) const;
uint32_t BuildSubId() const;
const AZStd::any& GetObject() const;
AZStd::any ReleaseObject();
const AZ::Data::AssetType& GetAssetType() const;
const AZStd::string& GetId() const;
private:
AZStd::any m_object;
SerializerFunction m_objectSerializer;
AZ::Data::AssetType m_assetType;
AZStd::string m_uniqueId;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,212 @@
/*
* 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 <AzCore/std/algorithm.h>
#include <AzCore/std/sort.h>
#include <AzToolsFramework/Prefab/Spawnable/SpawnableMetaDataBuilder.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::Add(AZStd::string_view key, bool value)
{
return AddGeneric(key, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::Add(AZStd::string_view key, uint64_t value)
{
return AddGeneric(key, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::Add(AZStd::string_view key, int64_t value)
{
return AddGeneric(key, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::Add(AZStd::string_view key, double value)
{
return AddGeneric(key, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::Add(AZStd::string_view key, AZStd::string value)
{
return AddGeneric(key, AZStd::move(value));
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AppendArray(AZStd::string_view arrayKey, bool value)
{
return AppendArrayGeneric(arrayKey, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AppendArray(AZStd::string_view arrayKey, uint64_t value)
{
return AppendArrayGeneric(arrayKey, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AppendArray(AZStd::string_view arrayKey, int64_t value)
{
return AppendArrayGeneric(arrayKey, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AppendArray(AZStd::string_view arrayKey, double value)
{
return AppendArrayGeneric(arrayKey, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AppendArray(AZStd::string_view arrayKey, AZStd::string value)
{
return AppendArrayGeneric(arrayKey, AZStd::move(value));
}
bool SpawnableMetaDataBuilder::Remove(AZStd::string_view key)
{
auto it = m_table.find(HashKey(key));
if (it != m_table.end())
{
RemoveAllEntriesIfArray(key, it);
m_table.erase(it);
return true;
}
return false;
}
bool SpawnableMetaDataBuilder::RemoveArrayEntry(AZStd::string_view arrayKey, uint64_t index)
{
return RemoveArrayEntry(arrayKey, aznumeric_cast<AzFramework::SpawnableMetaDataArrayIndex>(index));
}
bool SpawnableMetaDataBuilder::RemoveArrayEntry(AZStd::string_view arrayKey, AzFramework::SpawnableMetaDataArrayIndex index)
{
auto it = m_table.find(HashKey(arrayKey));
if (it != m_table.end())
{
if (AzFramework::SpawnableMetaDataArraySize* size =
AZStd::get_if<AzFramework::SpawnableMetaDataArraySize>(&it->second); size != nullptr)
{
if (index < *size)
{
AZ::HashValue64 indexHash = HashArrayKey(arrayKey, index);
index++;
for (; index < *size; ++index)
{
AZ::HashValue64 nextIndexHash = HashArrayKey(arrayKey, index);
m_table[indexHash] = AZStd::move(m_table[nextIndexHash]);
indexHash = nextIndexHash;
}
[[maybe_unused]] size_t removedCount = m_table.erase(indexHash);
AZ_Assert(removedCount == 1, "RemoveArrayEntry did not correctly detect an edge case.");
(*size)--;
return true;
}
}
}
return false;
}
size_t SpawnableMetaDataBuilder::GetEntryCount() const
{
return m_table.size();
}
AzFramework::SpawnableMetaData SpawnableMetaDataBuilder::BuildMetaData() const
{
AzFramework::SpawnableMetaData::Table readOnlyTable;
readOnlyTable.reserve(m_table.size());
AZStd::transform(m_table.begin(), m_table.end(), AZStd::back_inserter(readOnlyTable),
[](const auto& entry)
{
return AzFramework::SpawnableMetaData::TableEntry(entry.first, entry.second);
});
AZStd::sort(readOnlyTable.begin(), readOnlyTable.end(),
[](const auto& lhs, const auto& rhs)
{
return lhs.first < rhs.first;
});
return AzFramework::SpawnableMetaData(AZStd::move(readOnlyTable));
}
AZ::HashValue64 SpawnableMetaDataBuilder::HashKey(AZStd::string_view key) const
{
return AZ::TypeHash64(reinterpret_cast<const uint8_t*>(key.data()), aznumeric_cast<uint64_t>(key.length()));
}
AZ::HashValue64 SpawnableMetaDataBuilder::HashArrayKey(AZStd::string_view arrayKey, uint64_t index) const
{
return AZ::TypeHash64(reinterpret_cast<const uint8_t*>(arrayKey.data()), aznumeric_cast<uint64_t>(arrayKey.length()),
aznumeric_caster(AzFramework::SpawnableMetaData::ArrayKeyRoot + index));
}
AZ::HashValue64 SpawnableMetaDataBuilder::HashArrayKey(AZStd::string_view arrayKey, AzFramework::SpawnableMetaDataArrayIndex index) const
{
return HashArrayKey(arrayKey, aznumeric_cast<uint64_t>(index));
}
template<typename T>
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AddGeneric(AZStd::string_view key, T&& value)
{
auto keyHash = HashKey(key);
auto it = m_table.find(keyHash);
if (it != m_table.end())
{
RemoveAllEntriesIfArray(key, it);
it->second = AZStd::forward<T>(value);
}
else
{
m_table.emplace(keyHash, AZStd::forward<T>(value));
}
return *this;
}
template<typename T>
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AppendArrayGeneric(AZStd::string_view arrayKey, T&& value)
{
auto arrayKeyHash = HashKey(arrayKey);
auto it = m_table.find(arrayKeyHash);
if (it != m_table.end())
{
if (AzFramework::SpawnableMetaDataArraySize* storedValue =
AZStd::get_if<AzFramework::SpawnableMetaDataArraySize>(&it->second); storedValue != nullptr)
{
m_table[HashArrayKey(arrayKey, (*storedValue)++)] = AZStd::forward<T>(value);
}
else
{
it->second = AzFramework::SpawnableMetaDataArraySize{ 1 };
m_table[HashArrayKey(arrayKey, 0)] = AZStd::forward<T>(value);
}
}
else
{
m_table.emplace(arrayKeyHash, AzFramework::SpawnableMetaDataArraySize{ 1 });
m_table[HashArrayKey(arrayKey, 0)] = AZStd::forward<T>(value);
}
return *this;
}
void SpawnableMetaDataBuilder::RemoveAllEntriesIfArray(AZStd::string_view arrayKey, Table::iterator sizeEntry)
{
if (AzFramework::SpawnableMetaDataArraySize* size =
AZStd::get_if<AzFramework::SpawnableMetaDataArraySize>(&sizeEntry->second); size != nullptr)
{
for (AzFramework::SpawnableMetaDataArrayIndex i{ 0 }; i < *size; ++i)
{
[[maybe_unused]] size_t removedCount = m_table.erase(HashArrayKey(arrayKey, i));
AZ_Assert(removedCount == 1, "RemoveArrayEntry did not correctly detect an edge case.");
}
}
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,61 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Utils/TypeHash.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzFramework/Spawnable/SpawnableMetaData.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class SpawnableMetaDataBuilder final
{
public:
SpawnableMetaDataBuilder& Add(AZStd::string_view key, bool value);
SpawnableMetaDataBuilder& Add(AZStd::string_view key, uint64_t value);
SpawnableMetaDataBuilder& Add(AZStd::string_view key, int64_t value);
SpawnableMetaDataBuilder& Add(AZStd::string_view key, double value);
SpawnableMetaDataBuilder& Add(AZStd::string_view key, AZStd::string value);
SpawnableMetaDataBuilder& AppendArray(AZStd::string_view arrayKey, bool value);
SpawnableMetaDataBuilder& AppendArray(AZStd::string_view arrayKey, uint64_t value);
SpawnableMetaDataBuilder& AppendArray(AZStd::string_view arrayKey, int64_t value);
SpawnableMetaDataBuilder& AppendArray(AZStd::string_view arrayKey, double value);
SpawnableMetaDataBuilder& AppendArray(AZStd::string_view arrayKey, AZStd::string value);
bool Remove(AZStd::string_view key);
bool RemoveArrayEntry(AZStd::string_view arrayKey, uint64_t index);
bool RemoveArrayEntry(AZStd::string_view arrayKey, AzFramework::SpawnableMetaDataArrayIndex index);
size_t GetEntryCount() const;
AzFramework::SpawnableMetaData BuildMetaData() const;
private:
using Table = AZStd::unordered_map<AZ::HashValue64, AzFramework::SpawnableMetaData::TableValue>;
AZ::HashValue64 HashKey(AZStd::string_view key) const;
AZ::HashValue64 HashArrayKey(AZStd::string_view arrayKey, uint64_t index) const;
AZ::HashValue64 HashArrayKey(AZStd::string_view arrayKey, AzFramework::SpawnableMetaDataArrayIndex index) const;
template<typename T>
SpawnableMetaDataBuilder& AddGeneric(AZStd::string_view key, T&& value);
template<typename T>
SpawnableMetaDataBuilder& AppendArrayGeneric(AZStd::string_view arrayKey, T&& value);
void RemoveAllEntriesIfArray(AZStd::string_view arrayKey, Table::iterator sizeEntry);
Table m_table;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,219 @@
/*
* 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/Spawnable/SpawnableUtils.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/EntityUtils.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzFramework/Spawnable/Spawnable.h>
namespace AzToolsFramework::Prefab::SpawnableUtils
{
AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom)
{
AzFramework::Spawnable spawnable;
AZStd::unique_ptr<Instance> instance(aznew Instance());
if (!Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*instance, prefabDom, false))
{
AZ_Assert(false,
"Failed to Load Prefab Instance from given Prefab DOM while Spawnable creation.");
}
else
{
AzFramework::Spawnable::EntityList& entities = spawnable.GetEntities();
instance->DetachNestedEntities([&entities](AZStd::unique_ptr<AZ::Entity> entity)
{
entities.emplace_back(AZStd::move(entity));
});
}
return spawnable;
}
void OrganizeEntitiesForSorting(
AzFramework::Spawnable::EntityList& entities,
AZStd::unordered_set<AZ::EntityId>& existingEntityIds,
AZStd::unordered_map<AZ::EntityId, AzFramework::Spawnable::EntityList>& parentIdToChildren,
AZStd::vector<AZ::EntityId>& candidateIds,
size_t& removedEntitiesCount)
{
existingEntityIds.clear();
parentIdToChildren.clear();
candidateIds.clear();
removedEntitiesCount = 0;
for (auto& entity : entities)
{
if (!entity)
{
++removedEntitiesCount;
continue;
}
AZ::EntityId entityId = entity->GetId();
if (!entityId.IsValid())
{
AZ_Warning("Entity", false, "Hierarchy sort found entity '%s' with invalid ID", entity->GetName().c_str());
++removedEntitiesCount;
continue;
}
if (!existingEntityIds.insert(entityId).second)
{
AZ_Warning("Entity", false, "Hierarchy sort found multiple entities using same ID as entity '%s' %s",
entity->GetName().c_str(),
entityId.ToString().c_str());
++removedEntitiesCount;
continue;
}
// search for any component that implements the TransformInterface.
// don't use EBus because we support sorting entities that haven't been initialized or activated.
// entities with no transform component will be treated like entities with no parent.
AZ::EntityId parentId;
if (AZ::TransformInterface* transformInterface =
AZ::EntityUtils::FindFirstDerivedComponent<AZ::TransformInterface>(entity.get()))
{
parentId = transformInterface->GetParentId();
if (parentId == entityId)
{
AZ_Warning("Entity", false, "Hierarchy sort found entity parented to itself '%s' %s",
entity->GetName().c_str(),
entityId.ToString().c_str());
parentId.SetInvalid();
}
}
auto& children = parentIdToChildren[parentId];
children.emplace_back(nullptr);
children.back().swap(entity);
}
// clear 'entities', we'll refill it in sorted order.
entities.clear();
// the first candidates should be the parents of the roots.
for (auto& parentChildrenPair : parentIdToChildren)
{
const AZ::EntityId& parentId = parentChildrenPair.first;
// we found a root if parent ID doesn't correspond to any entity in the list
if (existingEntityIds.find(parentId) == existingEntityIds.end())
{
candidateIds.push_back(parentId);
}
}
}
void TraceParentingLoop(
const AZ::EntityId& parentFromLoopId,
const AZStd::unordered_map<AZ::EntityId, AzFramework::Spawnable::EntityList>& parentIdToChildren)
{
// Find name to use in warning message
AZStd::string_view parentFromLoopName;
for (const auto& parentIdChildrenPair : parentIdToChildren)
{
for (const auto& entity : parentIdChildrenPair.second)
{
if (entity->GetId() == parentFromLoopId)
{
parentFromLoopName = entity->GetName();
break;
}
if (!parentFromLoopName.empty())
{
break;
}
}
}
AZ_Warning("Entity", false, "Hierarchy sort found parenting loop involving entity '%.*s' %s",
AZ_STRING_ARG(parentFromLoopName),
parentFromLoopId.ToString().c_str());
}
void SortEntitiesByTransformHierarchy(AzFramework::Spawnable& spawnable)
{
auto& entities = spawnable.GetEntities();
const size_t originalEntityCount = entities.size();
// IDs of those present in 'entities'. Does not include parent ID if parent not found in 'entities'
AZStd::unordered_set<AZ::EntityId> existingEntityIds;
// map children by their parent ID (even if parent not found in 'entities')
AZStd::unordered_map<AZ::EntityId, AzFramework::Spawnable::EntityList> parentIdToChildren;
// use 'candidateIds' to track the parent IDs we're going to process next.
AZStd::vector<AZ::EntityId> candidateIds;
candidateIds.reserve(originalEntityCount + 1);
size_t removedCount = 0;
OrganizeEntitiesForSorting(entities, existingEntityIds, parentIdToChildren, candidateIds, removedCount);
// process candidates until everything is sorted:
// - add candidate's children to the final sorted order
// - add candidate's children to list of candidates, so we can process *their* children in a future loop
// - erase parent/children entry from parentToChildrenIds
// - continue until nothing is left in parentToChildrenIds
for (size_t candidateIndex = 0; !parentIdToChildren.empty(); ++candidateIndex)
{
// if there are no more candidates, but there are still unsorted children, then we have an infinite loop.
// pick an arbitrary parent from the loop to be the next candidate.
if (candidateIndex == candidateIds.size())
{
const AZ::EntityId& parentFromLoopId = parentIdToChildren.begin()->first;
#ifdef AZ_ENABLE_TRACING
TraceParentingLoop(parentFromLoopId, parentIdToChildren);
#endif // AZ_ENABLE_TRACING
candidateIds.push_back(parentFromLoopId);
}
const AZ::EntityId& parentId = candidateIds[candidateIndex];
auto foundChildren = parentIdToChildren.find(parentId);
if (foundChildren != parentIdToChildren.end())
{
for (auto& child : foundChildren->second)
{
candidateIds.push_back(child->GetId());
entities.emplace_back(nullptr);
entities.back().swap(child);
}
parentIdToChildren.erase(foundChildren);
}
}
AZ_Assert(entities.size() + removedCount == originalEntityCount,
"Wrong number of entities after sort. Original entity count = %zu, Sorted entity count = %zu, Removed entity count = %zu",
originalEntityCount,
entities.size(),
removedCount
);
}
} // namespace AzToolsFramework::Prefab::SpawnableUtils
@@ -0,0 +1,23 @@
/*
* 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.
*
*/
#pragma once
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
namespace AzToolsFramework::Prefab::SpawnableUtils
{
AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom);
void SortEntitiesByTransformHierarchy(AzFramework::Spawnable& spawnable);
} // namespace AzToolsFramework::Prefab::SpawnableUtils