[Hooks] Use a better unique identifier for registry tables.

Initially used the function reference of the original function, but this caused issues with inherited functions. This commit changes it so that we're using our own internal hook function reference as the identifier.
This commit is contained in:
FireSiku 2019-01-16 01:25:45 -05:00
parent bf67d37ad8
commit b409b9b01b

View file

@ -92,16 +92,16 @@ end
-- For any given original function, return the newest entry of the hook_chain.
-- Since all hooks of the chain contain the call to the previous one, we don't need to do any manual loops.
-- This continues until the end of the chain, where the original function is called.
local function get_hook_chain(orig)
local function get_hook_chain(orig, unique_id)
local hook_registry = _hooks
local hooks = hook_registry[HOOK_TYPE_NORMAL][orig]
local hooks = hook_registry[HOOK_TYPE_NORMAL][unique_id]
if hooks and #hooks > 0 then
return hooks[#hooks]
end
-- We can't simply return orig here, or it would cause origins to depend on load order.
return function(...)
if hook_registry[HOOK_TYPE_ORIGIN][orig] then
return hook_registry[HOOK_TYPE_ORIGIN][orig](...)
if hook_registry[HOOK_TYPE_ORIGIN][unique_id] then
return hook_registry[HOOK_TYPE_ORIGIN][unique_id](...)
else
return orig(...)
end
@ -109,11 +109,12 @@ local function get_hook_chain(orig)
end
-- Returns a table containing hook data inside of it.
local function create_hook_data(mod, obj, handler, hook_type)
local function create_hook_data(mod, obj, orig, handler, hook_type)
return {
active = mod:is_enabled(),
hook_type = hook_type,
handler = handler,
orig = orig,
obj = obj,
}
end
@ -122,12 +123,13 @@ end
-- Note: If a previous hook is removed from the table, these functions wouldn't be updated
-- This would break the chain, solution is to not remove the hooks, simply make them inactive
-- Make sure inactive hooks that rely on the chain still call the next function seamlessly.
local function create_specialized_hook(mod, orig, hook_type)
local function create_specialized_hook(mod, unique_id, hook_type)
local func
local hook_data = _registry[mod][orig]
local hook_data = _registry[mod][unique_id]
local orig = hook_data.orig
-- Determine the previous function in the hook stack
local previous_hook = get_hook_chain(orig)
local previous_hook = get_hook_chain(orig, unique_id)
if hook_type == HOOK_TYPE_NORMAL then
func = function(...)
@ -160,12 +162,14 @@ end
-- Once all hooks that are part of the chain have been executed, we can go over the safe hooks.
-- Note: We need to keep the return values in mind in case another function depends on them.
-- At this point in the execution, Obj has already been type-checked and cannot be a string anymore.
-- We then use this internal hook as a unique identifier, which can we can also call by using obj[method]
local function create_internal_hook(orig, obj, method)
local fn = function(...)
local hook_chain = get_hook_chain(orig)
local fn
fn = function(...)
local hook_chain = get_hook_chain(orig, fn)
local num_values, values = get_return_values( hook_chain(...) )
local safe_hooks = _hooks[HOOK_TYPE_SAFE][orig]
local safe_hooks = _hooks[HOOK_TYPE_SAFE][fn]
if safe_hooks and #safe_hooks > 0 then
for i = 1, #safe_hooks do safe_hooks[i](...) end
end
@ -175,31 +179,33 @@ local function create_internal_hook(orig, obj, method)
if not _origs[obj] then _origs[obj] = {} end
_origs[obj][method] = orig
obj[method] = fn
return fn
end
-- This function handles the handling the hook data and adding them to the registry.
-- Origin Hooks have to be unique by nature so we have to make sure we don't allow multiple mods to do it.
local function create_hook(mod, orig, obj, method, handler, func_name, hook_type)
mod:info("(%s): Hooking '%s' from [%s] (Origin: %s)", func_name, method, obj, orig)
local unique_id
if not is_orig_hooked(obj, method) then
create_internal_hook(orig, obj, method)
unique_id = create_internal_hook(orig, obj, method)
end
-- Check to make sure it wasn't hooked before
local hook_data = _registry[mod][orig]
local hook_data = _registry[mod][unique_id]
if not hook_data then
_registry[mod][orig] = create_hook_data(mod, obj, handler, hook_type)
_registry[mod][unique_id] = create_hook_data(mod, obj, orig, handler, hook_type)
local hook_registry = _hooks[hook_type]
if hook_type == HOOK_TYPE_ORIGIN then
if hook_registry[orig] then
if hook_registry[unique_id] then
mod:error("(%s): Attempting to hook origin of already hooked function %s", func_name, method)
else
hook_registry[orig] = create_specialized_hook(mod, orig, hook_type)
hook_registry[unique_id] = create_specialized_hook(mod, unique_id, hook_type)
end
else
table.insert(hook_registry[orig], create_specialized_hook(mod, orig, hook_type) )
table.insert(hook_registry[unique_id], create_specialized_hook(mod, unique_id, hook_type) )
end
else
-- If hook_data already exists and it's the same hook_type, we can safely change the hook handler.
@ -319,8 +325,8 @@ local function generic_hook_toggle(mod, obj, method, enabled_state)
local orig = get_orig_function(obj, method)
if _registry[mod][orig] then
_registry[mod][orig].active = enabled_state
if _registry[mod][obj[method]] then
_registry[mod][obj[method]].active = enabled_state
else
-- This has the potential for mod-breaking behavior, but not guaranteed
mod:warning("(%s): trying to toggle hook that doesn't exist: %s", func_name, method)