ScriptComponentTests.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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 <AzCore/Asset/AssetManagerComponent.h>
  9. #include <AzCore/RTTI/BehaviorContext.h>
  10. #include <AzCore/Script/ScriptAsset.h>
  11. #include <AzCore/Script/ScriptSystemComponent.h>
  12. #include <AzCore/Script/ScriptContext.h>
  13. #include <AzCore/UnitTest/TestTypes.h>
  14. #include <AzToolsFramework/ToolsComponents/ScriptEditorComponent.h>
  15. #include "EntityTestbed.h"
  16. extern "C" {
  17. # include <Lua/lualib.h>
  18. # include <Lua/lauxlib.h>
  19. }
  20. namespace UnitTest
  21. {
  22. using namespace AZ;
  23. using namespace AzFramework;
  24. // Global Properties used for Testing
  25. int mySubValue = 0;
  26. int myReloadValue = 0;
  27. class ScriptComponentTest
  28. : public UnitTest::LeakDetectionFixture
  29. {
  30. public:
  31. AZ_TYPE_INFO(ScriptComponentTest, "{85CDBD49-70FF-416A-8154-B5525EDD30D4}");
  32. void SetUp() override
  33. {
  34. ComponentApplication::Descriptor appDesc;
  35. appDesc.m_memoryBlocksByteSize = 100 * 1024 * 1024;
  36. //appDesc.m_recordsMode = AllocationRecords::RECORD_FULL;
  37. //appDesc.m_stackRecordLevels = 20;
  38. AZ::ComponentApplication::StartupParameters startupParameters;
  39. startupParameters.m_loadSettingsRegistry = false;
  40. Entity* systemEntity = m_app.Create(appDesc, startupParameters);
  41. systemEntity->CreateComponent(AZ::TypeId{ "{CAE3A025-FAC9-4537-B39E-0A800A2326DF}" }); // JobManager component
  42. systemEntity->CreateComponent<StreamerComponent>();
  43. systemEntity->CreateComponent<AssetManagerComponent>();
  44. systemEntity->CreateComponent(AZ::TypeId{ "{A316662A-6C3E-43E6-BC61-4B375D0D83B4}" }); // Usersettings component
  45. systemEntity->CreateComponent<ScriptSystemComponent>();
  46. systemEntity->Init();
  47. systemEntity->Activate();
  48. ScriptSystemRequestBus::BroadcastResult(m_scriptContext, &ScriptSystemRequestBus::Events::GetContext, DefaultScriptContextId);
  49. AZ::ComponentApplicationBus::BroadcastResult(m_behaviorContext, &AZ::ComponentApplicationBus::Events::GetBehaviorContext);
  50. AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  51. AzToolsFramework::Components::ScriptEditorComponent::CreateDescriptor(); // descriptor is deleted by app
  52. AzToolsFramework::Components::ScriptEditorComponent::Reflect(m_serializeContext);
  53. ScriptComponent::CreateDescriptor(); // descriptor is deleted by app
  54. ScriptComponent::Reflect(m_serializeContext);
  55. }
  56. void TearDown() override
  57. {
  58. m_app.Destroy();
  59. }
  60. AZStd::optional<Data::Asset<ScriptAsset>> CreateAndLoadScriptAsset(const AZStd::string& script, ScriptContext& scriptContext, Uuid id = Uuid::CreateRandom())
  61. {
  62. AzFramework::ScriptCompileRequest compileRequest;
  63. compileRequest.m_errorWindow = "LuaTests";
  64. AZ::IO::MemoryStream inputStream(script.data(), script.size());
  65. compileRequest.m_input = &inputStream;
  66. if (CompileScript(compileRequest, scriptContext))
  67. {
  68. Data::Asset<ScriptAsset> scriptAsset = Data::AssetManager::Instance().CreateAsset<ScriptAsset>(Data::AssetId(id));
  69. scriptAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad);
  70. scriptAsset.Get()->m_data = compileRequest.m_luaScriptDataOut;
  71. Data::AssetManagerBus::Broadcast(&Data::AssetManagerBus::Events::OnAssetReady, scriptAsset);
  72. m_app.Tick();
  73. m_app.TickSystem(); // flush assets etc.
  74. return scriptAsset;
  75. }
  76. return AZStd::nullopt;
  77. }
  78. static ScriptComponent* BuildGameEntity(const Data::Asset<ScriptAsset>& scriptAsset, Entity& gameEntity)
  79. {
  80. // We first setup the ScriptEditorComponent.
  81. // After a script asset is loaded the ScriptEditorComponent builds the properties table.
  82. // BuildGameEntity() hands off the properties table to the game runtime ScriptComponent.
  83. Entity editorEntity;
  84. auto* scriptEditorComponent = editorEntity.CreateComponent<AzToolsFramework::Components::ScriptEditorComponent>();
  85. scriptEditorComponent->SetScript(scriptAsset);
  86. editorEntity.Init();
  87. editorEntity.Activate();
  88. scriptEditorComponent->LoadScript();
  89. scriptEditorComponent->BuildGameEntity(&gameEntity);
  90. auto* scriptComponent = gameEntity.FindComponent<ScriptComponent>();
  91. return scriptComponent;
  92. }
  93. ComponentApplication m_app;
  94. ScriptContext* m_scriptContext = nullptr;
  95. BehaviorContext* m_behaviorContext = nullptr;
  96. SerializeContext* m_serializeContext = nullptr;
  97. };
  98. TEST_F(ScriptComponentTest, ScriptInstancesCanReadButDontModifySourceTable)
  99. {
  100. // make sure script instances don't can read only share data, but don't modify the source table
  101. const AZStd::string script = "test = {\
  102. --[[test with no properties table as this should work too!]]\
  103. state = {\
  104. mysubstate = {\
  105. mysubvalue = 2,\
  106. },\
  107. myvalue = 0,\
  108. },\
  109. }\
  110. function test:OnActivate()\
  111. self.state.mysubstate.mysubvalue = 5\
  112. end\
  113. return test;";
  114. auto scriptAssetOpt = CreateAndLoadScriptAsset(script, *m_scriptContext);
  115. AZ_TEST_ASSERT(scriptAssetOpt);
  116. auto& scriptAsset = *scriptAssetOpt;
  117. auto* entity1 = aznew Entity();
  118. entity1->CreateComponent<ScriptComponent>()->SetScript(scriptAsset);
  119. entity1->Init();
  120. entity1->Activate();
  121. auto* entity2 = aznew Entity();
  122. entity2->CreateComponent<ScriptComponent>()->SetScript(scriptAsset);
  123. entity2->Init();
  124. entity2->Activate();
  125. m_behaviorContext->Property("globalMySubValue", BehaviorValueProperty(&mySubValue));
  126. m_scriptContext->Execute("globalMySubValue = test.state.mysubstate.mysubvalue", "Read my subvalue");
  127. AZ_TEST_ASSERT(mySubValue == 2); // we should not have changed test. table but the instance table of each component.
  128. delete entity1;
  129. delete entity2;
  130. }
  131. TEST_F(ScriptComponentTest, ScriptReloads)
  132. {
  133. // Test script reload
  134. m_behaviorContext->Property("myReloadValue", BehaviorValueProperty(&myReloadValue));
  135. const AZStd::string script1 ="local testReload = {}\
  136. function testReload:OnActivate()\
  137. myReloadValue = 1\
  138. end\
  139. function testReload:OnDeactivate()\
  140. myReloadValue = 0\
  141. end\
  142. return testReload;";
  143. auto scriptAssetOpt = CreateAndLoadScriptAsset(script1, *m_scriptContext);
  144. AZ_TEST_ASSERT(scriptAssetOpt);
  145. auto& scriptAsset1 = *scriptAssetOpt;
  146. auto* entity = aznew Entity();
  147. entity->CreateComponent<ScriptComponent>()->SetScript(scriptAsset1);
  148. entity->Init();
  149. entity->Activate();
  150. // test value, it should set during activation of the first script
  151. AZ_TEST_ASSERT(myReloadValue == 1);
  152. const AZStd::string script2 ="local testReload = {}\
  153. myReloadValue = 5\
  154. return testReload";
  155. // modify the asset, re-use the previous ID
  156. Data::Asset<ScriptAsset> scriptAsset2(aznew ScriptAsset(scriptAsset1.GetId()), AZ::Data::AssetLoadBehavior::Default);
  157. {
  158. AzFramework::ScriptCompileRequest compileRequest;
  159. compileRequest.m_errorWindow = "LuaTests";
  160. AZ::IO::MemoryStream inputStream(script2.data(), script2.size());
  161. compileRequest.m_input = &inputStream;
  162. if (CompileScript(compileRequest, *m_scriptContext))
  163. {
  164. scriptAsset2.Get()->m_data = compileRequest.m_luaScriptDataOut;
  165. }
  166. }
  167. // When reloading script assets from files, ScriptSystemComponent would clear old script caches automatically in the
  168. // function `ScriptSystemComponent::LoadAssetData()`. But here we are changing script directly in memory, therefore we
  169. // need to clear old cache manually.
  170. AZ::ScriptSystemRequestBus::Broadcast(&AZ::ScriptSystemRequestBus::Events::ClearAssetReferences, scriptAsset1.GetId());
  171. // trigger reload
  172. Data::AssetManager::Instance().ReloadAssetFromData(scriptAsset2);
  173. // ReloadAssetFromData is (now) a queued event
  174. // Need to tick subsystems here to receive reload event.
  175. m_app.Tick();
  176. m_app.TickSystem();
  177. // test value with the reloaded value
  178. EXPECT_EQ(5, myReloadValue);
  179. delete entity;
  180. }
  181. TEST_F(ScriptComponentTest, LuaPropertiesAreDiscovered)
  182. {
  183. const AZStd::string script = "local test = {\
  184. Properties = {\
  185. myNum = { default = 2 },\
  186. },\
  187. }\
  188. function test:OnActivate()\
  189. self.Properties.myNum = 5\
  190. end\
  191. return test";
  192. auto scriptAssetOpt = CreateAndLoadScriptAsset(script, *m_scriptContext);
  193. AZ_TEST_ASSERT(scriptAssetOpt);
  194. auto& scriptAsset = *scriptAssetOpt;
  195. Entity editorEntity, gameEntity;
  196. auto* scriptComponent = BuildGameEntity(scriptAsset, gameEntity);
  197. EXPECT_NE(scriptComponent->GetScriptProperty("myNum"), nullptr);
  198. }
  199. } // namespace UnitTest