Files
XCEngine/tests/scripting/test_mono_script_runtime.cpp

582 lines
28 KiB
C++

#include <gtest/gtest.h>
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/LightComponent.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Core/Math/Vector4.h>
#include <XCEngine/Scene/Scene.h>
#include <XCEngine/Scripting/Mono/MonoScriptRuntime.h>
#include <XCEngine/Scripting/ScriptComponent.h>
#include <XCEngine/Scripting/ScriptEngine.h>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
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<MonoScriptRuntime>(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<Scene>(sceneName);
return scene.get();
}
ScriptComponent* AddScript(GameObject* gameObject, const std::string& namespaceName, const std::string& className) {
ScriptComponent* component = gameObject->AddComponent<ScriptComponent>();
component->SetScriptClass("GameScripts", namespaceName, className);
return component;
}
ScriptEngine* engine = nullptr;
std::unique_ptr<MonoScriptRuntime> runtime;
std::unique_ptr<Scene> scene;
};
TEST_F(MonoScriptRuntimeTest, InitializesAndDiscoversConcreteMonoBehaviourClasses) {
const std::vector<std::string> 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<CameraComponent>();
LightComponent* light = host->AddComponent<LightComponent>();
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, 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, 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