#include "ScriptEngine.h" #include "ScriptBindings.h" #include "scriptstdstring.h" #include "scriptbuilder.h" #include #include #include #include #include "log/log.h" ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), drawFunc(nullptr), currentModule(nullptr), hasValidScript(false) { } ScriptEngine::~ScriptEngine() { Shutdown(); } bool ScriptEngine::Initialize() { engine = asCreateScriptEngine(); if (!engine) { log_error("Failed to create AngelScript engine"); return false; } debugger = new CDebugger(); debugger->SetEngine(engine); // Register std::string RegisterStdString(engine); // Register script bindings ScriptBindings::RegisterAll(engine); // Set message callback int r = engine->SetMessageCallback(asFUNCTION(MessageCallback), nullptr, asCALL_CDECL); if (r < 0) { log_error("Failed to set message callback"); return false; } return true; } void ScriptEngine::Shutdown() { ClearCachedFunctions(); if (debugger) { debugger->SetEngine(nullptr); delete debugger; debugger = nullptr; } if (engine) { engine->ShutDownAndRelease(); engine = nullptr; } currentModule = nullptr; hasValidScript = false; } bool ScriptEngine::CompileScript(const std::string& filename) { if (!engine) return false; // Try to compile in a temporary module first using CScriptBuilder CScriptBuilder tempBuilder; tempBuilder.SetIncludeCallback(IncludeCallback, nullptr); int r = tempBuilder.StartNewModule(engine, "temp"); if (r < 0) { log_error("Failed to start new temp module"); return false; } r = tempBuilder.AddSectionFromFile(filename.c_str()); if (r < 0) { log_error("Failed to add script section from file: %s", filename.c_str()); engine->DiscardModule("temp"); return false; } r = tempBuilder.BuildModule(); if (r < 0) { log_error("Failed to build script: %s", filename.c_str()); engine->DiscardModule("temp"); return false; } // If we get here, compilation succeeded // Now safely replace the main module if (currentModule) { engine->DiscardModule("main"); } // Create new main module with the working code using CScriptBuilder CScriptBuilder mainBuilder; mainBuilder.SetIncludeCallback(IncludeCallback, nullptr); r = mainBuilder.StartNewModule(engine, "main"); if (r >= 0) { r = mainBuilder.AddSectionFromFile(filename.c_str()); } if (r >= 0) { r = mainBuilder.BuildModule(); } // Clean up temp module engine->DiscardModule("temp"); if (r < 0) { // This shouldn't happen since we already tested compilation log_error("Unexpected error when creating main module"); ClearCachedFunctions(); return false; } // Get the newly created module currentModule = engine->GetModule("main"); // Cache new functions updateFunc = currentModule->GetFunctionByName("Update"); drawFunc = currentModule->GetFunctionByName("Draw"); hasValidScript = true; // If debugger is present, add a function breakpoint on Update to stop when Update is entered // The debugger will transform function breakpoints into file breakpoints when the function is entered if (debugger && updateFunc) { debugger->AddFuncBreakPoint("Update"); } log_info("Script compiled and cached: %s", filename.c_str()); return true; } void ScriptEngine::CallScriptFunction(asIScriptFunction* func, float dt) { if (!func || !engine || !hasValidScript) return; asIScriptContext* ctx = engine->CreateContext(); if (!ctx) return; if (debugger) { ctx->SetLineCallback(asMETHOD(CDebugger, LineCallback), debugger, asCALL_THISCALL); } int r = ctx->Prepare(func); if (r < 0) { ctx->Release(); return; } if (func->GetParamCount() == 1) { ctx->SetArgFloat(0, dt); } r = ctx->Execute(); if (r != asEXECUTION_FINISHED) { if (r == asEXECUTION_EXCEPTION) { log_error("Script exception: %s", ctx->GetExceptionString()); } } ctx->Release(); } void ScriptEngine::ClearCachedFunctions() { updateFunc = nullptr; drawFunc = nullptr; hasValidScript = false; } void ScriptEngine::GarbageCollect() { if (engine) { engine->GarbageCollect(); } } void ScriptEngine::AddFileBreakPoint(const std::string &file, int line) { if (debugger) debugger->AddFileBreakPoint(file, line); } void ScriptEngine::AddFuncBreakPoint(const std::string &func) { if (debugger) debugger->AddFuncBreakPoint(func); } void ScriptEngine::MessageCallback(const asSMessageInfo* msg, void*) { // Map AngelScript message types to log levels if (msg->type == asMSGTYPE_ERROR) { log_error("%s (%d): %s", msg->section, msg->row, msg->message); } else if (msg->type == asMSGTYPE_WARNING) { log_warn("%s (%d): %s", msg->section, msg->row, msg->message); } else { log_info("%s (%d): %s", msg->section, msg->row, msg->message); } } int ScriptEngine::IncludeCallback(const char* include, const char* from, CScriptBuilder* builder, void* userParam) { // Extract just the filename from the from path for cleaner logging const char* fromFile = from; if (from) { const char* lastSlash = strrchr(from, '/'); const char* lastBackslash = strrchr(from, '\\'); const char* lastSep = (lastSlash > lastBackslash) ? lastSlash : lastBackslash; if (lastSep) { fromFile = lastSep + 1; } } // Log the include request with clean filenames log_info("Including: %s (from: %s)", include, fromFile ? fromFile : "main"); // Build the full path relative to the including file's directory std::string fullPath; if (from && strlen(from) > 0) { // Extract directory from the 'from' path std::string fromPath(from); size_t lastSlash = fromPath.find_last_of("/\\"); if (lastSlash != std::string::npos) { // Get the directory part std::string directory = fromPath.substr(0, lastSlash + 1); fullPath = directory + include; } else { // No directory in from path, use include as-is fullPath = include; } } else { fullPath = include; } // Try to add the included file int r = builder->AddSectionFromFile(fullPath.c_str()); if (r < 0) { log_error("Failed to include file: %s (tried path: %s)", include, fullPath.c_str()); } else { log_info("Successfully included: %s", include); } return r; } std::string ScriptEngine::ReadFile(const std::string& filename) { std::ifstream file(filename); if (!file) return ""; std::stringstream ss; ss << file.rdbuf(); return ss.str(); }