A hot-reloading method supporting Lua modules

I wrote a little script to support hot reloading of lua modules.

The example code below shows how it could be used

local hotr = require('scripts.hotr')

local Game = hotr.MakeReloadable('Game')

function Game.New()
    local o = {}
    setmetatable(o, self.__mt)
    -- This just ensures that you can set values after reloading, and it will save it to
    -- o instead of the new class returned by hotr.MakeReloadable
    -- (I could bake this into MakeReloadable)
    self.__mt.__new_index = function(obj, key, value)
        rawset(obj, key, value)
    end
    return o
end

function Game:LogStatus()
    -- print('Game status ...')  -- Uncomment after reloading
end

This allows you to change the code for a class, which will be reflected in all active objects.

It uses a proxy metatable that allows you to reload a module, create a new class entirely, and then hook that onto the proxy metatable held by the objects.

This is the hotr module.

-- hotr.lua
----------
local M = {}

-- Named map of metatables, e.g. 'game.player'.
local metatable_map = {}

function M.Reload(modname)
	if not package.loaded[modname] then
		print(string.format('%s not loaded anyway ...', modname))
	end
	package.loaded[modname] = nil
	if package.loaded[modname] then
		print(string.format('Huh ... %s should not be loaded right now', modname))
	end

	local succ, vals = pcall(function() require(modname) end)
	if succ then
		return vals
	end
	
	print(string.format("### ERROR: Failed to load %s\n---\n%s\n===", modname, vals))
end

-- Registers metatable the first time, returns afterwards.
---@param class_name string
function M.GetMt(class_name)
	if metatable_map[class_name] == nil then
		metatable_map[class_name] = {}
	end

	local clz = metatable_map[class_name]
	return clz
end

function M.GetClass(class_name)
	return getmetatable(M.GetMt(class_name))
end

function M.MakeReloadable(class_name, prototype)
	local class = { __mt = M.GetMt(class_name) }

	-- The prototype can be referenced in the object constructor.
	if prototype ~= nil then
		class.__prototype = prototype
	end

	setmetatable(class.__mt, class)
	class.__mt.__index = class


	return class
end

return M
3 Likes