From b409b9b01b1d456bf83f7ba37b38f2f6a507deb9 Mon Sep 17 00:00:00 2001 From: FireSiku Date: Wed, 16 Jan 2019 01:25:45 -0500 Subject: [PATCH] [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. --- vmf/scripts/mods/vmf/modules/core/hooks.lua | 44 ++++++++++++--------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/vmf/scripts/mods/vmf/modules/core/hooks.lua b/vmf/scripts/mods/vmf/modules/core/hooks.lua index a0c7434..03de458 100644 --- a/vmf/scripts/mods/vmf/modules/core/hooks.lua +++ b/vmf/scripts/mods/vmf/modules/core/hooks.lua @@ -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)