fix: resolve crashes on invalid script hot reload
This commit is contained in:
@@ -14,6 +14,7 @@ public:
|
||||
private:
|
||||
ScriptEngine scriptEngine;
|
||||
HotReload* hotReload;
|
||||
bool scriptCompilationError;
|
||||
|
||||
static const int WINDOW_WIDTH = 800;
|
||||
static const int WINDOW_HEIGHT = 600;
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user