fix: resolve crashes on invalid script hot reload

This commit is contained in:
2025-11-05 23:36:44 +13:00
parent 29d52c4578
commit 28407e9ebd
5 changed files with 86 additions and 23 deletions

View File

@@ -14,6 +14,7 @@ public:
private:
ScriptEngine scriptEngine;
HotReload* hotReload;
bool scriptCompilationError;
static const int WINDOW_WIDTH = 800;
static const int WINDOW_HEIGHT = 600;

View File

@@ -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();
};

View File

@@ -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);
}

View File

@@ -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());

View File

@@ -6,7 +6,7 @@
#include <sstream>
#include <assert.h>
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();