feat(scripting): add mono csharp runtime foundation
This commit is contained in:
@@ -8,6 +8,12 @@ set(SCRIPTING_TEST_SOURCES
|
||||
test_script_engine.cpp
|
||||
)
|
||||
|
||||
if(XCENGINE_ENABLE_MONO_SCRIPTING)
|
||||
list(APPEND SCRIPTING_TEST_SOURCES
|
||||
test_mono_script_runtime.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
add_executable(scripting_tests ${SCRIPTING_TEST_SOURCES})
|
||||
|
||||
if(MSVC)
|
||||
@@ -26,5 +32,27 @@ target_include_directories(scripting_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
)
|
||||
|
||||
if(TARGET xcengine_managed_assemblies)
|
||||
add_dependencies(scripting_tests xcengine_managed_assemblies)
|
||||
|
||||
file(TO_CMAKE_PATH "${XCENGINE_MANAGED_OUTPUT_DIR}" XCENGINE_MANAGED_OUTPUT_DIR_CMAKE)
|
||||
file(TO_CMAKE_PATH "${XCENGINE_SCRIPT_CORE_DLL}" XCENGINE_SCRIPT_CORE_DLL_CMAKE)
|
||||
file(TO_CMAKE_PATH "${XCENGINE_GAME_SCRIPTS_DLL}" XCENGINE_GAME_SCRIPTS_DLL_CMAKE)
|
||||
|
||||
target_compile_definitions(scripting_tests PRIVATE
|
||||
XCENGINE_TEST_MANAGED_OUTPUT_DIR=\"${XCENGINE_MANAGED_OUTPUT_DIR_CMAKE}\"
|
||||
XCENGINE_TEST_SCRIPT_CORE_DLL=\"${XCENGINE_SCRIPT_CORE_DLL_CMAKE}\"
|
||||
XCENGINE_TEST_GAME_SCRIPTS_DLL=\"${XCENGINE_GAME_SCRIPTS_DLL_CMAKE}\"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32 AND EXISTS "${CMAKE_SOURCE_DIR}/engine/third_party/assimp/bin/assimp-vc143-mt.dll")
|
||||
add_custom_command(TARGET scripting_tests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_SOURCE_DIR}/engine/third_party/assimp/bin/assimp-vc143-mt.dll
|
||||
$<TARGET_FILE_DIR:scripting_tests>/assimp-vc143-mt.dll
|
||||
)
|
||||
endif()
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(scripting_tests)
|
||||
|
||||
581
tests/scripting/test_mono_script_runtime.cpp
Normal file
581
tests/scripting/test_mono_script_runtime.cpp
Normal file
@@ -0,0 +1,581 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user