diff --git a/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h b/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h index a7b0733b..8266c900 100644 --- a/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h +++ b/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h @@ -53,6 +53,8 @@ public: bool HasManagedInstance(const ScriptComponent* component) const; size_t GetManagedInstanceCount() const { return m_instances.size(); } + MonoObject* GetManagedInstanceObject(const ScriptComponent* component) const; + MonoObject* CreateManagedComponentWrapper(MonoClass* componentClass, uint64_t gameObjectUUID); bool TryGetFieldValue( const ScriptComponent* component, diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 8ee2b406..8e9582a4 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -9,6 +9,7 @@ #include "Debug/Logger.h" #include "Scene/Scene.h" #include "Scripting/ScriptComponent.h" +#include "Scripting/ScriptEngine.h" #include #include @@ -38,6 +39,7 @@ struct MonoRootState { enum class ManagedComponentKind { Unknown, + Script, Transform, Camera, Light, @@ -45,6 +47,14 @@ enum class ManagedComponentKind { MeshRenderer, }; +struct ManagedComponentTypeInfo { + ManagedComponentKind kind = ManagedComponentKind::Unknown; + MonoClass* monoClass = nullptr; + std::string assemblyName; + std::string namespaceName; + std::string className; +}; + MonoRootState& GetMonoRootState() { static MonoRootState state; return state; @@ -94,40 +104,62 @@ std::string MonoStringToUtf8(MonoString* stringObject) { return result; } -ManagedComponentKind ResolveManagedComponentKind(MonoReflectionType* reflectionType) { +MonoScriptRuntime* GetActiveMonoScriptRuntime() { + return dynamic_cast(ScriptEngine::Get().GetRuntime()); +} + +ManagedComponentTypeInfo ResolveManagedComponentTypeInfo(MonoReflectionType* reflectionType) { + ManagedComponentTypeInfo typeInfo; if (!reflectionType) { - return ManagedComponentKind::Unknown; + return typeInfo; } MonoType* monoType = mono_reflection_type_get_type(reflectionType); if (!monoType) { - return ManagedComponentKind::Unknown; + return typeInfo; } MonoClass* monoClass = mono_class_from_mono_type(monoType); if (!monoClass) { - return ManagedComponentKind::Unknown; + return typeInfo; } - const std::string namespaceName = SafeString(mono_class_get_namespace(monoClass)); - const std::string className = SafeString(mono_class_get_name(monoClass)); - if (namespaceName == "XCEngine" && className == "Transform") { - return ManagedComponentKind::Transform; + typeInfo.monoClass = monoClass; + typeInfo.namespaceName = SafeString(mono_class_get_namespace(monoClass)); + typeInfo.className = SafeString(mono_class_get_name(monoClass)); + + if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "Transform") { + typeInfo.kind = ManagedComponentKind::Transform; + return typeInfo; } - if (namespaceName == "XCEngine" && className == "Camera") { - return ManagedComponentKind::Camera; + if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "Camera") { + typeInfo.kind = ManagedComponentKind::Camera; + return typeInfo; } - if (namespaceName == "XCEngine" && className == "Light") { - return ManagedComponentKind::Light; + if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "Light") { + typeInfo.kind = ManagedComponentKind::Light; + return typeInfo; } - if (namespaceName == "XCEngine" && className == "MeshFilter") { - return ManagedComponentKind::MeshFilter; + if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "MeshFilter") { + typeInfo.kind = ManagedComponentKind::MeshFilter; + return typeInfo; } - if (namespaceName == "XCEngine" && className == "MeshRenderer") { - return ManagedComponentKind::MeshRenderer; + if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "MeshRenderer") { + typeInfo.kind = ManagedComponentKind::MeshRenderer; + return typeInfo; } - return ManagedComponentKind::Unknown; + MonoScriptRuntime* runtime = GetActiveMonoScriptRuntime(); + if (runtime + && runtime->IsClassAvailable( + runtime->GetSettings().appAssemblyName, + typeInfo.namespaceName, + typeInfo.className)) { + typeInfo.kind = ManagedComponentKind::Script; + typeInfo.assemblyName = runtime->GetSettings().appAssemblyName; + } + + return typeInfo; } Components::GameObject* FindGameObjectByUUIDRecursive(Components::GameObject* gameObject, uint64_t uuid) { @@ -214,6 +246,7 @@ bool HasNativeComponent(Components::GameObject* gameObject, ManagedComponentKind return gameObject->GetComponent() != nullptr; case ManagedComponentKind::MeshRenderer: return gameObject->GetComponent() != nullptr; + case ManagedComponentKind::Script: case ManagedComponentKind::Unknown: return false; } @@ -245,6 +278,7 @@ Components::Component* AddOrGetNativeComponent(Components::GameObject* gameObjec return gameObject->GetComponent() ? static_cast(gameObject->GetComponent()) : static_cast(gameObject->AddComponent()); + case ManagedComponentKind::Script: case ManagedComponentKind::Unknown: return nullptr; } @@ -252,6 +286,28 @@ Components::Component* AddOrGetNativeComponent(Components::GameObject* gameObjec return nullptr; } +ScriptComponent* FindMatchingScriptComponent( + Components::GameObject* gameObject, + const ManagedComponentTypeInfo& typeInfo) { + if (!gameObject || typeInfo.kind != ManagedComponentKind::Script) { + return nullptr; + } + + for (ScriptComponent* component : gameObject->GetComponents()) { + if (!component || !component->HasScriptClass()) { + continue; + } + + if (component->GetAssemblyName() == typeInfo.assemblyName + && component->GetNamespaceName() == typeInfo.namespaceName + && component->GetClassName() == typeInfo.className) { + return component; + } + } + + return nullptr; +} + Components::CameraComponent* FindCameraComponent(uint64_t gameObjectUUID) { Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID); return gameObject ? gameObject->GetComponent() : nullptr; @@ -337,21 +393,69 @@ void InternalCall_GameObject_SetActive(uint64_t gameObjectUUID, mono_bool active mono_bool InternalCall_GameObject_HasComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) { Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID); - return HasNativeComponent(gameObject, ResolveManagedComponentKind(componentType)) ? 1 : 0; -} - -uint64_t InternalCall_GameObject_GetComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) { - Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID); - if (!HasNativeComponent(gameObject, ResolveManagedComponentKind(componentType))) { - return 0; + const ManagedComponentTypeInfo typeInfo = ResolveManagedComponentTypeInfo(componentType); + if (typeInfo.kind == ManagedComponentKind::Script) { + return FindMatchingScriptComponent(gameObject, typeInfo) ? 1 : 0; } - return gameObjectUUID; + return HasNativeComponent(gameObject, typeInfo.kind) ? 1 : 0; } -uint64_t InternalCall_GameObject_AddComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) { +MonoObject* InternalCall_GameObject_GetComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) { Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID); - return AddOrGetNativeComponent(gameObject, ResolveManagedComponentKind(componentType)) ? gameObjectUUID : 0; + if (!gameObject) { + return nullptr; + } + + MonoScriptRuntime* runtime = GetActiveMonoScriptRuntime(); + if (!runtime) { + return nullptr; + } + + const ManagedComponentTypeInfo typeInfo = ResolveManagedComponentTypeInfo(componentType); + if (typeInfo.kind == ManagedComponentKind::Script) { + ScriptComponent* component = FindMatchingScriptComponent(gameObject, typeInfo); + if (!component) { + return nullptr; + } + + if (!runtime->HasManagedInstance(component)) { + ScriptEngine::Get().OnScriptComponentEnabled(component); + } + + return runtime->GetManagedInstanceObject(component); + } + + if (!HasNativeComponent(gameObject, typeInfo.kind) || !typeInfo.monoClass) { + return nullptr; + } + + return runtime->CreateManagedComponentWrapper(typeInfo.monoClass, gameObjectUUID); +} + +MonoObject* InternalCall_GameObject_AddComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) { + Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID); + if (!gameObject) { + return nullptr; + } + + MonoScriptRuntime* runtime = GetActiveMonoScriptRuntime(); + if (!runtime) { + return nullptr; + } + + const ManagedComponentTypeInfo typeInfo = ResolveManagedComponentTypeInfo(componentType); + if (typeInfo.kind == ManagedComponentKind::Script) { + ScriptComponent* component = gameObject->AddComponent(); + component->SetScriptClass(typeInfo.assemblyName, typeInfo.namespaceName, typeInfo.className); + return runtime->GetManagedInstanceObject(component); + } + + if (!AddOrGetNativeComponent(gameObject, typeInfo.kind) || !typeInfo.monoClass) { + return nullptr; + } + + return runtime->CreateManagedComponentWrapper(typeInfo.monoClass, gameObjectUUID); } uint64_t InternalCall_GameObject_Find(MonoString* name) { @@ -1199,6 +1303,11 @@ bool MonoScriptRuntime::HasManagedInstance(const ScriptComponent* component) con return instanceData != nullptr && GetManagedObject(*instanceData) != nullptr; } +MonoObject* MonoScriptRuntime::GetManagedInstanceObject(const ScriptComponent* component) const { + const InstanceData* instanceData = FindInstance(component); + return instanceData ? GetManagedObject(*instanceData) : nullptr; +} + bool MonoScriptRuntime::TryGetFieldValue( const ScriptComponent* component, const std::string& fieldName, @@ -1766,6 +1875,37 @@ bool MonoScriptRuntime::ApplyStoredFields( return true; } +MonoObject* MonoScriptRuntime::CreateManagedComponentWrapper(MonoClass* componentClass, uint64_t gameObjectUUID) { + if (!m_initialized || !componentClass || gameObjectUUID == 0) { + return nullptr; + } + + SetCurrentDomain(); + + MonoMethod* constructor = mono_class_get_method_from_name(componentClass, ".ctor", 1); + if (!constructor) { + return nullptr; + } + + MonoObject* managedObject = mono_object_new(m_appDomain, componentClass); + if (!managedObject) { + return nullptr; + } + + void* args[1]; + uint64_t uuidArgument = gameObjectUUID; + args[0] = &uuidArgument; + + MonoObject* exception = nullptr; + mono_runtime_invoke(constructor, managedObject, args, &exception); + if (exception) { + RecordException(exception); + return nullptr; + } + + return managedObject; +} + MonoObject* MonoScriptRuntime::CreateManagedGameObject(uint64_t gameObjectUUID) { if (gameObjectUUID == 0 || !m_gameObjectClass || !m_gameObjectConstructor) { return nullptr; diff --git a/engine/src/Scripting/ScriptEngine.cpp b/engine/src/Scripting/ScriptEngine.cpp index 6f26bd8e..eca772bb 100644 --- a/engine/src/Scripting/ScriptEngine.cpp +++ b/engine/src/Scripting/ScriptEngine.cpp @@ -37,7 +37,8 @@ void ScriptEngine::OnRuntimeStart(Components::Scene* scene) { CollectScriptComponents(root); } - for (const ScriptInstanceKey& key : m_scriptOrder) { + const std::vector startupKeys = m_scriptOrder; + for (const ScriptInstanceKey& key : startupKeys) { auto it = m_scriptStates.find(key); if (it == m_scriptStates.end()) { continue; @@ -87,7 +88,8 @@ void ScriptEngine::OnFixedUpdate(float fixedDeltaTime) { return; } - for (const ScriptInstanceKey& key : m_scriptOrder) { + const std::vector updateKeys = m_scriptOrder; + for (const ScriptInstanceKey& key : updateKeys) { auto it = m_scriptStates.find(key); if (it == m_scriptStates.end()) { continue; @@ -107,7 +109,8 @@ void ScriptEngine::OnUpdate(float deltaTime) { return; } - for (const ScriptInstanceKey& key : m_scriptOrder) { + const std::vector updateKeys = m_scriptOrder; + for (const ScriptInstanceKey& key : updateKeys) { auto it = m_scriptStates.find(key); if (it == m_scriptStates.end()) { continue; @@ -133,7 +136,8 @@ void ScriptEngine::OnLateUpdate(float deltaTime) { return; } - for (const ScriptInstanceKey& key : m_scriptOrder) { + const std::vector updateKeys = m_scriptOrder; + for (const ScriptInstanceKey& key : updateKeys) { auto it = m_scriptStates.find(key); if (it == m_scriptStates.end()) { continue; diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index 6dc7216e..9f52125e 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -72,6 +72,7 @@ set(XCENGINE_SCRIPT_CORE_SOURCES set(XCENGINE_GAME_SCRIPT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/BuiltinComponentProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/AddComponentProbe.cs + ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/ScriptComponentApiProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/RuntimeGameObjectProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/HierarchyProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/LifecycleProbe.cs diff --git a/managed/GameScripts/ScriptComponentApiProbe.cs b/managed/GameScripts/ScriptComponentApiProbe.cs new file mode 100644 index 00000000..8d4e7630 --- /dev/null +++ b/managed/GameScripts/ScriptComponentApiProbe.cs @@ -0,0 +1,74 @@ +using System; +using XCEngine; + +namespace Gameplay +{ + public sealed class ScriptComponentTargetProbe : MonoBehaviour + { + public int AwakeCount; + public int StartCount; + public int HostCallCount; + public string HostName = string.Empty; + + public void Awake() + { + AwakeCount++; + HostName = gameObject.Name; + } + + public void Start() + { + StartCount++; + } + + public void IncrementFromHost() + { + HostCallCount++; + } + } + + public sealed class ScriptComponentApiProbe : MonoBehaviour + { + public bool InitialHasTarget; + public bool InitialLookupReturnedNull; + public bool AddedTarget; + public bool HasTargetAfterAdd; + public bool LookupSucceededAfterAdd; + public bool TryGetSucceededAfterAdd; + public bool ReturnedSameInstance; + public bool TargetEnabledAfterAdd; + public int ObservedTargetAwakeCount; + public int ObservedTargetStartCount; + public int ObservedTargetHostCallCount; + public string ObservedTargetHostName = string.Empty; + + public void Start() + { + InitialHasTarget = HasComponent(); + InitialLookupReturnedNull = GetComponent() == null; + + ScriptComponentTargetProbe added = AddComponent(); + AddedTarget = added != null; + HasTargetAfterAdd = HasComponent(); + + ScriptComponentTargetProbe resolved = GetComponent(); + TryGetSucceededAfterAdd = TryGetComponent(out ScriptComponentTargetProbe tried); + LookupSucceededAfterAdd = resolved != null; + ReturnedSameInstance = added != null + && ReferenceEquals(added, resolved) + && ReferenceEquals(added, tried); + + if (added == null) + { + return; + } + + added.IncrementFromHost(); + TargetEnabledAfterAdd = added.enabled; + ObservedTargetAwakeCount = added.AwakeCount; + ObservedTargetStartCount = added.StartCount; + ObservedTargetHostCallCount = added.HostCallCount; + ObservedTargetHostName = added.HostName ?? string.Empty; + } + } +} \ No newline at end of file diff --git a/managed/XCEngine.ScriptCore/GameObject.cs b/managed/XCEngine.ScriptCore/GameObject.cs index b5d174d2..269baa54 100644 --- a/managed/XCEngine.ScriptCore/GameObject.cs +++ b/managed/XCEngine.ScriptCore/GameObject.cs @@ -64,14 +64,12 @@ namespace XCEngine public T GetComponent() where T : Component { - ulong componentOwnerUUID = InternalCalls.GameObject_GetComponent(UUID, typeof(T)); - return Component.Create(componentOwnerUUID); + return InternalCalls.GameObject_GetComponent(UUID, typeof(T)) as T; } public T AddComponent() where T : Component { - ulong componentOwnerUUID = InternalCalls.GameObject_AddComponent(UUID, typeof(T)); - return Component.Create(componentOwnerUUID); + return InternalCalls.GameObject_AddComponent(UUID, typeof(T)) as T; } public bool TryGetComponent(out T component) where T : Component diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index 43660cb0..9f44eab1 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -36,10 +36,10 @@ namespace XCEngine internal static extern bool GameObject_HasComponent(ulong gameObjectUUID, Type componentType); [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern ulong GameObject_GetComponent(ulong gameObjectUUID, Type componentType); + internal static extern Component GameObject_GetComponent(ulong gameObjectUUID, Type componentType); [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern ulong GameObject_AddComponent(ulong gameObjectUUID, Type componentType); + internal static extern Component GameObject_AddComponent(ulong gameObjectUUID, Type componentType); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern ulong GameObject_Find(string name); diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index cd9a2af2..af631a3b 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -28,6 +28,24 @@ void ExpectVector3Near(const XCEngine::Math::Vector3& actual, const XCEngine::Ma EXPECT_NEAR(actual.z, expected.z, tolerance); } +ScriptComponent* FindScriptComponentByClass(GameObject* gameObject, const std::string& namespaceName, const std::string& className) { + if (!gameObject) { + return nullptr; + } + + for (ScriptComponent* component : gameObject->GetComponents()) { + if (!component) { + continue; + } + + if (component->GetNamespaceName() == namespaceName && component->GetClassName() == className) { + return component; + } + } + + return nullptr; +} + MonoScriptRuntime::Settings CreateMonoSettings() { MonoScriptRuntime::Settings settings; settings.assemblyDirectory = XCENGINE_TEST_MANAGED_OUTPUT_DIR; @@ -562,6 +580,81 @@ TEST_F(MonoScriptRuntimeTest, GameObjectAddComponentApiCreatesBuiltinComponentsA EXPECT_EQ(meshRenderer->GetRenderLayer(), 6u); } +TEST_F(MonoScriptRuntimeTest, GameObjectComponentApiSupportsManagedScriptTypes) { + Scene* runtimeScene = CreateScene("MonoRuntimeScene"); + GameObject* host = runtimeScene->CreateGameObject("Host"); + ScriptComponent* component = AddScript(host, "Gameplay", "ScriptComponentApiProbe"); + + engine->OnRuntimeStart(runtimeScene); + engine->OnUpdate(0.016f); + + bool initialHasTarget = true; + bool initialLookupReturnedNull = false; + bool addedTarget = false; + bool hasTargetAfterAdd = false; + bool lookupSucceededAfterAdd = false; + bool tryGetSucceededAfterAdd = false; + bool returnedSameInstance = false; + bool targetEnabledAfterAdd = false; + int32_t observedTargetAwakeCount = 0; + int32_t observedTargetStartCount = -1; + int32_t observedTargetHostCallCount = 0; + std::string observedTargetHostName; + + EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasTarget", initialHasTarget)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialLookupReturnedNull", initialLookupReturnedNull)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedTarget", addedTarget)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasTargetAfterAdd", hasTargetAfterAdd)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "LookupSucceededAfterAdd", lookupSucceededAfterAdd)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "TryGetSucceededAfterAdd", tryGetSucceededAfterAdd)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ReturnedSameInstance", returnedSameInstance)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "TargetEnabledAfterAdd", targetEnabledAfterAdd)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedTargetAwakeCount", observedTargetAwakeCount)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedTargetStartCount", observedTargetStartCount)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedTargetHostCallCount", observedTargetHostCallCount)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedTargetHostName", observedTargetHostName)); + + EXPECT_FALSE(initialHasTarget); + EXPECT_TRUE(initialLookupReturnedNull); + EXPECT_TRUE(addedTarget); + EXPECT_TRUE(hasTargetAfterAdd); + EXPECT_TRUE(lookupSucceededAfterAdd); + EXPECT_TRUE(tryGetSucceededAfterAdd); + EXPECT_TRUE(returnedSameInstance); + EXPECT_TRUE(targetEnabledAfterAdd); + EXPECT_EQ(observedTargetAwakeCount, 1); + EXPECT_EQ(observedTargetStartCount, 0); + EXPECT_EQ(observedTargetHostCallCount, 1); + EXPECT_EQ(observedTargetHostName, "Host"); + + ASSERT_EQ(host->GetComponents().size(), 2u); + ScriptComponent* targetScript = FindScriptComponentByClass(host, "Gameplay", "ScriptComponentTargetProbe"); + ASSERT_NE(targetScript, nullptr); + EXPECT_TRUE(runtime->HasManagedInstance(targetScript)); + EXPECT_EQ(runtime->GetManagedInstanceCount(), 2u); + + int32_t awakeCount = 0; + int32_t startCount = -1; + int32_t hostCallCount = 0; + std::string hostName; + + EXPECT_TRUE(runtime->TryGetFieldValue(targetScript, "AwakeCount", awakeCount)); + EXPECT_TRUE(runtime->TryGetFieldValue(targetScript, "StartCount", startCount)); + EXPECT_TRUE(runtime->TryGetFieldValue(targetScript, "HostCallCount", hostCallCount)); + EXPECT_TRUE(runtime->TryGetFieldValue(targetScript, "HostName", hostName)); + + EXPECT_EQ(awakeCount, 1); + EXPECT_EQ(startCount, 0); + EXPECT_EQ(hostCallCount, 1); + EXPECT_EQ(hostName, "Host"); + + engine->OnUpdate(0.016f); + + startCount = 0; + EXPECT_TRUE(runtime->TryGetFieldValue(targetScript, "StartCount", startCount)); + EXPECT_EQ(startCount, 1); +} + TEST_F(MonoScriptRuntimeTest, GameObjectRuntimeApiCreatesFindsAndDestroysSceneObjects) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host");