Skip to content

Instantly share code, notes, and snippets.

@waldnercharles
Last active May 26, 2025 20:46
Show Gist options
  • Save waldnercharles/a3e314afccfac40ce4fbf42b83093c54 to your computer and use it in GitHub Desktop.
Save waldnercharles/a3e314afccfac40ce4fbf42b83093c54 to your computer and use it in GitHub Desktop.
-- Shallow copy a table
local function shallow_copy(t)
local copy = {}
for k, v in pairs(t) do
copy[k] = v
end
return copy
end
-- Escape reserved characters
local function escape_reserved_characters(str)
return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1")
end
-- Remove leading and trailing whitepsace
local function trim(str, chars)
if not chars then
return str:match("^[%s]*(.-)[%s]*$")
end
chars = escape_reserved_characters(chars)
return str:match("^[" .. chars .. "]*(.-)[" .. chars .. "]*$")
end
-- Hotswap every module that has been loaded.
function hotswap_all()
for k, v in pairs(package.loaded) do
print(v)
local ok, err = hotswap(k)
end
end
-- Hotswap a specific module. This will update its values in the globals table with new values.
function hotswap(module_name)
local old_globals = shallow_copy(_G)
local already_updated = {}
local function update_table_recursive(old, new)
-- Break loops
if already_updated[old] then
return
end
already_updated[old] = true
local old_metatable, new_metatable = getmetatable(old), getmetatable(new)
if old_metatable and new_metatable then
update_table_recursive(old_metatable, new_metatable)
end
for k, v in pairs(new) do
if type(old[k]) == "table" and type(v) == "table" then
update_table_recursive(old[k], v)
else
old[k] = v
end
end
end
local success, module = pcall(require, module_name)
module = success and module or nil
local err
local function on_error(msg)
for k in pairs(_G) do
_G[k] = old_globals[k]
end
err = trim(msg)
end
xpcall(function()
package.loaded[module_name] = nil
local new_module = require(module_name)
if type(module) == "table" and type(new_module) == "table" then
update_table_recursive(module, new_module)
end
for k, v in pairs(old_globals) do
if v ~= _G[k] and type(v) == "table" and type(_G[k]) == "table" then
update_table_recursive(v, _G[k])
_G[k] = v
end
end
end, on_error)
package.loaded[module_name] = module
if err then
return false, err
end
return true, nil
end
namespace fs = std::filesystem;
std::string normalize_path(const fs::path &path)
{
std::string path_str = path.string();
std::replace(path_str.begin(), path_str.end(), '\\', '/');
return path_str;
}
bool hot_reload_dir(const fs::path &base_dir)
{
static fs::file_time_type last_write_time = fs::file_time_type::min();
fs::file_time_type max_last_write_time = last_write_time;
std::stack<fs::path> dirs;
dirs.emplace("");
while (!dirs.empty()) {
fs::path current_dir = dirs.top();
dirs.pop();
for (const auto &entry : fs::directory_iterator(base_dir / current_dir)) {
const fs::file_status entry_status = entry.status();
fs::path entry_path = current_dir / entry.path().filename(); // I feel like using this operator is going to get me cancelled.
if (fs::is_regular_file(entry_status) && entry_path.extension() == ".lua") {
fs::file_time_type entry_last_write_time = fs::last_write_time(entry);
if (entry_last_write_time > last_write_time) {
max_last_write_time = max_last_write_time > entry_last_write_time ? max_last_write_time : entry_last_write_time;
bool ok;
String err;
String lua_module = normalize_path(entry_path.replace_extension()).c_str();
REF_CallLuaFunction(L, "hotswap", { ok, err }, lua_module);
if (!ok) {
printf("Failed to hotswap %s: %s\n", lua_module.c_str(), err.c_str());
} else {
printf("Hotswapped %s\n", lua_module.c_str());
}
}
} else if (fs::is_directory(entry_status)) {
dirs.emplace(entry_path);
}
}
}
bool updated = max_last_write_time > last_write_time;
last_write_time = max_last_write_time;
return updated;
}
bool hot_reload()
{
return hot_reload_dir(fs::current_path());
}
void hot_reload()
{
static uint64_t last_modified = 0;
uint64_t max_last_modified = last_modified;
const char *ext = sintern(".lua");
for (const Path &f : Directory::enumerate("/")) {
CF_Stat stat = {};
if (cf_sintern(cf_path_get_ext(f)) == ext && !cf_is_error(cf_fs_stat(f, &stat)) && stat.last_modified_time > last_modified) {
max_last_modified = cf_max(max_last_modified, stat.last_modified_time);
bool ok;
String err;
REF_CallLuaFunction(L, "hotswap", { ok, err }, f.filename_no_ext());
if (!ok) {
printf("Failed to hotswap %s: %s\n", f.c_str(), err.c_str());
} else {
printf("Hotswapped %s\n", f.c_str());
}
}
}
last_modified = max_last_modified;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment