Procedural Prefabs: Stable transform component ID (#6227)

* Set Procedural Prefab Transform ComponentId to the hash of the file path.

Procedural prefabs needs to have a deterministic transform component ID in order for saved patches still work when the prefab is regenerated.

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>

* Switch to Cityhash instead of XXHash since prefab system is using that elsewhere

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>

* Added unit test

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>

* Fix missing include

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>
This commit is contained in:
amzn-mike
2021-12-10 13:20:31 -06:00
committed by GitHub
parent ca49eedc5f
commit 8f134a82d1
2 changed files with 55 additions and 8 deletions
@@ -10,6 +10,7 @@
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Utils/TypeHash.h>
#include <AzToolsFramework/ToolsComponents/EditorLockComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityComponent.h>
#include <Prefab/PrefabSystemComponentInterface.h>
@@ -73,12 +74,24 @@ namespace AzToolsFramework::Prefab
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(result, &AzToolsFramework::ToolsApplicationRequestBus::Events::FindCommonRootInactive,
entities, commonRoot, &topLevelEntities);
auto containerEntity = AZStd::make_unique<AZ::Entity>();
containerEntity->CreateComponent<Components::TransformComponent>();
containerEntity->CreateComponent<Components::EditorLockComponent>();
containerEntity->CreateComponent<Components::EditorVisibilityComponent>();
containerEntity->CreateComponent<Prefab::EditorPrefabComponent>();
{
auto transformComponent = containerEntity->CreateComponent<Components::TransformComponent>();
// Because procedural prefabs need to be deterministic we need to set the component ID to something unique and non-random
// The prefab that references the proc prefab will store a patch that references the transform component by it's component ID
// If this ID is not stable, the proc prefab will lose its position, parenting, etc data next time it is regenerated
auto hash = TypeHash64(reinterpret_cast<const uint8_t*>(filePath.data()), filePath.length(), AZ::HashValue64{0});
transformComponent->SetId(static_cast<AZ::ComponentId>(hash));
}
for (AZ::Entity* entity : topLevelEntities)
{
AzToolsFramework::Components::TransformComponent* transformComponent =
@@ -92,7 +105,7 @@ namespace AzToolsFramework::Prefab
auto prefab = m_prefabSystemComponentInterface->CreatePrefab(
entities, {}, AZ::IO::PathView(AZStd::string_view(filePath)), AZStd::move(containerEntity));
if (!prefab)
{
AZ_Error("PrefabSystemComponenent", false, "Failed to create prefab %s", filePath.c_str());
@@ -48,11 +48,45 @@ namespace UnitTest
}
};
TEST_F(PrefabScriptingTest, CreatePrefabTemplate_GeneratesContainerWithStableTransformComponentId)
{
AZ::EntityId entityId;
AzToolsFramework::EntityUtilityBus::BroadcastResult(entityId, &AzToolsFramework::EntityUtilityBus::Events::CreateEditorReadyEntity, "test");
TemplateId templateId1;
PrefabSystemScriptingBus::BroadcastResult(templateId1, &PrefabSystemScriptingBus::Events::CreatePrefabTemplate, AZStd::vector{ entityId }, "test.prefab");
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
auto instance1 = prefabSystemComponentInterface->InstantiatePrefab(templateId1);
// Clear all templates to reset the system
prefabSystemComponentInterface->RemoveAllTemplates();
TemplateId templateId2;
PrefabSystemScriptingBus::BroadcastResult(templateId2, &PrefabSystemScriptingBus::Events::CreatePrefabTemplate, AZStd::vector{ entityId }, "test.prefab");
auto instance2 = prefabSystemComponentInterface->InstantiatePrefab(templateId2);
auto referenceWrapper1 = instance1->GetContainerEntity();
auto referenceWrapper2 = instance2->GetContainerEntity();
ASSERT_TRUE(referenceWrapper1);
ASSERT_TRUE(referenceWrapper2);
auto transformComponent1 = referenceWrapper1->get().FindComponent<AzToolsFramework::Components::TransformComponent>();
auto transformComponent2 = referenceWrapper2->get().FindComponent<AzToolsFramework::Components::TransformComponent>();
ASSERT_NE(transformComponent1, nullptr);
ASSERT_NE(transformComponent2, nullptr);
ASSERT_EQ(transformComponent1->GetId(), transformComponent2->GetId());
}
TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
@@ -76,7 +110,7 @@ namespace UnitTest
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
@@ -99,7 +133,7 @@ namespace UnitTest
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
AZ_TEST_START_TRACE_SUPPRESSION;
sc.Execute(R"LUA(
@@ -119,7 +153,7 @@ namespace UnitTest
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
@@ -132,7 +166,7 @@ namespace UnitTest
g_globalPrefabString = my_result:GetValue()
end
)LUA");
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
prefabSystemComponentInterface->RemoveAllTemplates();
@@ -169,5 +203,5 @@ namespace UnitTest
g_globalPrefabString.set_capacity(0); // Free all memory
}
}