From 28407e9ebde0db06ef3462a8a2e5f0296f9b795c Mon Sep 17 00:00:00 2001 From: Nick Koirala Date: Wed, 5 Nov 2025 23:36:44 +1300 Subject: [PATCH] fix: resolve crashes on invalid script hot reload --- include/Application.h | 1 + include/ScriptEngine.h | 3 ++ scripts/test.as | 5 +-- src/Application.cpp | 17 +++++++-- src/ScriptEngine.cpp | 83 ++++++++++++++++++++++++++++++++++-------- 5 files changed, 86 insertions(+), 23 deletions(-) diff --git a/include/Application.h b/include/Application.h index d524cdd..c50413f 100644 --- a/include/Application.h +++ b/include/Application.h @@ -14,6 +14,7 @@ public: private: ScriptEngine scriptEngine; HotReload* hotReload; + bool scriptCompilationError; static const int WINDOW_WIDTH = 800; static const int WINDOW_HEIGHT = 600; diff --git a/include/ScriptEngine.h b/include/ScriptEngine.h index f294d44..b6f294f 100644 --- a/include/ScriptEngine.h +++ b/include/ScriptEngine.h @@ -21,7 +21,10 @@ private: asIScriptEngine* engine; asIScriptFunction* updateFunc; asIScriptFunction* drawFunc; + asIScriptModule* currentModule; + bool hasValidScript; static void MessageCallback(const asSMessageInfo* msg, void* param); std::string ReadFile(const std::string& filename); + void ClearCachedFunctions(); }; \ No newline at end of file diff --git a/scripts/test.as b/scripts/test.as index c3b7c19..209cfb9 100644 --- a/scripts/test.as +++ b/scripts/test.as @@ -2,11 +2,10 @@ float x = 50; float y = 100; void Update(float dt) { - x += 50 * dt; // move text horizontally + x += 50 * dt; if (x > 800) x = 0; } void Draw() { - DrawText("Hello from AngelScript!", int(x), int(y), 20, 0xFF0000FF); - + DrawText("Hello from AngelScript - Working perfectly!", int(x), int(y), 20, 0xFF0000FF); } \ No newline at end of file diff --git a/src/Application.cpp b/src/Application.cpp index 4816c42..d458f4c 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -7,7 +7,7 @@ const char* Application::WINDOW_TITLE = "Raylib + AngelScript"; const char* Application::SCRIPT_FILE = "scripts/test.as"; -Application::Application() : hotReload(nullptr) { +Application::Application() : hotReload(nullptr), scriptCompilationError(false) { } Application::~Application() { @@ -29,7 +29,7 @@ bool Application::Initialize() { hotReload = new HotReload(SCRIPT_FILE); // Compile initial script - scriptEngine.CompileScript(SCRIPT_FILE); + scriptCompilationError = !scriptEngine.CompileScript(SCRIPT_FILE); return true; } @@ -49,7 +49,11 @@ void Application::Run() { void Application::Update(float deltaTime) { // Check for hot reload if (hotReload && hotReload->CheckForChanges()) { - scriptEngine.CompileScript(SCRIPT_FILE); + bool success = scriptEngine.CompileScript(SCRIPT_FILE); + scriptCompilationError = !success; + if (!success) { + std::cout << "Script compilation failed - keeping previous version\n"; + } } // Call script Update function @@ -60,7 +64,12 @@ void Application::Draw() { BeginDrawing(); ClearBackground(RAYWHITE); - DrawText("Modify scripts/test.as to hot-reload!", 50, 50, 20, DARKGRAY); + // Show script error status in top left if there's an error, otherwise show normal message + if (scriptCompilationError) { + DrawText("SCRIPT ERROR - Check console for details", 10, 10, 16, RED); + } else { + DrawText("Modify scripts/test.as to hot-reload!", 50, 50, 20, DARKGRAY); + } // Call script Draw function scriptEngine.CallScriptFunction(scriptEngine.GetDrawFunction()); diff --git a/src/ScriptEngine.cpp b/src/ScriptEngine.cpp index 224add8..19d671a 100644 --- a/src/ScriptEngine.cpp +++ b/src/ScriptEngine.cpp @@ -6,7 +6,7 @@ #include #include -ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), drawFunc(nullptr) { +ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), drawFunc(nullptr), currentModule(nullptr), hasValidScript(false) { } ScriptEngine::~ScriptEngine() { @@ -37,54 +37,105 @@ bool ScriptEngine::Initialize() { } void ScriptEngine::Shutdown() { + ClearCachedFunctions(); if (engine) { engine->ShutDownAndRelease(); engine = nullptr; } - updateFunc = nullptr; - drawFunc = nullptr; + currentModule = nullptr; + hasValidScript = false; } bool ScriptEngine::CompileScript(const std::string& filename) { if (!engine) return false; - engine->GarbageCollect(); - asIScriptModule* mod = engine->GetModule("main", asGM_ALWAYS_CREATE); - std::string code = ReadFile(filename); if (code.empty()) { std::cerr << "Failed to read script file: " << filename << "\n"; return false; } - int r = mod->AddScriptSection(filename.c_str(), code.c_str()); - if (r < 0) return false; + // Try to compile in a temporary module first + asIScriptModule* tempMod = engine->GetModule("temp", asGM_ALWAYS_CREATE); + + int r = tempMod->AddScriptSection(filename.c_str(), code.c_str()); + if (r < 0) { + std::cerr << "Failed to add script section for: " << filename << "\n"; + engine->DiscardModule("temp"); + return false; + } - r = mod->Build(); - if (r < 0) return false; + r = tempMod->Build(); + if (r < 0) { + std::cerr << "Failed to build script: " << filename << "\n"; + engine->DiscardModule("temp"); + return false; + } - // Cache Update(float dt) and Draw() functions if they exist - updateFunc = mod->GetFunctionByName("Update"); - drawFunc = mod->GetFunctionByName("Draw"); + // 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 + currentModule = engine->GetModule("main", asGM_ALWAYS_CREATE); + r = currentModule->AddScriptSection(filename.c_str(), code.c_str()); + if (r >= 0) { + r = currentModule->Build(); + } + + // Clean up temp module + engine->DiscardModule("temp"); + + if (r < 0) { + // This shouldn't happen since we already tested compilation + std::cerr << "Unexpected error when creating main module\n"; + ClearCachedFunctions(); + return false; + } + + // Cache new functions + updateFunc = currentModule->GetFunctionByName("Update"); + drawFunc = currentModule->GetFunctionByName("Draw"); + hasValidScript = true; std::cout << "Script compiled and cached: " << filename << "\n"; return true; } void ScriptEngine::CallScriptFunction(asIScriptFunction* func, float dt) { - if (!func || !engine) return; + if (!func || !engine || !hasValidScript) return; asIScriptContext* ctx = engine->CreateContext(); - ctx->Prepare(func); + if (!ctx) return; + + int r = ctx->Prepare(func); + if (r < 0) { + ctx->Release(); + return; + } if (func->GetParamCount() == 1) { ctx->SetArgFloat(0, dt); } - ctx->Execute(); + r = ctx->Execute(); + if (r != asEXECUTION_FINISHED) { + if (r == asEXECUTION_EXCEPTION) { + std::cerr << "Script exception: " << ctx->GetExceptionString() << "\n"; + } + } + ctx->Release(); } +void ScriptEngine::ClearCachedFunctions() { + updateFunc = nullptr; + drawFunc = nullptr; + hasValidScript = false; +} + void ScriptEngine::GarbageCollect() { if (engine) { engine->GarbageCollect();