First Commit on new Git-Base, yey!

This commit is contained in:
2026-01-22 12:30:53 -05:00
parent 2061d588e7
commit 303b835ba7
93 changed files with 17252 additions and 1138 deletions

View File

@@ -0,0 +1,452 @@
#include "ManagedScriptRuntime.h"
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <vector>
#if MODULARITY_USE_MONO
#include <mono/jit/jit.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/debug-helpers.h>
#include <mono/metadata/mono-config.h>
#include <mono/metadata/threads.h>
#endif
#if MODULARITY_USE_MONO
namespace {
std::string trim(std::string value) {
auto is_space = [](unsigned char c) { return std::isspace(c) != 0; };
while (!value.empty() && is_space(static_cast<unsigned char>(value.front()))) {
value.erase(value.begin());
}
while (!value.empty() && is_space(static_cast<unsigned char>(value.back()))) {
value.pop_back();
}
return value;
}
std::string stripAssemblyQualifier(const std::string& typeName) {
auto comma = typeName.find(',');
if (comma == std::string::npos) {
return trim(typeName);
}
return trim(typeName.substr(0, comma));
}
bool splitNamespaceAndName(const std::string& fullName, std::string& nameSpace, std::string& name) {
auto dot = fullName.rfind('.');
if (dot == std::string::npos) {
nameSpace.clear();
name = fullName;
return !name.empty();
}
nameSpace = fullName.substr(0, dot);
name = fullName.substr(dot + 1);
return !name.empty();
}
std::string monoExceptionToString(MonoObject* exc) {
if (!exc) return "Unknown managed exception";
MonoString* strObj = mono_object_to_string(exc, nullptr);
if (!strObj) return "Managed exception (no ToString)";
char* utf8 = mono_string_to_utf8(strObj);
std::string out = utf8 ? utf8 : "Managed exception (utf8 conversion failed)";
mono_free(utf8);
return out;
}
fs::path resolveMonoRoot() {
const char* env = std::getenv("MODU_MONO_ROOT");
if (env && env[0] != '\0') {
return fs::path(env);
}
std::vector<fs::path> candidates;
fs::path cwd = fs::current_path();
candidates.push_back(cwd / "src" / "ThirdParty" / "mono");
candidates.push_back(cwd / ".." / "src" / "ThirdParty" / "mono");
candidates.push_back(cwd / "ThirdParty" / "mono");
candidates.push_back(cwd / ".." / "ThirdParty" / "mono");
for (const auto& path : candidates) {
std::error_code ec;
if (fs::exists(path, ec)) {
return path;
}
}
return {};
}
void configureMonoFromRoot(const fs::path& root) {
fs::path libDir = root / "lib";
fs::path etcDir = root / "etc";
mono_set_dirs(libDir.string().c_str(), etcDir.string().c_str());
fs::path assembliesDir = root / "lib" / "mono" / "4.5";
if (fs::exists(assembliesDir)) {
mono_set_assemblies_path(assembliesDir.string().c_str());
}
mono_config_parse(nullptr);
}
std::string toKey(const fs::path& assemblyPath, const std::string& typeName) {
return assemblyPath.lexically_normal().string() + "|" + typeName;
}
} // namespace
struct ManagedScriptRuntime::MonoState {
MonoDomain* rootDomain = nullptr;
MonoDomain* scriptDomain = nullptr;
fs::path monoRoot;
};
ManagedScriptRuntime::~ManagedScriptRuntime() {
unloadAll();
monoState.reset();
}
void ManagedScriptRuntime::MonoStateDeleter::operator()(MonoState* state) const {
if (!state) return;
if (state->rootDomain) {
if (state->scriptDomain) {
mono_domain_set(state->rootDomain, false);
mono_domain_unload(state->scriptDomain);
state->scriptDomain = nullptr;
}
mono_jit_cleanup(state->rootDomain);
}
delete state;
}
bool ManagedScriptRuntime::ensureHost(const fs::path& assemblyPath) {
(void)assemblyPath;
if (monoState && monoState->rootDomain && monoState->scriptDomain) return true;
if (!monoState) {
fs::path monoRoot = resolveMonoRoot();
if (monoRoot.empty()) {
lastError = "Mono root not found. Set MODU_MONO_ROOT or vendor Mono in src/ThirdParty/mono.";
return false;
}
auto* state = new MonoState();
state->monoRoot = monoRoot;
configureMonoFromRoot(monoRoot);
state->rootDomain = mono_jit_init_version("Modularity", "v4.0.30319");
if (!state->rootDomain) {
delete state;
lastError = "Failed to initialize Mono JIT";
return false;
}
monoState.reset(state);
std::cerr << "[Managed] Mono root: " << monoRoot << std::endl;
}
if (!monoState->scriptDomain) {
monoState->scriptDomain = mono_domain_create_appdomain(const_cast<char*>("ModularityScripts"), nullptr);
if (!monoState->scriptDomain) {
lastError = "Failed to create Mono appdomain";
return false;
}
}
mono_domain_set(monoState->scriptDomain, false);
mono_thread_attach(monoState->scriptDomain);
return true;
}
static MonoAssembly* loadAssembly(MonoDomain* domain, const fs::path& assemblyPath, MonoImage** outImage,
std::string& error) {
if (!outImage) {
error = "Internal error: missing image output";
return nullptr;
}
*outImage = nullptr;
std::error_code ec;
if (!fs::exists(assemblyPath, ec)) {
error = "Missing managed assembly: " + assemblyPath.string();
return nullptr;
}
mono_domain_set(domain, false);
MonoAssembly* assembly = mono_domain_assembly_open(domain, assemblyPath.string().c_str());
if (!assembly) {
error = "Mono failed to load assembly: " + assemblyPath.string();
return nullptr;
}
MonoImage* image = mono_assembly_get_image(assembly);
if (!image) {
error = "Mono failed to get image: " + assemblyPath.string();
return nullptr;
}
*outImage = image;
return assembly;
}
bool ManagedScriptRuntime::ensureApiInjected(const fs::path& assemblyPath) {
if (apiInjected) return true;
if (!monoState || !monoState->scriptDomain) return false;
std::string error;
MonoImage* image = nullptr;
MonoAssembly* assembly = loadAssembly(monoState->scriptDomain, assemblyPath, &image, error);
if (!assembly || !image) {
lastError = error;
return false;
}
MonoClass* hostClass = mono_class_from_name(image, "ModuCPP", "Host");
if (!hostClass) {
lastError = "Managed class ModuCPP.Host not found";
return false;
}
MonoMethod* setApiMethod = mono_class_get_method_from_name(hostClass, "SetNativeApi", 1);
if (!setApiMethod) {
lastError = "Managed method ModuCPP.Host.SetNativeApi not found";
return false;
}
mono_domain_set(monoState->scriptDomain, false);
mono_thread_attach(monoState->scriptDomain);
intptr_t apiPtr = reinterpret_cast<intptr_t>(&api);
void* args[1] = { &apiPtr };
MonoObject* exc = nullptr;
mono_runtime_invoke(setApiMethod, nullptr, args, &exc);
if (exc) {
lastError = monoExceptionToString(exc);
return false;
}
apiInjected = true;
return true;
}
bool ManagedScriptRuntime::loadModuleMethods(Module& mod, const fs::path& assemblyPath,
const std::string& typeName) {
if (!monoState || !monoState->scriptDomain || typeName.empty()) {
lastError = "Managed script type is required";
return false;
}
std::string error;
MonoImage* image = nullptr;
MonoAssembly* assembly = loadAssembly(monoState->scriptDomain, assemblyPath, &image, error);
if (!assembly || !image) {
lastError = error;
return false;
}
std::string normalized = stripAssemblyQualifier(typeName);
std::string nameSpace;
std::string className;
if (!splitNamespaceAndName(normalized, nameSpace, className)) {
lastError = "Managed script type name is invalid";
return false;
}
MonoClass* klass = mono_class_from_name(image, nameSpace.c_str(), className.c_str());
if (!klass) {
lastError = "Managed type not found: " + normalized;
return false;
}
MonoMethod* inspector = mono_class_get_method_from_name(klass, "Script_OnInspector", 1);
MonoMethod* begin = mono_class_get_method_from_name(klass, "Script_Begin", 2);
MonoMethod* spec = mono_class_get_method_from_name(klass, "Script_Spec", 2);
MonoMethod* testEditor = mono_class_get_method_from_name(klass, "Script_TestEditor", 2);
MonoMethod* update = mono_class_get_method_from_name(klass, "Script_Update", 2);
MonoMethod* tickUpdate = mono_class_get_method_from_name(klass, "Script_TickUpdate", 2);
mod.inspectorMethod = inspector;
mod.beginMethod = begin;
mod.specMethod = spec;
mod.testEditorMethod = testEditor;
mod.updateMethod = update;
mod.tickUpdateMethod = tickUpdate;
if (!inspector && !begin && !spec && !testEditor && !update && !tickUpdate) {
lastError = "No managed script exports found";
return false;
}
return true;
}
ManagedScriptRuntime::Module* ManagedScriptRuntime::getModule(const fs::path& assemblyPath,
const std::string& typeName) {
lastError.clear();
if (assemblyPath.empty()) {
lastError = "Managed assembly path is empty";
return nullptr;
}
std::string key = toKey(assemblyPath, typeName);
auto it = modules.find(key);
if (it != modules.end()) return &it->second;
if (!ensureHost(assemblyPath)) return nullptr;
if (!ensureApiInjected(assemblyPath)) return nullptr;
Module mod;
mod.assemblyPath = assemblyPath;
mod.typeName = typeName;
if (!loadModuleMethods(mod, assemblyPath, typeName)) return nullptr;
modules[key] = mod;
return &modules[key];
}
static bool invokeMonoMethod(MonoDomain* domain, MonoMethod* method, ScriptContext* ctx,
float deltaTime, bool hasDelta, std::string& error) {
if (!method) return false;
mono_domain_set(domain, false);
mono_thread_attach(domain);
intptr_t ctxPtr = reinterpret_cast<intptr_t>(ctx);
void* argsWithDelta[2] = { &ctxPtr, &deltaTime };
void* argsNoDelta[1] = { &ctxPtr };
MonoObject* exc = nullptr;
mono_runtime_invoke(method, nullptr, hasDelta ? argsWithDelta : argsNoDelta, &exc);
if (exc) {
error = monoExceptionToString(exc);
return false;
}
return true;
}
bool ManagedScriptRuntime::invokeInspector(const fs::path& assemblyPath, const std::string& typeName,
ScriptContext& ctx) {
Module* mod = getModule(assemblyPath, typeName);
if (!mod) return false;
MonoMethod* inspector = reinterpret_cast<MonoMethod*>(mod->inspectorMethod);
if (!inspector) {
lastError.clear();
return false;
}
std::string error;
bool ok = invokeMonoMethod(monoState->scriptDomain, inspector, &ctx, 0.0f, false, error);
if (!ok) {
lastError = error;
}
return ok;
}
bool ManagedScriptRuntime::hasInspector(const fs::path& assemblyPath, const std::string& typeName) {
Module* mod = getModule(assemblyPath, typeName);
if (!mod) return false;
if (!mod->inspectorMethod) {
lastError.clear();
return false;
}
return true;
}
void ManagedScriptRuntime::tickModule(const fs::path& assemblyPath, const std::string& typeName,
ScriptContext& ctx, float deltaTime,
bool runSpec, bool runTest) {
Module* mod = getModule(assemblyPath, typeName);
if (!mod) return;
int objId = ctx.object ? ctx.object->id : -1;
MonoMethod* begin = reinterpret_cast<MonoMethod*>(mod->beginMethod);
if (objId >= 0 && begin && mod->beginCalledObjects.find(objId) == mod->beginCalledObjects.end()) {
std::string error;
if (!invokeMonoMethod(monoState->scriptDomain, begin, &ctx, deltaTime, true, error)) {
lastError = error;
return;
}
mod->beginCalledObjects.insert(objId);
}
MonoMethod* tickUpdate = reinterpret_cast<MonoMethod*>(mod->tickUpdateMethod);
MonoMethod* update = reinterpret_cast<MonoMethod*>(mod->updateMethod);
if (tickUpdate || update) {
std::string error;
if (!invokeMonoMethod(monoState->scriptDomain, tickUpdate ? tickUpdate : update,
&ctx, deltaTime, true, error)) {
lastError = error;
return;
}
}
if (runSpec) {
MonoMethod* spec = reinterpret_cast<MonoMethod*>(mod->specMethod);
if (spec) {
std::string error;
if (!invokeMonoMethod(monoState->scriptDomain, spec, &ctx, deltaTime, true, error)) {
lastError = error;
return;
}
}
}
if (runTest) {
MonoMethod* test = reinterpret_cast<MonoMethod*>(mod->testEditorMethod);
if (test) {
std::string error;
if (!invokeMonoMethod(monoState->scriptDomain, test, &ctx, deltaTime, true, error)) {
lastError = error;
return;
}
}
}
}
void ManagedScriptRuntime::unloadAll() {
modules.clear();
apiInjected = false;
lastError.clear();
if (monoState && monoState->rootDomain && monoState->scriptDomain) {
mono_domain_set(monoState->rootDomain, false);
mono_domain_unload(monoState->scriptDomain);
monoState->scriptDomain = nullptr;
}
}
#else
ManagedScriptRuntime::~ManagedScriptRuntime() = default;
struct ManagedScriptRuntime::MonoState {};
void ManagedScriptRuntime::MonoStateDeleter::operator()(MonoState* state) const {
delete state;
}
bool ManagedScriptRuntime::hasInspector(const fs::path& assemblyPath, const std::string& typeName) {
(void)assemblyPath;
(void)typeName;
lastError = "Managed scripts disabled (Mono not built).";
return false;
}
bool ManagedScriptRuntime::invokeInspector(const fs::path& assemblyPath, const std::string& typeName, ScriptContext& ctx) {
(void)assemblyPath;
(void)typeName;
(void)ctx;
lastError = "Managed scripts disabled (Mono not built).";
return false;
}
void ManagedScriptRuntime::tickModule(const fs::path& assemblyPath, const std::string& typeName,
ScriptContext& ctx, float deltaTime, bool runSpec, bool runTest) {
(void)assemblyPath;
(void)typeName;
(void)ctx;
(void)deltaTime;
(void)runSpec;
(void)runTest;
lastError = "Managed scripts disabled (Mono not built).";
}
void ManagedScriptRuntime::unloadAll() {
modules.clear();
monoState.reset();
apiInjected = false;
lastError.clear();
}
#endif