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 <github43132@proton.me>
This commit is contained in:
Ronald Caesar
2026-06-08 11:43:38 -04:00
parent a9ecd3b50b
commit cb12ae4809
3 changed files with 44 additions and 31 deletions
+9
View File
@@ -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 ***/
+1
View File
@@ -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
}
+34 -31
View File
@@ -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;