Merge remote-tracking branch 'upstream/development' into compiletime_filerequest_code2

This commit is contained in:
nemerle
2022-01-08 21:25:48 +01:00
785 changed files with 141969 additions and 22455 deletions
@@ -144,7 +144,10 @@ namespace AzToolsFramework
{
emit ClearStringFilter();
emit ClearTypeFilter();
m_sourceFilterModel->FilterUpdatedSlotImmediate();
if (m_sourceFilterModel)
{
m_sourceFilterModel->FilterUpdatedSlotImmediate();
}
}
void AssetBrowserTableView::Update()
@@ -415,8 +415,8 @@ namespace AzToolsFramework
{
// Construct the runtime entities and products
bool readyToCreateRootSpawnable = m_playInEditorData.m_assetsCache.IsActivated();
if (!readyToCreateRootSpawnable &&
!m_playInEditorData.m_assetsCache.Activate(Prefab::PrefabConversionUtils::PlayInEditor))
if (!readyToCreateRootSpawnable && !m_playInEditorData.m_assetsCache.Activate(Prefab::PrefabConversionUtils::PlayInEditor))
{
AZ_Error("Prefab", false, "Failed to create a prefab processing stack from key '%.*s'.", AZ_STRING_ARG(Prefab::PrefabConversionUtils::PlayInEditor));
return;
@@ -184,6 +184,10 @@ namespace AzToolsFramework
const float halfGridSquareCount = float(gridSquareCount) * 0.5f;
const float halfGridSize = halfGridSquareCount * squareSize;
const float fadeLineLength = cl_viewportFadeLineDistanceScale * squareSize;
// ensure AuxGeomDraw::OpacityType::Translucent render state is set
debugDisplay.SetAlpha(0.5f);
for (size_t lineIndex = 0; lineIndex <= gridSquareCount; ++lineIndex)
{
const float lineOffset = -halfGridSize + (lineIndex * squareSize);
@@ -142,14 +142,14 @@ namespace AzToolsFramework
return m_templateSourcePath;
}
void Instance::SetTemplateSourcePath(AZ::IO::PathView sourcePath)
void Instance::SetTemplateSourcePath(AZ::IO::Path sourcePath)
{
m_templateSourcePath = sourcePath;
m_templateSourcePath = AZStd::move(sourcePath);
}
void Instance::SetContainerEntityName(AZStd::string_view containerName)
void Instance::SetContainerEntityName(AZStd::string containerName)
{
m_containerEntity->SetName(containerName);
m_containerEntity->SetName(AZStd::move(containerName));
}
bool Instance::AddEntity(AZ::Entity& entity)
@@ -608,6 +608,31 @@ namespace AzToolsFramework
}
AZ::EntityId Instance::GetEntityIdFromAliasPath(AliasPathView relativeAliasPath) const
{
return GetInstanceAndEntityIdFromAliasPath(relativeAliasPath).second;
}
AZStd::pair<Instance*, AZ::EntityId> Instance::GetInstanceAndEntityIdFromAliasPath(AliasPathView relativeAliasPath)
{
Instance* instance = this;
AliasPathView path = relativeAliasPath.ParentPath();
for (auto it : path)
{
InstanceOptionalReference child = instance->FindNestedInstance(it.Native());
if (child.has_value())
{
instance = &(child->get());
}
else
{
return AZStd::pair<Instance*, AZ::EntityId>(nullptr, AZ::EntityId());
}
}
return AZStd::pair<Instance*, AZ::EntityId>(instance, instance->GetEntityId(relativeAliasPath.Filename().Native()));
}
AZStd::pair<const Instance*, AZ::EntityId> Instance::GetInstanceAndEntityIdFromAliasPath(AliasPathView relativeAliasPath) const
{
const Instance* instance = this;
AliasPathView path = relativeAliasPath.ParentPath();
@@ -620,11 +645,11 @@ namespace AzToolsFramework
}
else
{
return AZ::EntityId();
return AZStd::pair<const Instance*, AZ::EntityId>(nullptr, AZ::EntityId());
}
}
return instance->GetEntityId(relativeAliasPath.Filename().Native());
return AZStd::pair<const Instance*, AZ::EntityId>(instance, instance->GetEntityId(relativeAliasPath.Filename().Native()));
}
AZStd::vector<InstanceAlias> Instance::GetNestedInstanceAliases(TemplateId templateId) const
@@ -80,8 +80,8 @@ namespace AzToolsFramework
void SetTemplateId(TemplateId templateId);
const AZ::IO::Path& GetTemplateSourcePath() const;
void SetTemplateSourcePath(AZ::IO::PathView sourcePath);
void SetContainerEntityName(AZStd::string_view containerName);
void SetTemplateSourcePath(AZ::IO::Path sourcePath);
void SetContainerEntityName(AZStd::string containerName);
bool AddEntity(AZ::Entity& entity);
bool AddEntity(AZStd::unique_ptr<AZ::Entity>&& entity);
@@ -169,6 +169,13 @@ namespace AzToolsFramework
* @return entityId, invalid ID if not found
*/
AZ::EntityId GetEntityIdFromAliasPath(AliasPathView relativeAliasPath) const;
/**
* Retrieves the instance pointer and entity id from an alias path that's relative to this instance.
*
* @return A pair with the Instance and entity id. The Instance is set to null and entityId is set to invalid if not found.
*/
AZStd::pair<Instance*, AZ::EntityId> GetInstanceAndEntityIdFromAliasPath(AliasPathView relativeAliasPath);
AZStd::pair<const Instance*, AZ::EntityId> GetInstanceAndEntityIdFromAliasPath(AliasPathView relativeAliasPath) const;
/**
@@ -37,13 +37,13 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
}
prefabProcessorContext.ListPrefabs(
[this, &serializeContext, &prefabProcessorContext]([[maybe_unused]] AZStd::string_view prefabName, PrefabDom& prefab)
[this, &serializeContext, &prefabProcessorContext](PrefabDocument& prefab)
{
auto result = RemoveEditorInfo(prefab, serializeContext, prefabProcessorContext);
if (!result)
{
AZ_Error(
"Prefab", false, "Converting to runtime Prefab '%.*s' failed, Error: %s .", AZ_STRING_ARG(prefabName),
"Prefab", false, "Converting to runtime Prefab '%s' failed, Error: %s .", prefab.GetName().c_str(),
result.GetError().c_str());
return;
}
@@ -58,10 +58,9 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
}
}
void EditorInfoRemover::GetEntitiesFromInstance(
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>& instance, EntityList& hierarchyEntities)
void EditorInfoRemover::GetEntitiesFromInstance(AzToolsFramework::Prefab::Instance& instance, EntityList& hierarchyEntities)
{
instance->GetAllEntitiesInHierarchy(
instance.GetAllEntitiesInHierarchy(
[&hierarchyEntities](const AZStd::unique_ptr<AZ::Entity>& entity)
{
hierarchyEntities.emplace_back(entity.get());
@@ -498,7 +497,7 @@ exportComponent, prefabProcessorContext);
}
EditorInfoRemover::RemoveEditorInfoResult EditorInfoRemover::RemoveEditorInfo(
PrefabDom& prefab,
PrefabDocument& prefab,
AZ::SerializeContext* serializeContext,
PrefabProcessorContext& prefabProcessorContext)
{
@@ -510,28 +509,10 @@ exportComponent, prefabProcessorContext);
m_componentRequirementsValidator.SetPlatformTags(prefabProcessorContext.GetPlatformTags());
// convert Prefab DOM into Prefab Instance.
AZStd::unique_ptr<Instance> instance(aznew Instance());
if (!Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*instance, prefab,
Prefab::PrefabDomUtils::LoadFlags::AssignRandomEntityId))
{
PrefabDomValueReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName);
AZStd::string errorMessage("Failed to Load Prefab Instance from given Prefab Dom during Removal of Editor Info.");
if (sourceReference.has_value() &&
sourceReference->get().IsString() &&
sourceReference->get().GetStringLength() != 0)
{
AZStd::string_view source(sourceReference->get().GetString(), sourceReference->get().GetStringLength());
errorMessage += AZStd::string::format("Prefab Source: %.*s", AZ_STRING_ARG(source));
}
return AZ::Failure(errorMessage);
}
// grab all nested entities from the Instance as source entities.
Instance& sourceInstance = prefab.GetInstance();
EntityList sourceEntities;
GetEntitiesFromInstance(instance, sourceEntities);
GetEntitiesFromInstance(sourceInstance, sourceEntities);
EntityList exportEntities;
@@ -597,7 +578,7 @@ exportComponent, prefabProcessorContext);
exportEntitiesMap.emplace(entity->GetId(), entity);
}
);
instance->RemoveNestedEntities(
sourceInstance.RemoveNestedEntities(
[&exportEntitiesMap](const AZStd::unique_ptr<AZ::Entity>& entity)
{
return exportEntitiesMap.find(entity->GetId()) == exportEntitiesMap.end();
@@ -605,7 +586,7 @@ exportComponent, prefabProcessorContext);
);
// replace entities of instance with exported ones.
instance->GetAllEntitiesInHierarchy(
sourceInstance.GetAllEntitiesInHierarchy(
[&exportEntitiesMap](AZStd::unique_ptr<AZ::Entity>& entity)
{
auto entityId = entity->GetId();
@@ -614,16 +595,6 @@ exportComponent, prefabProcessorContext);
}
);
// save the final result in the target Prefab DOM.
PrefabDom filteredPrefab;
if (!PrefabDomUtils::StoreInstanceInPrefabDom(*instance, filteredPrefab))
{
return AZ::Failure(AZStd::string::format(
"Saving exported Prefab Instance within a Prefab Dom failed.")
);
}
prefab.Swap(filteredPrefab);
return AZ::Success();
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -43,7 +43,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
using RemoveEditorInfoResult = AZ::Outcome<void, AZStd::string>;
RemoveEditorInfoResult RemoveEditorInfo(
PrefabDom& prefab,
PrefabDocument& prefab,
AZ::SerializeContext* serializeContext,
PrefabProcessorContext& prefabProcessorContext);
@@ -51,8 +51,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
protected:
using EntityList = AZStd::vector<AZ::Entity*>;
static void GetEntitiesFromInstance(
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>& instance, EntityList& hierarchyEntities);
static void GetEntitiesFromInstance(AzToolsFramework::Prefab::Instance& instance, EntityList& hierarchyEntities);
static bool ReadComponentAttribute(
AZ::Component* component,
@@ -0,0 +1,69 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
enum class EntityAliasType : uint8_t
{
Disable, //!< No alias is added.
OptionalReplace, //!< At runtime the entity might be replaced. If the alias is disabled the original entity will be spawned.
//!< The original entity will be left in the spawnable and a copy is returned.
Replace, //!< At runtime the entity will be replaced. If the alias is disabled nothing will be spawned. The original
//!< entity is returned and a blank entity is left.
Additional, //!< At runtime the alias entity will be added as an additional but unrelated entity with a new entity id.
//!< An empty entity will be returned.
Merge //!< At runtime the components in both entities will be merged. An empty entity will be returned. The added
//!< components may no conflict with the entities already in the root entity.
};
enum class EntityAliasSpawnableLoadBehavior : uint8_t
{
NoLoad, //!< Don't load the spawnable referenced in the entity alias. Loading will be up to the caller.
QueueLoad, //!< Queue the spawnable referenced in the entity alias for loading. This will be an async load because asset
//!< handlers aren't allowed to start a blocking load as this can lead to deadlocks. This option will allow
//!< to disable loading the referenced spawnable through the event fired from the spawnables asset handler.
DependentLoad //!< The spawnable referenced in the entity alias is made a dependency of the spawnable that holds the entity
//!< alias. This will cause the spawnable to be automatically loaded along with the owning spawnable.
};
struct EntityAliasSpawnableLink
{
EntityAliasSpawnableLink(AzFramework::Spawnable& spawnable, AZ::EntityId index);
AzFramework::Spawnable& m_spawnable;
AZ::EntityId m_index;
};
struct EntityAliasPrefabLink
{
EntityAliasPrefabLink(AZStd::string prefabName, AzToolsFramework::Prefab::AliasPath alias);
AZStd::string m_prefabName;
AzToolsFramework::Prefab::AliasPath m_alias;
};
struct EntityAliasStore
{
using LinkStore = AZStd::variant<AZStd::monostate, EntityAliasSpawnableLink, EntityAliasPrefabLink>;
LinkStore m_source;
LinkStore m_target;
uint32_t m_tag;
AzFramework::Spawnable::EntityAliasType m_aliasType;
EntityAliasSpawnableLoadBehavior m_loadBehavior;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -112,9 +112,9 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
// Use a random uuid as this is only a temporary source.
PrefabConversionUtils::PrefabProcessorContext context(AZ::Uuid::CreateRandom());
PrefabDom copy;
copy.CopyFrom(templateReference->get().GetPrefabDom(), copy.GetAllocator(), false);
context.AddPrefab(spawnableName, AZStd::move(copy));
PrefabDocument document(spawnableName);
document.SetPrefabDom(templateReference->get().GetPrefabDom());
context.AddPrefab(AZStd::move(document));
m_converter.ProcessPrefab(context);
if (!context.HasCompletedSuccessfully() || context.GetProcessedObjects().empty())
@@ -25,9 +25,9 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
AZ::DataStream::StreamType serializationFormat = m_serializationFormat == SerializationFormats::Binary ?
AZ::DataStream::StreamType::ST_BINARY : AZ::DataStream::StreamType::ST_XML;
context.ListPrefabs([&context, serializationFormat](AZStd::string_view prefabName, PrefabDom& prefab)
context.ListPrefabs([&context, serializationFormat](PrefabDocument& prefab)
{
ProcessPrefab(context, prefabName, prefab, serializationFormat);
ProcessPrefab(context, prefab, serializationFormat);
});
}
@@ -45,12 +45,12 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
}
}
void PrefabCatchmentProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab,
void PrefabCatchmentProcessor::ProcessPrefab(PrefabProcessorContext& context, PrefabDocument& prefab,
AZ::DataStream::StreamType serializationFormat)
{
using namespace AzToolsFramework::Prefab::SpawnableUtils;
AZStd::string uniqueName = prefabName;
AZStd::string uniqueName = prefab.GetName();
uniqueName += AzFramework::Spawnable::DotFileExtension;
auto serializer = [serializationFormat](AZStd::vector<uint8_t>& output, const ProcessedObjectStore& object) -> bool
@@ -64,45 +64,34 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
AZStd::move(uniqueName), context.GetSourceUuid(), AZStd::move(serializer));
AZ_Assert(spawnable, "Failed to create a new spawnable.");
Instance instance;
if (Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(
instance, prefab, object.GetReferencedAssets(),
Prefab::PrefabDomUtils::LoadFlags::AssignRandomEntityId)) // Always assign random entity ids because the spawnable is
// going to be used to create clones of the entities.
{
// Resolve entity aliases that store PrefabDOM information to use the spawnable instead. This is done before the entities are
// moved from the instance as they'd otherwise can't be found.
context.ResolveSpawnableEntityAliases(prefabName, *spawnable, instance);
Instance& instance = prefab.GetInstance();
// Resolve entity aliases that store PrefabDOM information to use the spawnable instead. This is done before the entities are
// moved from the instance as they'd otherwise can't be found.
context.ResolveSpawnableEntityAliases(prefab.GetName(), *spawnable, instance);
AzFramework::Spawnable::EntityList& entities = spawnable->GetEntities();
instance.DetachAllEntitiesInHierarchy(
[&entities, &context](AZStd::unique_ptr<AZ::Entity> entity)
AzFramework::Spawnable::EntityList& entities = spawnable->GetEntities();
instance.DetachAllEntitiesInHierarchy(
[&entities, &context](AZStd::unique_ptr<AZ::Entity> entity)
{
if (entity)
{
if (entity)
entity->InvalidateDependencies();
AZ::Entity::DependencySortOutcome evaluation = entity->EvaluateDependenciesGetDetails();
if (evaluation.IsSuccess())
{
entity->InvalidateDependencies();
AZ::Entity::DependencySortOutcome evaluation = entity->EvaluateDependenciesGetDetails();
if (evaluation.IsSuccess())
{
entities.emplace_back(AZStd::move(entity));
}
else
{
AZ_Error(
"Prefabs", false, "Entity '%s' %s cannot be activated for the following reason: %s",
entity->GetName().c_str(), entity->GetId().ToString().c_str(), evaluation.GetError().m_message.c_str());
context.ErrorEncountered();
}
entities.emplace_back(AZStd::move(entity));
}
});
else
{
AZ_Error(
"Prefabs", false, "Entity '%s' %s cannot be activated for the following reason: %s",
entity->GetName().c_str(), entity->GetId().ToString().c_str(), evaluation.GetError().m_message.c_str());
context.ErrorEncountered();
}
}
});
SpawnableUtils::SortEntitiesByTransformHierarchy(*spawnable);
context.GetProcessedObjects().push_back(AZStd::move(object));
}
else
{
AZ_Error("Prefabs", false, "Failed to convert prefab '%.*s' to a spawnable.", AZ_STRING_ARG(prefabName));
context.ErrorEncountered();
}
SpawnableUtils::SortEntitiesByTransformHierarchy(*spawnable);
context.GetProcessedObjects().push_back(AZStd::move(object));
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -40,8 +40,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
static void Reflect(AZ::ReflectContext* context);
protected:
static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab,
AZ::DataStream::StreamType serializationFormat);
static void ProcessPrefab(PrefabProcessorContext& context, PrefabDocument& prefab, AZ::DataStream::StreamType serializationFormat);
SerializationFormats m_serializationFormat{ SerializationFormats::Binary };
};
@@ -0,0 +1,151 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabDocument.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
PrefabDocument::PrefabDocument(AZStd::string name)
: m_name(AZStd::move(name))
, m_instance(AZStd::make_unique<AzToolsFramework::Prefab::Instance>())
{
m_instance->SetTemplateSourcePath(AZ::IO::Path("InMemory") / m_name);
}
bool PrefabDocument::SetPrefabDom(const PrefabDom& prefab)
{
if (ConstructInstanceFromPrefabDom(prefab))
{
constexpr bool copyConstStrings = true;
m_dom.CopyFrom(prefab, m_dom.GetAllocator(), copyConstStrings);
return true;
}
else
{
return false;
}
}
bool PrefabDocument::SetPrefabDom(PrefabDom&& prefab)
{
if (ConstructInstanceFromPrefabDom(prefab))
{
m_dom = AZStd::move(prefab);
return true;
}
else
{
return false;
}
}
const AZStd::string& PrefabDocument::GetName() const
{
return m_name;
}
const PrefabDom& PrefabDocument::GetDom() const
{
if (m_isDirty)
{
m_isDirty = !PrefabDomUtils::StoreInstanceInPrefabDom(*m_instance, m_dom);
}
return m_dom;
}
PrefabDom&& PrefabDocument::TakeDom()
{
if (m_isDirty)
{
[[maybe_unused]] bool storedSuccessfully = PrefabDomUtils::StoreInstanceInPrefabDom(*m_instance, m_dom);
AZ_Assert(storedSuccessfully, "Failed to store Instance '%s' to PrefabDom.", m_name.c_str());
m_isDirty = false;
}
// After the PrefabDom is moved an empty PrefabDom is left behind. This should be reflected in the Instance,
// so reset it so it's empty as well.
m_instance->Reset();
m_instance->SetTemplateSourcePath(AZ::IO::Path("InMemory") / m_name);
return AZStd::move(m_dom);
}
void PrefabDocument::ListEntitiesWithComponentType(
AZ::TypeId componentType, const AZStd::function<bool(AzToolsFramework::Prefab::AliasPath&&)>& callback) const
{
m_instance->GetAllEntitiesInHierarchyConst(
[this, &componentType, &callback](const AZ::Entity& entity) -> bool
{
if (entity.FindComponent(componentType))
{
return callback(m_instance->GetAliasPathRelativeToInstance(entity.GetId()));
}
else
{
return true;
}
});
}
AZ::Entity* PrefabDocument::CreateEntityAlias(
PrefabDocument& source,
AzToolsFramework::Prefab::AliasPathView entity,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
uint32_t tag,
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context)
{
auto&& [sourceInstance, entityId] = source.m_instance->GetInstanceAndEntityIdFromAliasPath(entity);
if (sourceInstance != nullptr && entityId.IsValid())
{
return SpawnableUtils::CreateEntityAlias(
source.m_name, *sourceInstance, m_name, *m_instance, entityId, aliasType, loadBehavior, tag, context);
}
else
{
return nullptr;
}
}
AzToolsFramework::Prefab::Instance& PrefabDocument::GetInstance()
{
// Assume that changes will be made to the instance.
m_isDirty = true;
return *m_instance;
}
const AzToolsFramework::Prefab::Instance& PrefabDocument::GetInstance() const
{
return *m_instance;
}
bool PrefabDocument::ConstructInstanceFromPrefabDom(const PrefabDom& prefab)
{
using namespace AzToolsFramework::Prefab;
m_instance->Reset();
if (PrefabDomUtils::LoadInstanceFromPrefabDom(*m_instance, prefab, PrefabDomUtils::LoadFlags::AssignRandomEntityId))
{
return true;
}
else
{
#ifdef AZ_ENABLE_TRACING
AZStd::string_view sourceName = m_name;
PrefabDomValueConstReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName);
if (sourceReference.has_value() && sourceReference->get().IsString() && sourceReference->get().GetStringLength() != 0)
{
sourceName = AZStd::string_view(sourceReference->get().GetString(), sourceReference->get().GetStringLength());
}
AZ_Error(
"PrefabDocument", false, "Failed to construct Prefab instance from given PrefabDOM '%.*s'.", AZ_STRING_ARG(sourceName));
#endif
return false;
}
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,66 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/Prefab/Spawnable/EntityAliasTypes.h>
#include <AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class PrefabProcessorContext;
class PrefabDocument final
{
public:
explicit PrefabDocument(AZStd::string name);
PrefabDocument(const PrefabDocument&) = delete;
PrefabDocument(PrefabDocument&&) = default;
PrefabDocument& operator=(const PrefabDocument&) = delete;
PrefabDocument& operator=(PrefabDocument&&) = default;
bool SetPrefabDom(const PrefabDom& prefab);
bool SetPrefabDom(PrefabDom&& prefab);
const AZStd::string& GetName() const;
const PrefabDom& GetDom() const;
PrefabDom&& TakeDom();
template<typename Component>
void ListEntitiesWithComponentType(const AZStd::function<bool(AzToolsFramework::Prefab::AliasPath&&)>& callback) const;
void ListEntitiesWithComponentType(
AZ::TypeId componentType, const AZStd::function<bool(AzToolsFramework::Prefab::AliasPath&&)>& callback) const;
AZ::Entity* CreateEntityAlias(
PrefabDocument& source,
AzToolsFramework::Prefab::AliasPathView entity,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
uint32_t tag,
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context);
// Where possible, prefer functions directly on the PrefabDocument Instead of using the Instance.
AzToolsFramework::Prefab::Instance& GetInstance();
const AzToolsFramework::Prefab::Instance& GetInstance() const;
private:
bool ConstructInstanceFromPrefabDom(const PrefabDom& prefab);
mutable PrefabDom m_dom;
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> m_instance;
AZStd::string m_name;
mutable bool m_isDirty{ false };
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
#include <AzToolsFramework/Prefab/Spawnable/PrefabDocument.inl>
@@ -0,0 +1,16 @@
/*
* 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
*
*/
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
template<typename Component>
void PrefabDocument::ListEntitiesWithComponentType(const AZStd::function<bool(AzToolsFramework::Prefab::AliasPath&&)>& callback) const
{
ListEntitiesWithComponentType(azrtti_typeid<Component>(), callback);
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -7,8 +7,10 @@
*/
#include <AzCore/Interface/Interface.h>
#include <AzCore/Component/EntityUtils.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabDocument.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
#include <AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h>
@@ -30,27 +32,42 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
: m_sourceUuid(sourceUuid)
{}
bool PrefabProcessorContext::AddPrefab(AZStd::string prefabName, PrefabDom prefab)
bool PrefabProcessorContext::AddPrefab(PrefabDocument&& document)
{
auto result = m_prefabs.emplace(AZStd::move(prefabName), AZStd::move(prefab));
return result.second;
}
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(AZStd::string_view, PrefabDom&)>& callback)
{
m_isIterating = true;
for (auto& it : m_prefabs)
AZStd::string name = document.GetName();
if (!m_prefabNames.contains(name))
{
callback(it.first, it.second);
m_prefabNames.emplace(AZStd::move(name));
// If currently iterating add to pending queue to avoid invalidating the container that's being iterated over.
PrefabContainer& container = m_isIterating ? m_pendingPrefabAdditions : m_prefabs;
container.push_back(AZStd::move(document));
return true;
}
m_isIterating = false;
return false;
}
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDom&)>& callback) const
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(PrefabDocument&)>& callback)
{
for (const auto& it : m_prefabs)
// Enable iterating state so the prefab container doesn't get invalided. Enabling this flag will cause new prefabs
// to be stored in a temporary buffer that can be moved into the regular prefab container after iterating.
m_isIterating = true;
for (PrefabDocument& document : m_prefabs)
{
callback(it.first, it.second);
callback(document);
}
m_isIterating = false;
m_prefabs.insert(
m_prefabs.end(), AZStd::make_move_iterator(m_pendingPrefabAdditions.begin()),
AZStd::make_move_iterator(m_pendingPrefabAdditions.end()));
m_pendingPrefabAdditions.clear();
}
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(const PrefabDocument&)>& callback) const
{
for (const PrefabDocument& document : m_prefabs)
{
callback(document);
}
}
@@ -132,6 +149,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
using namespace AzToolsFramework::Prefab;
// Resolve prefab links into spawnable links for the provided spawnable.
for (EntityAliasStore& entityAlias : m_entityAliases)
{
auto sourcePrefab = AZStd::get_if<EntityAliasPrefabLink>(&entityAlias.m_source);
@@ -224,11 +242,35 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
it = aliasVisitors.emplace(source->m_spawnable.GetId(), AZStd::move(visitor)).first;
}
it->second.AddAlias(
AZ::Data::Asset<AzFramework::Spawnable>(&target->m_spawnable, loadBehavior), alias.m_tag, sourceIndex, targetIndex,
AZ::Data::Asset<AzFramework::Spawnable>(target->m_spawnable.GetId(), azrtti_typeid<AzFramework::Spawnable>()), alias.m_tag,
sourceIndex, targetIndex,
alias.m_aliasType, alias.m_loadBehavior == EntityAliasSpawnableLoadBehavior::QueueLoad);
// Register the dependency between the two spawnables.
RegisterProductAssetDependency(source->m_spawnable.GetId(), target->m_spawnable.GetId(), loadBehavior);
// Patch up all entity ids so the alias points to the same entity id if needed.
switch (alias.m_aliasType)
{
case AzFramework::Spawnable::EntityAliasType::Original:
continue;
case AzFramework::Spawnable::EntityAliasType::Disable:
continue;
case AzFramework::Spawnable::EntityAliasType::Replace:
break; // Requires entity id for alias in source and target spawnable matches.
case AzFramework::Spawnable::EntityAliasType::Additional:
continue;
case AzFramework::Spawnable::EntityAliasType::Merge:
break; // Requires entity id for alias in source and target spawnable matches.
default:
continue;
}
auto entityIdMapper = [source, target](const AZ::EntityId& originalId, bool /*isEntityId*/) -> AZ::EntityId
{
return originalId == target->m_index ? source->m_index : originalId;
};
AZ::EntityUtils::ReplaceEntityIdsAndEntityRefs(&target->m_spawnable, entityIdMapper);
}
}
@@ -21,60 +21,12 @@
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/Prefab/Spawnable/EntityAliasTypes.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabDocument.h>
#include <AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
enum class EntityAliasType : uint8_t
{
Disable, //!< No alias is added.
OptionalReplace, //!< At runtime the entity might be replaced. If the alias is disabled the original entity will be spawned.
//!< The original entity will be left in the spawnable and a copy is returned.
Replace, //!< At runtime the entity will be replaced. If the alias is disabled nothing will be spawned. The original
//!< entity is returned and a blank entity is left.
Additional, //!< At runtime the alias entity will be added as an additional but unrelated entity with a new entity id.
//!< An empty entity will be returned.
Merge //!< At runtime the components in both entities will be merged. An empty entity will be returned. The added
//!< components may no conflict with the entities already in the root entity.
};
enum class EntityAliasSpawnableLoadBehavior : uint8_t
{
NoLoad, //!< Don't load the spawnable referenced in the entity alias. Loading will be up to the caller.
QueueLoad, //!< Queue the spawnable referenced in the entity alias for loading. This will be an async load because asset
//!< handlers aren't allowed to start a blocking load as this can lead to deadlocks. This option will allow
//!< to disable loading the referenced spawnable through the event fired from the spawnables asset handler.
DependentLoad //!< The spawnable referenced in the entity alias is made a dependency of the spawnable that holds the entity
//!< alias. This will cause the spawnable to be automatically loaded along with the owning spawnable.
};
struct EntityAliasSpawnableLink
{
EntityAliasSpawnableLink(AzFramework::Spawnable& spawnable, AZ::EntityId index);
AzFramework::Spawnable& m_spawnable;
AZ::EntityId m_index;
};
struct EntityAliasPrefabLink
{
EntityAliasPrefabLink(AZStd::string prefabName, AzToolsFramework::Prefab::AliasPath alias);
AZStd::string m_prefabName;
AzToolsFramework::Prefab::AliasPath m_alias;
};
struct EntityAliasStore
{
using LinkStore = AZStd::variant<AZStd::monostate, EntityAliasSpawnableLink, EntityAliasPrefabLink>;
LinkStore m_source;
LinkStore m_target;
uint32_t m_tag;
AzFramework::Spawnable::EntityAliasType m_aliasType;
EntityAliasSpawnableLoadBehavior m_loadBehavior;
};
struct AssetDependencyInfo
{
AZ::Data::AssetId m_assetId;
@@ -93,9 +45,9 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
explicit PrefabProcessorContext(const AZ::Uuid& sourceUuid);
virtual ~PrefabProcessorContext() = default;
virtual bool AddPrefab(AZStd::string prefabName, PrefabDom prefab);
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, PrefabDom&)>& callback);
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDom&)>& callback) const;
virtual bool AddPrefab(PrefabDocument&& document);
virtual void ListPrefabs(const AZStd::function<void(PrefabDocument&)>& callback);
virtual void ListPrefabs(const AZStd::function<void(const PrefabDocument&)>& callback) const;
virtual bool HasPrefabs() const;
virtual bool RegisterSpawnableProductAssetDependency(
@@ -128,12 +80,15 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
virtual void ErrorEncountered();
protected:
using NamedPrefabContainer = AZStd::unordered_map<AZStd::string, PrefabDom>;
using PrefabNames = AZStd::unordered_set<AZStd::string>;
using PrefabContainer = AZStd::vector<PrefabDocument>;
using SpawnableEntityAliasStore = AZStd::vector<EntityAliasStore>;
AZ::Data::AssetLoadBehavior ToAssetLoadBehavior(EntityAliasSpawnableLoadBehavior loadBehavior) const;
NamedPrefabContainer m_prefabs;
PrefabContainer m_prefabs;
PrefabContainer m_pendingPrefabAdditions;
PrefabNames m_prefabNames;
SpawnableEntityAliasStore m_entityAliases;
ProcessedObjectStoreContainer m_products;
ProductAssetDependencyContainer m_registeredProductAssetDependencies;
@@ -16,10 +16,12 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
namespace AzToolsFramework::Prefab::SpawnableUtils
{
@@ -52,14 +54,32 @@ namespace AzToolsFramework::Prefab::SpawnableUtils
return result;
}
const AZ::Entity* FindEntity(AZ::EntityId entityId, const AzToolsFramework::Prefab::Instance& source)
{
const AZ::Entity* result = nullptr;
source.GetConstEntities(
[&result, entityId](const AZ::Entity& entity)
{
if (entity.GetId() != entityId)
{
return true;
}
else
{
result = &entity;
return false;
}
});
return result;
}
AZ::Entity* FindEntity(AZ::EntityId entityId, AzFramework::Spawnable& source)
{
uint32_t index = AzToolsFramework::Prefab::SpawnableUtils::FindEntityIndex(entityId, source);
return index != InvalidEntityIndex ? source.GetEntities()[index].get() : nullptr;
}
template<typename T>
AZStd::unique_ptr<AZ::Entity> CloneEntity(AZ::EntityId entityId, T& source)
AZStd::unique_ptr<AZ::Entity> CloneEntity(AZ::EntityId entityId, AzToolsFramework::Prefab::Instance& source)
{
AZ::Entity* target = Internal::FindEntity(entityId, source);
AZ_Assert(
@@ -74,41 +94,30 @@ namespace AzToolsFramework::Prefab::SpawnableUtils
return clone;
}
AZStd::unique_ptr<AZ::Entity> ReplaceEntityWithPlaceholder(AZ::EntityId entityId, AzToolsFramework::Prefab::Instance& source)
AZStd::unique_ptr<AZ::Entity> ReplaceEntityWithPlaceholder(
AZ::EntityId entityId,
[[maybe_unused]] AZStd::string_view sourcePrefabName,
AzToolsFramework::Prefab::Instance& source)
{
auto&& [instance, alias] = source.FindInstanceAndAlias(entityId);
AZ_Assert(
instance, "SpawnbleUtils were unable to locate entity alias with id %zu in Instance '%s' for replacing.",
aznumeric_cast<AZ::u64>(entityId), source.GetTemplateSourcePath().c_str());
instance, "SpawnbleUtils were unable to locate entity alias with id %zu in Instance '%.*s' for replacing.",
aznumeric_cast<AZ::u64>(entityId), AZ_STRING_ARG(sourcePrefabName));
EntityOptionalReference entityData = instance->GetEntity(alias);
AZ_Assert(
entityData.has_value(), "SpawnbleUtils were unable to locate entity '%.*s' in Instance '%s' for replacing.",
AZ_STRING_ARG(alias), source.GetTemplateSourcePath().c_str());
auto placeholder = AZStd::make_unique<AZ::Entity>(entityData->get().GetId(), entityData->get().GetName());
entityData.has_value(), "SpawnbleUtils were unable to locate entity '%.*s' in Instance '%.*s' for replacing.",
AZ_STRING_ARG(alias), AZ_STRING_ARG(sourcePrefabName));
// A new entity id can be used for the placeholder as `ReplaceEntity` will swap the entity ids.
auto placeholder = AZStd::make_unique<AZ::Entity>(AZ::Entity::MakeId(), entityData->get().GetName());
return instance->ReplaceEntity(AZStd::move(placeholder), alias);
}
AZStd::unique_ptr<AZ::Entity> ReplaceEntityWithPlaceholder(AZ::EntityId entityId, AzFramework::Spawnable& source)
{
uint32_t index = AzToolsFramework::Prefab::SpawnableUtils::FindEntityIndex(entityId, source);
AZ_Assert(
index != InvalidEntityIndex, "SpawnbleUtils were unable to locate entity alias with id %zu in Spawnable for replacing.",
aznumeric_cast<AZ::u64>(entityId));
AZStd::unique_ptr<AZ::Entity> original = AZStd::move(source.GetEntities()[index]);
AZ_Assert(
original, "SpawnbleUtils were unable to locate entity with id %zu in Spawnable for replacing.",
aznumeric_cast<AZ::u64>(entityId));
source.GetEntities()[index] = AZStd::make_unique<AZ::Entity>(original->GetId(), original->GetName());
return original;
}
template<typename Source>
AZStd::pair<AZStd::unique_ptr<AZ::Entity>, AzFramework::Spawnable::EntityAliasType> ApplyAlias(
Source& source, AZ::EntityId entityId, AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType)
AZStd::string_view sourcePrefabName,
AzToolsFramework::Prefab::Instance& source,
AZ::EntityId entityId,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType)
{
namespace PCU = AzToolsFramework::Prefab::PrefabConversionUtils;
using ResultPair = AZStd::pair<AZStd::unique_ptr<AZ::Entity>, AzFramework::Spawnable::EntityAliasType>;
@@ -121,12 +130,13 @@ namespace AzToolsFramework::Prefab::SpawnableUtils
case PCU::EntityAliasType::OptionalReplace:
return ResultPair(CloneEntity(entityId, source), AzFramework::Spawnable::EntityAliasType::Replace);
case PCU::EntityAliasType::Replace:
return ResultPair(ReplaceEntityWithPlaceholder(entityId, source), AzFramework::Spawnable::EntityAliasType::Replace);
return ResultPair(
ReplaceEntityWithPlaceholder(entityId, sourcePrefabName, source),
AzFramework::Spawnable::EntityAliasType::Replace);
case PCU::EntityAliasType::Additional:
ResultPair(AZStd::make_unique<AZ::Entity>(AZ::Entity::MakeId()), AzFramework::Spawnable::EntityAliasType::Additional);
return ResultPair(AZStd::make_unique<AZ::Entity>(), AzFramework::Spawnable::EntityAliasType::Additional);
case PCU::EntityAliasType::Merge:
// Use the same entity id as the original entity so at runtime the entity ids can be verified to match.
ResultPair(AZStd::make_unique<AZ::Entity>(entityId), AzFramework::Spawnable::EntityAliasType::Merge);
return ResultPair(AZStd::make_unique<AZ::Entity>(), AzFramework::Spawnable::EntityAliasType::Merge);
default:
AZ_Assert(
false, "Invalid PrefabProcessorContext::EntityAliasType type (%i) provided.", aznumeric_cast<uint64_t>(aliasType));
@@ -182,7 +192,8 @@ namespace AzToolsFramework::Prefab::SpawnableUtils
AliasPath alias = source.GetAliasPathRelativeToInstance(entityId);
if (!alias.empty())
{
auto&& [replacement, storedAliasType] = Internal::ApplyAlias(source, entityId, aliasType);
auto&& [replacement, storedAliasType] =
Internal::ApplyAlias(sourcePrefabName, source, entityId, aliasType);
if (replacement)
{
AZ::Entity* result = replacement.get();
@@ -212,84 +223,6 @@ namespace AzToolsFramework::Prefab::SpawnableUtils
}
}
AZ::Entity* CreateEntityAlias(
AZStd::string sourcePrefabName,
AzToolsFramework::Prefab::Instance& source,
AzFramework::Spawnable& target,
AZ::EntityId entityId,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
uint32_t tag,
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context)
{
using namespace AzToolsFramework::Prefab::PrefabConversionUtils;
AliasPath alias = source.GetAliasPathRelativeToInstance(entityId);
if (!alias.empty())
{
auto&& [replacement, storedAliasType] = Internal::ApplyAlias(source, entityId, aliasType);
if (replacement)
{
AZ::Entity* result = replacement.get();
target.GetEntities().push_back(AZStd::move(replacement));
EntityAliasStore store;
store.m_aliasType = storedAliasType;
store.m_source.emplace<EntityAliasPrefabLink>(AZStd::move(sourcePrefabName), AZStd::move(alias));
store.m_target.emplace<EntityAliasSpawnableLink>(target, result->GetId());
store.m_tag = tag;
store.m_loadBehavior = loadBehavior;
context.RegisterSpawnableEntityAlias(AZStd::move(store));
return result;
}
else
{
AZ_Assert(false, "A replacement for entity with id %zu could not be created.", static_cast<AZ::u64>(entityId));
return nullptr;
}
}
else
{
AZ_Assert(false, "Entity with id %llu was not found in the source prefab.", static_cast<AZ::u64>(entityId));
return nullptr;
}
}
AZ::Entity* CreateEntityAlias(
AzFramework::Spawnable& source,
AzFramework::Spawnable& target,
AZ::EntityId entityId,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
uint32_t tag,
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context)
{
using namespace AzToolsFramework::Prefab::PrefabConversionUtils;
auto&& [replacement, storedAliasType] = Internal::ApplyAlias(source, entityId, aliasType);
if (replacement)
{
AZ::Entity* result = replacement.get();
target.GetEntities().push_back(AZStd::move(replacement));
EntityAliasStore store;
store.m_aliasType = storedAliasType;
store.m_source.emplace<EntityAliasSpawnableLink>(source, entityId);
store.m_target.emplace<EntityAliasSpawnableLink>(target, result->GetId());
store.m_tag = tag;
store.m_loadBehavior = loadBehavior;
context.RegisterSpawnableEntityAlias(AZStd::move(store));
return result;
}
else
{
AZ_Assert(false, "A replacement for entity with id %zu could not be created.", static_cast<AZ::u64>(entityId));
return nullptr;
}
}
uint32_t FindEntityIndex(AZ::EntityId entity, const AzFramework::Spawnable& spawnable)
{
auto begin = spawnable.GetEntities().begin();
@@ -12,7 +12,7 @@
#include <AzCore/std/limits.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
#include <AzToolsFramework/Prefab/Spawnable/EntityAliasTypes.h>
namespace AZ
{
@@ -24,6 +24,11 @@ namespace AzToolsFramework::Prefab
class Instance;
}
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class PrefabProcessorContext;
}
namespace AzToolsFramework::Prefab::SpawnableUtils
{
static constexpr uint32_t InvalidEntityIndex = AZStd::numeric_limits<uint32_t>::max();
@@ -41,24 +46,7 @@ namespace AzToolsFramework::Prefab::SpawnableUtils
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
uint32_t tag,
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context);
AZ::Entity* CreateEntityAlias(
AZStd::string sourcePrefabName,
AzToolsFramework::Prefab::Instance& source,
AzFramework::Spawnable& target,
AZ::EntityId entityId,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
uint32_t tag,
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context);
AZ::Entity* CreateEntityAlias(
AzFramework::Spawnable& source,
AzFramework::Spawnable& target,
AZ::EntityId entityId,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType,
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
uint32_t tag,
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context);
uint32_t FindEntityIndex(AZ::EntityId entity, const AzFramework::Spawnable& spawnable);
void SortEntitiesByTransformHierarchy(AzFramework::Spawnable& spawnable);
@@ -2666,19 +2666,15 @@ namespace AzToolsFramework
if (!isPathSafeForAssets)
{
// Put an error in the console, so the log files have info about this error, or the user can look up the error after dismissing it.
AZStd::string errorMessage = "You can save slices only to your game project folder or the Gems folder. Update the location and try again.\n\n"
"You can also review and update your save locations in the AssetProcessorPlatformConfig.ini file.";
AZStd::string errorMessage = "You can save slices only to your game project folder or the Gems folder. Update the location and try again.\n\n";
AZ_Error("Slice", false, errorMessage.c_str());
QString learnMoreLink(QObject::tr(""));
QString learnMoreDescription(QObject::tr(" <a href='%1'>Learn more</a>").arg(learnMoreLink));
// Display a pop-up, the logs are easy to miss. This will make sure a user who encounters this error immediately knows their slice save has failed.
QMessageBox msgBox(activeWindow);
msgBox.setIcon(QMessageBox::Icon::Warning);
msgBox.setTextFormat(Qt::RichText);
msgBox.setWindowTitle(QObject::tr("Invalid save location"));
msgBox.setText(QString("%1 %2").arg(QObject::tr(errorMessage.c_str())).arg(learnMoreDescription));
msgBox.setText(QString("%1").arg(QObject::tr(errorMessage.c_str())));
msgBox.setStandardButtons(QMessageBox::Cancel | QMessageBox::Retry);
msgBox.setDefaultButton(QMessageBox::Retry);
const int response = msgBox.exec();
@@ -2689,7 +2685,16 @@ namespace AzToolsFramework
// so set the suggested save path to a known valid location.
if (assetSafeFolders.size() > 0)
{
retrySavePath = assetSafeFolders[0];
QStringList strList = slicePath.split("/");
if (strList.size() > 0)
{
retrySavePath = assetSafeFolders[0] + ("/" + strList[strList.size() - 1]).toUtf8().data();
}
else
{
retrySavePath = assetSafeFolders[0];
}
}
return SliceSaveResult::Retry;
case QMessageBox::Cancel:
@@ -840,8 +840,6 @@ namespace AzToolsFramework
richLabel->setTextFormat(Qt::RichText);
}
richLabel->setText(data);
richLabel->setGeometry(options.rect);
richLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
richLabel->setPalette(options.palette);
@@ -1117,6 +1117,8 @@ namespace AzToolsFramework
m_listModel->SearchStringChanged(filterString);
m_proxyModel->UpdateFilter();
m_gui->m_objectTree->expandAll();
}
void EntityOutlinerWidget::OnFilterChanged(const AzQtComponents::SearchTypeFilterList& activeTypeFilters)
@@ -421,6 +421,7 @@ namespace AzToolsFramework
{
QString label{ text };
m_nameLabel->setText(label);
m_nameLabel->setOpenExternalLinks(true);
m_nameLabel->setVisible(!label.isEmpty());
// setting the stretches to 0 in case of an empty label really hides the label (i.e. even the reserved space)
m_mainLayout->setStretch(0, label.isEmpty() ? 0 : LabelColumnStretch);
@@ -30,8 +30,7 @@ namespace UnitTest
void MousePressAndMove(
QWidget* widget, const QPoint& initialPositionWidget, const QPoint& mouseDelta, const Qt::MouseButton mouseButton)
{
QPoint position = widget->mapToGlobal(initialPositionWidget);
QTest::mousePress(widget, mouseButton, Qt::NoModifier, position);
QTest::mousePress(widget, mouseButton, Qt::NoModifier, initialPositionWidget);
MouseMove(widget, initialPositionWidget, mouseDelta, mouseButton);
}
@@ -45,14 +44,15 @@ namespace UnitTest
// - https://lists.qt-project.org/pipermail/development/2019-July/036873.html
void MouseMove(QWidget* widget, const QPoint& initialPositionWidget, const QPoint& mouseDelta, const Qt::MouseButton mouseButton)
{
QPoint nextPosition = widget->mapToGlobal(initialPositionWidget + mouseDelta);
const QPoint nextLocalPosition = initialPositionWidget + mouseDelta;
const QPoint nextGlobalPosition = widget->mapToGlobal(nextLocalPosition);
// ^1 To ensure a mouse move event is fired we must call the test mouse move function
// and also send a mouse move event that matches. Each on their own do not appear to
// work - please see the links above for more context.
QTest::mouseMove(widget, nextPosition);
QTest::mouseMove(widget, nextLocalPosition);
QMouseEvent mouseMoveEvent(
QEvent::MouseMove, QPointF(nextPosition), QPointF(nextPosition), Qt::NoButton, mouseButton, Qt::NoModifier);
QEvent::MouseMove, QPointF(nextLocalPosition), QPointF(nextGlobalPosition), Qt::NoButton, mouseButton, Qt::NoModifier);
QApplication::sendEvent(widget, &mouseMoveEvent);
}
@@ -157,6 +157,23 @@ namespace UnitTest
return QWidget::event(event);
}
MouseMoveDetector::MouseMoveDetector(QWidget* parent)
: QObject(parent)
{
}
bool MouseMoveDetector::eventFilter(QObject* watched, QEvent* event)
{
if (const auto eventType = event->type(); eventType == QEvent::Type::MouseMove)
{
auto mouseEvent = static_cast<QMouseEvent*>(event);
m_mouseGlobalPosition = mouseEvent->globalPos();
m_mouseLocalPosition = mouseEvent->pos();
}
return QObject::eventFilter(watched, event);
}
void TestEditorActions::Connect()
{
using AzToolsFramework::GetEntityContextId;
@@ -571,3 +588,5 @@ namespace UnitTest
sliceAssets.clear();
}
} // namespace UnitTest
#include <moc_AzToolsFrameworkTestHelpers.cpp>
@@ -111,10 +111,29 @@ namespace UnitTest
{
Q_OBJECT
public:
FocusInteractionWidget(QWidget* parent = nullptr) : QWidget(parent) {}
FocusInteractionWidget(QWidget* parent = nullptr)
: QWidget(parent)
{
}
bool event(QEvent* event) override;
};
/// Records mouse move events and stores the local and global position of the cursor.
/// @note To use, install as an event filter for the widget being interacted with
/// e.g. m_testWidget->installEventFilter(&m_mouseMoveDetector);
class MouseMoveDetector : public QObject
{
Q_OBJECT
public:
MouseMoveDetector(QWidget* parent = nullptr);
bool eventFilter([[maybe_unused]] QObject* watched, QEvent* event) override;
QPoint m_mouseGlobalPosition;
QPoint m_mouseLocalPosition;
};
/// Stores actions registered for either normal mode (regular viewport) editing and
/// component mode editing.
class TestEditorActions
@@ -723,6 +723,7 @@ set(FILES
Prefab/Spawnable/EditorOnlyEntityHandler/UiEditorOnlyEntityHandler.cpp
Prefab/Spawnable/EditorOnlyEntityHandler/WorldEditorOnlyEntityHandler.h
Prefab/Spawnable/EditorOnlyEntityHandler/WorldEditorOnlyEntityHandler.cpp
Prefab/Spawnable/EntityAliasTypes.h
Prefab/Spawnable/InMemorySpawnableAssetContainer.h
Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp
Prefab/Spawnable/PrefabCatchmentProcessor.h
@@ -730,6 +731,9 @@ set(FILES
Prefab/Spawnable/PrefabConversionPipeline.h
Prefab/Spawnable/PrefabConversionPipeline.cpp
Prefab/Spawnable/PrefabConverterStackProfileNames.h
Prefab/Spawnable/PrefabDocument.h
Prefab/Spawnable/PrefabDocument.inl
Prefab/Spawnable/PrefabDocument.cpp
Prefab/Spawnable/ProcesedObjectStore.h
Prefab/Spawnable/ProcesedObjectStore.cpp
Prefab/Spawnable/PrefabProcessor.h
@@ -0,0 +1,88 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <QObject>
#include <QWidget>
namespace UnitTest
{
class AzToolsFrameworkTestHelpersFixture : public AllocatorsTestFixture
{
public:
void SetUp() override
{
AllocatorsTestFixture::SetUp();
m_rootWidget = AZStd::make_unique<QWidget>();
m_rootWidget->setFixedSize(0, 0);
m_rootWidget->setMouseTracking(true);
m_rootWidget->move(0, 0); // explicitly set the widget to be in the upper left corner
m_mouseMoveDetector = AZStd::make_unique<MouseMoveDetector>();
m_rootWidget->installEventFilter(m_mouseMoveDetector.get());
}
void TearDown() override
{
m_rootWidget->removeEventFilter(m_mouseMoveDetector.get());
m_rootWidget.reset();
m_mouseMoveDetector.reset();
AllocatorsTestFixture::TearDown();
}
AZStd::unique_ptr<QWidget> m_rootWidget;
AZStd::unique_ptr<MouseMoveDetector> m_mouseMoveDetector;
};
struct MouseMoveParams
{
QSize m_widgetSize;
QPoint m_widgetPosition;
QPoint m_localCursorPosition;
QPoint m_cursorDelta;
};
class MouseMoveAzToolsFrameworkTestHelperFixture
: public AzToolsFrameworkTestHelpersFixture
, public ::testing::WithParamInterface<MouseMoveParams>
{
};
TEST_P(MouseMoveAzToolsFrameworkTestHelperFixture, MouseMoveCorrectlyTransformsCursorPositionInGlobalAndLocalSpace)
{
// given
const MouseMoveParams mouseMoveParams = GetParam();
m_rootWidget->move(mouseMoveParams.m_widgetPosition);
m_rootWidget->setFixedSize(mouseMoveParams.m_widgetSize);
// when
MouseMove(m_rootWidget.get(), mouseMoveParams.m_localCursorPosition, mouseMoveParams.m_cursorDelta);
// then
const QPoint mouseLocalPosition = m_mouseMoveDetector->m_mouseLocalPosition;
const QPoint mouseLocalPositionFromGlobal = m_rootWidget->mapFromGlobal(m_mouseMoveDetector->m_mouseGlobalPosition);
const QPoint expectedPosition = mouseMoveParams.m_localCursorPosition + mouseMoveParams.m_cursorDelta;
using ::testing::Eq;
EXPECT_THAT(mouseLocalPosition.x(), Eq(expectedPosition.x()));
EXPECT_THAT(mouseLocalPosition.y(), Eq(expectedPosition.y()));
EXPECT_THAT(mouseLocalPositionFromGlobal.x(), Eq(expectedPosition.x()));
EXPECT_THAT(mouseLocalPositionFromGlobal.y(), Eq(expectedPosition.y()));
}
INSTANTIATE_TEST_CASE_P(
All,
MouseMoveAzToolsFrameworkTestHelperFixture,
testing::Values(
MouseMoveParams{ QSize(100, 100), QPoint(0, 0), QPoint(0, 0), QPoint(10, 10) },
MouseMoveParams{ QSize(100, 100), QPoint(100, 100), QPoint(0, 0), QPoint(10, 10) },
MouseMoveParams{ QSize(100, 100), QPoint(20, 20), QPoint(50, 50), QPoint(20, 20) }));
} // namespace UnitTest
@@ -25,7 +25,7 @@ namespace Benchmark
for (auto _ : state)
{
state.PauseTiming();
m_spawnTicket = new AzFramework::EntitySpawnTicket(m_spawnableAsset);
m_spawnTicket = aznew AzFramework::EntitySpawnTicket(m_spawnableAsset);
state.ResumeTiming();
for (uint64_t spwanableCounter = 0; spwanableCounter < spawnAllEntitiesCallCount; spwanableCounter++)
@@ -62,7 +62,7 @@ namespace Benchmark
for (auto _ : state)
{
state.PauseTiming();
m_spawnTicket = new AzFramework::EntitySpawnTicket(m_spawnableAsset);
m_spawnTicket = aznew AzFramework::EntitySpawnTicket(m_spawnableAsset);
state.ResumeTiming();
AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*m_spawnTicket);
@@ -93,15 +93,16 @@ namespace Benchmark
SetUpSpawnableAsset(entityCountInSpawnable);
auto spawner = AzFramework::SpawnableEntitiesInterface::Get();
for (auto _ : state)
{
state.PauseTiming();
m_spawnTicket = new AzFramework::EntitySpawnTicket(m_spawnableAsset);
m_spawnTicket = aznew AzFramework::EntitySpawnTicket(m_spawnableAsset);
state.ResumeTiming();
for (uint64_t spawnCallCounter = 0; spawnCallCounter < spawnCallCount; spawnCallCounter++)
{
AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*m_spawnTicket);
spawner->SpawnAllEntities(*m_spawnTicket);
}
m_rootSpawnableInterface->ProcessSpawnableQueue();
@@ -9,6 +9,7 @@
#include <Prefab/SpawnableRemoveEditorInfoTestFixture.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabDocument.h>
#include <AzToolsFramework/ToolsComponents/EditorOnlyEntityComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorOnlyEntityComponentBus.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
@@ -201,15 +202,14 @@ namespace UnitTest
{
ConvertSourceEntitiesToPrefab();
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument prefab("Test");
prefab.SetPrefabDom(m_prefabDom);
const bool actualResult =
m_editorInfoRemover.RemoveEditorInfo(m_prefabDom, m_serializeContext, m_prefabProcessorContext).IsSuccess();
m_editorInfoRemover.RemoveEditorInfo(prefab, m_serializeContext, m_prefabProcessorContext).IsSuccess();
EXPECT_EQ(expectedResult, actualResult);
AZStd::unique_ptr<Instance> convertedInstance(aznew Instance());
ASSERT_TRUE(AzToolsFramework::Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*convertedInstance, m_prefabDom));
convertedInstance->DetachAllEntitiesInHierarchy(
prefab.GetInstance().DetachAllEntitiesInHierarchy(
[this](AZStd::unique_ptr<AZ::Entity> entity)
{
m_runtimeEntities.emplace_back(entity.release());
@@ -74,10 +74,6 @@ namespace UnitTest
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserFilterModel> m_filterModel;
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserTableModel> m_tableModel;
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTesterAssetBrowser;
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTesterFilterModel;
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTesterTableModel;
QVector<int> m_folderIds = { 13, 14, 15 };
QVector<int> m_sourceIDs = { 1, 2, 3, 4, 5 };
QVector<int> m_productIDs = { 1, 2, 3, 4, 5 };
@@ -96,9 +92,6 @@ namespace UnitTest
m_filterModel->setSourceModel(m_assetBrowserComponent->GetAssetBrowserModel());
m_tableModel->setSourceModel(m_filterModel.get());
m_modelTesterAssetBrowser = AZStd::make_unique<QAbstractItemModelTester>(m_assetBrowserComponent->GetAssetBrowserModel());
m_modelTesterFilterModel = AZStd::make_unique<QAbstractItemModelTester>(m_filterModel.get());
m_modelTesterTableModel = AZStd::make_unique<QAbstractItemModelTester>(m_tableModel.get());
m_searchWidget = AZStd::make_unique<AzToolsFramework::AssetBrowser::SearchWidget>();
// Setup String filters
@@ -110,10 +103,6 @@ namespace UnitTest
void AssetBrowserTest::TearDownEditorFixtureImpl()
{
m_modelTesterAssetBrowser.reset();
m_modelTesterFilterModel.reset();
m_modelTesterTableModel.reset();
m_tableModel.reset();
m_filterModel.reset();
m_assetBrowserComponent->Deactivate();
@@ -12,6 +12,7 @@ set(FILES
AssetFileInfoListComparison.cpp
AssetSeedManager.cpp
AssetSystemMocks.h
AzToolsFrameworkTestHelpersTest.cpp
BoundsTestComponent.cpp
BoundsTestComponent.h
ComponentAdapterTests.cpp