feat(scripting): add runtime gameobject lifecycle api

This commit is contained in:
2026-03-27 16:30:16 +08:00
parent 26035e3940
commit a72f9f7f05
10 changed files with 395 additions and 0 deletions

View File

@@ -562,6 +562,146 @@ TEST_F(MonoScriptRuntimeTest, GameObjectAddComponentApiCreatesBuiltinComponentsA
EXPECT_EQ(meshRenderer->GetRenderLayer(), 6u);
}
TEST_F(MonoScriptRuntimeTest, GameObjectRuntimeApiCreatesFindsAndDestroysSceneObjects) {
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
GameObject* host = runtimeScene->CreateGameObject("Host");
ScriptComponent* component = AddScript(host, "Gameplay", "RuntimeGameObjectProbe");
engine->OnRuntimeStart(runtimeScene);
engine->OnUpdate(0.016f);
bool missingBeforeCreate = false;
bool createdRootSucceeded = false;
bool createdChildSucceeded = false;
bool foundRootSucceeded = false;
bool foundChildSucceeded = false;
std::string observedFoundRootName;
std::string observedFoundChildParentName;
int32_t observedRootChildCountBeforeDestroy = 0;
bool cameraLookupSucceeded = false;
bool meshFilterLookupSucceeded = false;
bool meshRendererLookupSucceeded = false;
float observedCameraFieldOfView = 0.0f;
std::string observedMeshPath;
int32_t observedMaterialCount = 0;
std::string observedMaterial0Path;
int32_t observedRenderLayer = 0;
bool missingChildAfterDestroy = false;
bool foundRootAfterDestroySucceeded = false;
int32_t observedRootChildCountAfterDestroy = -1;
EXPECT_TRUE(runtime->TryGetFieldValue(component, "MissingBeforeCreate", missingBeforeCreate));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "CreatedRootSucceeded", createdRootSucceeded));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "CreatedChildSucceeded", createdChildSucceeded));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "FoundRootSucceeded", foundRootSucceeded));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "FoundChildSucceeded", foundChildSucceeded));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedFoundRootName", observedFoundRootName));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedFoundChildParentName", observedFoundChildParentName));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRootChildCountBeforeDestroy", observedRootChildCountBeforeDestroy));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "CameraLookupSucceeded", cameraLookupSucceeded));
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, "ObservedMeshPath", observedMeshPath));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterialCount", observedMaterialCount));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial0Path", observedMaterial0Path));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRenderLayer", observedRenderLayer));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "MissingChildAfterDestroy", missingChildAfterDestroy));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "FoundRootAfterDestroySucceeded", foundRootAfterDestroySucceeded));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRootChildCountAfterDestroy", observedRootChildCountAfterDestroy));
EXPECT_TRUE(missingBeforeCreate);
EXPECT_TRUE(createdRootSucceeded);
EXPECT_TRUE(createdChildSucceeded);
EXPECT_TRUE(foundRootSucceeded);
EXPECT_TRUE(foundChildSucceeded);
EXPECT_EQ(observedFoundRootName, "RuntimeCreatedRoot");
EXPECT_EQ(observedFoundChildParentName, "RuntimeCreatedRoot");
EXPECT_EQ(observedRootChildCountBeforeDestroy, 1);
EXPECT_TRUE(cameraLookupSucceeded);
EXPECT_TRUE(meshFilterLookupSucceeded);
EXPECT_TRUE(meshRendererLookupSucceeded);
EXPECT_FLOAT_EQ(observedCameraFieldOfView, 68.0f);
EXPECT_EQ(observedMeshPath, "Meshes/runtime_created.mesh");
EXPECT_EQ(observedMaterialCount, 1);
EXPECT_EQ(observedMaterial0Path, "Materials/runtime_created.mat");
EXPECT_EQ(observedRenderLayer, 4);
EXPECT_TRUE(missingChildAfterDestroy);
EXPECT_TRUE(foundRootAfterDestroySucceeded);
EXPECT_EQ(observedRootChildCountAfterDestroy, 0);
GameObject* createdRoot = runtimeScene->Find("RuntimeCreatedRoot");
ASSERT_NE(createdRoot, nullptr);
EXPECT_EQ(runtimeScene->Find("RuntimeCreatedChild"), nullptr);
EXPECT_EQ(createdRoot->GetParent(), nullptr);
EXPECT_EQ(createdRoot->GetChildCount(), 0u);
CameraComponent* camera = createdRoot->GetComponent<CameraComponent>();
MeshFilterComponent* meshFilter = createdRoot->GetComponent<MeshFilterComponent>();
MeshRendererComponent* meshRenderer = createdRoot->GetComponent<MeshRendererComponent>();
ASSERT_NE(camera, nullptr);
ASSERT_NE(meshFilter, nullptr);
ASSERT_NE(meshRenderer, nullptr);
EXPECT_FLOAT_EQ(camera->GetFieldOfView(), 68.0f);
EXPECT_EQ(meshFilter->GetMeshPath(), "Meshes/runtime_created.mesh");
ASSERT_EQ(meshRenderer->GetMaterialCount(), 1u);
EXPECT_EQ(meshRenderer->GetMaterialPath(0), "Materials/runtime_created.mat");
EXPECT_EQ(meshRenderer->GetRenderLayer(), 4u);
}
TEST_F(MonoScriptRuntimeTest, RuntimeCreatedScriptComponentCreatesManagedInstanceAfterClassAssignment) {
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
engine->OnRuntimeStart(runtimeScene);
GameObject* spawned = runtimeScene->CreateGameObject("RuntimeSpawned");
ScriptComponent* component = spawned->AddComponent<ScriptComponent>();
component->GetFieldStorage().SetFieldValue("Label", "RuntimeConfigured");
component->SetScriptClass("GameScripts", "Gameplay", "LifecycleProbe");
EXPECT_TRUE(runtime->HasManagedInstance(component));
engine->OnUpdate(0.016f);
int32_t awakeCount = 0;
int32_t enableCount = 0;
int32_t startCount = 0;
int32_t updateCount = 0;
std::string label;
std::string observedGameObjectName;
bool wasAwakened = false;
bool observedEnabled = false;
bool observedActiveSelf = false;
bool observedActiveInHierarchy = false;
bool observedIsActiveAndEnabled = false;
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, "UpdateCount", updateCount));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "Label", label));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedGameObjectName", observedGameObjectName));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "WasAwakened", wasAwakened));
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_EQ(awakeCount, 1);
EXPECT_EQ(enableCount, 1);
EXPECT_EQ(startCount, 1);
EXPECT_EQ(updateCount, 1);
EXPECT_EQ(label, "RuntimeConfigured|Awake");
EXPECT_EQ(observedGameObjectName, "RuntimeSpawned_Managed");
EXPECT_TRUE(wasAwakened);
EXPECT_TRUE(observedEnabled);
EXPECT_TRUE(observedActiveSelf);
EXPECT_TRUE(observedActiveInHierarchy);
EXPECT_TRUE(observedIsActiveAndEnabled);
EXPECT_EQ(spawned->GetName(), "RuntimeSpawned_Managed");
}
TEST_F(MonoScriptRuntimeTest, TransformHierarchyApiExposesParentChildAndReparenting) {
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
GameObject* root = runtimeScene->CreateGameObject("Root");

View File

@@ -236,4 +236,34 @@ TEST_F(ScriptEngineTest, DestroyingGameObjectWhileRuntimeRunningDestroysTrackedS
EXPECT_EQ(engine->GetTrackedScriptCount(), 0u);
}
TEST_F(ScriptEngineTest, RuntimeCreatedScriptComponentIsTrackedImmediatelyAndStartsOnNextUpdate) {
Scene* runtimeScene = CreateScene("RuntimeScene");
engine->OnRuntimeStart(runtimeScene);
runtime.Clear();
GameObject* spawned = runtimeScene->CreateGameObject("Spawned");
ScriptComponent* component = AddScriptComponent(spawned, "Gameplay", "RuntimeSpawned");
EXPECT_EQ(engine->GetTrackedScriptCount(), 1u);
EXPECT_TRUE(engine->HasTrackedScriptComponent(component));
EXPECT_TRUE(engine->HasRuntimeInstance(component));
const std::vector<std::string> expectedBeforeUpdate = {
"Create:Spawned:Gameplay.RuntimeSpawned",
"Awake:Spawned:Gameplay.RuntimeSpawned",
"OnEnable:Spawned:Gameplay.RuntimeSpawned"
};
EXPECT_EQ(runtime.events, expectedBeforeUpdate);
runtime.Clear();
engine->OnUpdate(0.016f);
const std::vector<std::string> expectedAfterUpdate = {
"Start:Spawned:Gameplay.RuntimeSpawned",
"Update:Spawned:Gameplay.RuntimeSpawned"
};
EXPECT_EQ(runtime.events, expectedAfterUpdate);
}
} // namespace