254 lines
7.9 KiB
C++
254 lines
7.9 KiB
C++
|
|
#include <gtest/gtest.h>
|
||
|
|
|
||
|
|
#include <XCEngine/Scene/SceneRuntime.h>
|
||
|
|
#include <XCEngine/Scripting/IScriptRuntime.h>
|
||
|
|
#include <XCEngine/Scripting/ScriptComponent.h>
|
||
|
|
#include <XCEngine/Scripting/ScriptEngine.h>
|
||
|
|
|
||
|
|
#include <memory>
|
||
|
|
#include <string>
|
||
|
|
#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 OrderedObserverComponent : public Component {
|
||
|
|
public:
|
||
|
|
explicit OrderedObserverComponent(std::vector<std::string>* events)
|
||
|
|
: m_events(events) {
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string GetName() const override { return "OrderedObserver"; }
|
||
|
|
|
||
|
|
void Start() override {
|
||
|
|
if (m_events) {
|
||
|
|
m_events->push_back("NativeStart:" + GetGameObject()->GetName());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void FixedUpdate() override {
|
||
|
|
if (m_events) {
|
||
|
|
m_events->push_back("NativeFixedUpdate:" + GetGameObject()->GetName());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void Update(float deltaTime) override {
|
||
|
|
(void)deltaTime;
|
||
|
|
if (m_events) {
|
||
|
|
m_events->push_back("NativeUpdate:" + GetGameObject()->GetName());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void LateUpdate(float deltaTime) override {
|
||
|
|
(void)deltaTime;
|
||
|
|
if (m_events) {
|
||
|
|
m_events->push_back("NativeLateUpdate:" + GetGameObject()->GetName());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
std::vector<std::string>* m_events = nullptr;
|
||
|
|
};
|
||
|
|
|
||
|
|
class RecordingScriptRuntime : public IScriptRuntime {
|
||
|
|
public:
|
||
|
|
explicit RecordingScriptRuntime(std::vector<std::string>* events)
|
||
|
|
: m_events(events) {
|
||
|
|
}
|
||
|
|
|
||
|
|
void OnRuntimeStart(Scene* scene) override {
|
||
|
|
if (m_events) {
|
||
|
|
m_events->push_back("RuntimeStart:" + (scene ? scene->GetName() : std::string("null")));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void OnRuntimeStop(Scene* scene) override {
|
||
|
|
if (m_events) {
|
||
|
|
m_events->push_back("RuntimeStop:" + (scene ? scene->GetName() : std::string("null")));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool CreateScriptInstance(const ScriptRuntimeContext& context) override {
|
||
|
|
if (m_events) {
|
||
|
|
m_events->push_back("Create:" + Describe(context));
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void DestroyScriptInstance(const ScriptRuntimeContext& context) override {
|
||
|
|
if (m_events) {
|
||
|
|
m_events->push_back("Destroy:" + Describe(context));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void InvokeMethod(
|
||
|
|
const ScriptRuntimeContext& context,
|
||
|
|
ScriptLifecycleMethod method,
|
||
|
|
float deltaTime) override {
|
||
|
|
(void)deltaTime;
|
||
|
|
if (m_events) {
|
||
|
|
m_events->push_back(LifecycleMethodToString(method) + ":" + Describe(context));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<std::string>* m_events = nullptr;
|
||
|
|
};
|
||
|
|
|
||
|
|
class SceneRuntimeTest : public ::testing::Test {
|
||
|
|
protected:
|
||
|
|
void SetUp() override {
|
||
|
|
scriptEngine = &ScriptEngine::Get();
|
||
|
|
scriptEngine->OnRuntimeStop();
|
||
|
|
runtimeImpl = std::make_unique<RecordingScriptRuntime>(&events);
|
||
|
|
scriptEngine->SetRuntime(runtimeImpl.get());
|
||
|
|
}
|
||
|
|
|
||
|
|
void TearDown() override {
|
||
|
|
runtime.Stop();
|
||
|
|
scriptEngine->OnRuntimeStop();
|
||
|
|
scriptEngine->SetRuntime(nullptr);
|
||
|
|
runtimeImpl.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;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<std::string> events;
|
||
|
|
std::unique_ptr<Scene> scene;
|
||
|
|
std::unique_ptr<RecordingScriptRuntime> runtimeImpl;
|
||
|
|
ScriptEngine* scriptEngine = nullptr;
|
||
|
|
SceneRuntime runtime;
|
||
|
|
};
|
||
|
|
|
||
|
|
TEST_F(SceneRuntimeTest, StartAndStopForwardToScriptEngine) {
|
||
|
|
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||
|
|
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||
|
|
AddScript(host, "Gameplay", "Bootstrap");
|
||
|
|
|
||
|
|
runtime.Start(runtimeScene);
|
||
|
|
runtime.Stop();
|
||
|
|
|
||
|
|
const std::vector<std::string> expected = {
|
||
|
|
"RuntimeStart:RuntimeScene",
|
||
|
|
"Create:Host:Gameplay.Bootstrap",
|
||
|
|
"Awake:Host:Gameplay.Bootstrap",
|
||
|
|
"OnEnable:Host:Gameplay.Bootstrap",
|
||
|
|
"OnDisable:Host:Gameplay.Bootstrap",
|
||
|
|
"OnDestroy:Host:Gameplay.Bootstrap",
|
||
|
|
"Destroy:Host:Gameplay.Bootstrap",
|
||
|
|
"RuntimeStop:RuntimeScene"
|
||
|
|
};
|
||
|
|
EXPECT_EQ(events, expected);
|
||
|
|
EXPECT_FALSE(runtime.IsRunning());
|
||
|
|
EXPECT_EQ(runtime.GetScene(), nullptr);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(SceneRuntimeTest, FrameOrderRunsScriptLifecycleBeforeNativeComponents) {
|
||
|
|
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||
|
|
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||
|
|
host->AddComponent<OrderedObserverComponent>(&events);
|
||
|
|
AddScript(host, "Gameplay", "Mover");
|
||
|
|
|
||
|
|
runtime.Start(runtimeScene);
|
||
|
|
events.clear();
|
||
|
|
|
||
|
|
runtime.FixedUpdate(0.02f);
|
||
|
|
runtime.Update(0.016f);
|
||
|
|
runtime.LateUpdate(0.016f);
|
||
|
|
|
||
|
|
const std::vector<std::string> expected = {
|
||
|
|
"FixedUpdate:Host:Gameplay.Mover",
|
||
|
|
"NativeFixedUpdate:Host",
|
||
|
|
"Start:Host:Gameplay.Mover",
|
||
|
|
"Update:Host:Gameplay.Mover",
|
||
|
|
"NativeStart:Host",
|
||
|
|
"NativeUpdate:Host",
|
||
|
|
"LateUpdate:Host:Gameplay.Mover",
|
||
|
|
"NativeLateUpdate:Host"
|
||
|
|
};
|
||
|
|
EXPECT_EQ(events, expected);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(SceneRuntimeTest, InactiveSceneSkipsFrameExecution) {
|
||
|
|
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||
|
|
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||
|
|
host->AddComponent<OrderedObserverComponent>(&events);
|
||
|
|
AddScript(host, "Gameplay", "PausedScript");
|
||
|
|
runtimeScene->SetActive(false);
|
||
|
|
|
||
|
|
runtime.Start(runtimeScene);
|
||
|
|
events.clear();
|
||
|
|
|
||
|
|
runtime.FixedUpdate(0.02f);
|
||
|
|
runtime.Update(0.016f);
|
||
|
|
runtime.LateUpdate(0.016f);
|
||
|
|
|
||
|
|
EXPECT_TRUE(events.empty());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(SceneRuntimeTest, StartingNewSceneStopsPreviousRuntimeFirst) {
|
||
|
|
Scene* firstScene = CreateScene("FirstScene");
|
||
|
|
GameObject* firstHost = firstScene->CreateGameObject("FirstHost");
|
||
|
|
AddScript(firstHost, "Gameplay", "FirstScript");
|
||
|
|
|
||
|
|
runtime.Start(firstScene);
|
||
|
|
|
||
|
|
std::unique_ptr<Scene> secondScene = std::make_unique<Scene>("SecondScene");
|
||
|
|
GameObject* secondHost = secondScene->CreateGameObject("SecondHost");
|
||
|
|
ScriptComponent* secondScript = AddScript(secondHost, "Gameplay", "SecondScript");
|
||
|
|
|
||
|
|
events.clear();
|
||
|
|
runtime.Start(secondScene.get());
|
||
|
|
|
||
|
|
const std::vector<std::string> expected = {
|
||
|
|
"OnDisable:FirstHost:Gameplay.FirstScript",
|
||
|
|
"OnDestroy:FirstHost:Gameplay.FirstScript",
|
||
|
|
"Destroy:FirstHost:Gameplay.FirstScript",
|
||
|
|
"RuntimeStop:FirstScene",
|
||
|
|
"RuntimeStart:SecondScene",
|
||
|
|
"Create:SecondHost:Gameplay.SecondScript",
|
||
|
|
"Awake:SecondHost:Gameplay.SecondScript",
|
||
|
|
"OnEnable:SecondHost:Gameplay.SecondScript"
|
||
|
|
};
|
||
|
|
EXPECT_EQ(events, expected);
|
||
|
|
EXPECT_EQ(runtime.GetScene(), secondScene.get());
|
||
|
|
EXPECT_TRUE(scriptEngine->HasRuntimeInstance(secondScript));
|
||
|
|
|
||
|
|
runtime.Stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|