#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace XCEngine::Components; using namespace XCEngine::Scripting; namespace { void ExpectVector3Near(const XCEngine::Math::Vector3& actual, const XCEngine::Math::Vector3& expected, float tolerance = 0.001f) { EXPECT_NEAR(actual.x, expected.x, tolerance); EXPECT_NEAR(actual.y, expected.y, tolerance); EXPECT_NEAR(actual.z, expected.z, tolerance); } MonoScriptRuntime::Settings CreateMonoSettings() { MonoScriptRuntime::Settings settings; settings.assemblyDirectory = XCENGINE_TEST_MANAGED_OUTPUT_DIR; settings.corlibDirectory = XCENGINE_TEST_MANAGED_OUTPUT_DIR; settings.coreAssemblyPath = XCENGINE_TEST_SCRIPT_CORE_DLL; settings.appAssemblyPath = XCENGINE_TEST_GAME_SCRIPTS_DLL; return settings; } class MonoScriptRuntimeTest : public ::testing::Test { protected: void SetUp() override { engine = &ScriptEngine::Get(); engine->OnRuntimeStop(); runtime = std::make_unique(CreateMonoSettings()); ASSERT_TRUE(runtime->Initialize()) << runtime->GetLastError(); engine->SetRuntime(runtime.get()); } void TearDown() override { engine->OnRuntimeStop(); engine->SetRuntime(nullptr); runtime.reset(); scene.reset(); } Scene* CreateScene(const std::string& sceneName) { scene = std::make_unique(sceneName); return scene.get(); } ScriptComponent* AddScript(GameObject* gameObject, const std::string& namespaceName, const std::string& className) { ScriptComponent* component = gameObject->AddComponent(); component->SetScriptClass("GameScripts", namespaceName, className); return component; } ScriptEngine* engine = nullptr; std::unique_ptr runtime; std::unique_ptr scene; }; TEST_F(MonoScriptRuntimeTest, InitializesAndDiscoversConcreteMonoBehaviourClasses) { const std::vector classNames = runtime->GetScriptClassNames("GameScripts"); EXPECT_TRUE(runtime->IsInitialized()); EXPECT_TRUE(runtime->IsClassAvailable("GameScripts", "Gameplay", "LifecycleProbe")); EXPECT_NE(std::find(classNames.begin(), classNames.end(), "Gameplay.LifecycleProbe"), classNames.end()); EXPECT_EQ(std::find(classNames.begin(), classNames.end(), "Gameplay.AbstractLifecycleProbe"), classNames.end()); EXPECT_EQ(std::find(classNames.begin(), classNames.end(), "Gameplay.UtilityHelper"), classNames.end()); } TEST_F(MonoScriptRuntimeTest, ScriptEngineAppliesStoredFieldsAndInvokesLifecycleMethods) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); GameObject* target = runtimeScene->CreateGameObject("Target"); ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe"); component->GetFieldStorage().SetFieldValue("Speed", 5.0f); component->GetFieldStorage().SetFieldValue("Label", "Configured"); component->GetFieldStorage().SetFieldValue("Target", GameObjectReference{target->GetUUID()}); component->GetFieldStorage().SetFieldValue("SpawnPoint", XCEngine::Math::Vector3(2.0f, 4.0f, 6.0f)); engine->OnRuntimeStart(runtimeScene); engine->OnFixedUpdate(0.02f); engine->OnUpdate(0.016f); engine->OnLateUpdate(0.016f); EXPECT_TRUE(runtime->HasManagedInstance(component)); EXPECT_EQ(runtime->GetManagedInstanceCount(), 1u); int32_t awakeCount = 0; int32_t enableCount = 0; int32_t startCount = 0; int32_t fixedUpdateCount = 0; int32_t updateCount = 0; int32_t lateUpdateCount = 0; bool wasAwakened = false; bool warningLogged = false; bool errorLogged = false; bool hasTransform = false; bool transformLookupSucceeded = false; bool hasUnsupportedComponent = true; bool unsupportedComponentLookupReturnedNull = false; bool targetResolved = false; bool rotationAccessed = false; bool scaleAccessed = false; bool transformAccessed = false; bool observedEnabled = false; bool observedActiveSelf = false; bool observedActiveInHierarchy = false; bool observedIsActiveAndEnabled = false; float speed = 0.0f; float observedFixedDeltaTime = 0.0f; float observedUpdateDeltaTime = 0.0f; float observedLateDeltaTime = 0.0f; std::string label; std::string observedGameObjectName; std::string observedTargetName; GameObjectReference targetReference; GameObjectReference selfReference; XCEngine::Math::Vector4 observedLocalRotation; XCEngine::Math::Vector3 observedLocalPosition; XCEngine::Math::Vector3 observedLocalScale; XCEngine::Math::Vector3 spawnPoint; EXPECT_TRUE(runtime->TryGetFieldValue(component, "AwakeCount", awakeCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "EnableCount", enableCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "StartCount", startCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "FixedUpdateCount", fixedUpdateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "UpdateCount", updateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "LateUpdateCount", lateUpdateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "WasAwakened", wasAwakened)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "WarningLogged", warningLogged)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ErrorLogged", errorLogged)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasTransform", hasTransform)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "TransformLookupSucceeded", transformLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasUnsupportedComponent", hasUnsupportedComponent)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "UnsupportedComponentLookupReturnedNull", unsupportedComponentLookupReturnedNull)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "TargetResolved", targetResolved)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "RotationAccessed", rotationAccessed)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ScaleAccessed", scaleAccessed)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "TransformAccessed", transformAccessed)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedEnabled", observedEnabled)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedActiveSelf", observedActiveSelf)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedActiveInHierarchy", observedActiveInHierarchy)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedIsActiveAndEnabled", observedIsActiveAndEnabled)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "Speed", speed)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedFixedDeltaTime", observedFixedDeltaTime)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdateDeltaTime", observedUpdateDeltaTime)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLateDeltaTime", observedLateDeltaTime)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "Label", label)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedGameObjectName", observedGameObjectName)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedTargetName", observedTargetName)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "Target", targetReference)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "SelfReference", selfReference)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLocalRotation", observedLocalRotation)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLocalPosition", observedLocalPosition)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLocalScale", observedLocalScale)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "SpawnPoint", spawnPoint)); EXPECT_EQ(awakeCount, 1); EXPECT_EQ(enableCount, 1); EXPECT_EQ(startCount, 1); EXPECT_EQ(fixedUpdateCount, 1); EXPECT_EQ(updateCount, 1); EXPECT_EQ(lateUpdateCount, 1); EXPECT_TRUE(wasAwakened); EXPECT_TRUE(warningLogged); EXPECT_TRUE(errorLogged); EXPECT_TRUE(hasTransform); EXPECT_TRUE(transformLookupSucceeded); EXPECT_FALSE(hasUnsupportedComponent); EXPECT_TRUE(unsupportedComponentLookupReturnedNull); EXPECT_TRUE(targetResolved); EXPECT_TRUE(rotationAccessed); EXPECT_TRUE(scaleAccessed); EXPECT_TRUE(transformAccessed); EXPECT_TRUE(observedEnabled); EXPECT_TRUE(observedActiveSelf); EXPECT_TRUE(observedActiveInHierarchy); EXPECT_TRUE(observedIsActiveAndEnabled); EXPECT_FLOAT_EQ(speed, 6.0f); EXPECT_FLOAT_EQ(observedFixedDeltaTime, 0.02f); EXPECT_FLOAT_EQ(observedUpdateDeltaTime, 0.016f); EXPECT_FLOAT_EQ(observedLateDeltaTime, 0.016f); EXPECT_EQ(label, "Configured|Awake"); EXPECT_EQ(observedGameObjectName, "Host_Managed"); EXPECT_EQ(observedTargetName, "Target"); EXPECT_EQ(targetReference, GameObjectReference{target->GetUUID()}); EXPECT_EQ(selfReference, GameObjectReference{host->GetUUID()}); EXPECT_EQ(observedLocalRotation, XCEngine::Math::Vector4(0.0f, 0.5f, 0.0f, 0.8660254f)); EXPECT_EQ(observedLocalPosition, XCEngine::Math::Vector3(8.0f, 8.0f, 9.0f)); EXPECT_EQ(observedLocalScale, XCEngine::Math::Vector3(3.0f, 3.0f, 4.0f)); EXPECT_EQ(spawnPoint, XCEngine::Math::Vector3(3.0f, 4.0f, 6.0f)); EXPECT_EQ(host->GetName(), "Host_Managed"); EXPECT_EQ(host->GetTransform()->GetLocalPosition(), XCEngine::Math::Vector3(8.0f, 8.0f, 9.0f)); EXPECT_EQ(host->GetTransform()->GetLocalScale(), XCEngine::Math::Vector3(3.0f, 3.0f, 4.0f)); const XCEngine::Math::Quaternion& localRotation = host->GetTransform()->GetLocalRotation(); EXPECT_FLOAT_EQ(localRotation.x, 0.0f); EXPECT_FLOAT_EQ(localRotation.y, 0.5f); EXPECT_FLOAT_EQ(localRotation.z, 0.0f); EXPECT_FLOAT_EQ(localRotation.w, 0.8660254f); } TEST_F(MonoScriptRuntimeTest, GameObjectComponentApiResolvesTransformAndRejectsUnsupportedManagedTypes) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe"); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); bool hasTransform = false; bool transformLookupSucceeded = false; bool hasUnsupportedComponent = true; bool unsupportedComponentLookupReturnedNull = false; EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasTransform", hasTransform)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "TransformLookupSucceeded", transformLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasUnsupportedComponent", hasUnsupportedComponent)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "UnsupportedComponentLookupReturnedNull", unsupportedComponentLookupReturnedNull)); EXPECT_TRUE(hasTransform); EXPECT_TRUE(transformLookupSucceeded); EXPECT_FALSE(hasUnsupportedComponent); EXPECT_TRUE(unsupportedComponentLookupReturnedNull); EXPECT_EQ(host->GetTransform()->GetLocalPosition(), XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f)); } TEST_F(MonoScriptRuntimeTest, ManagedBuiltInComponentWrappersReadAndWriteCameraAndLight) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); CameraComponent* camera = host->AddComponent(); LightComponent* light = host->AddComponent(); ScriptComponent* component = AddScript(host, "Gameplay", "BuiltinComponentProbe"); camera->SetFieldOfView(52.0f); camera->SetNearClipPlane(0.2f); camera->SetFarClipPlane(300.0f); camera->SetDepth(1.5f); camera->SetPrimary(true); light->SetIntensity(1.25f); light->SetRange(12.0f); light->SetSpotAngle(33.0f); light->SetCastsShadows(false); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); bool hasCamera = false; bool hasLight = false; bool cameraLookupSucceeded = false; bool lightLookupSucceeded = false; bool observedPrimary = false; bool observedCastsShadows = true; float observedFieldOfView = 0.0f; float observedNearClipPlane = 0.0f; float observedFarClipPlane = 0.0f; float observedDepth = 0.0f; float observedIntensity = 0.0f; float observedRange = 0.0f; float observedSpotAngle = 0.0f; EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasCamera", hasCamera)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasLight", hasLight)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "CameraLookupSucceeded", cameraLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "LightLookupSucceeded", lightLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedFieldOfView", observedFieldOfView)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedNearClipPlane", observedNearClipPlane)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedFarClipPlane", observedFarClipPlane)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedDepth", observedDepth)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedPrimary", observedPrimary)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedIntensity", observedIntensity)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRange", observedRange)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedSpotAngle", observedSpotAngle)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedCastsShadows", observedCastsShadows)); EXPECT_TRUE(hasCamera); EXPECT_TRUE(hasLight); EXPECT_TRUE(cameraLookupSucceeded); EXPECT_TRUE(lightLookupSucceeded); EXPECT_FLOAT_EQ(observedFieldOfView, 52.0f); EXPECT_FLOAT_EQ(observedNearClipPlane, 0.2f); EXPECT_FLOAT_EQ(observedFarClipPlane, 300.0f); EXPECT_FLOAT_EQ(observedDepth, 1.5f); EXPECT_TRUE(observedPrimary); EXPECT_FLOAT_EQ(observedIntensity, 1.25f); EXPECT_FLOAT_EQ(observedRange, 12.0f); EXPECT_FLOAT_EQ(observedSpotAngle, 33.0f); EXPECT_FALSE(observedCastsShadows); EXPECT_FLOAT_EQ(camera->GetFieldOfView(), 75.0f); EXPECT_FLOAT_EQ(camera->GetNearClipPlane(), 0.3f); EXPECT_FLOAT_EQ(camera->GetFarClipPlane(), 500.0f); EXPECT_FLOAT_EQ(camera->GetDepth(), 3.0f); EXPECT_FALSE(camera->IsPrimary()); EXPECT_FLOAT_EQ(light->GetIntensity(), 2.5f); EXPECT_FLOAT_EQ(light->GetRange(), 42.0f); EXPECT_FLOAT_EQ(light->GetSpotAngle(), 55.0f); EXPECT_TRUE(light->GetCastsShadows()); } TEST_F(MonoScriptRuntimeTest, ManagedMeshComponentWrappersReadAndWritePathsAndFlags) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); MeshFilterComponent* meshFilter = host->AddComponent(); MeshRendererComponent* meshRenderer = host->AddComponent(); ScriptComponent* component = AddScript(host, "Gameplay", "MeshComponentProbe"); meshFilter->SetMeshPath("Meshes/initial.mesh"); meshRenderer->SetMaterialPath(0, "Materials/initial.mat"); meshRenderer->SetCastShadows(true); meshRenderer->SetReceiveShadows(false); meshRenderer->SetRenderLayer(7); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); bool hasMeshFilter = false; bool hasMeshRenderer = false; bool meshFilterLookupSucceeded = false; bool meshRendererLookupSucceeded = false; std::string observedInitialMeshPath; std::string observedUpdatedMeshPath; int32_t observedInitialMaterialCount = 0; std::string observedInitialMaterial0Path; bool observedInitialCastShadows = false; bool observedInitialReceiveShadows = true; int32_t observedInitialRenderLayer = 0; int32_t observedUpdatedMaterialCount = 0; std::string observedUpdatedMaterial1Path; bool observedUpdatedCastShadows = true; bool observedUpdatedReceiveShadows = false; int32_t observedUpdatedRenderLayer = 0; EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasMeshFilter", hasMeshFilter)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasMeshRenderer", hasMeshRenderer)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "MeshFilterLookupSucceeded", meshFilterLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "MeshRendererLookupSucceeded", meshRendererLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialMeshPath", observedInitialMeshPath)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedMeshPath", observedUpdatedMeshPath)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialMaterialCount", observedInitialMaterialCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialMaterial0Path", observedInitialMaterial0Path)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialCastShadows", observedInitialCastShadows)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialReceiveShadows", observedInitialReceiveShadows)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialRenderLayer", observedInitialRenderLayer)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedMaterialCount", observedUpdatedMaterialCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedMaterial1Path", observedUpdatedMaterial1Path)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedCastShadows", observedUpdatedCastShadows)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedReceiveShadows", observedUpdatedReceiveShadows)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedRenderLayer", observedUpdatedRenderLayer)); EXPECT_TRUE(hasMeshFilter); EXPECT_TRUE(hasMeshRenderer); EXPECT_TRUE(meshFilterLookupSucceeded); EXPECT_TRUE(meshRendererLookupSucceeded); EXPECT_EQ(observedInitialMeshPath, "Meshes/initial.mesh"); EXPECT_EQ(observedUpdatedMeshPath, "Meshes/runtime_override.mesh"); EXPECT_EQ(observedInitialMaterialCount, 1); EXPECT_EQ(observedInitialMaterial0Path, "Materials/initial.mat"); EXPECT_TRUE(observedInitialCastShadows); EXPECT_FALSE(observedInitialReceiveShadows); EXPECT_EQ(observedInitialRenderLayer, 7); EXPECT_EQ(observedUpdatedMaterialCount, 2); EXPECT_EQ(observedUpdatedMaterial1Path, "Materials/runtime_override.mat"); EXPECT_FALSE(observedUpdatedCastShadows); EXPECT_TRUE(observedUpdatedReceiveShadows); EXPECT_EQ(observedUpdatedRenderLayer, 11); EXPECT_EQ(meshFilter->GetMeshPath(), "Meshes/runtime_override.mesh"); ASSERT_EQ(meshRenderer->GetMaterialCount(), 2u); EXPECT_EQ(meshRenderer->GetMaterialPath(0), "Materials/initial.mat"); EXPECT_EQ(meshRenderer->GetMaterialPath(1), "Materials/runtime_override.mat"); EXPECT_FALSE(meshRenderer->GetCastShadows()); EXPECT_TRUE(meshRenderer->GetReceiveShadows()); EXPECT_EQ(meshRenderer->GetRenderLayer(), 11u); } TEST_F(MonoScriptRuntimeTest, ManagedMeshRendererWrapperHandlesClearAndBoundaryCases) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); MeshRendererComponent* meshRenderer = host->AddComponent(); ScriptComponent* component = AddScript(host, "Gameplay", "MeshRendererEdgeCaseProbe"); meshRenderer->SetMaterialPath(0, "Materials/initial0.mat"); meshRenderer->SetMaterialPath(1, "Materials/initial1.mat"); meshRenderer->SetCastShadows(false); meshRenderer->SetReceiveShadows(true); meshRenderer->SetRenderLayer(9); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); int32_t observedInitialMaterialCount = 0; std::string observedNegativeIndexPath; std::string observedOutOfRangePathBeforeClear; std::string observedMaterial0PathAfterNegativeWrite; std::string observedMaterial1PathAfterNegativeWrite; int32_t observedMaterialCountAfterNegativeWrite = 0; int32_t observedRenderLayerAfterNegativeWrite = -1; int32_t observedMaterialCountAfterClear = -1; std::string observedMaterial0PathAfterClear; std::string observedMaterial3PathAfterClear; bool observedCastShadowsAfterClear = true; bool observedReceiveShadowsAfterClear = false; int32_t observedRenderLayerAfterClear = -1; EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialMaterialCount", observedInitialMaterialCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedNegativeIndexPath", observedNegativeIndexPath)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedOutOfRangePathBeforeClear", observedOutOfRangePathBeforeClear)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial0PathAfterNegativeWrite", observedMaterial0PathAfterNegativeWrite)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial1PathAfterNegativeWrite", observedMaterial1PathAfterNegativeWrite)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterialCountAfterNegativeWrite", observedMaterialCountAfterNegativeWrite)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRenderLayerAfterNegativeWrite", observedRenderLayerAfterNegativeWrite)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterialCountAfterClear", observedMaterialCountAfterClear)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial0PathAfterClear", observedMaterial0PathAfterClear)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial3PathAfterClear", observedMaterial3PathAfterClear)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedCastShadowsAfterClear", observedCastShadowsAfterClear)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedReceiveShadowsAfterClear", observedReceiveShadowsAfterClear)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRenderLayerAfterClear", observedRenderLayerAfterClear)); EXPECT_EQ(observedInitialMaterialCount, 2); EXPECT_EQ(observedNegativeIndexPath, ""); EXPECT_EQ(observedOutOfRangePathBeforeClear, ""); EXPECT_EQ(observedMaterial0PathAfterNegativeWrite, "Materials/initial0.mat"); EXPECT_EQ(observedMaterial1PathAfterNegativeWrite, "Materials/initial1.mat"); EXPECT_EQ(observedMaterialCountAfterNegativeWrite, 2); EXPECT_EQ(observedRenderLayerAfterNegativeWrite, 0); EXPECT_EQ(observedMaterialCountAfterClear, 0); EXPECT_EQ(observedMaterial0PathAfterClear, ""); EXPECT_EQ(observedMaterial3PathAfterClear, ""); EXPECT_FALSE(observedCastShadowsAfterClear); EXPECT_TRUE(observedReceiveShadowsAfterClear); EXPECT_EQ(observedRenderLayerAfterClear, 0); EXPECT_EQ(meshRenderer->GetMaterialCount(), 0u); EXPECT_EQ(meshRenderer->GetMaterialPath(0), ""); EXPECT_EQ(meshRenderer->GetMaterialPath(3), ""); EXPECT_FALSE(meshRenderer->GetCastShadows()); EXPECT_TRUE(meshRenderer->GetReceiveShadows()); EXPECT_EQ(meshRenderer->GetRenderLayer(), 0u); } TEST_F(MonoScriptRuntimeTest, GameObjectAddComponentApiCreatesBuiltinComponentsAndAvoidsDuplicates) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "AddComponentProbe"); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); bool initialHasCamera = true; bool initialHasLight = true; bool initialHasMeshFilter = true; bool initialHasMeshRenderer = true; bool addedTransform = false; bool addedCamera = false; bool addedLight = false; bool addedMeshFilter = false; bool addedMeshRenderer = false; bool hasCameraAfterAdd = false; bool hasLightAfterAdd = false; bool hasMeshFilterAfterAdd = false; bool hasMeshRendererAfterAdd = false; bool cameraLookupSucceeded = false; bool lightLookupSucceeded = false; bool meshFilterLookupSucceeded = false; bool meshRendererLookupSucceeded = false; float observedCameraFieldOfView = 0.0f; float observedLightIntensity = 0.0f; std::string observedMeshPath; int32_t observedMaterialCount = 0; std::string observedMaterial0Path; int32_t observedRenderLayer = 0; EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasCamera", initialHasCamera)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasLight", initialHasLight)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasMeshFilter", initialHasMeshFilter)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasMeshRenderer", initialHasMeshRenderer)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedTransform", addedTransform)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedCamera", addedCamera)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedLight", addedLight)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedMeshFilter", addedMeshFilter)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedMeshRenderer", addedMeshRenderer)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasCameraAfterAdd", hasCameraAfterAdd)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasLightAfterAdd", hasLightAfterAdd)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasMeshFilterAfterAdd", hasMeshFilterAfterAdd)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasMeshRendererAfterAdd", hasMeshRendererAfterAdd)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "CameraLookupSucceeded", cameraLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "LightLookupSucceeded", lightLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "MeshFilterLookupSucceeded", meshFilterLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "MeshRendererLookupSucceeded", meshRendererLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedCameraFieldOfView", observedCameraFieldOfView)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLightIntensity", observedLightIntensity)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMeshPath", observedMeshPath)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterialCount", observedMaterialCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial0Path", observedMaterial0Path)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRenderLayer", observedRenderLayer)); EXPECT_FALSE(initialHasCamera); EXPECT_FALSE(initialHasLight); EXPECT_FALSE(initialHasMeshFilter); EXPECT_FALSE(initialHasMeshRenderer); EXPECT_TRUE(addedTransform); EXPECT_TRUE(addedCamera); EXPECT_TRUE(addedLight); EXPECT_TRUE(addedMeshFilter); EXPECT_TRUE(addedMeshRenderer); EXPECT_TRUE(hasCameraAfterAdd); EXPECT_TRUE(hasLightAfterAdd); EXPECT_TRUE(hasMeshFilterAfterAdd); EXPECT_TRUE(hasMeshRendererAfterAdd); EXPECT_TRUE(cameraLookupSucceeded); EXPECT_TRUE(lightLookupSucceeded); EXPECT_TRUE(meshFilterLookupSucceeded); EXPECT_TRUE(meshRendererLookupSucceeded); EXPECT_FLOAT_EQ(observedCameraFieldOfView, 82.0f); EXPECT_FLOAT_EQ(observedLightIntensity, 4.5f); EXPECT_EQ(observedMeshPath, "Meshes/added.mesh"); EXPECT_EQ(observedMaterialCount, 1); EXPECT_EQ(observedMaterial0Path, "Materials/added.mat"); EXPECT_EQ(observedRenderLayer, 6); ASSERT_NE(host->GetTransform(), nullptr); EXPECT_EQ(host->GetComponents().size(), 1u); EXPECT_EQ(host->GetComponents().size(), 1u); EXPECT_EQ(host->GetComponents().size(), 1u); EXPECT_EQ(host->GetComponents().size(), 1u); CameraComponent* camera = host->GetComponent(); LightComponent* light = host->GetComponent(); MeshFilterComponent* meshFilter = host->GetComponent(); MeshRendererComponent* meshRenderer = host->GetComponent(); ASSERT_NE(camera, nullptr); ASSERT_NE(light, nullptr); ASSERT_NE(meshFilter, nullptr); ASSERT_NE(meshRenderer, nullptr); EXPECT_FLOAT_EQ(camera->GetFieldOfView(), 82.0f); EXPECT_FLOAT_EQ(light->GetIntensity(), 4.5f); EXPECT_EQ(meshFilter->GetMeshPath(), "Meshes/added.mesh"); ASSERT_EQ(meshRenderer->GetMaterialCount(), 1u); EXPECT_EQ(meshRenderer->GetMaterialPath(0), "Materials/added.mat"); EXPECT_EQ(meshRenderer->GetRenderLayer(), 6u); } TEST_F(MonoScriptRuntimeTest, TransformHierarchyApiExposesParentChildAndReparenting) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* root = runtimeScene->CreateGameObject("Root"); GameObject* host = runtimeScene->CreateGameObject("Host", root); GameObject* child = runtimeScene->CreateGameObject("Child", host); GameObject* reparentTarget = runtimeScene->CreateGameObject("ReparentTarget"); ScriptComponent* component = AddScript(host, "Gameplay", "HierarchyProbe"); component->GetFieldStorage().SetFieldValue("ReparentTarget", GameObjectReference{reparentTarget->GetUUID()}); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); int32_t observedChildCount = 0; bool parentLookupSucceeded = false; bool childLookupSucceeded = false; std::string observedParentName; std::string observedFirstChildName; std::string reparentedChildParentName; EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedChildCount", observedChildCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ParentLookupSucceeded", parentLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ChildLookupSucceeded", childLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedParentName", observedParentName)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedFirstChildName", observedFirstChildName)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ReparentedChildParentName", reparentedChildParentName)); EXPECT_TRUE(parentLookupSucceeded); EXPECT_TRUE(childLookupSucceeded); EXPECT_EQ(observedChildCount, 1); EXPECT_EQ(observedParentName, "Root"); EXPECT_EQ(observedFirstChildName, "Child"); EXPECT_EQ(reparentedChildParentName, "ReparentTarget"); EXPECT_EQ(host->GetParent(), root); EXPECT_EQ(host->GetChildCount(), 0u); EXPECT_EQ(child->GetParent(), reparentTarget); EXPECT_EQ(reparentTarget->GetChildCount(), 1u); } TEST_F(MonoScriptRuntimeTest, TransformSpaceApiReadsAndWritesWorldAndLocalValues) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* parent = runtimeScene->CreateGameObject("Parent"); GameObject* host = runtimeScene->CreateGameObject("Host", parent); ScriptComponent* component = AddScript(host, "Gameplay", "TransformSpaceProbe"); parent->GetTransform()->SetLocalPosition(XCEngine::Math::Vector3(10.0f, 0.0f, 0.0f)); parent->GetTransform()->SetLocalScale(XCEngine::Math::Vector3(2.0f, 2.0f, 2.0f)); host->GetTransform()->SetLocalPosition(XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f)); host->GetTransform()->SetLocalScale(XCEngine::Math::Vector3(1.0f, 1.0f, 1.0f)); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); XCEngine::Math::Vector3 observedInitialWorldPosition; XCEngine::Math::Vector3 observedInitialWorldScale; XCEngine::Math::Vector3 observedInitialLocalEulerAngles; XCEngine::Math::Vector3 observedEulerAfterSet; XCEngine::Math::Vector3 observedWorldPositionAfterSet; XCEngine::Math::Vector3 observedWorldScaleAfterSet; EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialWorldPosition", observedInitialWorldPosition)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialWorldScale", observedInitialWorldScale)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialLocalEulerAngles", observedInitialLocalEulerAngles)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedEulerAfterSet", observedEulerAfterSet)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedWorldPositionAfterSet", observedWorldPositionAfterSet)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedWorldScaleAfterSet", observedWorldScaleAfterSet)); EXPECT_EQ(observedInitialWorldPosition, XCEngine::Math::Vector3(12.0f, 4.0f, 6.0f)); EXPECT_EQ(observedInitialWorldScale, XCEngine::Math::Vector3(2.0f, 2.0f, 2.0f)); EXPECT_EQ(observedInitialLocalEulerAngles, XCEngine::Math::Vector3(0.0f, 0.0f, 0.0f)); EXPECT_NEAR(observedEulerAfterSet.x, 0.0f, 0.001f); EXPECT_NEAR(observedEulerAfterSet.y, 45.0f, 0.001f); EXPECT_NEAR(observedEulerAfterSet.z, 0.0f, 0.001f); EXPECT_EQ(observedWorldPositionAfterSet, XCEngine::Math::Vector3(20.0f, 6.0f, 10.0f)); EXPECT_EQ(observedWorldScaleAfterSet, XCEngine::Math::Vector3(6.0f, 8.0f, 10.0f)); EXPECT_EQ(host->GetTransform()->GetLocalPosition(), XCEngine::Math::Vector3(5.0f, 3.0f, 5.0f)); EXPECT_EQ(host->GetTransform()->GetPosition(), XCEngine::Math::Vector3(20.0f, 6.0f, 10.0f)); EXPECT_EQ(host->GetTransform()->GetLocalScale(), XCEngine::Math::Vector3(3.0f, 4.0f, 5.0f)); EXPECT_EQ(host->GetTransform()->GetScale(), XCEngine::Math::Vector3(6.0f, 8.0f, 10.0f)); const XCEngine::Math::Quaternion& localRotation = host->GetTransform()->GetLocalRotation(); EXPECT_FLOAT_EQ(localRotation.x, 0.0f); EXPECT_FLOAT_EQ(localRotation.y, 0.5f); EXPECT_FLOAT_EQ(localRotation.z, 0.0f); EXPECT_FLOAT_EQ(localRotation.w, 0.8660254f); } TEST_F(MonoScriptRuntimeTest, TransformMotionApiExposesDirectionVectorsAndAppliesOperations) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "TransformMotionProbe"); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); XCEngine::Math::Vector3 observedForward; XCEngine::Math::Vector3 observedRight; XCEngine::Math::Vector3 observedUp; XCEngine::Math::Vector3 observedPositionAfterSelfTranslate; XCEngine::Math::Vector3 observedPositionAfterWorldTranslate; XCEngine::Math::Vector3 observedForwardAfterWorldRotate; XCEngine::Math::Vector3 observedForwardAfterSelfRotate; XCEngine::Math::Vector3 observedForwardAfterLookAt; EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedForward", observedForward)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRight", observedRight)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUp", observedUp)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedPositionAfterSelfTranslate", observedPositionAfterSelfTranslate)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedPositionAfterWorldTranslate", observedPositionAfterWorldTranslate)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedForwardAfterWorldRotate", observedForwardAfterWorldRotate)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedForwardAfterSelfRotate", observedForwardAfterSelfRotate)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedForwardAfterLookAt", observedForwardAfterLookAt)); ExpectVector3Near(observedForward, XCEngine::Math::Vector3(1.0f, 0.0f, 0.0f)); ExpectVector3Near(observedRight, XCEngine::Math::Vector3(0.0f, 0.0f, -1.0f)); ExpectVector3Near(observedUp, XCEngine::Math::Vector3(0.0f, 1.0f, 0.0f)); ExpectVector3Near(observedPositionAfterSelfTranslate, XCEngine::Math::Vector3(2.0f, 0.0f, 0.0f)); ExpectVector3Near(observedPositionAfterWorldTranslate, XCEngine::Math::Vector3(2.0f, 0.0f, 3.0f)); ExpectVector3Near(observedForwardAfterWorldRotate, XCEngine::Math::Vector3(0.0f, 0.0f, 1.0f)); ExpectVector3Near(observedForwardAfterSelfRotate, XCEngine::Math::Vector3(1.0f, 0.0f, 0.0f)); ExpectVector3Near(observedForwardAfterLookAt, XCEngine::Math::Vector3(0.0f, 0.0f, 1.0f)); ExpectVector3Near(host->GetTransform()->GetPosition(), XCEngine::Math::Vector3(2.0f, 0.0f, 3.0f)); ExpectVector3Near(host->GetTransform()->GetForward(), XCEngine::Math::Vector3(0.0f, 0.0f, 1.0f)); } TEST_F(MonoScriptRuntimeTest, TransformConversionApiTransformsPointsAndDirectionsBetweenSpaces) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "TransformConversionProbe"); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); XCEngine::Math::Vector3 observedWorldPoint; XCEngine::Math::Vector3 observedLocalPoint; XCEngine::Math::Vector3 observedWorldDirection; XCEngine::Math::Vector3 observedLocalDirection; EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedWorldPoint", observedWorldPoint)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLocalPoint", observedLocalPoint)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedWorldDirection", observedWorldDirection)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLocalDirection", observedLocalDirection)); ExpectVector3Near(observedWorldPoint, XCEngine::Math::Vector3(7.0f, 0.0f, 1.0f)); ExpectVector3Near(observedLocalPoint, XCEngine::Math::Vector3(0.0f, 0.0f, 2.0f)); ExpectVector3Near(observedWorldDirection, XCEngine::Math::Vector3(1.0f, 0.0f, 0.0f)); ExpectVector3Near(observedLocalDirection, XCEngine::Math::Vector3(0.0f, 0.0f, 1.0f)); ExpectVector3Near(host->GetTransform()->TransformPoint(XCEngine::Math::Vector3(0.0f, 0.0f, 2.0f)), XCEngine::Math::Vector3(7.0f, 0.0f, 1.0f)); ExpectVector3Near(host->GetTransform()->InverseTransformDirection(XCEngine::Math::Vector3(1.0f, 0.0f, 0.0f)), XCEngine::Math::Vector3(0.0f, 0.0f, 1.0f)); } TEST_F(MonoScriptRuntimeTest, TransformOrientationOverloadsSupportAxisAngleAndCustomUpVector) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "TransformOrientationProbe"); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); XCEngine::Math::Vector3 observedForwardAfterAxisAngleRotate; XCEngine::Math::Vector3 observedForwardAfterWorldAxisAngleRotate; XCEngine::Math::Vector3 observedForwardAfterDefaultLookAt; XCEngine::Math::Vector3 observedRightAfterDefaultLookAt; XCEngine::Math::Vector3 observedUpAfterDefaultLookAt; XCEngine::Math::Vector3 observedForwardAfterLookAtWithUp; XCEngine::Math::Vector3 observedRightAfterLookAtWithUp; XCEngine::Math::Vector3 observedUpAfterLookAtWithUp; EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedForwardAfterAxisAngleRotate", observedForwardAfterAxisAngleRotate)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedForwardAfterWorldAxisAngleRotate", observedForwardAfterWorldAxisAngleRotate)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedForwardAfterDefaultLookAt", observedForwardAfterDefaultLookAt)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRightAfterDefaultLookAt", observedRightAfterDefaultLookAt)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpAfterDefaultLookAt", observedUpAfterDefaultLookAt)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedForwardAfterLookAtWithUp", observedForwardAfterLookAtWithUp)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRightAfterLookAtWithUp", observedRightAfterLookAtWithUp)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpAfterLookAtWithUp", observedUpAfterLookAtWithUp)); ExpectVector3Near(observedForwardAfterAxisAngleRotate, XCEngine::Math::Vector3(1.0f, 0.0f, 0.0f)); ExpectVector3Near(observedForwardAfterWorldAxisAngleRotate, XCEngine::Math::Vector3(1.0f, 0.0f, 0.0f)); EXPECT_NEAR(observedForwardAfterDefaultLookAt.Magnitude(), 1.0f, 0.001f); EXPECT_NEAR(observedRightAfterDefaultLookAt.Magnitude(), 1.0f, 0.001f); EXPECT_NEAR(observedUpAfterDefaultLookAt.Magnitude(), 1.0f, 0.001f); EXPECT_NEAR(observedForwardAfterLookAtWithUp.Magnitude(), 1.0f, 0.001f); EXPECT_NEAR(observedRightAfterLookAtWithUp.Magnitude(), 1.0f, 0.001f); EXPECT_NEAR(observedUpAfterLookAtWithUp.Magnitude(), 1.0f, 0.001f); EXPECT_GT((observedRightAfterLookAtWithUp - observedRightAfterDefaultLookAt).Magnitude(), 0.5f); EXPECT_GT((observedUpAfterLookAtWithUp - observedUpAfterDefaultLookAt).Magnitude(), 0.5f); ExpectVector3Near(host->GetTransform()->GetForward(), observedForwardAfterLookAtWithUp); ExpectVector3Near(host->GetTransform()->GetRight(), observedRightAfterLookAtWithUp); ExpectVector3Near(host->GetTransform()->GetUp(), observedUpAfterLookAtWithUp); } TEST_F(MonoScriptRuntimeTest, ManagedBehaviourCanDisableItselfThroughEnabledProperty) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe"); component->GetFieldStorage().SetFieldValue("DisableSelfOnFirstUpdate", true); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); engine->OnLateUpdate(0.016f); int32_t updateCount = 0; int32_t lateUpdateCount = 0; int32_t disableCount = 0; bool observedEnabled = false; bool observedIsActiveAndEnabled = false; EXPECT_TRUE(runtime->TryGetFieldValue(component, "UpdateCount", updateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "LateUpdateCount", lateUpdateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "DisableCount", disableCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedEnabled", observedEnabled)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedIsActiveAndEnabled", observedIsActiveAndEnabled)); EXPECT_FALSE(component->IsEnabled()); EXPECT_EQ(updateCount, 1); EXPECT_EQ(lateUpdateCount, 0); EXPECT_EQ(disableCount, 1); EXPECT_TRUE(observedEnabled); EXPECT_TRUE(observedIsActiveAndEnabled); engine->OnUpdate(0.016f); EXPECT_TRUE(runtime->TryGetFieldValue(component, "UpdateCount", updateCount)); EXPECT_EQ(updateCount, 1); } TEST_F(MonoScriptRuntimeTest, ManagedScriptCanDeactivateItsHostGameObject) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe"); component->GetFieldStorage().SetFieldValue("DeactivateGameObjectOnFirstUpdate", true); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); engine->OnLateUpdate(0.016f); int32_t updateCount = 0; int32_t lateUpdateCount = 0; int32_t disableCount = 0; bool observedActiveSelf = false; bool observedActiveInHierarchy = false; EXPECT_TRUE(runtime->TryGetFieldValue(component, "UpdateCount", updateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "LateUpdateCount", lateUpdateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "DisableCount", disableCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedActiveSelf", observedActiveSelf)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedActiveInHierarchy", observedActiveInHierarchy)); EXPECT_FALSE(host->IsActive()); EXPECT_FALSE(host->IsActiveInHierarchy()); EXPECT_TRUE(component->IsEnabled()); EXPECT_EQ(updateCount, 1); EXPECT_EQ(lateUpdateCount, 0); EXPECT_EQ(disableCount, 1); EXPECT_TRUE(observedActiveSelf); EXPECT_TRUE(observedActiveInHierarchy); engine->OnUpdate(0.016f); EXPECT_TRUE(runtime->TryGetFieldValue(component, "UpdateCount", updateCount)); EXPECT_EQ(updateCount, 1); } TEST_F(MonoScriptRuntimeTest, DisableEnablePreservesSingleInstanceAndStartOnlyRunsOnce) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe"); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); component->SetEnabled(false); int32_t disableCount = 0; EXPECT_TRUE(runtime->TryGetFieldValue(component, "DisableCount", disableCount)); EXPECT_EQ(disableCount, 1); EXPECT_EQ(runtime->GetManagedInstanceCount(), 1u); component->SetEnabled(true); engine->OnUpdate(0.016f); int32_t enableCount = 0; int32_t startCount = 0; int32_t updateCount = 0; EXPECT_TRUE(runtime->TryGetFieldValue(component, "EnableCount", enableCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "StartCount", startCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "UpdateCount", updateCount)); EXPECT_EQ(enableCount, 2); EXPECT_EQ(startCount, 1); EXPECT_EQ(updateCount, 2); engine->OnRuntimeStop(); EXPECT_FALSE(runtime->HasManagedInstance(component)); EXPECT_EQ(runtime->GetManagedInstanceCount(), 0u); } } // namespace