Integrate XCUI runtime context into SceneRuntime

This commit is contained in:
2026-04-05 06:52:17 +08:00
parent edf434aa03
commit d46dcbfa9e
7 changed files with 323 additions and 2 deletions

View File

@@ -4,13 +4,22 @@
#include <XCEngine/Scripting/IScriptRuntime.h>
#include <XCEngine/Scripting/ScriptComponent.h>
#include <XCEngine/Scripting/ScriptEngine.h>
#include <XCEngine/UI/Runtime/UIScreenStackController.h>
#include <XCEngine/UI/Runtime/UIScreenTypes.h>
#include <XCEngine/UI/Runtime/UISystem.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <memory>
#include <string>
#include <vector>
using namespace XCEngine::Components;
using namespace XCEngine::Scripting;
using XCEngine::UI::Runtime::UIScreenAsset;
namespace fs = std::filesystem;
namespace {
@@ -67,6 +76,61 @@ private:
std::vector<std::string>* m_events = nullptr;
};
class TempFileScope {
public:
TempFileScope(std::string stem, std::string extension, std::string contents) {
const auto uniqueId = std::to_string(
std::chrono::steady_clock::now().time_since_epoch().count());
m_path = fs::temp_directory_path() / (std::move(stem) + "_" + uniqueId + std::move(extension));
std::ofstream output(m_path, std::ios::binary | std::ios::trunc);
output << contents;
}
~TempFileScope() {
std::error_code ec;
fs::remove(m_path, ec);
}
const fs::path& Path() const {
return m_path;
}
private:
fs::path m_path = {};
};
std::string BuildViewMarkup(const char* heroTitle) {
return
"<View name=\"Runtime Screen\">\n"
" <Column id=\"root\" padding=\"18\" gap=\"10\">\n"
" <Card id=\"hero\" title=\"" + std::string(heroTitle) + "\" subtitle=\"Shared XCUI runtime layer\" />\n"
" <Text id=\"status\" text=\"Ready for play\" />\n"
" </Column>\n"
"</View>\n";
}
UIScreenAsset BuildScreenAsset(const fs::path& viewPath, const char* screenId) {
UIScreenAsset screen = {};
screen.screenId = screenId;
screen.documentPath = viewPath.string();
return screen;
}
bool DrawDataContainsText(
const XCEngine::UI::UIDrawData& drawData,
const std::string& text) {
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == XCEngine::UI::UIDrawCommandType::Text &&
command.text == text) {
return true;
}
}
}
return false;
}
class RecordingScriptRuntime : public IScriptRuntime {
public:
explicit RecordingScriptRuntime(std::vector<std::string>* events)
@@ -304,4 +368,66 @@ TEST_F(SceneRuntimeTest, StartingNewSceneStopsPreviousRuntimeFirst) {
runtime.Stop();
}
TEST_F(SceneRuntimeTest, UpdateTicksUiRuntimeAndClearsQueuedInputEvents) {
Scene* runtimeScene = CreateScene("RuntimeScene");
runtime.Start(runtimeScene);
runtime.SetUIViewportRect(XCEngine::UI::UIRect(0.0f, 0.0f, 800.0f, 480.0f));
runtime.SetUIFocused(true);
TempFileScope menuView("xcui_scene_runtime_menu", ".xcui", BuildViewMarkup("Runtime Menu"));
const auto layerId = runtime.GetUIScreenStackController().PushMenu(
BuildScreenAsset(menuView.Path(), "runtime.menu"),
"menu");
ASSERT_NE(layerId, 0u);
XCEngine::UI::UIInputEvent textEvent = {};
textEvent.type = XCEngine::UI::UIInputEventType::Character;
textEvent.character = 'A';
runtime.QueueUIInputEvent(textEvent);
runtime.Update(0.016f);
const auto& firstFrame = runtime.GetLastUIFrame();
ASSERT_EQ(firstFrame.presentedLayerCount, 1u);
ASSERT_EQ(firstFrame.layers.size(), 1u);
EXPECT_EQ(firstFrame.frameIndex, 1u);
EXPECT_EQ(firstFrame.layers.front().stats.inputEventCount, 1u);
EXPECT_TRUE(DrawDataContainsText(firstFrame.drawData, "Runtime Menu"));
runtime.Update(0.016f);
const auto& secondFrame = runtime.GetLastUIFrame();
ASSERT_EQ(secondFrame.presentedLayerCount, 1u);
ASSERT_EQ(secondFrame.layers.size(), 1u);
EXPECT_EQ(secondFrame.frameIndex, 2u);
EXPECT_EQ(secondFrame.layers.front().stats.inputEventCount, 0u);
}
TEST_F(SceneRuntimeTest, StopClearsUiRuntimeState) {
Scene* runtimeScene = CreateScene("RuntimeScene");
runtime.Start(runtimeScene);
runtime.SetUIViewportRect(XCEngine::UI::UIRect(0.0f, 0.0f, 800.0f, 480.0f));
runtime.SetUIFocused(true);
TempFileScope menuView("xcui_scene_runtime_pause", ".xcui", BuildViewMarkup("Pause Menu"));
const auto layerId = runtime.GetUIScreenStackController().PushMenu(
BuildScreenAsset(menuView.Path(), "runtime.pause"),
"pause");
ASSERT_NE(layerId, 0u);
runtime.Update(0.016f);
ASSERT_EQ(runtime.GetUISystem().GetLayerCount(), 1u);
ASSERT_EQ(runtime.GetUIScreenStackController().GetEntryCount(), 1u);
ASSERT_EQ(runtime.GetLastUIFrame().presentedLayerCount, 1u);
runtime.Stop();
EXPECT_FALSE(runtime.IsRunning());
EXPECT_EQ(runtime.GetScene(), nullptr);
EXPECT_EQ(runtime.GetUISystem().GetLayerCount(), 0u);
EXPECT_EQ(runtime.GetUIScreenStackController().GetEntryCount(), 0u);
EXPECT_EQ(runtime.GetLastUIFrame().presentedLayerCount, 0u);
EXPECT_TRUE(runtime.GetLastUIFrame().layers.empty());
}
} // namespace