#include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #endif 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(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 { 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(std::move(settings)); ASSERT_TRUE(runtime->Initialize()) << runtime->GetLastError(); } std::unique_ptr runtime; }; 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 classNames = runtime->GetScriptClassNames("GameScripts"); std::vector 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 fields; ASSERT_TRUE(runtime->TryGetClassFieldMetadata("GameScripts", "ProjectScripts", "ProjectScriptProbe", fields)); const std::vector expectedFields = { {"EnabledOnBoot", ScriptFieldType::Bool}, {"Label", ScriptFieldType::String}, {"Speed", ScriptFieldType::Float}, }; EXPECT_EQ(fields, expectedFields); std::vector 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(defaultValues[0].value), true); EXPECT_EQ(defaultValues[1].fieldName, "Label"); EXPECT_EQ(defaultValues[1].type, ScriptFieldType::String); EXPECT_EQ(std::get(defaultValues[1].value), "ProjectScriptProbe"); EXPECT_EQ(defaultValues[2].fieldName, "Speed"); EXPECT_EQ(defaultValues[2].type, ScriptFieldType::Float); EXPECT_FLOAT_EQ(std::get(defaultValues[2].value), 2.5f); } TEST_F(ProjectScriptAssemblyTest, DiscoversProjectAssetRenderPipelineAssetClasses) { std::vector classes; ASSERT_TRUE(runtime->TryGetAvailableRenderPipelineAssetClasses(classes)); EXPECT_NE( std::find( classes.begin(), classes.end(), ScriptClassDescriptor{ "GameScripts", "ProjectScripts", "ProjectUniversalFeaturePipelineAsset"}), 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 assetRuntime = bridge->CreateAssetRuntime(descriptor); ASSERT_NE(assetRuntime, nullptr); EXPECT_NE(assetRuntime->GetPipelineRendererAsset(), nullptr); std::unique_ptr 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::RHI::Format::R8G8B8A8_UNorm); const XCEngine::Rendering::RenderGraphTextureHandle sourceColor = graphBuilder.ImportTexture( "ProjectManagedPostProcessSource", colorDesc, reinterpret_cast(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(); } } // namespace