Merge pull request #6468 from aws-lumberyard-dev/Prefabs/ProcessStackAddPrefabBug

Spawnable Entity Alias improvements
This commit is contained in:
Ronald Koppers
2022-01-06 14:58:27 -08:00
committed by GitHub
29 changed files with 715 additions and 408 deletions
@@ -10,6 +10,7 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/TypeSafeIntegral.h>
#include <AzCore/std/functional.h>
#include <AzFramework/Spawnable/Spawnable.h>
@@ -164,6 +165,8 @@ namespace AzFramework
public:
friend class SpawnableEntitiesDefinition;
AZ_CLASS_ALLOCATOR(AzFramework::EntitySpawnTicket, AZ::SystemAllocator, 0);
using Id = uint32_t;
EntitySpawnTicket() = default;
@@ -499,12 +499,8 @@ namespace AzFramework
for (auto it = newEntitiesBegin; it != newEntitiesEnd; ++it)
{
AZ::Entity* clone = (*it);
// The entity component framework doesn't handle entities without TransformComponent safely.
if (!clone->GetComponents().empty())
{
clone->SetSpawnTicketId(request.m_ticketId);
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
}
clone->SetSpawnTicketId(request.m_ticketId);
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, clone);
}
// Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context.
@@ -636,12 +632,8 @@ namespace AzFramework
for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it)
{
AZ::Entity* clone = (*it);
// The entity component framework doesn't handle entities without TransformComponent safely.
if (!clone->GetComponents().empty())
{
clone->SetSpawnTicketId(request.m_ticketId);
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
}
clone->SetSpawnTicketId(request.m_ticketId);
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
}
if (request.m_completionCallback)
@@ -668,7 +660,7 @@ namespace AzFramework
{
if (entity != nullptr)
{
// Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager.
// Setting it to 0 is needed to avoid the infinite loop between GameEntityContext and SpawnableEntitiesManager.
entity->SetSpawnTicketId(0);
GameEntityContextRequestBus::Broadcast(
&GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId());
@@ -702,7 +694,7 @@ namespace AzFramework
{
if (*entityIterator != nullptr && (*entityIterator)->GetId() == request.m_entityId)
{
// Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager.
// Setting it to 0 is needed to avoid the infinite loop between GameEntityContext and SpawnableEntitiesManager.
(*entityIterator)->SetSpawnTicketId(0);
GameEntityContextRequestBus::Broadcast(
&GameEntityContextRequestBus::Events::DestroyGameEntity, (*entityIterator)->GetId());
@@ -949,11 +941,6 @@ namespace AzFramework
GameEntityContextRequestBus::Broadcast(
&GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId());
}
else
{
// Entities without components wouldn't have been send to the GameEntityContext.
delete entity;
}
}
delete request.m_ticket;
@@ -77,6 +77,12 @@ namespace UnitTest
public:
AZ_COMPONENT(TargetSpawnableComponent, "{B4041561-63A7-4E1E-80F1-78C08D497960}");
TargetSpawnableComponent() = default;
explicit TargetSpawnableComponent(AZ::EntityId parent)
: m_parent(parent)
{
}
void Activate() override {}
void Deactivate() override {}
@@ -84,14 +90,19 @@ namespace UnitTest
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
{
serializeContext->Class<TargetSpawnableComponent, AZ::Component>();
serializeContext->Class<TargetSpawnableComponent, AZ::Component>()
->Field("Parent", &TargetSpawnableComponent::m_parent);
}
}
AZ::EntityId m_parent;
};
class SpawnableEntitiesManagerTest : public AllocatorsFixture
{
public:
constexpr static AZ::u64 EntityIdStartId = 40;
void SetUp() override
{
AllocatorsFixture::SetUp();
@@ -111,7 +122,7 @@ namespace UnitTest
m_spawnable = aznew AzFramework::Spawnable(
AZ::Data::AssetId::CreateString("{EB2E8A2B-F253-4A90-BBF4-55F2EED786B8}:0"), AZ::Data::AssetData::AssetStatus::Ready);
m_spawnableAsset = new AZ::Data::Asset<AzFramework::Spawnable>(m_spawnable, AZ::Data::AssetLoadBehavior::Default);
m_ticket = new AzFramework::EntitySpawnTicket(*m_spawnableAsset);
m_ticket = aznew AzFramework::EntitySpawnTicket(*m_spawnableAsset);
auto managerInterface = AzFramework::SpawnableEntitiesInterface::Get();
m_manager = azrtti_cast<AzFramework::SpawnableEntitiesManager*>(managerInterface);
@@ -147,22 +158,43 @@ namespace UnitTest
{
auto entry = AZStd::make_unique<AZ::Entity>();
entry->AddComponent(aznew SourceSpawnableComponent());
entry->SetId(AZ::EntityId(EntityIdStartId + i));
entities.push_back(AZStd::move(entry));
}
}
AZ::Data::Asset<AzFramework::Spawnable> CreateTargetSpawnable(size_t numElements)
AZ::Data::Asset<AzFramework::Spawnable> CreateTargetSpawnable(size_t numElements, bool requiresMatchingEntityIds)
{
auto target = aznew AzFramework::Spawnable(
AZ::Data::AssetId(AZ::Uuid("{716CD8C3-0BA8-4F32-B579-0EC7C967796F}")), AZ::Data::AssetData::AssetStatus::Ready);
AzFramework::Spawnable::EntityList& entities = target->GetEntities();
entities.reserve(numElements);
for (size_t i = 0; i < numElements; ++i)
if (requiresMatchingEntityIds)
{
auto entry = AZStd::make_unique<AZ::Entity>();
entry->AddComponent(aznew TargetSpawnableComponent());
entities.push_back(AZStd::move(entry));
for (size_t i = 0; i < numElements; ++i)
{
auto entry = AZStd::make_unique<AZ::Entity>();
if (i != 0)
{
entry->AddComponent(aznew TargetSpawnableComponent(AZ::EntityId(EntityIdStartId + i - 1)));
}
else
{
entry->AddComponent(aznew TargetSpawnableComponent());
}
entry->SetId(AZ::EntityId(EntityIdStartId + i));
entities.push_back(AZStd::move(entry));
}
}
else
{
for (size_t i = 0; i < numElements; ++i)
{
auto entry = AZStd::make_unique<AZ::Entity>();
entry->AddComponent(aznew TargetSpawnableComponent());
entities.push_back(AZStd::move(entry));
}
}
return AZ::Data::Asset<AzFramework::Spawnable>(target, AZ::Data::AssetLoadBehavior::NoLoad);
@@ -212,6 +244,38 @@ namespace UnitTest
return true;
}
static bool DoParentEntityIdsMatch(AzFramework::SpawnableConstEntityContainerView entities)
{
if (entities.empty())
{
return false;
}
const AZ::Entity* previous = nullptr;
for (const AZ::Entity* entity : entities)
{
if (entity)
{
if (previous)
{
if (TargetSpawnableComponent* link = entity->FindComponent<TargetSpawnableComponent>(); link != nullptr)
{
if (link->m_parent != previous->GetId())
{
return false;
}
}
previous = entity;
}
}
else
{
return false;
}
}
return true;
}
static bool IsEveryOtherEntityAReplacement(AzFramework::SpawnableConstEntityContainerView entities)
{
bool onAlternative = true;
@@ -516,7 +580,7 @@ namespace UnitTest
// Make sure we start with a fresh ticket each time, or else each iteration through this loop would continue to build up
// more and more entities.
delete m_ticket;
m_ticket = new AzFramework::EntitySpawnTicket(*m_spawnableAsset);
m_ticket = aznew AzFramework::EntitySpawnTicket(*m_spawnableAsset);
constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
@@ -599,7 +663,8 @@ namespace UnitTest
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
constexpr bool requiresMatchingEntityIds = true;
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4, requiresMatchingEntityIds);
InsertEntityAliases<4>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
@@ -608,11 +673,13 @@ namespace UnitTest
size_t spawnedEntitiesCount = 0;
bool allReplaced = false;
auto callback = [&spawnedEntitiesCount, &allReplaced](
bool allEntityIdsPatched = false;
auto callback = [&spawnedEntitiesCount, &allReplaced, &allEntityIdsPatched](
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
allReplaced = AreAllEntitiesReplaced(entities);
allEntityIdsPatched = DoParentEntityIdsMatch(entities);
};
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
@@ -621,6 +688,7 @@ namespace UnitTest
EXPECT_EQ(4, spawnedEntitiesCount);
EXPECT_TRUE(allReplaced);
EXPECT_TRUE(allEntityIdsPatched);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged)
@@ -628,7 +696,8 @@ namespace UnitTest
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
constexpr bool requiresMatchingEntityIds = false;
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4, requiresMatchingEntityIds);
InsertEntityAliases<4>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
@@ -637,11 +706,13 @@ namespace UnitTest
size_t spawnedEntitiesCount = 0;
bool allAdded = false;
auto callback = [&spawnedEntitiesCount, &allAdded](
bool allEntityIdsPatched = false;
auto callback = [&spawnedEntitiesCount, &allAdded, &allEntityIdsPatched](
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
allAdded = IsEveryOtherEntityAReplacement(entities);
allEntityIdsPatched = DoParentEntityIdsMatch(entities);
};
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
@@ -650,6 +721,7 @@ namespace UnitTest
EXPECT_EQ(8, spawnedEntitiesCount);
EXPECT_TRUE(allAdded);
EXPECT_TRUE(allEntityIdsPatched);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged)
@@ -657,7 +729,8 @@ namespace UnitTest
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
constexpr bool requiresMatchingEntityIds = true;
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4, requiresMatchingEntityIds);
InsertEntityAliases<4>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge,
@@ -666,11 +739,13 @@ namespace UnitTest
size_t spawnedEntitiesCount = 0;
bool allMerged = false;
auto callback = [&spawnedEntitiesCount, &allMerged](
bool allEntityIdsPatched = false;
auto callback = [&spawnedEntitiesCount, &allMerged, &allEntityIdsPatched](
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
allMerged = AreAllMerged(entities);
allEntityIdsPatched = DoParentEntityIdsMatch(entities);
};
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
@@ -679,6 +754,7 @@ namespace UnitTest
EXPECT_EQ(4, spawnedEntitiesCount);
EXPECT_TRUE(allMerged);
EXPECT_TRUE(allEntityIdsPatched);
}
//
@@ -1095,7 +1171,8 @@ namespace UnitTest
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
constexpr bool requiresMatchingEntityIds = true;
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4, requiresMatchingEntityIds);
InsertEntityAliases<4>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
@@ -1106,11 +1183,13 @@ namespace UnitTest
size_t spawnedEntitiesCount = 0;
bool allReplaced = false;
auto callback = [&spawnedEntitiesCount, &allReplaced](
bool allEntityIdsPatched = false;
auto callback = [&spawnedEntitiesCount, &allReplaced, &allEntityIdsPatched](
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
allReplaced = AreAllEntitiesReplaced(entities);
allEntityIdsPatched = DoParentEntityIdsMatch(entities);
};
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
@@ -1119,6 +1198,7 @@ namespace UnitTest
EXPECT_EQ(4, spawnedEntitiesCount);
EXPECT_TRUE(allReplaced);
EXPECT_TRUE(allEntityIdsPatched);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged)
@@ -1126,7 +1206,8 @@ namespace UnitTest
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
constexpr bool requiresMatchingEntityIds = false;
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4, requiresMatchingEntityIds);
InsertEntityAliases<4>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
@@ -1137,12 +1218,14 @@ namespace UnitTest
size_t spawnedEntitiesCount = 0;
bool allAdded = false;
bool allEntityIdsPatched = false;
auto callback =
[&spawnedEntitiesCount, &allAdded](
[&spawnedEntitiesCount, &allAdded, &allEntityIdsPatched](
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
allAdded = IsEveryOtherEntityAReplacement(entities);
allEntityIdsPatched = DoParentEntityIdsMatch(entities);
};
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
@@ -1151,6 +1234,7 @@ namespace UnitTest
EXPECT_EQ(8, spawnedEntitiesCount);
EXPECT_TRUE(allAdded);
EXPECT_TRUE(allEntityIdsPatched);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged)
@@ -1158,7 +1242,8 @@ namespace UnitTest
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
constexpr bool requiresMatchingEntityIds = true;
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4, requiresMatchingEntityIds);
InsertEntityAliases<4>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge,
@@ -1169,11 +1254,13 @@ namespace UnitTest
size_t spawnedEntitiesCount = 0;
bool allMerged = false;
auto callback = [&spawnedEntitiesCount, &allMerged](
bool allEntityIdsPatched = false;
auto callback = [&spawnedEntitiesCount, &allMerged, &allEntityIdsPatched](
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
allMerged = AreAllMerged(entities);
allEntityIdsPatched = DoParentEntityIdsMatch(entities);
};
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
@@ -1182,6 +1269,7 @@ namespace UnitTest
EXPECT_EQ(4, spawnedEntitiesCount);
EXPECT_TRUE(allMerged);
EXPECT_TRUE(allEntityIdsPatched);
}
//
@@ -1302,6 +1390,36 @@ namespace UnitTest
// ClaimEntities
//
TEST_F(SpawnableEntitiesManagerTest, ClaimEntities_Call_AllEntitiesWereClaimedAndNotDeleted)
{
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZStd::vector<AZ::Entity*> claimedEntities;
auto callback = [&claimedEntities](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView container)
{
for (AZ::Entity* entity : container)
{
claimedEntities.push_back(entity);
}
};
{
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
m_manager->SpawnAllEntities(ticket);
m_manager->ClaimEntities(ticket, AZStd::move(callback));
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
}
EXPECT_EQ(NumEntities, claimedEntities.size());
// If these calls fail it means that the ticket has still deleted the entities, so they weren't properly claimed.
for (AZ::Entity* entity : claimedEntities)
{
delete entity;
}
}
TEST_F(SpawnableEntitiesManagerTest, ClaimEntities_DeleteTicketBeforeCall_NoCrash)
{
auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView) {};
@@ -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;
@@ -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);
@@ -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
@@ -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());
@@ -20,11 +20,10 @@
namespace Multiplayer
{
using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessor;
using AzToolsFramework::Prefab::PrefabConversionUtils::ProcessedObjectStore;
void NetworkPrefabProcessor::Process(PrefabProcessorContext& context)
void NetworkPrefabProcessor::Process(AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context)
{
using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument;
IMultiplayerTools* mpTools = AZ::Interface<IMultiplayerTools>::Get();
if (mpTools)
{
@@ -33,11 +32,17 @@ namespace Multiplayer
AZ::DataStream::StreamType serializationFormat = GetAzSerializationFormat();
context.ListPrefabs([&context, serializationFormat](AZStd::string_view prefabName, PrefabDom& prefab) {
ProcessPrefab(context, prefabName, prefab, serializationFormat);
});
bool networkPrefabsAdded = false;
context.ListPrefabs(
[&networkPrefabsAdded, &context, serializationFormat](PrefabDocument& prefab)
{
if (ProcessPrefab(context, prefab, serializationFormat))
{
networkPrefabsAdded = true;
}
});
if (mpTools && !context.GetProcessedObjects().empty())
if (mpTools && networkPrefabsAdded)
{
mpTools->SetDidProcessNetworkPrefabs(true);
}
@@ -59,28 +64,6 @@ namespace Multiplayer
}
}
static AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> LoadInstanceFromPrefab(const PrefabDom& prefab)
{
using namespace AzToolsFramework::Prefab;
// convert Prefab DOM into Prefab Instance.
AZStd::unique_ptr<Instance> sourceInstance(aznew Instance());
if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*sourceInstance, prefab, PrefabDomUtils::LoadFlags::AssignRandomEntityId))
{
PrefabDomValueConstReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName);
AZStd::string errorMessage("NetworkPrefabProcessor: Failed to Load Prefab Instance from given Prefab Dom.");
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));
}
AZ_Error("NetworkPrefabProcessor", false, errorMessage.c_str());
return nullptr;
}
return sourceInstance;
}
static void GatherNetEntities(
AzToolsFramework::Prefab::Instance* instance,
AZStd::unordered_map<AZ::Entity*, AzToolsFramework::Prefab::Instance*>& entityToInstanceMap,
@@ -103,18 +86,15 @@ namespace Multiplayer
});
}
void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab, AZ::DataStream::StreamType serializationFormat)
bool NetworkPrefabProcessor::ProcessPrefab(
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context,
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument& prefab,
AZ::DataStream::StreamType serializationFormat)
{
using AzToolsFramework::Prefab::PrefabConversionUtils::ProcessedObjectStore;
using namespace AzToolsFramework::Prefab;
// convert Prefab DOM into Prefab Instance.
AZStd::unique_ptr<Instance> sourceInstance = LoadInstanceFromPrefab(prefab);
if (!sourceInstance)
{
return;
}
AZStd::string uniqueName = prefabName;
AZStd::string uniqueName = prefab.GetName();
uniqueName += ".network.spawnable";
auto serializer = [serializationFormat](AZStd::vector<uint8_t>& output, const ProcessedObjectStore& object) -> bool {
@@ -127,15 +107,16 @@ namespace Multiplayer
ProcessedObjectStore::Create<AzFramework::Spawnable>(uniqueName, context.GetSourceUuid(), AZStd::move(serializer));
auto& netSpawnableEntities = networkSpawnable->GetEntities();
Instance& sourceInstance = prefab.GetInstance();
// Grab all net entities with their corresponding Instances to handle nested prefabs correctly
AZStd::unordered_map<AZ::Entity*, AzToolsFramework::Prefab::Instance*> netEntityToInstanceMap;
AZStd::vector<AZ::Entity*> prefabNetEntities;
GatherNetEntities(sourceInstance.get(), netEntityToInstanceMap, prefabNetEntities);
GatherNetEntities(&sourceInstance, netEntityToInstanceMap, prefabNetEntities);
if (prefabNetEntities.empty())
{
// No networked entities in the prefab, no need to do anything in this processor.
return;
return false;
}
// Sort the entities prior to processing. The entities will end up in the net spawnable in this order.
@@ -182,7 +163,7 @@ namespace Multiplayer
// Add net spawnable asset holder to the prefab root
{
EntityOptionalReference containerEntityRef = sourceInstance->GetContainerEntity();
EntityOptionalReference containerEntityRef = sourceInstance.GetContainerEntity();
if (containerEntityRef.has_value())
{
auto* networkSpawnableHolderComponent = containerEntityRef.value().get().CreateComponent<NetworkSpawnableHolderComponent>();
@@ -193,18 +174,12 @@ namespace Multiplayer
AZ::Entity* networkSpawnableHolderEntity = aznew AZ::Entity(uniqueName);
auto* networkSpawnableHolderComponent = networkSpawnableHolderEntity->CreateComponent<NetworkSpawnableHolderComponent>();
networkSpawnableHolderComponent->SetNetworkSpawnableAsset(networkSpawnableAsset);
sourceInstance->AddEntity(*networkSpawnableHolderEntity);
sourceInstance.AddEntity(*networkSpawnableHolderEntity);
}
}
// save the final result in the target Prefab DOM.
if (!PrefabDomUtils::StoreInstanceInPrefabDom(*sourceInstance, prefab))
{
AZ_Error("NetworkPrefabProcessor", false, "Saving exported Prefab Instance within a Prefab Dom failed.");
return;
}
context.GetProcessedObjects().push_back(AZStd::move(object));
return true;
}
AZ::DataStream::StreamType NetworkPrefabProcessor::GetAzSerializationFormat() const
@@ -14,23 +14,23 @@
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class PrefabProcessorContext;
class PrefabDocument;
}
namespace Multiplayer
{
using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessor;
using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext;
using AzToolsFramework::Prefab::PrefabDom;
class NetworkPrefabProcessor : public PrefabProcessor
class NetworkPrefabProcessor : public AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessor
{
public:
AZ_CLASS_ALLOCATOR(NetworkPrefabProcessor, AZ::SystemAllocator, 0);
AZ_RTTI(Multiplayer::NetworkPrefabProcessor, "{AF6C36DA-CBB9-4DF4-AE2D-7BC6CCE65176}", PrefabProcessor);
AZ_RTTI(
Multiplayer::NetworkPrefabProcessor,
"{AF6C36DA-CBB9-4DF4-AE2D-7BC6CCE65176}",
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessor);
~NetworkPrefabProcessor() override = default;
void Process(PrefabProcessorContext& context) override;
void Process(AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context) override;
static void Reflect(AZ::ReflectContext* context);
@@ -44,7 +44,10 @@ namespace Multiplayer
AZ::DataStream::StreamType GetAzSerializationFormat() const;
protected:
static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab, AZ::DataStream::StreamType serializationFormat);
static bool ProcessPrefab(
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context,
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument& prefab,
AZ::DataStream::StreamType serializationFormat);
SerializationFormats m_serializationFormat = SerializationFormats::Binary;
};
@@ -57,6 +57,7 @@ namespace UnitTest
TEST_F(PrefabProcessingTestFixture, NetworkPrefabProcessor_ProcessPrefabTwoEntities_NetEntityGoesToNetSpawnable)
{
using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext;
using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument;
AZStd::vector<AZ::Entity*> entities;
@@ -74,7 +75,9 @@ namespace UnitTest
// Add the prefab into the Prefab Processor Context
const AZStd::string prefabName = "testPrefab";
PrefabProcessorContext prefabProcessorContext{AZ::Uuid::CreateRandom()};
prefabProcessorContext.AddPrefab(prefabName, AZStd::move(prefabDom));
PrefabDocument document(prefabName);
ASSERT_TRUE(document.SetPrefabDom(AZStd::move(prefabDom)));
prefabProcessorContext.AddPrefab(AZStd::move(document));
// Request NetworkPrefabProcessor to process the prefab
Multiplayer::NetworkPrefabProcessor processor;
@@ -237,7 +237,7 @@ namespace AZ::Prefab
bool PrefabBuilderComponent::ProcessPrefab(
const AZ::PlatformTagSet& platformTags, const char* filePath, AZ::IO::PathView tempDirPath, const AZ::Uuid& sourceFileUuid,
AzToolsFramework::Prefab::PrefabDom& mutableRootDom, AZStd::vector<AssetBuilderSDK::JobProduct>& jobProducts)
AzToolsFramework::Prefab::PrefabDom&& rootDom, AZStd::vector<AssetBuilderSDK::JobProduct>& jobProducts)
{
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext context(sourceFileUuid);
AZStd::string rootPrefabName;
@@ -247,7 +247,9 @@ namespace AZ::Prefab
filePath);
return false;
}
context.AddPrefab(AZStd::move(rootPrefabName), AZStd::move(mutableRootDom));
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument rootDocument(AZStd::move(rootPrefabName));
rootDocument.SetPrefabDom(AZStd::move(rootDom));
context.AddPrefab(AZStd::move(rootDocument));
context.SetPlatformTags(AZStd::move(platformTags));
@@ -319,8 +321,8 @@ namespace AZ::Prefab
});
if (ProcessPrefab(
platformTags, request.m_fullPath.c_str(), request.m_tempDirPath.c_str(), request.m_sourceFileUUID, mutableRootDom,
response.m_outputProducts))
platformTags, request.m_fullPath.c_str(), request.m_tempDirPath.c_str(), request.m_sourceFileUUID,
AZStd::move(mutableRootDom), response.m_outputProducts))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
@@ -53,7 +53,7 @@ namespace AZ::Prefab
const AzToolsFramework::Prefab::PrefabDom& genericDocument);
bool ProcessPrefab(
const AZ::PlatformTagSet& platformTags, const char* filePath, AZ::IO::PathView tempDirPath, const AZ::Uuid& sourceFileUuid,
AzToolsFramework::Prefab::PrefabDom& mutableRootDom,
AzToolsFramework::Prefab::PrefabDom&& rootDom,
AZStd::vector<AssetBuilderSDK::JobProduct>& jobProducts);
protected:
@@ -106,7 +106,8 @@ namespace UnitTest
AzToolsFramework::Prefab::PrefabDom prefabDom;
prefabDom.CopyFrom(prefabSystemComponentInterface->FindTemplateDom(parentInstance->GetTemplateId()), prefabDom.GetAllocator(), false);
ASSERT_TRUE(prefabBuilderComponent.ProcessPrefab({AZ::Crc32("pc")}, "parent.prefab", "unused", AZ::Uuid(), prefabDom, jobProducts));
ASSERT_TRUE(prefabBuilderComponent.ProcessPrefab(
{ AZ::Crc32("pc") }, "parent.prefab", "unused", AZ::Uuid(), AZStd::move(prefabDom), jobProducts));
ASSERT_EQ(jobProducts.size(), 1);
ASSERT_EQ(jobProducts[0].m_dependencies.size(), 1);
@@ -342,7 +342,7 @@ namespace Vegetation
// Create the EntitySpawnTicket here. This pointer is going to get handed off to the vegetation system as opaque instance data,
// where it will be tracked and held onto for the lifetime of the vegetation instance. The vegetation system will pass it back
// in to DestroyInstance at the end of the lifetime, so that's the one place where we will delete the ticket pointers.
AzFramework::EntitySpawnTicket* ticket = new AzFramework::EntitySpawnTicket(m_spawnableAsset);
AzFramework::EntitySpawnTicket* ticket = aznew AzFramework::EntitySpawnTicket(m_spawnableAsset);
if (ticket->IsValid())
{
// Track the ticket that we've created.