diff --git a/editor/src/Application.cpp b/editor/src/Application.cpp index 6768c9ed..5adaad94 100644 --- a/editor/src/Application.cpp +++ b/editor/src/Application.cpp @@ -49,12 +49,23 @@ void Application::InitializeScriptingRuntime(const std::string& projectPath) { settings.assemblyDirectory = assemblyDirectory; settings.corlibDirectory = assemblyDirectory; settings.coreAssemblyPath = assemblyDirectory / L"XCEngine.ScriptCore.dll"; - settings.engineAssemblies.push_back( - ::XCEngine::Scripting::MonoScriptRuntime::ManagedAssemblyDescriptor{ - "XCEngine.RenderPipelines.Universal", - assemblyDirectory / L"XCEngine.RenderPipelines.Universal.dll"}); settings.appAssemblyPath = assemblyDirectory / L"GameScripts.dll"; + std::string assemblyDiscoveryError; + if (!::XCEngine::Scripting::MonoScriptRuntime::DiscoverEngineAssemblies( + settings, + &assemblyDiscoveryError)) { + m_scriptRuntimeStatus.statusMessage = + "Failed to discover engine managed assemblies: " + + assemblyDiscoveryError; + logger.Warning( + Debug::LogCategory::Scripting, + m_scriptRuntimeStatus.statusMessage.c_str()); + ::XCEngine::Scripting::ScriptEngine::Get().SetRuntime(nullptr); + ProjectGraphicsSettings::ApplyCurrentSelection(projectPath); + return; + } + std::error_code ec; const bool hasCoreAssembly = fs::exists(settings.coreAssemblyPath, ec); ec.clear(); diff --git a/editor/src/Scripting/EditorScriptAssemblyBuilder.cpp b/editor/src/Scripting/EditorScriptAssemblyBuilder.cpp index 2bd9f4ca..32a3cff6 100644 --- a/editor/src/Scripting/EditorScriptAssemblyBuilder.cpp +++ b/editor/src/Scripting/EditorScriptAssemblyBuilder.cpp @@ -3,9 +3,12 @@ #include "Platform/Win32Utf8.h" #include "Scripting/EditorScriptAssemblyBuilderUtils.h" +#include + #include #include +#include #include #include @@ -203,6 +206,42 @@ EditorScriptAssemblyBuildResult BuildFailure(const std::string& message) { return EditorScriptAssemblyBuildResult{false, message}; } +bool WriteManagedEngineAssemblyManifest( + const std::filesystem::path& outputPath, + const std::vector& assemblyNames, + std::string& outError) { + std::error_code ec; + std::filesystem::create_directories(outputPath.parent_path(), ec); + if (ec) { + outError = + "Failed to create the engine assembly manifest directory: " + + ScriptBuilderPathToUtf8(outputPath.parent_path()); + return false; + } + + std::ofstream output(outputPath, std::ios::out | std::ios::trunc); + if (!output.is_open()) { + outError = + "Failed to create the engine assembly manifest: " + + ScriptBuilderPathToUtf8(outputPath); + return false; + } + + for (const std::string& assemblyName : assemblyNames) { + output << assemblyName << "\n"; + } + + output.close(); + if (!output.good()) { + outError = + "Failed to write the engine assembly manifest: " + + ScriptBuilderPathToUtf8(outputPath); + return false; + } + + return true; +} + bool RunCSharpCompiler( const std::filesystem::path& dotnetExecutable, const std::filesystem::path& cscDllPath, @@ -252,6 +291,9 @@ EditorScriptAssemblyBuildResult EditorScriptAssemblyBuilder::RebuildProjectAssem const fs::path scriptCoreOutputPath = outputDirectory / "XCEngine.ScriptCore.dll"; const fs::path renderPipelinesUniversalOutputPath = outputDirectory / "XCEngine.RenderPipelines.Universal.dll"; + const fs::path engineAssemblyManifestPath = + outputDirectory / + ::XCEngine::Scripting::MonoScriptRuntime::EngineAssemblyManifestFileName; const fs::path gameScriptsOutputPath = outputDirectory / "GameScripts.dll"; const fs::path corlibOutputPath = outputDirectory / "mscorlib.dll"; const fs::path monoCorlibSourcePath = monoRoot / "binary" / "mscorlib.dll"; @@ -346,6 +388,8 @@ EditorScriptAssemblyBuildResult EditorScriptAssemblyBuilder::RebuildProjectAssem } std::string compileError; + const std::vector engineAssemblyNames = { + "XCEngine.RenderPipelines.Universal"}; if (!RunCSharpCompiler( dotnetExecutable, cscDllPath, @@ -402,6 +446,13 @@ EditorScriptAssemblyBuildResult EditorScriptAssemblyBuilder::RebuildProjectAssem return BuildFailure("Failed to build GameScripts.dll: " + compileError); } + if (!WriteManagedEngineAssemblyManifest( + engineAssemblyManifestPath, + engineAssemblyNames, + compileError)) { + return BuildFailure(compileError); + } + return EditorScriptAssemblyBuildResult{ true, "Rebuilt script assemblies in " + ScriptBuilderPathToUtf8(outputDirectory) diff --git a/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h b/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h index de940ea9..ea871a18 100644 --- a/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h +++ b/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h @@ -54,6 +54,14 @@ public: explicit MonoScriptRuntime(Settings settings = {}); ~MonoScriptRuntime() override; + static constexpr const char* EngineAssemblyManifestFileName = + "XCEngine.EngineAssemblies.txt"; + static std::filesystem::path GetEngineAssemblyManifestPath( + const std::filesystem::path& assemblyDirectory); + static bool DiscoverEngineAssemblies( + Settings& ioSettings, + std::string* outError = nullptr); + bool Initialize(); void Shutdown(); @@ -190,7 +198,7 @@ private: MonoImage* image = nullptr; }; - void ResolveSettings(); + bool ResolveSettings(); bool InitializeRootDomain(); bool CreateAppDomain(); void DestroyAppDomain(); diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index eded24d7..65d064b4 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -37,8 +37,11 @@ #include #include +#include #include +#include #include +#include #include namespace XCEngine { @@ -357,6 +360,197 @@ std::string TrimAssemblyName(const std::string& assemblyName) { return assemblyName; } +std::string TrimWhitespace(std::string value) { + auto isWhitespace = [](unsigned char ch) { + return std::isspace(ch) != 0; + }; + + value.erase( + value.begin(), + std::find_if( + value.begin(), + value.end(), + [&](char ch) { + return !isWhitespace(static_cast(ch)); + })); + value.erase( + std::find_if( + value.rbegin(), + value.rend(), + [&](char ch) { + return !isWhitespace(static_cast(ch)); + }).base(), + value.end()); + return value; +} + +bool NormalizeManagedAssemblyDescriptor( + MonoScriptRuntime::ManagedAssemblyDescriptor& descriptor, + const std::filesystem::path& assemblyDirectory, + std::unordered_set& ioAssemblyNames, + std::string* outError) { + descriptor.name = TrimAssemblyName(TrimWhitespace(descriptor.name)); + if (descriptor.name.empty() && !descriptor.path.empty()) { + descriptor.name = + TrimAssemblyName(descriptor.path.stem().string()); + } + + if (descriptor.name.empty()) { + if (outError != nullptr) { + *outError = "Managed engine assembly name is empty."; + } + return false; + } + + if (descriptor.path.empty() && !assemblyDirectory.empty()) { + descriptor.path = assemblyDirectory / (descriptor.name + ".dll"); + } + + if (!descriptor.path.empty()) { + descriptor.path = descriptor.path.lexically_normal(); + } + + if (!ioAssemblyNames.insert(descriptor.name).second) { + if (outError != nullptr) { + *outError = + "Managed engine assembly name is duplicated: " + + descriptor.name; + } + return false; + } + + return true; +} + +bool LoadManagedAssemblyManifest( + const std::filesystem::path& assemblyDirectory, + const std::filesystem::path& manifestPath, + std::vector& outAssemblies, + std::string* outError) { + outAssemblies.clear(); + + std::error_code ec; + if (manifestPath.empty() || + !std::filesystem::exists(manifestPath, ec)) { + return true; + } + + std::ifstream input(manifestPath); + if (!input.is_open()) { + if (outError != nullptr) { + *outError = + "Failed to open managed engine assembly manifest: " + + manifestPath.string(); + } + return false; + } + + std::unordered_set assemblyNames; + std::string line; + size_t lineNumber = 0u; + while (std::getline(input, line)) { + ++lineNumber; + + const size_t commentStart = line.find('#'); + if (commentStart != std::string::npos) { + line.erase(commentStart); + } + + line = TrimWhitespace(std::move(line)); + if (line.empty()) { + continue; + } + + MonoScriptRuntime::ManagedAssemblyDescriptor descriptor; + const size_t separator = line.find('='); + if (separator == std::string::npos) { + descriptor.name = line; + } else { + descriptor.name = line.substr(0u, separator); + std::string relativePath = + TrimWhitespace(line.substr(separator + 1u)); + if (!relativePath.empty()) { + descriptor.path = + (assemblyDirectory / relativePath).lexically_normal(); + } + } + + if (!NormalizeManagedAssemblyDescriptor( + descriptor, + assemblyDirectory, + assemblyNames, + outError)) { + if (outError != nullptr && !outError->empty()) { + *outError += + " Manifest: " + manifestPath.string() + + " line " + std::to_string(lineNumber) + "."; + } + return false; + } + + outAssemblies.push_back(std::move(descriptor)); + } + + return true; +} + +bool DiscoverManagedAssembliesByConvention( + const MonoScriptRuntime::Settings& settings, + std::vector& outAssemblies) { + outAssemblies.clear(); + + std::error_code ec; + if (settings.assemblyDirectory.empty() || + !std::filesystem::exists(settings.assemblyDirectory, ec)) { + return true; + } + + const std::string coreAssemblyName = + TrimAssemblyName(settings.coreAssemblyName); + const std::string appAssemblyName = + TrimAssemblyName(settings.appAssemblyName); + std::unordered_set reservedNames = { + coreAssemblyName, + appAssemblyName, + "mscorlib"}; + + for (std::filesystem::directory_iterator it( + settings.assemblyDirectory, + ec), end; + it != end && !ec; + it.increment(ec)) { + if (ec || !it->is_regular_file(ec)) { + continue; + } + + const std::filesystem::path path = it->path(); + if (path.extension() != ".dll") { + continue; + } + + const std::string assemblyName = + TrimAssemblyName(path.stem().string()); + if (assemblyName.rfind("XCEngine.", 0u) != 0u || + reservedNames.contains(assemblyName)) { + continue; + } + + outAssemblies.push_back( + MonoScriptRuntime::ManagedAssemblyDescriptor{ + assemblyName, + path.lexically_normal()}); + } + + std::sort( + outAssemblies.begin(), + outAssemblies.end(), + [](const MonoScriptRuntime::ManagedAssemblyDescriptor& lhs, + const MonoScriptRuntime::ManagedAssemblyDescriptor& rhs) { + return lhs.name < rhs.name; + }); + return true; +} + MonoScriptRuntime::ManagedAssemblyDescriptor BuildManagedAssemblyDescriptor( const std::string& assemblyName, const std::filesystem::path& assemblyPath) { @@ -3799,8 +3993,10 @@ MonoScriptRuntime::~MonoScriptRuntime() { } bool MonoScriptRuntime::Initialize() { - ResolveSettings(); m_lastError.clear(); + if (!ResolveSettings()) { + return false; + } if (m_initialized) { return true; @@ -4293,7 +4489,61 @@ size_t MonoScriptRuntime::InstanceKeyHasher::operator()(const InstanceKey& key) return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2)); } -void MonoScriptRuntime::ResolveSettings() { +std::filesystem::path MonoScriptRuntime::GetEngineAssemblyManifestPath( + const std::filesystem::path& assemblyDirectory) { + return assemblyDirectory / EngineAssemblyManifestFileName; +} + +bool MonoScriptRuntime::DiscoverEngineAssemblies( + Settings& ioSettings, + std::string* outError) { + if (!ioSettings.engineAssemblies.empty()) { + std::unordered_set assemblyNames; + for (ManagedAssemblyDescriptor& assembly : + ioSettings.engineAssemblies) { + if (!NormalizeManagedAssemblyDescriptor( + assembly, + ioSettings.assemblyDirectory, + assemblyNames, + outError)) { + return false; + } + } + return true; + } + + if (ioSettings.assemblyDirectory.empty()) { + return true; + } + + std::vector discoveredAssemblies; + const std::filesystem::path manifestPath = + GetEngineAssemblyManifestPath(ioSettings.assemblyDirectory); + if (!LoadManagedAssemblyManifest( + ioSettings.assemblyDirectory, + manifestPath, + discoveredAssemblies, + outError)) { + return false; + } + + if (discoveredAssemblies.empty() && + !DiscoverManagedAssembliesByConvention( + ioSettings, + discoveredAssemblies)) { + if (outError != nullptr) { + *outError = + "Failed to discover managed engine assemblies in " + + ioSettings.assemblyDirectory.string(); + } + return false; + } + + ioSettings.engineAssemblies = std::move(discoveredAssemblies); + return true; +} + +bool MonoScriptRuntime::ResolveSettings() { if (!m_settings.coreAssemblyPath.empty() && m_settings.assemblyDirectory.empty()) { m_settings.assemblyDirectory = m_settings.coreAssemblyPath.parent_path(); } @@ -4318,18 +4568,13 @@ void MonoScriptRuntime::ResolveSettings() { } } - for (ManagedAssemblyDescriptor& assembly : m_settings.engineAssemblies) { - if (assembly.name.empty() && !assembly.path.empty()) { - assembly.name = assembly.path.stem().string(); - } - - if (assembly.path.empty() && - !m_settings.assemblyDirectory.empty() && - !assembly.name.empty()) { - assembly.path = - m_settings.assemblyDirectory / (assembly.name + ".dll"); - } + std::string discoveryError; + if (!DiscoverEngineAssemblies(m_settings, &discoveryError)) { + SetError(discoveryError); + return false; } + + return true; } bool MonoScriptRuntime::InitializeRootDomain() { diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index 13f23e18..68d0058c 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -34,6 +34,16 @@ set(XCENGINE_CSC_DLL "C:/Program Files/dotnet/sdk/${XCENGINE_DOTNET_SDK_VERSION} set(XCENGINE_NET472_REFERENCE_DIR "C:/Program Files (x86)/Reference Assemblies/Microsoft/Framework/.NETFramework/v4.7.2") set(XCENGINE_MONO_CORLIB_DIR "${XCENGINE_MONO_ROOT_DIR}/binary" CACHE PATH "Directory containing the bundled Mono corlib") set(XCENGINE_MONO_MSCORLIB_PATH "${XCENGINE_MONO_CORLIB_DIR}/mscorlib.dll") +set(XCENGINE_ENGINE_MANAGED_ASSEMBLY_NAMES + XCEngine.RenderPipelines.Universal) +string(REPLACE ";" "\n" XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST_CONTENT "${XCENGINE_ENGINE_MANAGED_ASSEMBLY_NAMES}") +set( + XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST_SOURCE + "${CMAKE_CURRENT_BINARY_DIR}/Generated/XCEngine.EngineAssemblies.txt") +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/Generated") +file(GENERATE + OUTPUT "${XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST_SOURCE}" + CONTENT "${XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST_CONTENT}\n") set(XCENGINE_SCRIPT_CORE_DLL "${XCENGINE_MANAGED_OUTPUT_DIR}/XCEngine.ScriptCore.dll" CACHE FILEPATH "Generated XCEngine.ScriptCore assembly") set( @@ -41,6 +51,11 @@ set( "${XCENGINE_MANAGED_OUTPUT_DIR}/XCEngine.RenderPipelines.Universal.dll" CACHE FILEPATH "Generated XCEngine.RenderPipelines.Universal assembly") +set( + XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST + "${XCENGINE_MANAGED_OUTPUT_DIR}/XCEngine.EngineAssemblies.txt" + CACHE FILEPATH + "Managed engine assembly manifest copied into the build managed output directory") set(XCENGINE_GAME_SCRIPTS_DLL "${XCENGINE_MANAGED_OUTPUT_DIR}/GameScripts.dll" CACHE FILEPATH "Generated GameScripts assembly") set( XCENGINE_PROJECT_ASSETS_DIR @@ -62,6 +77,11 @@ set( "${XCENGINE_PROJECT_MANAGED_OUTPUT_DIR}/XCEngine.RenderPipelines.Universal.dll" CACHE FILEPATH "Generated render pipelines universal assembly copied into the project script assembly directory") +set( + XCENGINE_PROJECT_ENGINE_MANAGED_ASSEMBLY_MANIFEST + "${XCENGINE_PROJECT_MANAGED_OUTPUT_DIR}/XCEngine.EngineAssemblies.txt" + CACHE FILEPATH + "Managed engine assembly manifest copied into the project script assembly directory") set( XCENGINE_PROJECT_GAME_SCRIPTS_DLL "${XCENGINE_PROJECT_MANAGED_OUTPUT_DIR}/GameScripts.dll" @@ -90,6 +110,12 @@ set( CACHE FILEPATH "RenderPipelines.Universal assembly used by scripting tests for project asset assembly validation" ) +set( + XCENGINE_SCRIPTING_TEST_PROJECT_ENGINE_MANAGED_ASSEMBLY_MANIFEST + "${XCENGINE_SCRIPTING_TEST_PROJECT_MANAGED_OUTPUT_DIR}/XCEngine.EngineAssemblies.txt" + CACHE FILEPATH + "Managed engine assembly manifest used by scripting tests for project asset assembly validation" +) set( XCENGINE_SCRIPTING_TEST_PROJECT_GAME_SCRIPTS_DLL "${XCENGINE_SCRIPTING_TEST_PROJECT_MANAGED_OUTPUT_DIR}/GameScripts.dll" @@ -308,6 +334,16 @@ add_custom_command( VERBATIM COMMENT "Copying mscorlib.dll for Mono runtime resolution") +add_custom_command( + OUTPUT ${XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST} + COMMAND ${CMAKE_COMMAND} -E make_directory ${XCENGINE_MANAGED_OUTPUT_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST_SOURCE} + ${XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST} + DEPENDS ${XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST_SOURCE} + VERBATIM + COMMENT "Copying the managed engine assembly manifest") + add_custom_target( xcengine_managed_assemblies DEPENDS @@ -315,6 +351,7 @@ add_custom_target( ${XCENGINE_RENDER_PIPELINES_UNIVERSAL_DLL} ${XCENGINE_GAME_SCRIPTS_DLL} ${XCENGINE_MANAGED_OUTPUT_DIR}/mscorlib.dll + ${XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST} ) add_custom_command( @@ -367,6 +404,16 @@ add_custom_command( VERBATIM COMMENT "Copying mscorlib.dll into the scripting test project assembly directory") +add_custom_command( + OUTPUT ${XCENGINE_SCRIPTING_TEST_PROJECT_ENGINE_MANAGED_ASSEMBLY_MANIFEST} + COMMAND ${CMAKE_COMMAND} -E make_directory ${XCENGINE_SCRIPTING_TEST_PROJECT_MANAGED_OUTPUT_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST_SOURCE} + ${XCENGINE_SCRIPTING_TEST_PROJECT_ENGINE_MANAGED_ASSEMBLY_MANIFEST} + DEPENDS ${XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST_SOURCE} + VERBATIM + COMMENT "Copying the managed engine assembly manifest into the scripting test project assembly directory") + add_custom_target( xcengine_test_project_managed_assemblies DEPENDS @@ -374,6 +421,7 @@ add_custom_target( ${XCENGINE_SCRIPTING_TEST_PROJECT_RENDER_PIPELINES_UNIVERSAL_DLL} ${XCENGINE_SCRIPTING_TEST_PROJECT_GAME_SCRIPTS_DLL} ${XCENGINE_SCRIPTING_TEST_PROJECT_MONO_MSCORLIB_PATH} + ${XCENGINE_SCRIPTING_TEST_PROJECT_ENGINE_MANAGED_ASSEMBLY_MANIFEST} ) add_custom_command( @@ -426,6 +474,16 @@ add_custom_command( VERBATIM COMMENT "Copying mscorlib.dll into the project script assembly directory") +add_custom_command( + OUTPUT ${XCENGINE_PROJECT_ENGINE_MANAGED_ASSEMBLY_MANIFEST} + COMMAND ${CMAKE_COMMAND} -E make_directory ${XCENGINE_PROJECT_MANAGED_OUTPUT_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST_SOURCE} + ${XCENGINE_PROJECT_ENGINE_MANAGED_ASSEMBLY_MANIFEST} + DEPENDS ${XCENGINE_ENGINE_MANAGED_ASSEMBLY_MANIFEST_SOURCE} + VERBATIM + COMMENT "Copying the managed engine assembly manifest into the project script assembly directory") + add_custom_target( xcengine_project_managed_assemblies ALL DEPENDS @@ -433,5 +491,6 @@ add_custom_target( ${XCENGINE_PROJECT_RENDER_PIPELINES_UNIVERSAL_DLL} ${XCENGINE_PROJECT_GAME_SCRIPTS_DLL} ${XCENGINE_PROJECT_MONO_MSCORLIB_PATH} + ${XCENGINE_PROJECT_ENGINE_MANAGED_ASSEMBLY_MANIFEST} ) diff --git a/tests/editor/CMakeLists.txt b/tests/editor/CMakeLists.txt index fccd5f87..ec004a80 100644 --- a/tests/editor/CMakeLists.txt +++ b/tests/editor/CMakeLists.txt @@ -169,17 +169,12 @@ if(XCENGINE_ENABLE_MONO_SCRIPTING AND TARGET 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_RENDER_PIPELINES_UNIVERSAL_DLL}" - XCENGINE_RENDER_PIPELINES_UNIVERSAL_DLL_CMAKE) file(TO_CMAKE_PATH "${XCENGINE_GAME_SCRIPTS_DLL}" XCENGINE_GAME_SCRIPTS_DLL_CMAKE) target_compile_definitions(editor_tests PRIVATE XCENGINE_ENABLE_MONO_SCRIPTING XCENGINE_TEST_MANAGED_OUTPUT_DIR="${XCENGINE_MANAGED_OUTPUT_DIR_CMAKE}" XCENGINE_TEST_SCRIPT_CORE_DLL="${XCENGINE_SCRIPT_CORE_DLL_CMAKE}" - XCENGINE_TEST_RENDER_PIPELINES_UNIVERSAL_DLL="${XCENGINE_RENDER_PIPELINES_UNIVERSAL_DLL_CMAKE}" XCENGINE_TEST_GAME_SCRIPTS_DLL="${XCENGINE_GAME_SCRIPTS_DLL_CMAKE}" ) endif() diff --git a/tests/editor/test_editor_script_assembly_builder.cpp b/tests/editor/test_editor_script_assembly_builder.cpp index 5be599c4..b63d4456 100644 --- a/tests/editor/test_editor_script_assembly_builder.cpp +++ b/tests/editor/test_editor_script_assembly_builder.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace XCEngine::Editor::Scripting { namespace { @@ -54,10 +55,30 @@ TEST_F(EditorScriptAssemblyBuilderTest, RebuildsProjectScriptAssembliesIntoLibra EditorScriptAssemblyBuilder::RebuildProjectAssemblies(m_projectRoot.string()); ASSERT_TRUE(result.succeeded) << result.message; - EXPECT_TRUE(std::filesystem::exists(m_projectRoot / "Library" / "ScriptAssemblies" / "XCEngine.ScriptCore.dll")); - EXPECT_TRUE(std::filesystem::exists(m_projectRoot / "Library" / "ScriptAssemblies" / "XCEngine.RenderPipelines.Universal.dll")); - EXPECT_TRUE(std::filesystem::exists(m_projectRoot / "Library" / "ScriptAssemblies" / "GameScripts.dll")); - EXPECT_TRUE(std::filesystem::exists(m_projectRoot / "Library" / "ScriptAssemblies" / "mscorlib.dll")); + const std::filesystem::path assemblyDirectory = + m_projectRoot / "Library" / "ScriptAssemblies"; + EXPECT_TRUE(std::filesystem::exists(assemblyDirectory / "XCEngine.ScriptCore.dll")); + EXPECT_TRUE(std::filesystem::exists(assemblyDirectory / "GameScripts.dll")); + EXPECT_TRUE(std::filesystem::exists(assemblyDirectory / "mscorlib.dll")); + EXPECT_TRUE(std::filesystem::exists( + XCEngine::Scripting::MonoScriptRuntime::GetEngineAssemblyManifestPath( + assemblyDirectory))); + + XCEngine::Scripting::MonoScriptRuntime::Settings settings; + settings.assemblyDirectory = assemblyDirectory; + settings.coreAssemblyPath = assemblyDirectory / "XCEngine.ScriptCore.dll"; + settings.appAssemblyPath = assemblyDirectory / "GameScripts.dll"; + std::string engineAssemblyError; + ASSERT_TRUE( + XCEngine::Scripting::MonoScriptRuntime::DiscoverEngineAssemblies( + settings, + &engineAssemblyError)) + << engineAssemblyError; + ASSERT_FALSE(settings.engineAssemblies.empty()); + for (const auto& assembly : settings.engineAssemblies) { + EXPECT_TRUE(std::filesystem::exists(assembly.path)) + << assembly.path.string(); + } } #ifdef XCENGINE_ENABLE_MONO_SCRIPTING @@ -80,12 +101,13 @@ TEST_F(EditorScriptAssemblyBuilderTest, RebuildFailsWhileLoadedAssemblyIsStillHe settings.assemblyDirectory = m_projectRoot / "Library" / "ScriptAssemblies"; settings.corlibDirectory = settings.assemblyDirectory; settings.coreAssemblyPath = settings.assemblyDirectory / "XCEngine.ScriptCore.dll"; - settings.engineAssemblies.push_back( - XCEngine::Scripting::MonoScriptRuntime::ManagedAssemblyDescriptor{ - "XCEngine.RenderPipelines.Universal", - settings.assemblyDirectory / - "XCEngine.RenderPipelines.Universal.dll"}); settings.appAssemblyPath = settings.assemblyDirectory / "GameScripts.dll"; + std::string engineAssemblyError; + ASSERT_TRUE( + XCEngine::Scripting::MonoScriptRuntime::DiscoverEngineAssemblies( + settings, + &engineAssemblyError)) + << engineAssemblyError; auto runtime = std::make_unique(settings); ASSERT_TRUE(runtime->Initialize()) << runtime->GetLastError(); diff --git a/tests/editor/test_play_session_controller_scripting.cpp b/tests/editor/test_play_session_controller_scripting.cpp index 447995f4..1e155e91 100644 --- a/tests/editor/test_play_session_controller_scripting.cpp +++ b/tests/editor/test_play_session_controller_scripting.cpp @@ -15,6 +15,7 @@ #include #include #include +#include using namespace XCEngine::Components; using namespace XCEngine::Scripting; @@ -27,10 +28,6 @@ MonoScriptRuntime::Settings CreateMonoSettings() { settings.assemblyDirectory = XCENGINE_TEST_MANAGED_OUTPUT_DIR; settings.corlibDirectory = XCENGINE_TEST_MANAGED_OUTPUT_DIR; settings.coreAssemblyPath = XCENGINE_TEST_SCRIPT_CORE_DLL; - settings.engineAssemblies.push_back( - MonoScriptRuntime::ManagedAssemblyDescriptor{ - "XCEngine.RenderPipelines.Universal", - XCENGINE_TEST_RENDER_PIPELINES_UNIVERSAL_DLL}); settings.appAssemblyPath = XCENGINE_TEST_GAME_SCRIPTS_DLL; return settings; } @@ -163,7 +160,15 @@ protected: engine = &ScriptEngine::Get(); engine->OnRuntimeStop(); - runtime = std::make_unique(CreateMonoSettings()); + MonoScriptRuntime::Settings settings = CreateMonoSettings(); + std::string engineAssemblyError; + ASSERT_TRUE( + MonoScriptRuntime::DiscoverEngineAssemblies( + settings, + &engineAssemblyError)) + << engineAssemblyError; + + runtime = std::make_unique(std::move(settings)); ASSERT_TRUE(runtime->Initialize()) << runtime->GetLastError(); engine->SetRuntime(runtime.get()); diff --git a/tests/scripting/CMakeLists.txt b/tests/scripting/CMakeLists.txt index 6cbbffdc..de4548bb 100644 --- a/tests/scripting/CMakeLists.txt +++ b/tests/scripting/CMakeLists.txt @@ -60,16 +60,11 @@ if(TARGET 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_RENDER_PIPELINES_UNIVERSAL_DLL}" - XCENGINE_RENDER_PIPELINES_UNIVERSAL_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_RENDER_PIPELINES_UNIVERSAL_DLL=\"${XCENGINE_RENDER_PIPELINES_UNIVERSAL_DLL_CMAKE}\" XCENGINE_TEST_GAME_SCRIPTS_DLL=\"${XCENGINE_GAME_SCRIPTS_DLL_CMAKE}\" ) endif() @@ -88,10 +83,6 @@ if(TARGET xcengine_test_project_managed_assemblies) TO_CMAKE_PATH "${XCENGINE_SCRIPTING_TEST_PROJECT_SCRIPT_CORE_DLL}" XCENGINE_PROJECT_SCRIPT_CORE_DLL_CMAKE) - file( - TO_CMAKE_PATH - "${XCENGINE_SCRIPTING_TEST_PROJECT_RENDER_PIPELINES_UNIVERSAL_DLL}" - XCENGINE_PROJECT_RENDER_PIPELINES_UNIVERSAL_DLL_CMAKE) file( TO_CMAKE_PATH "${XCENGINE_SCRIPTING_TEST_PROJECT_GAME_SCRIPTS_DLL}" @@ -100,7 +91,6 @@ if(TARGET xcengine_test_project_managed_assemblies) 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_RENDER_PIPELINES_UNIVERSAL_DLL=\"${XCENGINE_PROJECT_RENDER_PIPELINES_UNIVERSAL_DLL_CMAKE}\" XCENGINE_TEST_PROJECT_GAME_SCRIPTS_DLL=\"${XCENGINE_PROJECT_GAME_SCRIPTS_DLL_CMAKE}\" ) endif() diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index 757c9fc6..1285f6f2 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -121,10 +121,6 @@ MonoScriptRuntime::Settings CreateMonoSettings() { settings.assemblyDirectory = XCENGINE_TEST_MANAGED_OUTPUT_DIR; settings.corlibDirectory = XCENGINE_TEST_MANAGED_OUTPUT_DIR; settings.coreAssemblyPath = XCENGINE_TEST_SCRIPT_CORE_DLL; - settings.engineAssemblies.push_back( - MonoScriptRuntime::ManagedAssemblyDescriptor{ - "XCEngine.RenderPipelines.Universal", - XCENGINE_TEST_RENDER_PIPELINES_UNIVERSAL_DLL}); settings.appAssemblyPath = XCENGINE_TEST_GAME_SCRIPTS_DLL; return settings; } @@ -139,7 +135,14 @@ protected: XCEngine::Resources::ResourceManager::Get().Shutdown(); XCEngine::Rendering::Pipelines::ClearConfiguredManagedRenderPipelineAssetDescriptor(); - runtime = std::make_unique(CreateMonoSettings()); + std::string engineAssemblyError; + MonoScriptRuntime::Settings settings = CreateMonoSettings(); + ASSERT_TRUE( + MonoScriptRuntime::DiscoverEngineAssemblies( + settings, + &engineAssemblyError)) + << engineAssemblyError; + runtime = std::make_unique(std::move(settings)); ASSERT_TRUE(runtime->Initialize()) << runtime->GetLastError(); engine->SetRuntime(runtime.get()); diff --git a/tests/scripting/test_project_script_assembly.cpp b/tests/scripting/test_project_script_assembly.cpp index dc024254..b97a7f7e 100644 --- a/tests/scripting/test_project_script_assembly.cpp +++ b/tests/scripting/test_project_script_assembly.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #ifdef _WIN32 @@ -58,26 +59,11 @@ std::filesystem::path ResolveProjectGameScriptsDllPath() { return ResolveProjectManagedOutputDirectory() / "GameScripts.dll"; } -std::filesystem::path ResolveProjectRenderPipelinesUniversalDllPath() { - constexpr const char* configuredPath = - XCENGINE_TEST_PROJECT_RENDER_PIPELINES_UNIVERSAL_DLL; - if (configuredPath[0] != '\0') { - return std::filesystem::path(configuredPath); - } - - return ResolveProjectManagedOutputDirectory() / - "XCEngine.RenderPipelines.Universal.dll"; -} - MonoScriptRuntime::Settings CreateProjectMonoSettings() { MonoScriptRuntime::Settings settings; settings.assemblyDirectory = ResolveProjectManagedOutputDirectory(); settings.corlibDirectory = settings.assemblyDirectory; settings.coreAssemblyPath = ResolveProjectScriptCoreDllPath(); - settings.engineAssemblies.push_back( - MonoScriptRuntime::ManagedAssemblyDescriptor{ - "XCEngine.RenderPipelines.Universal", - ResolveProjectRenderPipelinesUniversalDllPath()}); settings.appAssemblyPath = ResolveProjectGameScriptsDllPath(); return settings; } @@ -86,12 +72,24 @@ class ProjectScriptAssemblyTest : public ::testing::Test { protected: void SetUp() override { ASSERT_TRUE(std::filesystem::exists(ResolveProjectScriptCoreDllPath())); - ASSERT_TRUE( - std::filesystem::exists( - ResolveProjectRenderPipelinesUniversalDllPath())); ASSERT_TRUE(std::filesystem::exists(ResolveProjectGameScriptsDllPath())); - runtime = std::make_unique(CreateProjectMonoSettings()); + 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(); }