ReferenceShapeTests.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzTest/AzTest.h>
  9. #include <AzCore/Component/ComponentApplication.h>
  10. #include <AzCore/UnitTest/TestTypes.h>
  11. #include <AzCore/Math/Random.h>
  12. #include <AzFramework/Components/TransformComponent.h>
  13. #include <LmbrCentral/Shape/MockShapes.h>
  14. #include <Shape/BoxShapeComponent.h>
  15. #include <Shape/ReferenceShapeComponent.h>
  16. #include <Shape/SphereShapeComponent.h>
  17. #include <ShapeThreadsafeTest.h>
  18. namespace UnitTest
  19. {
  20. class ReferenceComponentTests
  21. : public LeakDetectionFixture
  22. {
  23. protected:
  24. AZ::ComponentApplication m_app;
  25. void SetUp() override
  26. {
  27. AZ::ComponentApplication::Descriptor appDesc;
  28. appDesc.m_memoryBlocksByteSize = 20 * 1024 * 1024;
  29. appDesc.m_recordingMode = AZ::Debug::AllocationRecords::Mode::RECORD_NO_RECORDS;
  30. AZ::ComponentApplication::StartupParameters startupParameters;
  31. startupParameters.m_loadSettingsRegistry = false;
  32. m_app.Create(appDesc, startupParameters);
  33. }
  34. void TearDown() override
  35. {
  36. m_app.Destroy();
  37. }
  38. template<typename Component, typename Configuration>
  39. AZStd::unique_ptr<AZ::Entity> CreateEntity(const Configuration& config, Component** ppComponent)
  40. {
  41. m_app.RegisterComponentDescriptor(Component::CreateDescriptor());
  42. auto entity = AZStd::make_unique<AZ::Entity>();
  43. if (ppComponent)
  44. {
  45. *ppComponent = entity->CreateComponent<Component>(config);
  46. }
  47. else
  48. {
  49. entity->CreateComponent<Component>(config);
  50. }
  51. entity->Init();
  52. EXPECT_EQ(AZ::Entity::State::Init, entity->GetState());
  53. entity->Activate();
  54. EXPECT_EQ(AZ::Entity::State::Active, entity->GetState());
  55. return entity;
  56. }
  57. template<typename ComponentA, typename ComponentB>
  58. bool IsComponentCompatible()
  59. {
  60. AZ::ComponentDescriptor::DependencyArrayType providedServicesA;
  61. ComponentA::GetProvidedServices(providedServicesA);
  62. AZ::ComponentDescriptor::DependencyArrayType incompatibleServicesB;
  63. ComponentB::GetIncompatibleServices(incompatibleServicesB);
  64. for (auto providedServiceA : providedServicesA)
  65. {
  66. for (auto incompatibleServiceB : incompatibleServicesB)
  67. {
  68. if (providedServiceA == incompatibleServiceB)
  69. {
  70. return false;
  71. }
  72. }
  73. }
  74. return true;
  75. }
  76. template<typename ComponentA, typename ComponentB>
  77. bool AreComponentsCompatible()
  78. {
  79. return IsComponentCompatible<ComponentA, ComponentB>() && IsComponentCompatible<ComponentB, ComponentA>();
  80. }
  81. };
  82. TEST_F(ReferenceComponentTests, VerifyCompatibility)
  83. {
  84. EXPECT_FALSE((AreComponentsCompatible<LmbrCentral::ReferenceShapeComponent, LmbrCentral::ReferenceShapeComponent>()));
  85. }
  86. TEST_F(ReferenceComponentTests, ReferenceShapeComponent_WithValidReference)
  87. {
  88. UnitTest::MockShape testShape;
  89. LmbrCentral::ReferenceShapeConfig config;
  90. config.m_shapeEntityId = testShape.m_entity.GetId();
  91. LmbrCentral::ReferenceShapeComponent* component;
  92. auto entity = CreateEntity(config, &component);
  93. AZ::RandomDistributionType randomDistribution = AZ::RandomDistributionType::Normal;
  94. AZ::Vector3 randPos = AZ::Vector3::CreateOne();
  95. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  96. randPos, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GenerateRandomPointInside, randomDistribution);
  97. EXPECT_EQ(AZ::Vector3::CreateZero(), randPos);
  98. testShape.m_aabb = AZ::Aabb::CreateFromPoint(AZ::Vector3(1.0f, 21.0f, 31.0f));
  99. AZ::Aabb resultAABB;
  100. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  101. resultAABB, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
  102. EXPECT_EQ(testShape.m_aabb, resultAABB);
  103. AZ::Crc32 resultCRC = {};
  104. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  105. resultCRC, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetShapeType);
  106. EXPECT_EQ(AZ_CRC_CE("TestShape"), resultCRC);
  107. testShape.m_localBounds = AZ::Aabb::CreateFromPoint(AZ::Vector3(1.0f, 21.0f, 31.0f));
  108. testShape.m_localTransform = AZ::Transform::CreateTranslation(testShape.m_localBounds.GetCenter());
  109. AZ::Transform resultTransform = AZ::Transform::CreateIdentity();
  110. AZ::Aabb resultBounds = AZ::Aabb::CreateNull();
  111. LmbrCentral::ShapeComponentRequestsBus::Event(
  112. entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetTransformAndLocalBounds, resultTransform, resultBounds);
  113. EXPECT_EQ(testShape.m_localTransform, resultTransform);
  114. EXPECT_EQ(testShape.m_localBounds, resultBounds);
  115. testShape.m_pointInside = true;
  116. bool resultPointInside = false;
  117. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  118. resultPointInside, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, AZ::Vector3::CreateZero());
  119. EXPECT_EQ(testShape.m_pointInside, resultPointInside);
  120. testShape.m_distanceSquaredFromPoint = 456.0f;
  121. float resultdistanceSquaredFromPoint = 0;
  122. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  123. resultdistanceSquaredFromPoint, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::DistanceSquaredFromPoint,
  124. AZ::Vector3::CreateZero());
  125. EXPECT_EQ(testShape.m_distanceSquaredFromPoint, resultdistanceSquaredFromPoint);
  126. testShape.m_intersectRay = false;
  127. bool resultIntersectRay = false;
  128. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  129. resultIntersectRay, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IntersectRay, AZ::Vector3::CreateZero(),
  130. AZ::Vector3::CreateZero(), 0.0f);
  131. EXPECT_TRUE(testShape.m_intersectRay == resultIntersectRay);
  132. }
  133. TEST_F(ReferenceComponentTests, ReferenceShapeComponent_WithInvalidReference)
  134. {
  135. LmbrCentral::ReferenceShapeConfig config;
  136. config.m_shapeEntityId = AZ::EntityId();
  137. LmbrCentral::ReferenceShapeComponent* component;
  138. auto entity = CreateEntity(config, &component);
  139. AZ::RandomDistributionType randomDistribution = AZ::RandomDistributionType::Normal;
  140. AZ::Vector3 randPos = AZ::Vector3::CreateOne();
  141. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  142. randPos, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GenerateRandomPointInside, randomDistribution);
  143. EXPECT_EQ(randPos, AZ::Vector3::CreateZero());
  144. AZ::Aabb resultAABB;
  145. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  146. resultAABB, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
  147. EXPECT_EQ(resultAABB, AZ::Aabb::CreateNull());
  148. AZ::Crc32 resultCRC;
  149. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  150. resultCRC, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetShapeType);
  151. EXPECT_EQ(resultCRC, AZ::Crc32(AZ::u32(0)));
  152. AZ::Transform resultTransform;
  153. AZ::Aabb resultBounds;
  154. LmbrCentral::ShapeComponentRequestsBus::Event(
  155. entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetTransformAndLocalBounds, resultTransform, resultBounds);
  156. EXPECT_EQ(resultTransform, AZ::Transform::CreateIdentity());
  157. EXPECT_EQ(resultBounds, AZ::Aabb::CreateNull());
  158. bool resultPointInside = true;
  159. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  160. resultPointInside, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, AZ::Vector3::CreateZero());
  161. EXPECT_EQ(resultPointInside, false);
  162. float resultdistanceSquaredFromPoint = 0;
  163. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  164. resultdistanceSquaredFromPoint, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::DistanceSquaredFromPoint,
  165. AZ::Vector3::CreateZero());
  166. EXPECT_EQ(resultdistanceSquaredFromPoint, FLT_MAX);
  167. bool resultIntersectRay = true;
  168. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  169. resultIntersectRay, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IntersectRay, AZ::Vector3::CreateZero(),
  170. AZ::Vector3::CreateZero(), 0.0f);
  171. EXPECT_EQ(resultIntersectRay, false);
  172. }
  173. TEST_F(ReferenceComponentTests, ShapeHasThreadsafeGetSetCalls)
  174. {
  175. // Verify that setting values from one thread and querying values from multiple other threads in parallel produces
  176. // correct, consistent results.
  177. m_app.RegisterComponentDescriptor(LmbrCentral::BoxShapeComponent::CreateDescriptor());
  178. m_app.RegisterComponentDescriptor(AzFramework::TransformComponent::CreateDescriptor());
  179. // Create two box shapes with the correct dimensions to pass the test.
  180. AZ::Entity boxEntities[2];
  181. for (auto& boxEntity : boxEntities)
  182. {
  183. boxEntity.CreateComponent<LmbrCentral::BoxShapeComponent>();
  184. boxEntity.CreateComponent<AzFramework::TransformComponent>();
  185. boxEntity.Init();
  186. boxEntity.Activate();
  187. LmbrCentral::BoxShapeComponentRequestsBus::Event(
  188. boxEntity.GetId(), &LmbrCentral::BoxShapeComponentRequestsBus::Events::SetBoxDimensions,
  189. AZ::Vector3(1.0f, 1.0f, ShapeThreadsafeTest::ShapeHeight));
  190. }
  191. // Create a reference shape that's initially pointing to the first box.
  192. LmbrCentral::ReferenceShapeConfig config;
  193. int boxEntityIndex = 0;
  194. config.m_shapeEntityId = boxEntities[boxEntityIndex].GetId();
  195. LmbrCentral::ReferenceShapeComponent* component;
  196. auto entity = CreateEntity(config, &component);
  197. // Define the function for setting unimportant dimensions on the shape while queries take place.
  198. auto setDimensionFn = [&boxEntities, &boxEntityIndex]
  199. (AZ::EntityId shapeEntityId, [[maybe_unused]] float minDimension, [[maybe_unused]] uint32_t dimensionVariance, [[maybe_unused]] float height)
  200. {
  201. // On every iteration, switch which box we're pointing to, then AFTER switching, set the previous box to invalid dimensions.
  202. // If the calls are threadsafe, we should always be querying a box with the correct dimensions.
  203. // If they aren't, we'll either be querying when not hooked up at all, or we'll get incorrect dimensions from querying a
  204. // "stale" box ID.
  205. int oldBoxEntityIndex = boxEntityIndex;
  206. boxEntityIndex = (boxEntityIndex + 1) % 2;
  207. // Make sure the new box we're switching to has valid dimensions that will pass the test.
  208. LmbrCentral::BoxShapeComponentRequestsBus::Event(
  209. boxEntities[boxEntityIndex].GetId(), &LmbrCentral::BoxShapeComponentRequestsBus::Events::SetBoxDimensions,
  210. AZ::Vector3(1.0f, 1.0f, height));
  211. // Switch to the new box
  212. LmbrCentral::ReferenceShapeRequestBus::Event(
  213. shapeEntityId, &LmbrCentral::ReferenceShapeRequestBus::Events::SetShapeEntityId, boxEntities[boxEntityIndex].GetId());
  214. // Set the previous box to invalid dimensions. If the get/set calls are threadsafe, nothing should be querying this shape
  215. // at this point, so this shouldn't have any effect.
  216. LmbrCentral::BoxShapeComponentRequestsBus::Event(
  217. boxEntities[oldBoxEntityIndex].GetId(), &LmbrCentral::BoxShapeComponentRequestsBus::Events::SetBoxDimensions,
  218. AZ::Vector3(1.0f, 1.0f, height / 4.0f));
  219. };
  220. // Run the test, which will run multiple queries in parallel with each other and with the dimension-setting function.
  221. // The number of iterations is arbitrary - it's set high enough to catch most failures, but low enough to keep the test
  222. // time to a minimum.
  223. const int numIterations = 30000;
  224. ShapeThreadsafeTest::TestShapeGetSetCallsAreThreadsafe(*entity, numIterations, setDimensionFn);
  225. }
  226. } // namespace UnitTest