Files
wnsrc/lua/includes/modules/hook.lua
lifestorm 9c918c46e5 Upload
2024-08-04 23:12:27 +03:00

287 lines
7.2 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local gmod = gmod
local pairs = pairs
local setmetatable = setmetatable
local isstring = isstring
local isnumber = isnumber
local isbool = isbool
local isfunction = isfunction
local insert = table.insert
local IsValid = IsValid
local type = type
local ErrorNoHaltWithStack = ErrorNoHaltWithStack
-- I just do this so glua-lint doesn't rage at me
do
_G.HOOK_MONITOR_HIGH = -2
_G.HOOK_HIGH = -1
_G.HOOK_NORMAL = 0
_G.HOOK_LOW = 1
_G.HOOK_MONITOR_LOW = 2
end
local HOOK_MONITOR_HIGH = HOOK_MONITOR_HIGH
local HOOK_HIGH = HOOK_HIGH
local HOOK_NORMAL = HOOK_NORMAL
local HOOK_LOW = HOOK_LOW
local HOOK_MONITOR_LOW = HOOK_MONITOR_LOW
module("hook")
local events = {}
local function find_hook(event, name)
for i = 1, event.n, 4 do
local _name = event[i]
if _name and _name == name then
return i
end
end
end
--[[
we are making a new event table so we don't mess up anything
when adding/removing hooks while hook.Call is running, this is how it works:
1- When (adding/removing a hook)/(editing a hook priority), we create a new event table to avoid messing up hook.Call call order if it's running,
and the old event table will be shadowed and can only be accessed from hook.Call if it's running
2- We make old event table have __index method to make sure if any hook got removed/edited we (stop it from running)/(run the new function)
]]
local function copy_event(event, event_name)
local new_event = {}
do
for i = 1, event.n do
local v = event[i]
if v then
insert(new_event, v)
end
end
new_event.n = #new_event
end
-- we use proxies here just to make __index work
-- https://stackoverflow.com/a/3122136
local proxy = {}
do
for i = 1, event.n do
proxy[i] = event[i]
event[i] = nil
end
proxy.n = event.n
event.n = nil
end
setmetatable(event, {
__index = function(_, key)
-- make event.n work
if isstring(key) then
return proxy[key]
end
local name = proxy[key - 1]
if not name then return end
local parent = events[event_name]
-- if hook got removed then don't run it
local pos = find_hook(parent, name)
if not pos then return end
-- if hook priority changed then it should be treated as a new hook, don't run it
if parent[pos + 3 --[[priority]]] ~= proxy[key + 2 --[[priority]]] then return end
return parent[pos + 1]
end
})
return new_event
end
--[[---------------------------------------------------------
Name: Add
Args: string hookName, any identifier, function func
Desc: Add a hook to listen to the specified event.
-----------------------------------------------------------]]
function Add(event_name, name, func, priority)
if not isstring(event_name) then ErrorNoHaltWithStack("bad argument #1 to 'Add' (string expected, got " .. type(event_name) .. ")") return end
if not isfunction(func) then ErrorNoHaltWithStack("bad argument #3 to 'Add' (function expected, got " .. type(func) .. ")") return end
local notValid = name == nil or isnumber(name) or isbool(name) or isfunction(name) or not name.IsValid or not IsValid(name)
if not isstring(name) and notValid then ErrorNoHaltWithStack("bad argument #2 to 'Add' (string expected, got " .. type(name) .. ")") return end
local real_func = func
if not isstring(name) then
func = function(...)
local isvalid = name.IsValid
if isvalid and isvalid(name) then
return real_func(name, ...)
end
Remove(event_name, name)
end
end
if not isnumber(priority) then
priority = HOOK_NORMAL
elseif priority < HOOK_MONITOR_HIGH then
priority = HOOK_MONITOR_HIGH
elseif priority > HOOK_MONITOR_LOW then
priority = HOOK_MONITOR_LOW
end
-- disallow returning in monitor hooks
if priority == HOOK_MONITOR_HIGH or priority == HOOK_MONITOR_LOW then
local _func = func
func = function(...)
_func(...)
end
end
local event = events[event_name]
if not event then
event = {
n = 0,
}
events[event_name] = event
end
local pos
if event then
local _pos = find_hook(event, name)
-- if hook exists and priority changed then remove the old one because it has to be treated as a new hook
if _pos and event[_pos + 3] ~= priority then
Remove(event_name, name)
else
-- just update the hook here because nothing changed but the function
pos = _pos
end
end
event = events[event_name]
if pos then
event[pos + 1] = func
event[pos + 2] = real_func
return
end
if priority == HOOK_MONITOR_LOW then
local n = event.n
event[n + 1] = name
event[n + 2] = func
event[n + 3] = real_func
event[n + 4] = priority
else
local event_pos = 4
for i = 4, event.n, 4 do
local _priority = event[i]
if priority < _priority then
if i < event_pos then
event_pos = i
end
elseif priority >= _priority then
event_pos = i + 4
end
end
insert(event, event_pos - 3, name)
insert(event, event_pos - 2, func)
insert(event, event_pos - 1, real_func)
insert(event, event_pos, priority)
end
event.n = event.n + 4
end
--[[---------------------------------------------------------
Name: Remove
Args: string hookName, identifier
Desc: Removes the hook with the given indentifier.
-----------------------------------------------------------]]
function Remove(event_name, name)
local event = events[event_name]
if not event then return end
local pos = find_hook(event, name)
if pos then
event[pos] = nil --[[name]]
event[pos + 1] = nil --[[func]]
event[pos + 2] = nil --[[real_func]]
event[pos + 3] = nil --[[priority]]
end
events[event_name] = copy_event(event, event_name)
end
--[[---------------------------------------------------------
Name: GetTable
Desc: Returns a table of all hooks.
-----------------------------------------------------------]]
function GetTable()
local new_events = {}
for event_name, event in pairs(events) do
local hooks = {}
for i = 1, event.n, 4 do
local name = event[i]
if name then
hooks[name] = event[i + 2] --[[real_func]]
end
end
new_events[event_name] = hooks
end
return new_events
end
--[[---------------------------------------------------------
Name: Call
Args: string hookName, table gamemodeTable, vararg args
Desc: Calls hooks associated with the hook name.
-----------------------------------------------------------]]
function Call(event_name, gm, ...)
local event = events[event_name]
if event then
local i, n = 2, event.n
::loop::
local func = event[i]
if func then
local a, b, c, d, e, f = func(...)
if a ~= nil then
return a, b, c, d, e, f
end
end
i = i + 4
if i <= n then
goto loop
end
end
--
-- Call the gamemode function
--
if not gm then return end
local GamemodeFunction = gm[event_name]
if not GamemodeFunction then return end
return GamemodeFunction(gm, ...)
end
--[[---------------------------------------------------------
Name: Run
Args: string hookName, vararg args
Desc: Calls hooks associated with the hook name.
-----------------------------------------------------------]]
function Run(name, ...)
return Call(name, gmod and gmod.GetGamemode() or nil, ...)
end