[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. -- 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. -- 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. -- 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 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 if hooks and #hooks > 0 then
return hooks[#hooks] return hooks[#hooks]
end end
-- We can't simply return orig here, or it would cause origins to depend on load order. -- We can't simply return orig here, or it would cause origins to depend on load order.
return function(...) return function(...)
if hook_registry[HOOK_TYPE_ORIGIN][orig] then if hook_registry[HOOK_TYPE_ORIGIN][unique_id] then
return hook_registry[HOOK_TYPE_ORIGIN][orig](...) return hook_registry[HOOK_TYPE_ORIGIN][unique_id](...)
else else
return orig(...) return orig(...)
end end
@ -109,11 +109,12 @@ local function get_hook_chain(orig)
end end
-- Returns a table containing hook data inside of it. -- 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 { return {
active = mod:is_enabled(), active = mod:is_enabled(),
hook_type = hook_type, hook_type = hook_type,
handler = handler, handler = handler,
orig = orig,
obj = obj, obj = obj,
} }
end end
@ -122,12 +123,13 @@ end
-- Note: If a previous hook is removed from the table, these functions wouldn't be updated -- 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 -- 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. -- 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 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 -- 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 if hook_type == HOOK_TYPE_NORMAL then
func = function(...) 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. -- 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. -- 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. -- 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 function create_internal_hook(orig, obj, method)
local fn = function(...) local fn
local hook_chain = get_hook_chain(orig) fn = function(...)
local hook_chain = get_hook_chain(orig, fn)
local num_values, values = get_return_values( hook_chain(...) ) 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 if safe_hooks and #safe_hooks > 0 then
for i = 1, #safe_hooks do safe_hooks[i](...) end for i = 1, #safe_hooks do safe_hooks[i](...) end
end end
@ -175,31 +179,33 @@ local function create_internal_hook(orig, obj, method)
if not _origs[obj] then _origs[obj] = {} end if not _origs[obj] then _origs[obj] = {} end
_origs[obj][method] = orig _origs[obj][method] = orig
obj[method] = fn obj[method] = fn
return fn
end end
-- This function handles the handling the hook data and adding them to the registry. -- 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. -- 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) 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) 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 if not is_orig_hooked(obj, method) then
create_internal_hook(orig, obj, method) unique_id = create_internal_hook(orig, obj, method)
end end
-- Check to make sure it wasn't hooked before -- 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 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] local hook_registry = _hooks[hook_type]
if hook_type == HOOK_TYPE_ORIGIN then 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) mod:error("(%s): Attempting to hook origin of already hooked function %s", func_name, method)
else else
hook_registry[orig] = create_specialized_hook(mod, orig, hook_type) hook_registry[unique_id] = create_specialized_hook(mod, unique_id, hook_type)
end end
else 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 end
else else
-- If hook_data already exists and it's the same hook_type, we can safely change the hook handler. -- 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) local orig = get_orig_function(obj, method)
if _registry[mod][orig] then if _registry[mod][obj[method]] then
_registry[mod][orig].active = enabled_state _registry[mod][obj[method]].active = enabled_state
else else
-- This has the potential for mod-breaking behavior, but not guaranteed -- 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) mod:warning("(%s): trying to toggle hook that doesn't exist: %s", func_name, method)