From cb12ae48098ef142914b5f3a2569d3eddedbf40a Mon Sep 17 00:00:00 2001 From: Ronald Caesar Date: Mon, 8 Jun 2026 11:43:38 -0400 Subject: [PATCH] tools/dashboard: make hot reloading safer If a script fails to load or lua throws an error during execution, the dashboard aborts which could get annoying when working with lua and FFI bindings without proper IntelliSense. This commit makes the dashboard stay alive during a hot reloading or lua runtime error. The dashboard will then only reload the lua file if it has been modified. This adds 'dashboard_backend_recover()' in dashboard_backend.h to recover lua's stack during a runtime error. This function simply destroys and reinit the ImGui window context. The dashboard will then run with a blank frame until the lua script has been reloaded. Signed-off-by: Ronald Caesar --- tools/dashboard/dashboard_backend.cpp | 9 ++++ tools/dashboard/dashboard_backend.h | 1 + tools/dashboard/main.c | 65 ++++++++++++++------------- 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/tools/dashboard/dashboard_backend.cpp b/tools/dashboard/dashboard_backend.cpp index 1219c13c..2ea7c869 100644 --- a/tools/dashboard/dashboard_backend.cpp +++ b/tools/dashboard/dashboard_backend.cpp @@ -47,6 +47,7 @@ extern "C" void dashboard_backend_render(void) { + (void)ImGui::GetCurrentContext(); ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); } @@ -58,4 +59,12 @@ extern "C" ImGui::DestroyContext(); } } + +void +dashboard_backend_recover(GLFWwindow *window) +{ + dashboard_backend_shutdown(); + dashboard_backend_init(window); +} + /*** end of file ***/ diff --git a/tools/dashboard/dashboard_backend.h b/tools/dashboard/dashboard_backend.h index e7d42ce4..68b3854e 100644 --- a/tools/dashboard/dashboard_backend.h +++ b/tools/dashboard/dashboard_backend.h @@ -12,6 +12,7 @@ extern "C" void dashboard_backend_new_frame(void); void dashboard_backend_render(void); void dashboard_backend_shutdown(void); + void dashboard_backend_recover(GLFWwindow *window); #ifdef __cplusplus } diff --git a/tools/dashboard/main.c b/tools/dashboard/main.c index 56584470..ba113ce7 100644 --- a/tools/dashboard/main.c +++ b/tools/dashboard/main.c @@ -63,12 +63,7 @@ main(void) lua_getfield(dashboard_context.lua, -1, "path"); const char *current_path = lua_tostring(dashboard_context.lua, -1); - bool hot_reload_status = reload_lua_script(dashboard_context.lua, &logger); - - if (false == hot_reload_status) - { - return EXIT_FAILURE; - } + bool is_script_valid = reload_lua_script(dashboard_context.lua, &logger); GLFWwindow *BAL_RESTRICT window = dashboard_context.window; lua_State *BAL_RESTRICT lua = dashboard_context.lua; @@ -83,41 +78,51 @@ main(void) // Hot-Reloading Check. if (current_mtime > last_mtime) { - last_mtime = current_mtime; - hot_reload_status = reload_lua_script(lua, &logger); + const bool previous_hot_reload_failed = !is_script_valid; + last_mtime = current_mtime; + is_script_valid = reload_lua_script(lua, &logger); - if (false == hot_reload_status) + if (BAL_UNLIKELY(is_script_valid && previous_hot_reload_failed)) { - break; + BAL_LOG_INFO(&logger, "Lua script reloaded successfully."); } } dashboard_backend_new_frame(); - lua_getglobal(lua, "dashboard_render"); - const int top_of_stack = -1; - if (lua_isfunction(lua, top_of_stack)) + if (BAL_LIKELY(true == is_script_valid)) { - lua_pushlightuserdata(lua, dashboard_backend_get_context()); - const int function_arguments = 1; - const int function_return_values = 0; - const int function_error_handler = 0; - if (lua_pcall(lua, function_arguments, function_return_values, function_error_handler) - != LUA_OK) + lua_getglobal(lua, "dashboard_render"); + const int top_of_stack = -1; + + if (BAL_LIKELY(lua_isfunction(lua, top_of_stack))) { - BAL_LOG_ERROR(&logger, - "Failed to call render lua render: %s", - lua_tostring(lua, top_of_stack)); + lua_pushlightuserdata(lua, dashboard_backend_get_context()); + const int function_arguments = 1; + const int function_return_values = 0; + const int function_error_handler = 0; + if (lua_pcall( + lua, function_arguments, function_return_values, function_error_handler) + != LUA_OK) + { + BAL_LOG_ERROR(&logger, + "Failed to call render lua render: %s", + lua_tostring(lua, top_of_stack)); + const int elements_to_pop_from_stack = 1; + lua_pop(lua, elements_to_pop_from_stack); + is_script_valid = false; + dashboard_backend_recover(window); + } + } + else + { + BAL_LOG_ERROR(&logger, "Failed to hot reload, lua render is missing."); const int elements_to_pop_from_stack = 1; lua_pop(lua, elements_to_pop_from_stack); + is_script_valid = false; + dashboard_backend_recover(window); } } - else - { - BAL_LOG_ERROR(&logger, "Failed to hot reload, lua render is missing."); - const int elements_to_pop_from_stack = 1; - lua_pop(lua, elements_to_pop_from_stack); - } dashboard_backend_render(); glfwSwapBuffers(window); @@ -158,9 +163,7 @@ reload_lua_script(lua_State *BAL_RESTRICT lua, bal_logger_t *BAL_RESTRICT logger if (luaL_dofile(lua, LUA_SCRIPT_PATH) != LUA_OK) { const int top_of_stack = -1; - BAL_LOG_ERROR(logger, - "Aborting Program: failed to load Lua script: %s", - lua_tostring(lua, top_of_stack)); + BAL_LOG_ERROR(logger, "Failed to load Lua script: %s.", lua_tostring(lua, top_of_stack)); const int elements_to_pop_from_stack = 1; lua_pop(lua, elements_to_pop_from_stack); return false;