tests: remove legacy test tree
This commit is contained in:
@@ -1,107 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
project(XCEngine_ScriptingTests)
|
||||
|
||||
function(xcengine_attach_recursive_build_target target dependency_target)
|
||||
add_custom_command(TARGET ${target} PRE_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}
|
||||
--config $<CONFIG>
|
||||
--target ${dependency_target}
|
||||
--
|
||||
/m:1
|
||||
/p:BuildProjectReferences=false
|
||||
VERBATIM
|
||||
COMMENT "Building ${dependency_target} before ${target}")
|
||||
endfunction()
|
||||
|
||||
set(SCRIPTING_TEST_SOURCES
|
||||
test_script_field_storage.cpp
|
||||
test_script_component.cpp
|
||||
test_script_engine.cpp
|
||||
)
|
||||
|
||||
if(XCENGINE_ENABLE_MONO_SCRIPTING)
|
||||
list(APPEND SCRIPTING_TEST_SOURCES
|
||||
test_mono_script_runtime.cpp
|
||||
)
|
||||
|
||||
if(TARGET xcengine_test_project_managed_assemblies)
|
||||
list(APPEND SCRIPTING_TEST_SOURCES
|
||||
test_project_script_assembly.cpp
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_executable(scripting_tests ${SCRIPTING_TEST_SOURCES})
|
||||
|
||||
if(MSVC)
|
||||
set_target_properties(scripting_tests PROPERTIES
|
||||
LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(scripting_tests PRIVATE
|
||||
XCEngine
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
target_include_directories(scripting_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
)
|
||||
|
||||
if(WIN32 AND XCENGINE_ENABLE_PHYSX)
|
||||
xcengine_copy_physx_runtime_dlls(scripting_tests)
|
||||
endif()
|
||||
|
||||
if(TARGET xcengine_managed_assemblies)
|
||||
add_dependencies(scripting_tests xcengine_managed_assemblies)
|
||||
xcengine_attach_recursive_build_target(scripting_tests xcengine_managed_assemblies)
|
||||
|
||||
file(TO_CMAKE_PATH "${XCENGINE_MANAGED_OUTPUT_DIR}" XCENGINE_MANAGED_OUTPUT_DIR_CMAKE)
|
||||
file(TO_CMAKE_PATH "${XCENGINE_SCRIPT_CORE_DLL}" XCENGINE_SCRIPT_CORE_DLL_CMAKE)
|
||||
file(TO_CMAKE_PATH "${XCENGINE_GAME_SCRIPTS_DLL}" XCENGINE_GAME_SCRIPTS_DLL_CMAKE)
|
||||
|
||||
target_compile_definitions(scripting_tests PRIVATE
|
||||
XCENGINE_TEST_MANAGED_OUTPUT_DIR=\"${XCENGINE_MANAGED_OUTPUT_DIR_CMAKE}\"
|
||||
XCENGINE_TEST_SCRIPT_CORE_DLL=\"${XCENGINE_SCRIPT_CORE_DLL_CMAKE}\"
|
||||
XCENGINE_TEST_GAME_SCRIPTS_DLL=\"${XCENGINE_GAME_SCRIPTS_DLL_CMAKE}\"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(TARGET xcengine_test_project_managed_assemblies)
|
||||
add_dependencies(scripting_tests xcengine_test_project_managed_assemblies)
|
||||
xcengine_attach_recursive_build_target(
|
||||
scripting_tests
|
||||
xcengine_test_project_managed_assemblies)
|
||||
|
||||
file(
|
||||
TO_CMAKE_PATH
|
||||
"${XCENGINE_SCRIPTING_TEST_PROJECT_MANAGED_OUTPUT_DIR}"
|
||||
XCENGINE_PROJECT_MANAGED_OUTPUT_DIR_CMAKE)
|
||||
file(
|
||||
TO_CMAKE_PATH
|
||||
"${XCENGINE_SCRIPTING_TEST_PROJECT_SCRIPT_CORE_DLL}"
|
||||
XCENGINE_PROJECT_SCRIPT_CORE_DLL_CMAKE)
|
||||
file(
|
||||
TO_CMAKE_PATH
|
||||
"${XCENGINE_SCRIPTING_TEST_PROJECT_GAME_SCRIPTS_DLL}"
|
||||
XCENGINE_PROJECT_GAME_SCRIPTS_DLL_CMAKE)
|
||||
|
||||
target_compile_definitions(scripting_tests PRIVATE
|
||||
XCENGINE_TEST_PROJECT_MANAGED_OUTPUT_DIR=\"${XCENGINE_PROJECT_MANAGED_OUTPUT_DIR_CMAKE}\"
|
||||
XCENGINE_TEST_PROJECT_SCRIPT_CORE_DLL=\"${XCENGINE_PROJECT_SCRIPT_CORE_DLL_CMAKE}\"
|
||||
XCENGINE_TEST_PROJECT_GAME_SCRIPTS_DLL=\"${XCENGINE_PROJECT_GAME_SCRIPTS_DLL_CMAKE}\"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32 AND EXISTS "${CMAKE_SOURCE_DIR}/engine/third_party/assimp/bin/assimp-vc143-mt.dll")
|
||||
add_custom_command(TARGET scripting_tests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_SOURCE_DIR}/engine/third_party/assimp/bin/assimp-vc143-mt.dll
|
||||
$<TARGET_FILE_DIR:scripting_tests>/assimp-vc143-mt.dll
|
||||
)
|
||||
endif()
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(scripting_tests)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,746 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Input/InputManager.h>
|
||||
#include <XCEngine/Rendering/Execution/CameraFrameRenderGraphFrameData.h>
|
||||
#include <XCEngine/Rendering/Graph/RenderGraph.h>
|
||||
#include <XCEngine/Rendering/Graph/RenderGraphCompiler.h>
|
||||
#include <XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
#include <XCEngine/Scripting/Mono/MonoScriptRuntime.h>
|
||||
#include <XCEngine/Scripting/ScriptComponent.h>
|
||||
#include <XCEngine/Scripting/ScriptEngine.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
using namespace XCEngine::Components;
|
||||
using namespace XCEngine::Scripting;
|
||||
|
||||
namespace {
|
||||
|
||||
std::filesystem::path GetExecutableDirectory() {
|
||||
#ifdef _WIN32
|
||||
std::wstring buffer(MAX_PATH, L'\0');
|
||||
const DWORD length = GetModuleFileNameW(nullptr, buffer.data(), static_cast<DWORD>(buffer.size()));
|
||||
if (length == 0 || length >= buffer.size()) {
|
||||
return std::filesystem::current_path();
|
||||
}
|
||||
|
||||
buffer.resize(length);
|
||||
return std::filesystem::path(buffer).parent_path();
|
||||
#else
|
||||
return std::filesystem::current_path();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveProjectManagedOutputDirectory() {
|
||||
constexpr const char* configuredDirectory = XCENGINE_TEST_PROJECT_MANAGED_OUTPUT_DIR;
|
||||
if (configuredDirectory[0] != '\0') {
|
||||
return std::filesystem::path(configuredDirectory);
|
||||
}
|
||||
|
||||
return (GetExecutableDirectory() / ".." / ".." / "managed" / "ProjectScriptAssemblies").lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveProjectScriptCoreDllPath() {
|
||||
constexpr const char* configuredPath = XCENGINE_TEST_PROJECT_SCRIPT_CORE_DLL;
|
||||
if (configuredPath[0] != '\0') {
|
||||
return std::filesystem::path(configuredPath);
|
||||
}
|
||||
|
||||
return ResolveProjectManagedOutputDirectory() / "XCEngine.ScriptCore.dll";
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveProjectGameScriptsDllPath() {
|
||||
constexpr const char* configuredPath = XCENGINE_TEST_PROJECT_GAME_SCRIPTS_DLL;
|
||||
if (configuredPath[0] != '\0') {
|
||||
return std::filesystem::path(configuredPath);
|
||||
}
|
||||
|
||||
return ResolveProjectManagedOutputDirectory() / "GameScripts.dll";
|
||||
}
|
||||
|
||||
MonoScriptRuntime::Settings CreateProjectMonoSettings() {
|
||||
MonoScriptRuntime::Settings settings;
|
||||
settings.assemblyDirectory = ResolveProjectManagedOutputDirectory();
|
||||
settings.corlibDirectory = settings.assemblyDirectory;
|
||||
settings.coreAssemblyPath = ResolveProjectScriptCoreDllPath();
|
||||
settings.appAssemblyPath = ResolveProjectGameScriptsDllPath();
|
||||
return settings;
|
||||
}
|
||||
|
||||
class ProjectScriptAssemblyTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
engine = &ScriptEngine::Get();
|
||||
engine->OnRuntimeStop();
|
||||
engine->SetRuntimeFixedDeltaTime(
|
||||
ScriptEngine::DefaultFixedDeltaTime);
|
||||
XCEngine::Input::InputManager::Get().Shutdown();
|
||||
XCEngine::Resources::ResourceManager::Get().Shutdown();
|
||||
XCEngine::Rendering::Pipelines::
|
||||
ClearConfiguredManagedRenderPipelineAssetDescriptor();
|
||||
|
||||
ASSERT_TRUE(std::filesystem::exists(ResolveProjectScriptCoreDllPath()));
|
||||
ASSERT_TRUE(std::filesystem::exists(ResolveProjectGameScriptsDllPath()));
|
||||
|
||||
MonoScriptRuntime::Settings settings =
|
||||
CreateProjectMonoSettings();
|
||||
std::string engineAssemblyError;
|
||||
ASSERT_TRUE(
|
||||
MonoScriptRuntime::DiscoverEngineAssemblies(
|
||||
settings,
|
||||
&engineAssemblyError))
|
||||
<< engineAssemblyError;
|
||||
ASSERT_FALSE(settings.engineAssemblies.empty());
|
||||
for (const MonoScriptRuntime::ManagedAssemblyDescriptor& assembly :
|
||||
settings.engineAssemblies) {
|
||||
ASSERT_TRUE(std::filesystem::exists(assembly.path))
|
||||
<< assembly.path.string();
|
||||
}
|
||||
|
||||
runtime = std::make_unique<MonoScriptRuntime>(std::move(settings));
|
||||
ASSERT_TRUE(runtime->Initialize()) << runtime->GetLastError();
|
||||
engine->SetRuntime(runtime.get());
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
if (engine != nullptr) {
|
||||
engine->OnRuntimeStop();
|
||||
engine->SetRuntime(nullptr);
|
||||
}
|
||||
|
||||
XCEngine::Input::InputManager::Get().Shutdown();
|
||||
XCEngine::Rendering::Pipelines::
|
||||
ClearConfiguredManagedRenderPipelineAssetDescriptor();
|
||||
runtime.reset();
|
||||
scene.reset();
|
||||
XCEngine::Resources::ResourceManager::Get().Shutdown();
|
||||
}
|
||||
|
||||
Scene* CreateScene(const std::string& sceneName) {
|
||||
scene = std::make_unique<Scene>(sceneName);
|
||||
return scene.get();
|
||||
}
|
||||
|
||||
ScriptComponent* AddProjectScript(
|
||||
GameObject* gameObject,
|
||||
const std::string& className) {
|
||||
ScriptComponent* component =
|
||||
gameObject->AddComponent<ScriptComponent>();
|
||||
component->SetScriptClass(
|
||||
"GameScripts",
|
||||
"ProjectScripts",
|
||||
className);
|
||||
return component;
|
||||
}
|
||||
|
||||
ScriptEngine* engine = nullptr;
|
||||
std::unique_ptr<MonoScriptRuntime> runtime;
|
||||
std::unique_ptr<Scene> scene;
|
||||
};
|
||||
|
||||
TEST_F(ProjectScriptAssemblyTest, InitializesFromProjectScriptAssemblyDirectory) {
|
||||
EXPECT_TRUE(runtime->IsInitialized());
|
||||
EXPECT_EQ(runtime->GetSettings().assemblyDirectory, ResolveProjectManagedOutputDirectory());
|
||||
EXPECT_EQ(runtime->GetSettings().appAssemblyPath, ResolveProjectGameScriptsDllPath());
|
||||
}
|
||||
|
||||
TEST_F(ProjectScriptAssemblyTest, DiscoversProjectAssetMonoBehaviourClassesAndFieldMetadata) {
|
||||
const std::vector<std::string> classNames = runtime->GetScriptClassNames("GameScripts");
|
||||
std::vector<ScriptClassDescriptor> classDescriptors;
|
||||
ASSERT_TRUE(runtime->TryGetAvailableScriptClasses(classDescriptors));
|
||||
|
||||
EXPECT_TRUE(runtime->IsClassAvailable("GameScripts", "ProjectScripts", "ProjectScriptProbe"));
|
||||
EXPECT_NE(
|
||||
std::find(classNames.begin(), classNames.end(), "ProjectScripts.ProjectScriptProbe"),
|
||||
classNames.end());
|
||||
EXPECT_NE(
|
||||
std::find(
|
||||
classDescriptors.begin(),
|
||||
classDescriptors.end(),
|
||||
ScriptClassDescriptor{"GameScripts", "ProjectScripts", "ProjectScriptProbe"}),
|
||||
classDescriptors.end());
|
||||
|
||||
std::vector<ScriptFieldMetadata> fields;
|
||||
ASSERT_TRUE(runtime->TryGetClassFieldMetadata("GameScripts", "ProjectScripts", "ProjectScriptProbe", fields));
|
||||
|
||||
const std::vector<ScriptFieldMetadata> expectedFields = {
|
||||
{"EnabledOnBoot", ScriptFieldType::Bool},
|
||||
{"Label", ScriptFieldType::String},
|
||||
{"Speed", ScriptFieldType::Float},
|
||||
};
|
||||
|
||||
EXPECT_EQ(fields, expectedFields);
|
||||
|
||||
std::vector<ScriptFieldDefaultValue> defaultValues;
|
||||
ASSERT_TRUE(runtime->TryGetClassFieldDefaultValues("GameScripts", "ProjectScripts", "ProjectScriptProbe", defaultValues));
|
||||
ASSERT_EQ(defaultValues.size(), 3u);
|
||||
|
||||
EXPECT_EQ(defaultValues[0].fieldName, "EnabledOnBoot");
|
||||
EXPECT_EQ(defaultValues[0].type, ScriptFieldType::Bool);
|
||||
EXPECT_EQ(std::get<bool>(defaultValues[0].value), true);
|
||||
|
||||
EXPECT_EQ(defaultValues[1].fieldName, "Label");
|
||||
EXPECT_EQ(defaultValues[1].type, ScriptFieldType::String);
|
||||
EXPECT_EQ(std::get<std::string>(defaultValues[1].value), "ProjectScriptProbe");
|
||||
|
||||
EXPECT_EQ(defaultValues[2].fieldName, "Speed");
|
||||
EXPECT_EQ(defaultValues[2].type, ScriptFieldType::Float);
|
||||
EXPECT_FLOAT_EQ(std::get<float>(defaultValues[2].value), 2.5f);
|
||||
}
|
||||
|
||||
TEST_F(ProjectScriptAssemblyTest, DiscoversProjectAssetRenderPipelineAssetClasses) {
|
||||
std::vector<ScriptClassDescriptor> classes;
|
||||
ASSERT_TRUE(runtime->TryGetAvailableRenderPipelineAssetClasses(classes));
|
||||
|
||||
EXPECT_NE(
|
||||
std::find(
|
||||
classes.begin(),
|
||||
classes.end(),
|
||||
ScriptClassDescriptor{
|
||||
"GameScripts",
|
||||
"ProjectScripts",
|
||||
"ProjectUniversalFeaturePipelineAsset"}),
|
||||
classes.end());
|
||||
EXPECT_NE(
|
||||
std::find(
|
||||
classes.begin(),
|
||||
classes.end(),
|
||||
ScriptClassDescriptor{
|
||||
"GameScripts",
|
||||
"ProjectScripts",
|
||||
"ProjectCustomRendererPipelineAsset"}),
|
||||
classes.end());
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ProjectScriptAssemblyTest,
|
||||
CreatesProjectAssetUniversalFeatureRuntimeThroughManagedBridge) {
|
||||
const auto bridge =
|
||||
XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge();
|
||||
ASSERT_NE(bridge, nullptr);
|
||||
|
||||
const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = {
|
||||
"GameScripts",
|
||||
"ProjectScripts",
|
||||
"ProjectUniversalFeaturePipelineAsset"
|
||||
};
|
||||
|
||||
std::shared_ptr<const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetRuntime>
|
||||
assetRuntime = bridge->CreateAssetRuntime(descriptor);
|
||||
ASSERT_NE(assetRuntime, nullptr);
|
||||
EXPECT_NE(assetRuntime->GetPipelineRendererAsset(), nullptr);
|
||||
|
||||
std::unique_ptr<XCEngine::Rendering::RenderPipelineStageRecorder> recorder =
|
||||
assetRuntime->CreateStageRecorder();
|
||||
ASSERT_NE(recorder, nullptr);
|
||||
|
||||
const XCEngine::Rendering::RenderContext renderContext = {};
|
||||
ASSERT_TRUE(recorder->Initialize(renderContext));
|
||||
EXPECT_TRUE(
|
||||
recorder->SupportsStageRenderGraph(
|
||||
XCEngine::Rendering::CameraFrameStage::MainScene));
|
||||
EXPECT_TRUE(
|
||||
recorder->SupportsStageRenderGraph(
|
||||
XCEngine::Rendering::CameraFrameStage::PostProcess));
|
||||
|
||||
XCEngine::Rendering::RenderGraph graph;
|
||||
XCEngine::Rendering::RenderGraphBuilder graphBuilder(graph);
|
||||
XCEngine::Rendering::RenderGraphTextureDesc colorDesc = {};
|
||||
colorDesc.width = 64u;
|
||||
colorDesc.height = 64u;
|
||||
colorDesc.format =
|
||||
static_cast<XCEngine::Core::uint32>(
|
||||
XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
const XCEngine::Rendering::RenderGraphTextureHandle sourceColor =
|
||||
graphBuilder.ImportTexture(
|
||||
"ProjectManagedPostProcessSource",
|
||||
colorDesc,
|
||||
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(901),
|
||||
{});
|
||||
const XCEngine::Rendering::RenderGraphTextureHandle outputColor =
|
||||
graphBuilder.CreateTransientTexture(
|
||||
"ProjectManagedPostProcessOutput",
|
||||
colorDesc);
|
||||
|
||||
const XCEngine::Rendering::RenderSceneData sceneData = {};
|
||||
const XCEngine::Rendering::RenderSurface surface(64u, 64u);
|
||||
bool executionSucceeded = true;
|
||||
XCEngine::Rendering::RenderGraphBlackboard blackboard = {};
|
||||
XCEngine::Rendering::EmplaceCameraFrameRenderGraphFrameData(blackboard)
|
||||
.resources.mainScene.color = sourceColor;
|
||||
const XCEngine::Rendering::RenderPipelineStageRenderGraphContext graphContext = {
|
||||
graphBuilder,
|
||||
"ProjectManagedPostProcess",
|
||||
XCEngine::Rendering::CameraFrameStage::PostProcess,
|
||||
renderContext,
|
||||
sceneData,
|
||||
surface,
|
||||
nullptr,
|
||||
nullptr,
|
||||
XCEngine::RHI::ResourceStates::Common,
|
||||
{},
|
||||
{ outputColor },
|
||||
{},
|
||||
{},
|
||||
&executionSucceeded,
|
||||
&blackboard
|
||||
};
|
||||
|
||||
EXPECT_TRUE(recorder->RecordStageRenderGraph(graphContext));
|
||||
|
||||
XCEngine::Rendering::CompiledRenderGraph compiledGraph = {};
|
||||
XCEngine::Containers::String errorMessage;
|
||||
ASSERT_TRUE(
|
||||
XCEngine::Rendering::RenderGraphCompiler::Compile(
|
||||
graph,
|
||||
compiledGraph,
|
||||
&errorMessage))
|
||||
<< errorMessage.CStr();
|
||||
ASSERT_EQ(compiledGraph.GetPassCount(), 1u);
|
||||
EXPECT_STREQ(
|
||||
compiledGraph.GetPassName(0).CStr(),
|
||||
"ProjectManagedPostProcess");
|
||||
|
||||
recorder->Shutdown();
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ProjectScriptAssemblyTest,
|
||||
CreatesProjectCustomRendererPipelineAssetThroughManagedBridge) {
|
||||
const auto bridge =
|
||||
XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge();
|
||||
ASSERT_NE(bridge, nullptr);
|
||||
|
||||
const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = {
|
||||
"GameScripts",
|
||||
"ProjectScripts",
|
||||
"ProjectCustomRendererPipelineAsset"
|
||||
};
|
||||
|
||||
std::shared_ptr<const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetRuntime>
|
||||
assetRuntime = bridge->CreateAssetRuntime(descriptor);
|
||||
ASSERT_NE(assetRuntime, nullptr);
|
||||
EXPECT_NE(assetRuntime->GetPipelineRendererAsset(), nullptr);
|
||||
|
||||
std::unique_ptr<XCEngine::Rendering::RenderPipelineStageRecorder> recorder =
|
||||
assetRuntime->CreateStageRecorder();
|
||||
ASSERT_NE(recorder, nullptr);
|
||||
|
||||
const XCEngine::Rendering::RenderContext renderContext = {};
|
||||
ASSERT_TRUE(recorder->Initialize(renderContext));
|
||||
EXPECT_TRUE(
|
||||
recorder->SupportsStageRenderGraph(
|
||||
XCEngine::Rendering::CameraFrameStage::MainScene));
|
||||
EXPECT_FALSE(
|
||||
recorder->SupportsStageRenderGraph(
|
||||
XCEngine::Rendering::CameraFrameStage::PostProcess));
|
||||
|
||||
XCEngine::Rendering::RenderGraph graph;
|
||||
XCEngine::Rendering::RenderGraphBuilder graphBuilder(graph);
|
||||
XCEngine::Rendering::RenderGraphTextureDesc colorDesc = {};
|
||||
colorDesc.width = 64u;
|
||||
colorDesc.height = 64u;
|
||||
colorDesc.format =
|
||||
static_cast<XCEngine::Core::uint32>(
|
||||
XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
XCEngine::Rendering::RenderGraphTextureDesc depthDesc = colorDesc;
|
||||
depthDesc.format =
|
||||
static_cast<XCEngine::Core::uint32>(
|
||||
XCEngine::RHI::Format::D32_Float);
|
||||
const XCEngine::Rendering::RenderGraphTextureHandle colorTarget =
|
||||
graphBuilder.CreateTransientTexture(
|
||||
"ProjectCustomMainSceneColor",
|
||||
colorDesc);
|
||||
const XCEngine::Rendering::RenderGraphTextureHandle depthTarget =
|
||||
graphBuilder.CreateTransientTexture(
|
||||
"ProjectCustomMainSceneDepth",
|
||||
depthDesc);
|
||||
|
||||
const XCEngine::Rendering::RenderSceneData sceneData = {};
|
||||
const XCEngine::Rendering::RenderSurface surface(64u, 64u);
|
||||
bool executionSucceeded = true;
|
||||
XCEngine::Rendering::RenderGraphBlackboard blackboard = {};
|
||||
const XCEngine::Rendering::RenderPipelineStageRenderGraphContext graphContext = {
|
||||
graphBuilder,
|
||||
"ProjectCustomMainScene",
|
||||
XCEngine::Rendering::CameraFrameStage::MainScene,
|
||||
renderContext,
|
||||
sceneData,
|
||||
surface,
|
||||
nullptr,
|
||||
nullptr,
|
||||
XCEngine::RHI::ResourceStates::Common,
|
||||
{},
|
||||
{ colorTarget },
|
||||
depthTarget,
|
||||
{},
|
||||
&executionSucceeded,
|
||||
&blackboard
|
||||
};
|
||||
|
||||
EXPECT_TRUE(recorder->RecordStageRenderGraph(graphContext));
|
||||
|
||||
XCEngine::Rendering::CompiledRenderGraph compiledGraph = {};
|
||||
XCEngine::Containers::String errorMessage;
|
||||
ASSERT_TRUE(
|
||||
XCEngine::Rendering::RenderGraphCompiler::Compile(
|
||||
graph,
|
||||
compiledGraph,
|
||||
&errorMessage))
|
||||
<< errorMessage.CStr();
|
||||
ASSERT_EQ(compiledGraph.GetPassCount(), 1u);
|
||||
EXPECT_STREQ(
|
||||
compiledGraph.GetPassName(0).CStr(),
|
||||
"ProjectCustomMainScene.Opaque");
|
||||
|
||||
recorder->Shutdown();
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ProjectScriptAssemblyTest,
|
||||
ProjectManagedBridgeRebuildsRendererAfterProjectRendererDataInvalidation) {
|
||||
Scene* runtimeScene =
|
||||
CreateScene("ProjectRendererInvalidationScene");
|
||||
GameObject* selectionObject =
|
||||
runtimeScene->CreateGameObject(
|
||||
"ProjectRendererInvalidationSelection");
|
||||
ScriptComponent* selectionScript =
|
||||
AddProjectScript(
|
||||
selectionObject,
|
||||
"ProjectRendererInvalidationRuntimeSelectionProbe");
|
||||
ASSERT_NE(selectionScript, nullptr);
|
||||
GameObject* observationObject =
|
||||
runtimeScene->CreateGameObject(
|
||||
"ProjectRendererInvalidationObservation");
|
||||
ScriptComponent* observationScript =
|
||||
AddProjectScript(
|
||||
observationObject,
|
||||
"ProjectRendererInvalidationObservationProbe");
|
||||
ASSERT_NE(observationScript, nullptr);
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor
|
||||
descriptor =
|
||||
XCEngine::Rendering::Pipelines::
|
||||
GetConfiguredManagedRenderPipelineAssetDescriptor();
|
||||
ASSERT_TRUE(descriptor.IsValid());
|
||||
ASSERT_NE(descriptor.managedAssetHandle, 0u);
|
||||
EXPECT_EQ(descriptor.assemblyName, "GameScripts");
|
||||
EXPECT_EQ(descriptor.namespaceName, "ProjectScripts");
|
||||
EXPECT_EQ(
|
||||
descriptor.className,
|
||||
"ProjectRendererInvalidationProbeAsset");
|
||||
|
||||
const auto bridge =
|
||||
XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge();
|
||||
ASSERT_NE(bridge, nullptr);
|
||||
|
||||
std::shared_ptr<const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetRuntime>
|
||||
assetRuntime = bridge->CreateAssetRuntime(descriptor);
|
||||
ASSERT_NE(assetRuntime, nullptr);
|
||||
|
||||
std::unique_ptr<XCEngine::Rendering::RenderPipelineStageRecorder>
|
||||
recorder = assetRuntime->CreateStageRecorder();
|
||||
ASSERT_NE(recorder, nullptr);
|
||||
|
||||
const XCEngine::Rendering::RenderContext context = {};
|
||||
ASSERT_TRUE(recorder->Initialize(context));
|
||||
ASSERT_TRUE(
|
||||
recorder->SupportsStageRenderGraph(
|
||||
XCEngine::Rendering::CameraFrameStage::MainScene));
|
||||
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
ASSERT_TRUE(
|
||||
recorder->SupportsStageRenderGraph(
|
||||
XCEngine::Rendering::CameraFrameStage::MainScene));
|
||||
|
||||
engine->OnUpdate(0.016f);
|
||||
EXPECT_TRUE(runtime->GetLastError().empty()) << runtime->GetLastError();
|
||||
|
||||
int observedCreatePipelineCallCount = 0;
|
||||
int observedDisposePipelineCallCount = 0;
|
||||
int observedCreateRendererCallCount = 0;
|
||||
int observedSetupRendererCallCount = 0;
|
||||
int observedCreateFeatureCallCount = 0;
|
||||
int observedDisposeRendererCallCount = 0;
|
||||
int observedDisposeFeatureCallCount = 0;
|
||||
int observedInvalidateRendererCallCount = 0;
|
||||
int observedRuntimeResourceVersionBeforeInvalidation = 0;
|
||||
int observedRuntimeResourceVersionAfterInvalidation = 0;
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedCreatePipelineCallCount",
|
||||
observedCreatePipelineCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedDisposePipelineCallCount",
|
||||
observedDisposePipelineCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedCreateRendererCallCount",
|
||||
observedCreateRendererCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedSetupRendererCallCount",
|
||||
observedSetupRendererCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedCreateFeatureCallCount",
|
||||
observedCreateFeatureCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedDisposeRendererCallCount",
|
||||
observedDisposeRendererCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedDisposeFeatureCallCount",
|
||||
observedDisposeFeatureCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedInvalidateRendererCallCount",
|
||||
observedInvalidateRendererCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedRuntimeResourceVersionBeforeInvalidation",
|
||||
observedRuntimeResourceVersionBeforeInvalidation));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedRuntimeResourceVersionAfterInvalidation",
|
||||
observedRuntimeResourceVersionAfterInvalidation));
|
||||
|
||||
EXPECT_EQ(observedCreatePipelineCallCount, 2);
|
||||
EXPECT_EQ(observedDisposePipelineCallCount, 1);
|
||||
EXPECT_EQ(observedCreateRendererCallCount, 2);
|
||||
EXPECT_EQ(observedSetupRendererCallCount, 2);
|
||||
EXPECT_EQ(observedCreateFeatureCallCount, 2);
|
||||
EXPECT_EQ(observedDisposeRendererCallCount, 1);
|
||||
EXPECT_EQ(observedDisposeFeatureCallCount, 1);
|
||||
EXPECT_EQ(observedInvalidateRendererCallCount, 1);
|
||||
EXPECT_GT(observedRuntimeResourceVersionBeforeInvalidation, 0);
|
||||
EXPECT_EQ(
|
||||
observedRuntimeResourceVersionAfterInvalidation,
|
||||
observedRuntimeResourceVersionBeforeInvalidation + 1);
|
||||
|
||||
recorder->Shutdown();
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ProjectScriptAssemblyTest,
|
||||
ProjectManagedBridgeReleasesProjectRendererCachesAcrossInvalidationAndAssetRuntimeRelease) {
|
||||
Scene* runtimeScene =
|
||||
CreateScene("ProjectPersistentFeatureLifecycleScene");
|
||||
GameObject* selectionObject =
|
||||
runtimeScene->CreateGameObject(
|
||||
"ProjectPersistentFeatureSelection");
|
||||
ScriptComponent* selectionScript =
|
||||
AddProjectScript(
|
||||
selectionObject,
|
||||
"ProjectPersistentFeatureRuntimeSelectionProbe");
|
||||
ASSERT_NE(selectionScript, nullptr);
|
||||
GameObject* observationObject =
|
||||
runtimeScene->CreateGameObject(
|
||||
"ProjectPersistentFeatureObservation");
|
||||
ScriptComponent* observationScript =
|
||||
AddProjectScript(
|
||||
observationObject,
|
||||
"ProjectPersistentFeatureObservationProbe");
|
||||
ASSERT_NE(observationScript, nullptr);
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor
|
||||
descriptor =
|
||||
XCEngine::Rendering::Pipelines::
|
||||
GetConfiguredManagedRenderPipelineAssetDescriptor();
|
||||
ASSERT_TRUE(descriptor.IsValid());
|
||||
ASSERT_NE(descriptor.managedAssetHandle, 0u);
|
||||
EXPECT_EQ(descriptor.assemblyName, "GameScripts");
|
||||
EXPECT_EQ(descriptor.namespaceName, "ProjectScripts");
|
||||
EXPECT_EQ(
|
||||
descriptor.className,
|
||||
"ProjectPersistentFeatureProbeAsset");
|
||||
|
||||
{
|
||||
const auto bridge =
|
||||
XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge();
|
||||
ASSERT_NE(bridge, nullptr);
|
||||
|
||||
std::shared_ptr<const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetRuntime>
|
||||
assetRuntime = bridge->CreateAssetRuntime(descriptor);
|
||||
ASSERT_NE(assetRuntime, nullptr);
|
||||
|
||||
std::unique_ptr<XCEngine::Rendering::RenderPipelineStageRecorder>
|
||||
recorder = assetRuntime->CreateStageRecorder();
|
||||
ASSERT_NE(recorder, nullptr);
|
||||
|
||||
const XCEngine::Rendering::RenderContext context = {};
|
||||
ASSERT_TRUE(recorder->Initialize(context));
|
||||
ASSERT_TRUE(
|
||||
recorder->SupportsStageRenderGraph(
|
||||
XCEngine::Rendering::CameraFrameStage::MainScene));
|
||||
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
ASSERT_TRUE(
|
||||
recorder->SupportsStageRenderGraph(
|
||||
XCEngine::Rendering::CameraFrameStage::MainScene));
|
||||
|
||||
recorder->Shutdown();
|
||||
}
|
||||
|
||||
engine->OnUpdate(0.016f);
|
||||
EXPECT_TRUE(runtime->GetLastError().empty()) << runtime->GetLastError();
|
||||
|
||||
int observedCreateRendererCallCount = 0;
|
||||
int observedCreateFeatureRuntimeCallCount = 0;
|
||||
int observedDisposeRendererCallCount = 0;
|
||||
int observedDisposeFeatureCallCount = 0;
|
||||
int observedInvalidateRendererCallCount = 0;
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedCreateRendererCallCount",
|
||||
observedCreateRendererCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedCreateFeatureRuntimeCallCount",
|
||||
observedCreateFeatureRuntimeCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedDisposeRendererCallCount",
|
||||
observedDisposeRendererCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedDisposeFeatureCallCount",
|
||||
observedDisposeFeatureCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedInvalidateRendererCallCount",
|
||||
observedInvalidateRendererCallCount));
|
||||
|
||||
EXPECT_EQ(observedCreateRendererCallCount, 2);
|
||||
EXPECT_EQ(observedCreateFeatureRuntimeCallCount, 2);
|
||||
EXPECT_EQ(observedDisposeRendererCallCount, 2);
|
||||
EXPECT_EQ(observedDisposeFeatureCallCount, 2);
|
||||
EXPECT_EQ(observedInvalidateRendererCallCount, 1);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ProjectScriptAssemblyTest,
|
||||
ProjectManagedBridgeRebuildsPipelineAfterProjectAssetInvalidation) {
|
||||
Scene* runtimeScene =
|
||||
CreateScene("ProjectAssetInvalidationScene");
|
||||
GameObject* selectionObject =
|
||||
runtimeScene->CreateGameObject(
|
||||
"ProjectAssetInvalidationSelection");
|
||||
ScriptComponent* selectionScript =
|
||||
AddProjectScript(
|
||||
selectionObject,
|
||||
"ProjectAssetInvalidationRuntimeSelectionProbe");
|
||||
ASSERT_NE(selectionScript, nullptr);
|
||||
GameObject* observationObject =
|
||||
runtimeScene->CreateGameObject(
|
||||
"ProjectAssetInvalidationObservation");
|
||||
ScriptComponent* observationScript =
|
||||
AddProjectScript(
|
||||
observationObject,
|
||||
"ProjectAssetInvalidationObservationProbe");
|
||||
ASSERT_NE(observationScript, nullptr);
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor
|
||||
descriptor =
|
||||
XCEngine::Rendering::Pipelines::
|
||||
GetConfiguredManagedRenderPipelineAssetDescriptor();
|
||||
ASSERT_TRUE(descriptor.IsValid());
|
||||
ASSERT_NE(descriptor.managedAssetHandle, 0u);
|
||||
EXPECT_EQ(descriptor.assemblyName, "GameScripts");
|
||||
EXPECT_EQ(descriptor.namespaceName, "ProjectScripts");
|
||||
EXPECT_EQ(
|
||||
descriptor.className,
|
||||
"ProjectAssetInvalidationProbeAsset");
|
||||
|
||||
const auto bridge =
|
||||
XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge();
|
||||
ASSERT_NE(bridge, nullptr);
|
||||
|
||||
std::shared_ptr<const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetRuntime>
|
||||
assetRuntime = bridge->CreateAssetRuntime(descriptor);
|
||||
ASSERT_NE(assetRuntime, nullptr);
|
||||
|
||||
std::unique_ptr<XCEngine::Rendering::RenderPipelineStageRecorder>
|
||||
recorder = assetRuntime->CreateStageRecorder();
|
||||
ASSERT_NE(recorder, nullptr);
|
||||
|
||||
const XCEngine::Rendering::RenderContext context = {};
|
||||
ASSERT_TRUE(recorder->Initialize(context));
|
||||
ASSERT_TRUE(
|
||||
recorder->SupportsStageRenderGraph(
|
||||
XCEngine::Rendering::CameraFrameStage::MainScene));
|
||||
ASSERT_FALSE(
|
||||
recorder->SupportsStageRenderGraph(
|
||||
XCEngine::Rendering::CameraFrameStage::PostProcess));
|
||||
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
ASSERT_FALSE(
|
||||
recorder->SupportsStageRenderGraph(
|
||||
XCEngine::Rendering::CameraFrameStage::MainScene));
|
||||
ASSERT_TRUE(
|
||||
recorder->SupportsStageRenderGraph(
|
||||
XCEngine::Rendering::CameraFrameStage::PostProcess));
|
||||
|
||||
engine->OnUpdate(0.016f);
|
||||
EXPECT_TRUE(runtime->GetLastError().empty()) << runtime->GetLastError();
|
||||
|
||||
int observedCreatePipelineCallCount = 0;
|
||||
int observedDisposePipelineCallCount = 0;
|
||||
int observedInvalidateAssetCallCount = 0;
|
||||
int observedLastCreatedSupportedStage = 0;
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedCreatePipelineCallCount",
|
||||
observedCreatePipelineCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedDisposePipelineCallCount",
|
||||
observedDisposePipelineCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedInvalidateAssetCallCount",
|
||||
observedInvalidateAssetCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(
|
||||
observationScript,
|
||||
"ObservedLastCreatedSupportedStage",
|
||||
observedLastCreatedSupportedStage));
|
||||
|
||||
EXPECT_EQ(observedCreatePipelineCallCount, 2);
|
||||
EXPECT_EQ(observedDisposePipelineCallCount, 1);
|
||||
EXPECT_EQ(observedInvalidateAssetCallCount, 1);
|
||||
EXPECT_EQ(
|
||||
observedLastCreatedSupportedStage,
|
||||
static_cast<int>(
|
||||
XCEngine::Rendering::CameraFrameStage::PostProcess));
|
||||
|
||||
recorder->Shutdown();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -1,147 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/Components/ComponentFactoryRegistry.h>
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
#include <XCEngine/Scripting/ScriptComponent.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using namespace XCEngine::Components;
|
||||
using namespace XCEngine::Scripting;
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(ScriptComponent_Test, DefaultsAreInitializedForNativeDataLayer) {
|
||||
ScriptComponent component;
|
||||
|
||||
EXPECT_EQ(component.GetName(), "ScriptComponent");
|
||||
EXPECT_NE(component.GetScriptComponentUUID(), 0u);
|
||||
EXPECT_EQ(component.GetAssemblyName(), "GameScripts");
|
||||
EXPECT_TRUE(component.GetNamespaceName().empty());
|
||||
EXPECT_TRUE(component.GetClassName().empty());
|
||||
EXPECT_FALSE(component.HasScriptClass());
|
||||
EXPECT_TRUE(component.GetFullClassName().empty());
|
||||
EXPECT_EQ(component.GetFieldStorage().GetFieldCount(), 0u);
|
||||
}
|
||||
|
||||
TEST(ScriptComponent_Test, FullClassNameComposesNamespaceAndClassName) {
|
||||
ScriptComponent component;
|
||||
|
||||
component.SetScriptClass("Gameplay.Characters", "PlayerController");
|
||||
EXPECT_EQ(component.GetFullClassName(), "Gameplay.Characters.PlayerController");
|
||||
|
||||
component.SetScriptClass("", "Mover");
|
||||
EXPECT_EQ(component.GetFullClassName(), "Mover");
|
||||
}
|
||||
|
||||
TEST(ScriptComponent_Test, SerializeRoundTripPreservesMetadataAndFields) {
|
||||
ScriptComponent original;
|
||||
original.SetScriptClass("GameScripts.Runtime", "Gameplay.Characters", "PlayerController");
|
||||
ASSERT_TRUE(original.GetFieldStorage().SetFieldValue("DisplayName", std::string("Hero=One;Ready|100%")));
|
||||
ASSERT_TRUE(original.GetFieldStorage().SetFieldValue("MoveSpeed", 6.25f));
|
||||
ASSERT_TRUE(original.GetFieldStorage().SetFieldValue("Target", GameObjectReference{9988}));
|
||||
ASSERT_TRUE(original.GetFieldStorage().SetFieldValue("TargetCamera", ComponentReference{9988, 0}));
|
||||
|
||||
std::ostringstream stream;
|
||||
original.Serialize(stream);
|
||||
|
||||
ScriptComponent restored;
|
||||
std::istringstream input(stream.str());
|
||||
restored.Deserialize(input);
|
||||
|
||||
std::string displayName;
|
||||
float moveSpeed = 0.0f;
|
||||
GameObjectReference target;
|
||||
ComponentReference targetCamera;
|
||||
|
||||
EXPECT_EQ(restored.GetScriptComponentUUID(), original.GetScriptComponentUUID());
|
||||
EXPECT_EQ(restored.GetAssemblyName(), "GameScripts.Runtime");
|
||||
EXPECT_EQ(restored.GetNamespaceName(), "Gameplay.Characters");
|
||||
EXPECT_EQ(restored.GetClassName(), "PlayerController");
|
||||
EXPECT_EQ(restored.GetFullClassName(), "Gameplay.Characters.PlayerController");
|
||||
EXPECT_TRUE(restored.GetFieldStorage().TryGetFieldValue("DisplayName", displayName));
|
||||
EXPECT_TRUE(restored.GetFieldStorage().TryGetFieldValue("MoveSpeed", moveSpeed));
|
||||
EXPECT_TRUE(restored.GetFieldStorage().TryGetFieldValue("Target", target));
|
||||
EXPECT_TRUE(restored.GetFieldStorage().TryGetFieldValue("TargetCamera", targetCamera));
|
||||
EXPECT_EQ(displayName, "Hero=One;Ready|100%");
|
||||
EXPECT_FLOAT_EQ(moveSpeed, 6.25f);
|
||||
EXPECT_EQ(target, GameObjectReference{9988});
|
||||
EXPECT_EQ(targetCamera, (ComponentReference{9988, 0}));
|
||||
}
|
||||
|
||||
TEST(ScriptComponent_Test, ComponentFactoryRegistryCreatesScriptComponents) {
|
||||
GameObject gameObject("ScriptHost");
|
||||
auto& registry = ComponentFactoryRegistry::Get();
|
||||
|
||||
EXPECT_TRUE(registry.IsRegistered("ScriptComponent"));
|
||||
|
||||
ScriptComponent* created = dynamic_cast<ScriptComponent*>(registry.CreateComponent(&gameObject, "ScriptComponent"));
|
||||
ASSERT_NE(created, nullptr);
|
||||
EXPECT_EQ(gameObject.GetComponents<ScriptComponent>().size(), 1u);
|
||||
}
|
||||
|
||||
TEST(ScriptComponent_Test, GameObjectCanOwnMultipleScriptComponents) {
|
||||
GameObject gameObject("ScriptHost");
|
||||
|
||||
ScriptComponent* first = gameObject.AddComponent<ScriptComponent>();
|
||||
ScriptComponent* second = gameObject.AddComponent<ScriptComponent>();
|
||||
|
||||
ASSERT_NE(first, nullptr);
|
||||
ASSERT_NE(second, nullptr);
|
||||
EXPECT_NE(first, second);
|
||||
EXPECT_EQ(gameObject.GetComponents<ScriptComponent>().size(), 2u);
|
||||
}
|
||||
|
||||
TEST(ScriptComponent_Test, SceneRoundTripPreservesMultipleScriptComponentsAndFields) {
|
||||
Scene scene("Script Scene");
|
||||
GameObject* host = scene.CreateGameObject("Host");
|
||||
GameObject* targetObject = scene.CreateGameObject("Target");
|
||||
|
||||
ScriptComponent* movement = host->AddComponent<ScriptComponent>();
|
||||
movement->SetScriptClass("GameScripts", "Gameplay", "MovementController");
|
||||
ASSERT_TRUE(movement->GetFieldStorage().SetFieldValue("Speed", 4.25f));
|
||||
ASSERT_TRUE(movement->GetFieldStorage().SetFieldValue("Target", GameObjectReference{targetObject->GetUUID()}));
|
||||
|
||||
ScriptComponent* inventory = host->AddComponent<ScriptComponent>();
|
||||
inventory->SetScriptClass("GameScripts", "Gameplay.Inventory", "InventoryWatcher");
|
||||
ASSERT_TRUE(inventory->GetFieldStorage().SetFieldValue("Label", std::string("Player|One;Ready=1")));
|
||||
ASSERT_TRUE(inventory->GetFieldStorage().SetFieldValue("Slots", int32_t(24)));
|
||||
|
||||
const uint64_t movementUUID = movement->GetScriptComponentUUID();
|
||||
const uint64_t inventoryUUID = inventory->GetScriptComponentUUID();
|
||||
|
||||
Scene loadedScene;
|
||||
loadedScene.DeserializeFromString(scene.SerializeToString());
|
||||
|
||||
GameObject* loadedHost = loadedScene.Find("Host");
|
||||
GameObject* loadedTarget = loadedScene.Find("Target");
|
||||
ASSERT_NE(loadedHost, nullptr);
|
||||
ASSERT_NE(loadedTarget, nullptr);
|
||||
|
||||
std::vector<ScriptComponent*> loadedScripts = loadedHost->GetComponents<ScriptComponent>();
|
||||
ASSERT_EQ(loadedScripts.size(), 2u);
|
||||
|
||||
float speed = 0.0f;
|
||||
GameObjectReference loadedReference;
|
||||
std::string label;
|
||||
int32_t slots = 0;
|
||||
|
||||
EXPECT_EQ(loadedScripts[0]->GetScriptComponentUUID(), movementUUID);
|
||||
EXPECT_EQ(loadedScripts[0]->GetAssemblyName(), "GameScripts");
|
||||
EXPECT_EQ(loadedScripts[0]->GetFullClassName(), "Gameplay.MovementController");
|
||||
EXPECT_TRUE(loadedScripts[0]->GetFieldStorage().TryGetFieldValue("Speed", speed));
|
||||
EXPECT_TRUE(loadedScripts[0]->GetFieldStorage().TryGetFieldValue("Target", loadedReference));
|
||||
EXPECT_FLOAT_EQ(speed, 4.25f);
|
||||
EXPECT_EQ(loadedReference, GameObjectReference{loadedTarget->GetUUID()});
|
||||
|
||||
EXPECT_EQ(loadedScripts[1]->GetScriptComponentUUID(), inventoryUUID);
|
||||
EXPECT_EQ(loadedScripts[1]->GetAssemblyName(), "GameScripts");
|
||||
EXPECT_EQ(loadedScripts[1]->GetFullClassName(), "Gameplay.Inventory.InventoryWatcher");
|
||||
EXPECT_TRUE(loadedScripts[1]->GetFieldStorage().TryGetFieldValue("Label", label));
|
||||
EXPECT_TRUE(loadedScripts[1]->GetFieldStorage().TryGetFieldValue("Slots", slots));
|
||||
EXPECT_EQ(label, "Player|One;Ready=1");
|
||||
EXPECT_EQ(slots, 24);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -1,873 +0,0 @@
|
||||
#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 <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#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";
|
||||
}
|
||||
|
||||
std::string PhysicsMessageToString(ScriptPhysicsMessage message) {
|
||||
switch (message) {
|
||||
case ScriptPhysicsMessage::CollisionEnter: return "CollisionEnter";
|
||||
case ScriptPhysicsMessage::CollisionStay: return "CollisionStay";
|
||||
case ScriptPhysicsMessage::CollisionExit: return "CollisionExit";
|
||||
case ScriptPhysicsMessage::TriggerEnter: return "TriggerEnter";
|
||||
case ScriptPhysicsMessage::TriggerStay: return "TriggerStay";
|
||||
case ScriptPhysicsMessage::TriggerExit: return "TriggerExit";
|
||||
}
|
||||
|
||||
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 TryGetAvailableScriptClasses(
|
||||
std::vector<ScriptClassDescriptor>& outClasses) const override {
|
||||
outClasses = scriptClasses;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryGetAvailableRenderPipelineAssetClasses(
|
||||
std::vector<ScriptClassDescriptor>& outClasses) const override {
|
||||
outClasses = renderPipelineAssetClasses;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryGetClassFieldMetadata(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className,
|
||||
std::vector<ScriptFieldMetadata>& outFields) const override {
|
||||
(void)assemblyName;
|
||||
(void)namespaceName;
|
||||
(void)className;
|
||||
outFields = fieldMetadata;
|
||||
return !outFields.empty();
|
||||
}
|
||||
|
||||
bool TryGetClassFieldDefaultValues(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className,
|
||||
std::vector<ScriptFieldDefaultValue>& outFields) const override {
|
||||
(void)assemblyName;
|
||||
(void)namespaceName;
|
||||
(void)className;
|
||||
outFields = fieldDefaultValues;
|
||||
return !outFields.empty();
|
||||
}
|
||||
|
||||
bool TrySetManagedFieldValue(
|
||||
const ScriptRuntimeContext& context,
|
||||
const std::string& fieldName,
|
||||
const ScriptFieldValue& value) override {
|
||||
(void)context;
|
||||
managedFieldValues[fieldName] = value;
|
||||
managedSetFieldNames.push_back(fieldName);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryGetManagedFieldValue(
|
||||
const ScriptRuntimeContext& context,
|
||||
const std::string& fieldName,
|
||||
ScriptFieldValue& outValue) const override {
|
||||
(void)context;
|
||||
|
||||
const auto it = managedFieldValues.find(fieldName);
|
||||
if (it == managedFieldValues.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SyncManagedFieldsToStorage(const ScriptRuntimeContext& context) override {
|
||||
(void)context;
|
||||
}
|
||||
|
||||
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 InvokePhysicsMessage(
|
||||
const ScriptRuntimeContext& context,
|
||||
ScriptPhysicsMessage message,
|
||||
GameObject* other) override {
|
||||
events.push_back(
|
||||
PhysicsMessageToString(message)
|
||||
+ ":"
|
||||
+ Describe(context)
|
||||
+ ":"
|
||||
+ (other ? other->GetName() : std::string("null")));
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
events.clear();
|
||||
}
|
||||
|
||||
std::vector<std::string> events;
|
||||
std::vector<ScriptClassDescriptor> scriptClasses;
|
||||
std::vector<ScriptClassDescriptor> renderPipelineAssetClasses;
|
||||
std::vector<ScriptFieldMetadata> fieldMetadata;
|
||||
std::vector<ScriptFieldDefaultValue> fieldDefaultValues;
|
||||
std::unordered_map<std::string, ScriptFieldValue> managedFieldValues;
|
||||
std::vector<std::string> managedSetFieldNames;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, ScriptClassDiscoveryApiReturnsSortedDescriptorsAndSupportsAssemblyFilter) {
|
||||
runtime.scriptClasses = {
|
||||
{"Tools", "", "UtilityProbe"},
|
||||
{"GameScripts", "Gameplay", "Zombie"},
|
||||
{"GameScripts", "", "Bootstrap"},
|
||||
{"GameScripts", "Gameplay", "Actor"},
|
||||
{"Broken", "", ""}
|
||||
};
|
||||
|
||||
std::vector<ScriptClassDescriptor> allClasses;
|
||||
ASSERT_TRUE(engine->TryGetAvailableScriptClasses(allClasses));
|
||||
|
||||
const std::vector<ScriptClassDescriptor> expectedAllClasses = {
|
||||
{"GameScripts", "", "Bootstrap"},
|
||||
{"GameScripts", "Gameplay", "Actor"},
|
||||
{"GameScripts", "Gameplay", "Zombie"},
|
||||
{"Tools", "", "UtilityProbe"}
|
||||
};
|
||||
EXPECT_EQ(allClasses, expectedAllClasses);
|
||||
EXPECT_EQ(allClasses[0].GetFullName(), "Bootstrap");
|
||||
EXPECT_EQ(allClasses[1].GetFullName(), "Gameplay.Actor");
|
||||
|
||||
std::vector<ScriptClassDescriptor> gameScriptClasses;
|
||||
ASSERT_TRUE(engine->TryGetAvailableScriptClasses(gameScriptClasses, "GameScripts"));
|
||||
|
||||
const std::vector<ScriptClassDescriptor> expectedGameScriptClasses = {
|
||||
{"GameScripts", "", "Bootstrap"},
|
||||
{"GameScripts", "Gameplay", "Actor"},
|
||||
{"GameScripts", "Gameplay", "Zombie"}
|
||||
};
|
||||
EXPECT_EQ(gameScriptClasses, expectedGameScriptClasses);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, ChangingScriptClassWhileRuntimeRunningRecreatesTrackedInstance) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "OldClass");
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
runtime.Clear();
|
||||
|
||||
component->SetScriptClass("GameScripts", "Gameplay", "NewClass");
|
||||
|
||||
const std::vector<std::string> expected = {
|
||||
"OnDisable:Host:Gameplay.NewClass",
|
||||
"OnDestroy:Host:Gameplay.NewClass",
|
||||
"Destroy:Host:Gameplay.NewClass",
|
||||
"Create:Host:Gameplay.NewClass",
|
||||
"Awake:Host:Gameplay.NewClass",
|
||||
"OnEnable:Host:Gameplay.NewClass"
|
||||
};
|
||||
EXPECT_EQ(runtime.events, expected);
|
||||
EXPECT_TRUE(engine->HasTrackedScriptComponent(component));
|
||||
EXPECT_TRUE(engine->HasRuntimeInstance(component));
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, DispatchPhysicsMessageInvokesTrackedActiveScripts) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
GameObject* other = runtimeScene->CreateGameObject("Other");
|
||||
AddScriptComponent(host, "Gameplay", "TriggerListener");
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
runtime.Clear();
|
||||
|
||||
engine->DispatchPhysicsMessage(host, ScriptPhysicsMessage::TriggerEnter, other);
|
||||
|
||||
const std::vector<std::string> expected = {
|
||||
"TriggerEnter:Host:Gameplay.TriggerListener:Other"
|
||||
};
|
||||
EXPECT_EQ(runtime.events, expected);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, DispatchPhysicsMessageSkipsInactiveAndDisabledScripts) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* activeHost = runtimeScene->CreateGameObject("ActiveHost");
|
||||
GameObject* inactiveHost = runtimeScene->CreateGameObject("InactiveHost");
|
||||
GameObject* disabledHost = runtimeScene->CreateGameObject("DisabledHost");
|
||||
GameObject* other = runtimeScene->CreateGameObject("Other");
|
||||
|
||||
AddScriptComponent(activeHost, "Gameplay", "ActiveListener");
|
||||
AddScriptComponent(inactiveHost, "Gameplay", "InactiveListener");
|
||||
ScriptComponent* disabledComponent = AddScriptComponent(disabledHost, "Gameplay", "DisabledListener");
|
||||
|
||||
inactiveHost->SetActive(false);
|
||||
disabledComponent->SetEnabled(false);
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
runtime.Clear();
|
||||
|
||||
engine->DispatchPhysicsMessage(activeHost, ScriptPhysicsMessage::CollisionEnter, other);
|
||||
engine->DispatchPhysicsMessage(inactiveHost, ScriptPhysicsMessage::CollisionEnter, other);
|
||||
engine->DispatchPhysicsMessage(disabledHost, ScriptPhysicsMessage::CollisionEnter, other);
|
||||
|
||||
const std::vector<std::string> expected = {
|
||||
"CollisionEnter:ActiveHost:Gameplay.ActiveListener:Other"
|
||||
};
|
||||
EXPECT_EQ(runtime.events, expected);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, ClearingScriptClassWhileRuntimeRunningDestroysTrackedInstance) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "OldClass");
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
runtime.Clear();
|
||||
|
||||
component->ClearScriptClass();
|
||||
|
||||
const std::vector<std::string> expected = {
|
||||
"OnDisable:Host:",
|
||||
"OnDestroy:Host:",
|
||||
"Destroy:Host:"
|
||||
};
|
||||
EXPECT_EQ(runtime.events, expected);
|
||||
EXPECT_FALSE(engine->HasTrackedScriptComponent(component));
|
||||
EXPECT_FALSE(engine->HasRuntimeInstance(component));
|
||||
EXPECT_FALSE(component->HasScriptClass());
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldReadApiPrefersLiveManagedValueAndFallsBackToStoredValue) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "RuntimeReadback");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
|
||||
std::string label;
|
||||
int32_t awakeCount = 0;
|
||||
|
||||
EXPECT_TRUE(engine->TryGetScriptFieldValue(component, "Label", label));
|
||||
EXPECT_EQ(label, "Stored");
|
||||
EXPECT_FALSE(engine->TryGetScriptFieldValue(component, "AwakeCount", awakeCount));
|
||||
|
||||
runtime.managedFieldValues["Label"] = std::string("Live");
|
||||
runtime.managedFieldValues["AwakeCount"] = int32_t(1);
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
EXPECT_TRUE(engine->TryGetScriptFieldValue(component, "Label", label));
|
||||
EXPECT_EQ(label, "Live");
|
||||
EXPECT_TRUE(engine->TryGetScriptFieldValue(component, "AwakeCount", awakeCount));
|
||||
EXPECT_EQ(awakeCount, 1);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldSnapshotApiCombinesMetadataStoredValuesAndLiveManagedValues) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "RuntimeSnapshot");
|
||||
|
||||
runtime.fieldMetadata = {
|
||||
{"AwakeCount", ScriptFieldType::Int32},
|
||||
{"Label", ScriptFieldType::String}
|
||||
};
|
||||
component->GetFieldStorage().SetFieldValue("AwakeCount", uint64_t(7));
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
component->GetFieldStorage().SetFieldValue("LegacyOnly", uint64_t(99));
|
||||
|
||||
ScriptFieldModel model;
|
||||
ASSERT_TRUE(engine->TryGetScriptFieldModel(component, model));
|
||||
EXPECT_EQ(model.classStatus, ScriptFieldClassStatus::Available);
|
||||
|
||||
std::vector<ScriptFieldSnapshot>& snapshots = model.fields;
|
||||
ASSERT_EQ(snapshots.size(), 3u);
|
||||
|
||||
const auto findSnapshot = [&snapshots](const std::string& fieldName) -> const ScriptFieldSnapshot* {
|
||||
const auto it = std::find_if(
|
||||
snapshots.begin(),
|
||||
snapshots.end(),
|
||||
[&fieldName](const ScriptFieldSnapshot& snapshot) {
|
||||
return snapshot.metadata.name == fieldName;
|
||||
});
|
||||
return it != snapshots.end() ? &(*it) : nullptr;
|
||||
};
|
||||
|
||||
const ScriptFieldSnapshot* awakeSnapshot = findSnapshot("AwakeCount");
|
||||
const ScriptFieldSnapshot* labelSnapshot = findSnapshot("Label");
|
||||
const ScriptFieldSnapshot* legacySnapshot = findSnapshot("LegacyOnly");
|
||||
|
||||
ASSERT_NE(awakeSnapshot, nullptr);
|
||||
ASSERT_NE(labelSnapshot, nullptr);
|
||||
ASSERT_NE(legacySnapshot, nullptr);
|
||||
|
||||
EXPECT_EQ(awakeSnapshot->metadata.type, ScriptFieldType::Int32);
|
||||
EXPECT_TRUE(awakeSnapshot->declaredInClass);
|
||||
EXPECT_TRUE(awakeSnapshot->hasDefaultValue);
|
||||
EXPECT_FALSE(awakeSnapshot->hasValue);
|
||||
EXPECT_EQ(awakeSnapshot->valueSource, ScriptFieldValueSource::DefaultValue);
|
||||
EXPECT_EQ(awakeSnapshot->issue, ScriptFieldIssue::TypeMismatch);
|
||||
EXPECT_TRUE(awakeSnapshot->hasStoredValue);
|
||||
EXPECT_EQ(awakeSnapshot->storedType, ScriptFieldType::UInt64);
|
||||
EXPECT_EQ(std::get<int32_t>(awakeSnapshot->defaultValue), 0);
|
||||
EXPECT_EQ(std::get<int32_t>(awakeSnapshot->value), 0);
|
||||
EXPECT_EQ(std::get<uint64_t>(awakeSnapshot->storedValue), 7u);
|
||||
|
||||
EXPECT_EQ(labelSnapshot->metadata.type, ScriptFieldType::String);
|
||||
EXPECT_TRUE(labelSnapshot->declaredInClass);
|
||||
EXPECT_TRUE(labelSnapshot->hasDefaultValue);
|
||||
EXPECT_TRUE(labelSnapshot->hasValue);
|
||||
EXPECT_EQ(labelSnapshot->valueSource, ScriptFieldValueSource::StoredValue);
|
||||
EXPECT_EQ(labelSnapshot->issue, ScriptFieldIssue::None);
|
||||
EXPECT_TRUE(labelSnapshot->hasStoredValue);
|
||||
EXPECT_EQ(labelSnapshot->storedType, ScriptFieldType::String);
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->defaultValue), "");
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->value), "Stored");
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->storedValue), "Stored");
|
||||
|
||||
EXPECT_EQ(legacySnapshot->metadata.type, ScriptFieldType::UInt64);
|
||||
EXPECT_FALSE(legacySnapshot->declaredInClass);
|
||||
EXPECT_FALSE(legacySnapshot->hasDefaultValue);
|
||||
EXPECT_TRUE(legacySnapshot->hasValue);
|
||||
EXPECT_EQ(legacySnapshot->valueSource, ScriptFieldValueSource::StoredValue);
|
||||
EXPECT_EQ(legacySnapshot->issue, ScriptFieldIssue::StoredOnly);
|
||||
EXPECT_TRUE(legacySnapshot->hasStoredValue);
|
||||
EXPECT_EQ(legacySnapshot->storedType, ScriptFieldType::UInt64);
|
||||
EXPECT_EQ(std::get<uint64_t>(legacySnapshot->value), 99u);
|
||||
EXPECT_EQ(std::get<uint64_t>(legacySnapshot->storedValue), 99u);
|
||||
|
||||
runtime.managedFieldValues["AwakeCount"] = int32_t(1);
|
||||
runtime.managedFieldValues["Label"] = std::string("Live");
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
ASSERT_TRUE(engine->TryGetScriptFieldModel(component, model));
|
||||
EXPECT_EQ(model.classStatus, ScriptFieldClassStatus::Available);
|
||||
awakeSnapshot = findSnapshot("AwakeCount");
|
||||
labelSnapshot = findSnapshot("Label");
|
||||
legacySnapshot = findSnapshot("LegacyOnly");
|
||||
|
||||
ASSERT_NE(awakeSnapshot, nullptr);
|
||||
ASSERT_NE(labelSnapshot, nullptr);
|
||||
ASSERT_NE(legacySnapshot, nullptr);
|
||||
|
||||
EXPECT_TRUE(awakeSnapshot->hasValue);
|
||||
EXPECT_TRUE(awakeSnapshot->hasDefaultValue);
|
||||
EXPECT_EQ(awakeSnapshot->valueSource, ScriptFieldValueSource::ManagedValue);
|
||||
EXPECT_EQ(awakeSnapshot->issue, ScriptFieldIssue::TypeMismatch);
|
||||
EXPECT_EQ(std::get<int32_t>(awakeSnapshot->defaultValue), 0);
|
||||
EXPECT_EQ(std::get<int32_t>(awakeSnapshot->value), 1);
|
||||
EXPECT_TRUE(labelSnapshot->hasValue);
|
||||
EXPECT_TRUE(labelSnapshot->hasDefaultValue);
|
||||
EXPECT_EQ(labelSnapshot->valueSource, ScriptFieldValueSource::ManagedValue);
|
||||
EXPECT_EQ(labelSnapshot->issue, ScriptFieldIssue::None);
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->defaultValue), "");
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->value), "Live");
|
||||
EXPECT_TRUE(legacySnapshot->hasValue);
|
||||
EXPECT_FALSE(legacySnapshot->hasDefaultValue);
|
||||
EXPECT_EQ(legacySnapshot->valueSource, ScriptFieldValueSource::StoredValue);
|
||||
EXPECT_EQ(legacySnapshot->issue, ScriptFieldIssue::StoredOnly);
|
||||
EXPECT_EQ(std::get<uint64_t>(legacySnapshot->value), 99u);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldModelReportsMissingScriptClassAndPreservesStoredFields) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "MissingScript");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
|
||||
ScriptFieldModel model;
|
||||
ASSERT_TRUE(engine->TryGetScriptFieldModel(component, model));
|
||||
|
||||
EXPECT_EQ(model.classStatus, ScriptFieldClassStatus::Missing);
|
||||
ASSERT_EQ(model.fields.size(), 1u);
|
||||
|
||||
const ScriptFieldSnapshot& field = model.fields[0];
|
||||
EXPECT_EQ(field.metadata.name, "Label");
|
||||
EXPECT_EQ(field.metadata.type, ScriptFieldType::String);
|
||||
EXPECT_FALSE(field.declaredInClass);
|
||||
EXPECT_FALSE(field.hasDefaultValue);
|
||||
EXPECT_TRUE(field.hasValue);
|
||||
EXPECT_EQ(field.valueSource, ScriptFieldValueSource::StoredValue);
|
||||
EXPECT_EQ(field.issue, ScriptFieldIssue::StoredOnly);
|
||||
EXPECT_TRUE(field.hasStoredValue);
|
||||
EXPECT_EQ(field.storedType, ScriptFieldType::String);
|
||||
EXPECT_EQ(std::get<std::string>(field.value), "Stored");
|
||||
EXPECT_EQ(std::get<std::string>(field.storedValue), "Stored");
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldModelUsesRuntimeClassDefaultValuesWhenAvailable) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "RuntimeDefaults");
|
||||
|
||||
runtime.fieldMetadata = {
|
||||
{"Health", ScriptFieldType::Int32},
|
||||
{"Label", ScriptFieldType::String},
|
||||
{"SpawnPoint", ScriptFieldType::Vector3}
|
||||
};
|
||||
runtime.fieldDefaultValues = {
|
||||
{"Health", ScriptFieldType::Int32, ScriptFieldValue(int32_t(17))},
|
||||
{"Label", ScriptFieldType::String, ScriptFieldValue(std::string("Seeded"))},
|
||||
{"SpawnPoint", ScriptFieldType::Vector3, ScriptFieldValue(XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f))}
|
||||
};
|
||||
|
||||
ScriptFieldModel model;
|
||||
ASSERT_TRUE(engine->TryGetScriptFieldModel(component, model));
|
||||
ASSERT_EQ(model.fields.size(), 3u);
|
||||
|
||||
const auto findSnapshot = [&model](const std::string& fieldName) -> const ScriptFieldSnapshot* {
|
||||
const auto it = std::find_if(
|
||||
model.fields.begin(),
|
||||
model.fields.end(),
|
||||
[&fieldName](const ScriptFieldSnapshot& snapshot) {
|
||||
return snapshot.metadata.name == fieldName;
|
||||
});
|
||||
return it != model.fields.end() ? &(*it) : nullptr;
|
||||
};
|
||||
|
||||
const ScriptFieldSnapshot* healthSnapshot = findSnapshot("Health");
|
||||
const ScriptFieldSnapshot* labelSnapshot = findSnapshot("Label");
|
||||
const ScriptFieldSnapshot* spawnPointSnapshot = findSnapshot("SpawnPoint");
|
||||
|
||||
ASSERT_NE(healthSnapshot, nullptr);
|
||||
ASSERT_NE(labelSnapshot, nullptr);
|
||||
ASSERT_NE(spawnPointSnapshot, nullptr);
|
||||
|
||||
EXPECT_TRUE(healthSnapshot->hasDefaultValue);
|
||||
EXPECT_FALSE(healthSnapshot->hasValue);
|
||||
EXPECT_EQ(healthSnapshot->valueSource, ScriptFieldValueSource::DefaultValue);
|
||||
EXPECT_EQ(std::get<int32_t>(healthSnapshot->defaultValue), 17);
|
||||
EXPECT_EQ(std::get<int32_t>(healthSnapshot->value), 17);
|
||||
|
||||
EXPECT_TRUE(labelSnapshot->hasDefaultValue);
|
||||
EXPECT_FALSE(labelSnapshot->hasValue);
|
||||
EXPECT_EQ(labelSnapshot->valueSource, ScriptFieldValueSource::DefaultValue);
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->defaultValue), "Seeded");
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->value), "Seeded");
|
||||
|
||||
EXPECT_TRUE(spawnPointSnapshot->hasDefaultValue);
|
||||
EXPECT_FALSE(spawnPointSnapshot->hasValue);
|
||||
EXPECT_EQ(spawnPointSnapshot->valueSource, ScriptFieldValueSource::DefaultValue);
|
||||
EXPECT_EQ(std::get<XCEngine::Math::Vector3>(spawnPointSnapshot->defaultValue), XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f));
|
||||
EXPECT_EQ(std::get<XCEngine::Math::Vector3>(spawnPointSnapshot->value), XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f));
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldWriteBatchApiReportsPerItemStatusAndAppliesValidValues) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "RuntimeBatchWrite");
|
||||
|
||||
runtime.fieldMetadata = {
|
||||
{"Speed", ScriptFieldType::Float},
|
||||
{"Label", ScriptFieldType::String}
|
||||
};
|
||||
component->GetFieldStorage().SetFieldValue("Speed", uint64_t(7));
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
component->GetFieldStorage().SetFieldValue("LegacyOnly", uint64_t(99));
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
const std::vector<ScriptFieldWriteRequest> requests = {
|
||||
{"Speed", ScriptFieldType::Float, ScriptFieldValue(5.0f)},
|
||||
{"Label", ScriptFieldType::String, ScriptFieldValue(std::string("Edited"))},
|
||||
{"LegacyOnly", ScriptFieldType::UInt64, ScriptFieldValue(uint64_t(100))},
|
||||
{"Missing", ScriptFieldType::Int32, ScriptFieldValue(int32_t(1))},
|
||||
{"Speed", ScriptFieldType::String, ScriptFieldValue(std::string("wrong"))},
|
||||
{"", ScriptFieldType::Int32, ScriptFieldValue(int32_t(2))},
|
||||
{"Label", ScriptFieldType::String, ScriptFieldValue(int32_t(3))}
|
||||
};
|
||||
|
||||
std::vector<ScriptFieldWriteResult> results;
|
||||
EXPECT_FALSE(engine->ApplyScriptFieldWrites(component, requests, results));
|
||||
ASSERT_EQ(results.size(), requests.size());
|
||||
|
||||
EXPECT_EQ(results[0].status, ScriptFieldWriteStatus::Applied);
|
||||
EXPECT_EQ(results[1].status, ScriptFieldWriteStatus::Applied);
|
||||
EXPECT_EQ(results[2].status, ScriptFieldWriteStatus::StoredOnlyField);
|
||||
EXPECT_EQ(results[3].status, ScriptFieldWriteStatus::UnknownField);
|
||||
EXPECT_EQ(results[4].status, ScriptFieldWriteStatus::TypeMismatch);
|
||||
EXPECT_EQ(results[5].status, ScriptFieldWriteStatus::EmptyFieldName);
|
||||
EXPECT_EQ(results[6].status, ScriptFieldWriteStatus::InvalidValue);
|
||||
|
||||
float speed = 0.0f;
|
||||
std::string label;
|
||||
uint64_t legacyOnly = 0;
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Speed", speed));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Label", label));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("LegacyOnly", legacyOnly));
|
||||
EXPECT_FLOAT_EQ(speed, 5.0f);
|
||||
EXPECT_EQ(label, "Edited");
|
||||
EXPECT_EQ(legacyOnly, 99u);
|
||||
|
||||
ASSERT_EQ(runtime.managedSetFieldNames.size(), 2u);
|
||||
EXPECT_EQ(runtime.managedSetFieldNames[0], "Speed");
|
||||
EXPECT_EQ(runtime.managedSetFieldNames[1], "Label");
|
||||
EXPECT_EQ(std::get<float>(runtime.managedFieldValues["Speed"]), 5.0f);
|
||||
EXPECT_EQ(std::get<std::string>(runtime.managedFieldValues["Label"]), "Edited");
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldWriteBatchApiAllowsStoredFieldsWhenScriptClassMetadataIsMissing) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "MissingScript");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
|
||||
const std::vector<ScriptFieldWriteRequest> requests = {
|
||||
{"Label", ScriptFieldType::String, ScriptFieldValue(std::string("Retained"))},
|
||||
{"Missing", ScriptFieldType::Int32, ScriptFieldValue(int32_t(1))}
|
||||
};
|
||||
|
||||
std::vector<ScriptFieldWriteResult> results;
|
||||
EXPECT_FALSE(engine->ApplyScriptFieldWrites(component, requests, results));
|
||||
ASSERT_EQ(results.size(), requests.size());
|
||||
|
||||
EXPECT_EQ(results[0].status, ScriptFieldWriteStatus::Applied);
|
||||
EXPECT_EQ(results[1].status, ScriptFieldWriteStatus::UnknownField);
|
||||
|
||||
std::string label;
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Label", label));
|
||||
EXPECT_EQ(label, "Retained");
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldClearApiReportsPerItemStatusAndClearsStoredAndLiveValues) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "RuntimeFieldClear");
|
||||
|
||||
runtime.fieldMetadata = {
|
||||
{"Label", ScriptFieldType::String},
|
||||
{"Speed", ScriptFieldType::Float}
|
||||
};
|
||||
runtime.fieldDefaultValues = {
|
||||
{"Label", ScriptFieldType::String, ScriptFieldValue(std::string("Seeded"))},
|
||||
{"Speed", ScriptFieldType::Float, ScriptFieldValue(6.5f)}
|
||||
};
|
||||
runtime.managedFieldValues["Speed"] = ScriptFieldValue(41.0f);
|
||||
runtime.managedFieldValues["Label"] = ScriptFieldValue(std::string("LiveValue"));
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Speed", 5.0f);
|
||||
component->GetFieldStorage().SetFieldValue("LegacyOnly", uint64_t(99));
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
const std::vector<ScriptFieldClearRequest> requests = {
|
||||
{"Speed"},
|
||||
{"Label"},
|
||||
{"LegacyOnly"},
|
||||
{"Missing"},
|
||||
{""}
|
||||
};
|
||||
|
||||
std::vector<ScriptFieldClearResult> results;
|
||||
EXPECT_FALSE(engine->ClearScriptFieldOverrides(component, requests, results));
|
||||
ASSERT_EQ(results.size(), requests.size());
|
||||
|
||||
EXPECT_EQ(results[0].status, ScriptFieldClearStatus::Applied);
|
||||
EXPECT_EQ(results[1].status, ScriptFieldClearStatus::Applied);
|
||||
EXPECT_EQ(results[2].status, ScriptFieldClearStatus::Applied);
|
||||
EXPECT_EQ(results[3].status, ScriptFieldClearStatus::UnknownField);
|
||||
EXPECT_EQ(results[4].status, ScriptFieldClearStatus::EmptyFieldName);
|
||||
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("Speed"));
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("Label"));
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("LegacyOnly"));
|
||||
|
||||
ASSERT_EQ(runtime.managedSetFieldNames.size(), 2u);
|
||||
EXPECT_EQ(runtime.managedSetFieldNames[0], "Speed");
|
||||
EXPECT_EQ(runtime.managedSetFieldNames[1], "Label");
|
||||
EXPECT_FLOAT_EQ(std::get<float>(runtime.managedFieldValues["Speed"]), 6.5f);
|
||||
EXPECT_EQ(std::get<std::string>(runtime.managedFieldValues["Label"]), "Seeded");
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldClearApiReportsNoValueToClearForDeclaredFieldWithoutStoredOrLiveValue) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "RuntimeFieldClear");
|
||||
|
||||
runtime.fieldMetadata = {
|
||||
{"Speed", ScriptFieldType::Float}
|
||||
};
|
||||
|
||||
const std::vector<ScriptFieldClearRequest> requests = {
|
||||
{"Speed"}
|
||||
};
|
||||
|
||||
std::vector<ScriptFieldClearResult> results;
|
||||
EXPECT_FALSE(engine->ClearScriptFieldOverrides(component, requests, results));
|
||||
ASSERT_EQ(results.size(), requests.size());
|
||||
|
||||
EXPECT_EQ(results[0].status, ScriptFieldClearStatus::NoValueToClear);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldClearApiAllowsRemovingStoredFieldsWhenScriptClassMetadataIsMissing) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "MissingScript");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
|
||||
const std::vector<ScriptFieldClearRequest> requests = {
|
||||
{"Label"},
|
||||
{"Missing"}
|
||||
};
|
||||
|
||||
std::vector<ScriptFieldClearResult> results;
|
||||
EXPECT_FALSE(engine->ClearScriptFieldOverrides(component, requests, results));
|
||||
ASSERT_EQ(results.size(), requests.size());
|
||||
|
||||
EXPECT_EQ(results[0].status, ScriptFieldClearStatus::Applied);
|
||||
EXPECT_EQ(results[1].status, ScriptFieldClearStatus::UnknownField);
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("Label"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -1,100 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/Scripting/ScriptFieldStorage.h>
|
||||
|
||||
using namespace XCEngine::Math;
|
||||
using namespace XCEngine::Scripting;
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(ScriptFieldStorage_Test, StoresRetrievesAndRemovesSupportedValues) {
|
||||
ScriptFieldStorage storage;
|
||||
|
||||
EXPECT_TRUE(storage.SetFieldValue("Speed", 3.5f));
|
||||
EXPECT_TRUE(storage.SetFieldValue("Accuracy", 0.875));
|
||||
EXPECT_TRUE(storage.SetFieldValue("IsAlive", true));
|
||||
EXPECT_TRUE(storage.SetFieldValue("Health", int32_t(125)));
|
||||
EXPECT_TRUE(storage.SetFieldValue("Score", uint64_t(9001)));
|
||||
EXPECT_TRUE(storage.SetFieldValue("DisplayName", std::string("Player One")));
|
||||
EXPECT_TRUE(storage.SetFieldValue("Offset2D", Vector2(1.0f, 2.0f)));
|
||||
EXPECT_TRUE(storage.SetFieldValue("Velocity", Vector3(3.0f, 4.0f, 5.0f)));
|
||||
EXPECT_TRUE(storage.SetFieldValue("Tint", Vector4(0.1f, 0.2f, 0.3f, 1.0f)));
|
||||
EXPECT_TRUE(storage.SetFieldValue("Target", GameObjectReference{42}));
|
||||
EXPECT_TRUE(storage.SetFieldValue("TargetCamera", ComponentReference{42, 0}));
|
||||
|
||||
float speed = 0.0f;
|
||||
double accuracy = 0.0;
|
||||
bool isAlive = false;
|
||||
int32_t health = 0;
|
||||
uint64_t score = 0;
|
||||
std::string displayName;
|
||||
Vector2 offset2D;
|
||||
Vector3 velocity;
|
||||
Vector4 tint;
|
||||
GameObjectReference target;
|
||||
ComponentReference targetCamera;
|
||||
|
||||
EXPECT_EQ(storage.GetFieldCount(), 11u);
|
||||
EXPECT_TRUE(storage.Contains("Velocity"));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Speed", speed));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Accuracy", accuracy));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("IsAlive", isAlive));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Health", health));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Score", score));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("DisplayName", displayName));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Offset2D", offset2D));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Velocity", velocity));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Tint", tint));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Target", target));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("TargetCamera", targetCamera));
|
||||
|
||||
EXPECT_FLOAT_EQ(speed, 3.5f);
|
||||
EXPECT_DOUBLE_EQ(accuracy, 0.875);
|
||||
EXPECT_TRUE(isAlive);
|
||||
EXPECT_EQ(health, 125);
|
||||
EXPECT_EQ(score, 9001u);
|
||||
EXPECT_EQ(displayName, "Player One");
|
||||
EXPECT_EQ(offset2D, Vector2(1.0f, 2.0f));
|
||||
EXPECT_EQ(velocity, Vector3(3.0f, 4.0f, 5.0f));
|
||||
EXPECT_EQ(tint, Vector4(0.1f, 0.2f, 0.3f, 1.0f));
|
||||
EXPECT_EQ(target, GameObjectReference{42});
|
||||
EXPECT_EQ(targetCamera, (ComponentReference{42, 0}));
|
||||
|
||||
EXPECT_TRUE(storage.Remove("IsAlive"));
|
||||
EXPECT_FALSE(storage.Contains("IsAlive"));
|
||||
EXPECT_EQ(storage.GetFieldCount(), 10u);
|
||||
}
|
||||
|
||||
TEST(ScriptFieldStorage_Test, SerializeRoundTripPreservesFieldValues) {
|
||||
ScriptFieldStorage original;
|
||||
ASSERT_TRUE(original.SetFieldValue("Message", std::string("Ready;Set=Go|100%\nLine2")));
|
||||
ASSERT_TRUE(original.SetFieldValue("Scale", Vector3(2.0f, 3.0f, 4.0f)));
|
||||
ASSERT_TRUE(original.SetFieldValue("Owner", GameObjectReference{778899}));
|
||||
ASSERT_TRUE(original.SetFieldValue("View", ComponentReference{778899, 123456}));
|
||||
ASSERT_TRUE(original.SetFieldValue("Counter", int32_t(-17)));
|
||||
|
||||
const std::string serialized = original.SerializeToString();
|
||||
|
||||
ScriptFieldStorage restored;
|
||||
restored.DeserializeFromString(serialized);
|
||||
|
||||
std::string message;
|
||||
Vector3 scale;
|
||||
GameObjectReference owner;
|
||||
ComponentReference view;
|
||||
int32_t counter = 0;
|
||||
|
||||
EXPECT_TRUE(restored.TryGetFieldValue("Message", message));
|
||||
EXPECT_TRUE(restored.TryGetFieldValue("Scale", scale));
|
||||
EXPECT_TRUE(restored.TryGetFieldValue("Owner", owner));
|
||||
EXPECT_TRUE(restored.TryGetFieldValue("View", view));
|
||||
EXPECT_TRUE(restored.TryGetFieldValue("Counter", counter));
|
||||
|
||||
EXPECT_EQ(message, "Ready;Set=Go|100%\nLine2");
|
||||
EXPECT_EQ(scale, Vector3(2.0f, 3.0f, 4.0f));
|
||||
EXPECT_EQ(owner, GameObjectReference{778899});
|
||||
EXPECT_EQ(view, (ComponentReference{778899, 123456}));
|
||||
EXPECT_EQ(counter, -17);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user