refactor(srp): unify engine managed assembly discovery

This commit is contained in:
2026-04-19 05:17:42 +08:00
parent 5fa209ab5d
commit 11a03a4b46
11 changed files with 458 additions and 71 deletions

View File

@@ -37,8 +37,11 @@
#include <mono/metadata/reflection.h>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <fstream>
#include <limits>
#include <unordered_set>
#include <utility>
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<unsigned char>(ch));
}));
value.erase(
std::find_if(
value.rbegin(),
value.rend(),
[&](char ch) {
return !isWhitespace(static_cast<unsigned char>(ch));
}).base(),
value.end());
return value;
}
bool NormalizeManagedAssemblyDescriptor(
MonoScriptRuntime::ManagedAssemblyDescriptor& descriptor,
const std::filesystem::path& assemblyDirectory,
std::unordered_set<std::string>& 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<MonoScriptRuntime::ManagedAssemblyDescriptor>& 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<std::string> 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<MonoScriptRuntime::ManagedAssemblyDescriptor>& 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<std::string> 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<std::string> 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<ManagedAssemblyDescriptor> 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() {