Add script runtime lifecycle skeleton
This commit is contained in:
@@ -5,6 +5,7 @@ project(XCEngine_ScriptingTests)
|
||||
set(SCRIPTING_TEST_SOURCES
|
||||
test_script_field_storage.cpp
|
||||
test_script_component.cpp
|
||||
test_script_engine.cpp
|
||||
)
|
||||
|
||||
add_executable(scripting_tests ${SCRIPTING_TEST_SOURCES})
|
||||
|
||||
239
tests/scripting/test_script_engine.cpp
Normal file
239
tests/scripting/test_script_engine.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
#include <XCEngine/Scripting/IScriptRuntime.h>
|
||||
#include <XCEngine/Scripting/ScriptComponent.h>
|
||||
#include <XCEngine/Scripting/ScriptEngine.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace XCEngine::Components;
|
||||
using namespace XCEngine::Scripting;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string LifecycleMethodToString(ScriptLifecycleMethod method) {
|
||||
switch (method) {
|
||||
case ScriptLifecycleMethod::Awake: return "Awake";
|
||||
case ScriptLifecycleMethod::OnEnable: return "OnEnable";
|
||||
case ScriptLifecycleMethod::Start: return "Start";
|
||||
case ScriptLifecycleMethod::FixedUpdate: return "FixedUpdate";
|
||||
case ScriptLifecycleMethod::Update: return "Update";
|
||||
case ScriptLifecycleMethod::LateUpdate: return "LateUpdate";
|
||||
case ScriptLifecycleMethod::OnDisable: return "OnDisable";
|
||||
case ScriptLifecycleMethod::OnDestroy: return "OnDestroy";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
class FakeScriptRuntime : public IScriptRuntime {
|
||||
public:
|
||||
void OnRuntimeStart(Scene* scene) override {
|
||||
events.push_back("RuntimeStart:" + (scene ? scene->GetName() : std::string("null")));
|
||||
}
|
||||
|
||||
void OnRuntimeStop(Scene* scene) override {
|
||||
events.push_back("RuntimeStop:" + (scene ? scene->GetName() : std::string("null")));
|
||||
}
|
||||
|
||||
bool CreateScriptInstance(const ScriptRuntimeContext& context) override {
|
||||
events.push_back("Create:" + Describe(context));
|
||||
return context.component != nullptr;
|
||||
}
|
||||
|
||||
void DestroyScriptInstance(const ScriptRuntimeContext& context) override {
|
||||
events.push_back("Destroy:" + Describe(context));
|
||||
}
|
||||
|
||||
void InvokeMethod(
|
||||
const ScriptRuntimeContext& context,
|
||||
ScriptLifecycleMethod method,
|
||||
float deltaTime) override {
|
||||
(void)deltaTime;
|
||||
events.push_back(LifecycleMethodToString(method) + ":" + Describe(context));
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
events.clear();
|
||||
}
|
||||
|
||||
std::vector<std::string> events;
|
||||
|
||||
private:
|
||||
static std::string Describe(const ScriptRuntimeContext& context) {
|
||||
const std::string gameObjectName = context.gameObject ? context.gameObject->GetName() : "null";
|
||||
const std::string className = context.component ? context.component->GetFullClassName() : "null";
|
||||
return gameObjectName + ":" + className;
|
||||
}
|
||||
};
|
||||
|
||||
class ScriptEngineTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
engine = &ScriptEngine::Get();
|
||||
engine->OnRuntimeStop();
|
||||
engine->SetRuntime(&runtime);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
engine->OnRuntimeStop();
|
||||
engine->SetRuntime(nullptr);
|
||||
scene.reset();
|
||||
}
|
||||
|
||||
ScriptComponent* AddScriptComponent(GameObject* gameObject, const std::string& namespaceName, const std::string& className) {
|
||||
ScriptComponent* component = gameObject->AddComponent<ScriptComponent>();
|
||||
component->SetScriptClass("GameScripts", namespaceName, className);
|
||||
return component;
|
||||
}
|
||||
|
||||
Scene* CreateScene(const std::string& sceneName) {
|
||||
scene = std::make_unique<Scene>(sceneName);
|
||||
return scene.get();
|
||||
}
|
||||
|
||||
ScriptEngine* engine = nullptr;
|
||||
FakeScriptRuntime runtime;
|
||||
std::unique_ptr<Scene> scene;
|
||||
};
|
||||
|
||||
TEST_F(ScriptEngineTest, RuntimeStartTracksActiveScriptsAndInvokesAwakeThenOnEnable) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "PlayerController");
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
EXPECT_TRUE(engine->IsRuntimeRunning());
|
||||
EXPECT_EQ(engine->GetRuntimeScene(), runtimeScene);
|
||||
EXPECT_EQ(engine->GetTrackedScriptCount(), 1u);
|
||||
EXPECT_TRUE(engine->HasTrackedScriptComponent(component));
|
||||
EXPECT_TRUE(engine->HasRuntimeInstance(component));
|
||||
|
||||
const std::vector<std::string> expected = {
|
||||
"RuntimeStart:RuntimeScene",
|
||||
"Create:Host:Gameplay.PlayerController",
|
||||
"Awake:Host:Gameplay.PlayerController",
|
||||
"OnEnable:Host:Gameplay.PlayerController"
|
||||
};
|
||||
EXPECT_EQ(runtime.events, expected);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, UpdateInvokesStartOnlyOnceAndRunsPerFrameMethods) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
AddScriptComponent(host, "Gameplay", "Mover");
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
runtime.Clear();
|
||||
|
||||
engine->OnUpdate(0.016f);
|
||||
engine->OnFixedUpdate(0.02f);
|
||||
engine->OnUpdate(0.016f);
|
||||
engine->OnLateUpdate(0.016f);
|
||||
|
||||
const std::vector<std::string> expected = {
|
||||
"Start:Host:Gameplay.Mover",
|
||||
"Update:Host:Gameplay.Mover",
|
||||
"FixedUpdate:Host:Gameplay.Mover",
|
||||
"Update:Host:Gameplay.Mover",
|
||||
"LateUpdate:Host:Gameplay.Mover"
|
||||
};
|
||||
EXPECT_EQ(runtime.events, expected);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, InactiveObjectDefersCreationUntilActivated) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "SpawnedLater");
|
||||
host->SetActive(false);
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
ASSERT_EQ(engine->GetTrackedScriptCount(), 1u);
|
||||
EXPECT_TRUE(engine->HasTrackedScriptComponent(component));
|
||||
EXPECT_FALSE(engine->HasRuntimeInstance(component));
|
||||
ASSERT_EQ(runtime.events.size(), 1u);
|
||||
EXPECT_EQ(runtime.events[0], "RuntimeStart:RuntimeScene");
|
||||
|
||||
runtime.Clear();
|
||||
host->SetActive(true);
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
const std::vector<std::string> expected = {
|
||||
"Create:Host:Gameplay.SpawnedLater",
|
||||
"Awake:Host:Gameplay.SpawnedLater",
|
||||
"OnEnable:Host:Gameplay.SpawnedLater",
|
||||
"Start:Host:Gameplay.SpawnedLater",
|
||||
"Update:Host:Gameplay.SpawnedLater"
|
||||
};
|
||||
EXPECT_EQ(runtime.events, expected);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, RuntimeStopInvokesDisableDestroyAndRuntimeStop) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
AddScriptComponent(host, "Gameplay", "ShutdownWatcher");
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
runtime.Clear();
|
||||
|
||||
engine->OnRuntimeStop();
|
||||
|
||||
const std::vector<std::string> expected = {
|
||||
"OnDisable:Host:Gameplay.ShutdownWatcher",
|
||||
"OnDestroy:Host:Gameplay.ShutdownWatcher",
|
||||
"Destroy:Host:Gameplay.ShutdownWatcher",
|
||||
"RuntimeStop:RuntimeScene"
|
||||
};
|
||||
EXPECT_EQ(runtime.events, expected);
|
||||
EXPECT_FALSE(engine->IsRuntimeRunning());
|
||||
EXPECT_EQ(engine->GetRuntimeScene(), nullptr);
|
||||
EXPECT_EQ(engine->GetTrackedScriptCount(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, ReEnablingScriptDoesNotInvokeStartTwice) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "ToggleWatcher");
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
engine->OnUpdate(0.016f);
|
||||
runtime.Clear();
|
||||
|
||||
component->SetEnabled(false);
|
||||
component->SetEnabled(true);
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
const std::vector<std::string> expected = {
|
||||
"OnDisable:Host:Gameplay.ToggleWatcher",
|
||||
"OnEnable:Host:Gameplay.ToggleWatcher",
|
||||
"Update:Host:Gameplay.ToggleWatcher"
|
||||
};
|
||||
EXPECT_EQ(runtime.events, expected);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, DestroyingGameObjectWhileRuntimeRunningDestroysTrackedScript) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
AddScriptComponent(host, "Gameplay", "DestroyWatcher");
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
runtime.Clear();
|
||||
|
||||
runtimeScene->DestroyGameObject(host);
|
||||
|
||||
const std::vector<std::string> expected = {
|
||||
"OnDisable:Host:Gameplay.DestroyWatcher",
|
||||
"OnDestroy:Host:Gameplay.DestroyWatcher",
|
||||
"Destroy:Host:Gameplay.DestroyWatcher"
|
||||
};
|
||||
EXPECT_EQ(runtime.events, expected);
|
||||
EXPECT_EQ(engine->GetTrackedScriptCount(), 0u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user