Advertisement
code_gs

Untitled

Apr 8th, 2019
200
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 11.42 KB | None | 0 0
  1. -- A hook system that iterates using a doubly linked list
  2. -- This allows for efficient removal without having to make
  3. -- any adjustments to the iteration process
  4. -- There are three types of hooks: pre-listeners, returning hooks, and post-listeners
  5. -- Only returning hooks can affect the values returned by hook.Call
  6. -- while listeners are passive hooks that cannot interrupt one another
  7. -- Aditionally, post-listeners receive a read-only table and count of all returns
  8. -- All hook types have an optional priority argument that can be used to guarantee call order
  9. -- The highest priority is called first, making the iteration order [inf -> -inf]
  10.  
  11. local gmod = gmod
  12. local select = select
  13. local newproxy = newproxy
  14. local ErrorNoHalt = ErrorNoHalt
  15. local setmetatable = setmetatable
  16. local table_insert = table.insert
  17. local table_remove = table.remove
  18.  
  19. hook = {}
  20.  
  21. --- Event object structure
  22.  
  23. -- Root of the linked list
  24. local PRE_HOOK_ROOT = 1
  25. local MAIN_HOOK_ROOT = 2
  26. local POST_HOOK_ROOT = 3
  27.  
  28. -- Hashtable to find hook objects by identifier
  29. -- Key = any identifier
  30. -- Value = hook object
  31. local PRE_ID_TO_INDEX = 4
  32. local MAIN_ID_TO_INDEX = 5
  33. local POST_ID_TO_INDEX = 6
  34.  
  35. -- Lookup hashtable for tails of every active priority
  36. -- This allows for quick insertion given an active priority number
  37. -- Key = Number[-inf, inf] priority
  38. -- Value = hook object
  39. local PRE_PRIORITY_LOOKUP = 7
  40. local MAIN_PRIORITY_LOOKUP = 8
  41. local POST_PRIORITY_LOOKUP = 9
  42.  
  43. -- Sorted array (from highest to lowest) of active priorities
  44. -- This is needed as hashtables don't have explicit ordering
  45. -- thus inserting a priority with no active hooks requires
  46. -- finding a hole between two other priorities
  47. -- Key = Number[1, inf) index
  48. -- Value = Number[-inf, inf] priority
  49. local PRE_PRIORITY_LIST = 10
  50. local MAIN_PRIORITY_LIST = 11
  51. local POST_PRIORITY_LIST = 12
  52.  
  53. -- Number[0, inf) of active priorities
  54. -- This is the length of the *_PRIORITY_LIST array
  55. local PRE_PRIORITY_COUNT = 13
  56. local MAIN_PRIORITY_COUNT = 14
  57. local POST_PRIORITY_COUNT = 15
  58.  
  59.  
  60. --- Hook object structure
  61.  
  62. local HOOK_PREV = 1 -- Previous hook object in the list
  63. local HOOK_NEXT = 2 -- Next hook object in the list
  64. local HOOK_PRIORITY = 3 -- Number[-inf, inf] priority of the hook
  65.  
  66. local Hooks = {}
  67.  
  68. local function TestValid(obj)
  69.     return obj.IsValid
  70. end
  71.  
  72. local function CreateHookObject(func, priority)
  73.     return setmetatable({
  74.         [HOOK_PREV] = nil,
  75.         [HOOK_NEXT] = nil,
  76.         [HOOK_PRIORITY] = priority
  77.     }, {__call = func})
  78. end
  79.  
  80. local function AddHook(event, name, func, priority, hooktbl, HOOK_ROOT, PRIORITY_LIST, PRIORITY_LOOKUP, PRIORITY_COUNT, ID_TO_INDEX)
  81.     if (event == nil) then
  82.         ErrorNoHalt("")
  83.         return
  84.     end
  85.    
  86.     -- hook.Add("event", function() end, PRIORITY)
  87.     if (priority == nil) then
  88.         if (!isfunction(name)) then
  89.             ErrorNoHalt("")
  90.             return
  91.         end
  92.        
  93.         -- Shift hook.Add* arguments over one
  94.         priority = func
  95.         func = name
  96.         name = nil
  97.     end
  98.    
  99.     if (priority == nil) then
  100.         priority = 0
  101.     elseif (!isnumber(priority)) then
  102.         ErrorNoHalt("")
  103.         return
  104.     end
  105.    
  106.     -- Automatically remove hooks that use an identifier with
  107.     -- an IsValid method when that method is no longer valid
  108.     if (name != nil && !isstring(name)) then
  109.         local success, IsValid = pcall(TestValid, name)
  110.        
  111.         if (success && isfunction(IsValid)) then
  112.             local oldfunc = func
  113.             func = function(...)
  114.                 if (!IsValid(name)) then
  115.                     hook.Remove(event, name)
  116.                     return nil
  117.                 end
  118.                
  119.                 return oldfunc(name, ...)
  120.             end
  121.         end
  122.     end
  123.    
  124.     local hooktbl = Hooks[event]
  125.    
  126.     if (hooktbl == nil) then
  127.         hooktbl = {
  128.             [PRE_HOOK_ROOT] = nil,
  129.             [MAIN_HOOK_ROOT] = nil,
  130.             [POST_HOOK_ROOT] = nil,
  131.            
  132.             [PRE_PRIORITY_LIST] = {},
  133.             [MAIN_PRIORITY_LIST] = {},
  134.             [POST_PRIORITY_LIST] = {},
  135.             [PRE_PRIORITY_LOOKUP] = {},
  136.             [MAIN_PRIORITY_LOOKUP] = {},
  137.             [POST_PRIORITY_LOOKUP] = {},
  138.             [PRE_PRIORITY_COUNT] = 0,
  139.             [MAIN_PRIORITY_COUNT] = 0,
  140.             [POST_PRIORITY_COUNT] = 0,
  141.            
  142.             [PRE_ID_TO_INDEX] = {},
  143.             [MAIN_ID_TO_INDEX] = {},
  144.             [POST_ID_TO_INDEX] = {}
  145.         }
  146.        
  147.         Hooks[event] = hooktbl
  148.     end
  149.    
  150.     local hookobj
  151.    
  152.     if (name == nil) then
  153.         hookobj = CreateHookObject(func, priority)
  154.     else
  155.         hookobj = hooktbl[ID_TO_INDEX][name]
  156.        
  157.         if (hookobj == nil) then
  158.             hookobj = CreateHookObject(func, priority)
  159.             hooktbl[ID_TO_INDEX][name] = hookobj
  160.         else
  161.             -- Update the function
  162.             getmetatable(hookobj).__call = func
  163.             local prevpriority = hookobj[HOOK_PRIORITY]
  164.            
  165.             -- If we have the same priority as before
  166.             -- no need to do anything else
  167.             if (priority == prevpriority) then
  168.                 return
  169.             end
  170.            
  171.             hookobj[HOOK_PRIORITY] = priority
  172.            
  173.             -- The priority has changed, remove the object from the list
  174.             -- and reinsert it as it were new
  175.            
  176.             local lastobj = hookobj[HOOK_PREV]
  177.             local nextobj = hookobj[HOOK_NEXT]
  178.            
  179.             -- If we're the root, promote the next object
  180.             if (lastobj == nil) then
  181.                 -- If we changed priority but we're the only object in the list
  182.                 -- keep our position and exit
  183.                 if (nextobj == nil) then
  184.                     return
  185.                 end
  186.                
  187.                 hooktbl[HOOK_ROOT] = nextobj
  188.                 nextobj[HOOK_PREV] = nil
  189.             else
  190.                 lastobj[HOOK_NEXT] = nextobj
  191.                
  192.                 if (nextobj != nil) then
  193.                     nextobj[HOOK_PREV] = lastobj
  194.                 end
  195.             end
  196.            
  197.             -- Remove the previous links
  198.             hookobj[HOOK_PREV] = nil
  199.             hookobj[HOOK_NEXT] = nil
  200.         end
  201.     end
  202.    
  203.     if (hooktbl[HOOK_ROOT] == nil) then
  204.         -- Only hook in the list
  205.         hooktbl[HOOK_ROOT] = hookobj
  206.         hooktbl[PRIORITY_LOOKUP][priority] = hookobj
  207.         hooktbl[PRIORITY_COUNT] = 1
  208.         hooktbl[PRIORITY_LIST][1] = priority
  209.     else
  210.         local lookup = hooktbl[PRIORITY_LOOKUP]
  211.         local prioritytail = lookup[priority]
  212.        
  213.         if (prioritytail == nil) then
  214.             local count = hooktbl[PRIORITY_COUNT]
  215.            
  216.             -- We already know we're gonna have to insert a new priority
  217.             -- Update the count now so we don't have to do the same for multiple branches
  218.             hooktbl[PRIORITY_COUNT] = count + 1
  219.            
  220.             local prioritylist = hooktbl[PRIORITY_LIST]
  221.            
  222.             for i = 1, count do
  223.                 if (prioritylist[i] < priority) then
  224.                     table_insert(prioritylist, i, priority)
  225.                    
  226.                     -- If we're the highest priority
  227.                     -- insert at the root and exit
  228.                     if (i == 1) then
  229.                         local prevroot = hooktbl[HOOK_ROOT]
  230.                         hookobj[HOOK_NEXT] = prevroot
  231.                         prevroot[HOOK_PREV] = hookobj
  232.                         hooktbl[HOOK_ROOT] = hookobj
  233.                         lookup[priority] = hookobj
  234.                        
  235.                         return
  236.                     end
  237.                    
  238.                     -- Use the closest priority on the lesser end as our insertion point
  239.                     prioritytail = lookup[prioritylist[i - 1]]
  240.                    
  241.                     goto PriorityFound
  242.                 end
  243.             end
  244.            
  245.             -- No lower priority found, we must be the lowest priority
  246.             prioritylist[count + 1] = priority
  247.             prioritytail = lookup[prioritylist[count]]
  248.            
  249.             ::PriorityFound::
  250.         end
  251.        
  252.         -- Linked list insertion
  253.         -- prioritytail[HOOK_PREV] -> prioritytail --> hookobj --> prioritytail[HOOK_NEXT]
  254.         hookobj[HOOK_PREV] = prioritytail
  255.         hookobj[HOOK_NEXT] = prioritytail[HOOK_NEXT]
  256.         prioritytail[HOOK_NEXT] = hookobj
  257.        
  258.         -- Update the priority's tail
  259.         lookup[priority] = hookobj
  260.     end
  261. end
  262.  
  263. function hook.Add(event, name, func, priority --[[= 0]])
  264.     AddHook(event, name, func, priority, hooktbl, MAIN_HOOK_ROOT, MAIN_PRIORITY_LIST, MAIN_PRIORITY_LOOKUP, MAIN_PRIORITY_COUNT, MAIN_ID_TO_INDEX)
  265. end
  266.  
  267. function hook.AddPreListener(event, name, func, priority --[[= 0]])
  268.     AddHook(event, name, func, priority, hooktbl, PRE_HOOK_ROOT, PRE_PRIORITY_LIST, PRE_PRIORITY_LOOKUP, PRE_PRIORITY_COUNT, PRE_ID_TO_INDEX)
  269. end
  270.  
  271. function hook.AddPostListener(event, name, func, priority --[[= 0]])
  272.     AddHook(event, name, func, priority, hooktbl, POST_HOOK_ROOT, POST_PRIORITY_LIST, POST_PRIORITY_LOOKUP, POST_PRIORITY_COUNT, POST_ID_TO_INDEX)
  273. end
  274.  
  275. local function Pack(...)
  276.     local num = select("#", ...)
  277.    
  278.     if (num != 0) then
  279.         return {...}, num
  280.     end
  281.    
  282.     return nil
  283. end
  284.  
  285. local function noop()
  286. end
  287.  
  288. local function noopret()
  289.     return nil
  290. end
  291.  
  292. function hook.Call(event, GM, ...)
  293.     local hooktbl = Hooks[event]
  294.     local hashooks = hooktbl != nil
  295.     local hasrets = false
  296.     local rets, num
  297.    
  298.     if (hashooks) then
  299.         -- Pre-listeners
  300.         local curhook = hooktbl[PRE_HOOK_ROOT]
  301.        
  302.         while (curhook != nil) do
  303.             curhook(...)
  304.             curhook = curhook[HOOK_NEXT]
  305.         end
  306.        
  307.         curhook = hooktbl[MAIN_HOOK_ROOT]
  308.        
  309.         -- Main hooks
  310.         while (curhook != nil) do
  311.             rets, num = Pack(curhook(...))
  312.            
  313.             if (rets != nil) then
  314.                 hasrets = true
  315.                 break
  316.             end
  317.            
  318.             curhook = curhook[HOOK_NEXT]
  319.         end
  320.     end
  321.    
  322.     -- Gamemode function
  323.     if (!hasrets) then
  324.         local GMfunc = GM[event]
  325.        
  326.         if (GMfunc != nil) then
  327.             rets, num = Pack(GMFunc(...))
  328.         end
  329.     end
  330.    
  331.     if (hashooks) then
  332.         -- Post-listeners (no returns from main hooks)
  333.         local curhook = hooktbl[POST_HOOK_ROOT]
  334.        
  335.         -- Don't create the read-only table unless we have a hook
  336.         if (curhook != nil) then
  337.             local readrets = setmetatable(newproxy(), {__index = hasrets && rets || noopret, __newindex = noop})
  338.            
  339.             repeat
  340.                 curhook(readrets, num, ...)
  341.                 curhook = curhook[HOOK_NEXT]
  342.             until (curhook == nil)
  343.         end
  344.     end
  345.    
  346.     if (hasrets) then
  347.         return unpack(rets, 1, num)
  348.     end
  349.    
  350.     return nil
  351. end
  352.  
  353. function hook.Run(name, ...)
  354.     return Call(name, gmod && gmod.GetGamemode() || nil, ...)
  355. end
  356.  
  357. local function RemoveHook(event, name, HOOK_ROOT, PRIORITY_LIST, PRIORITY_LOOKUP, PRIORITY_COUNT, ID_TO_INDEX)
  358.     if (event == nil) then
  359.         ErrorNoHalt("")
  360.         return
  361.     end
  362.    
  363.     if (name == nil) then
  364.         ErrorNoHalt("")
  365.         return
  366.     end
  367.    
  368.     local hooktbl = Hooks[event]
  369.    
  370.     if (hooktbl == nil) then
  371.         return
  372.     end
  373.    
  374.     local hookobj = hooktbl[ID_TO_INDEX][name]
  375.    
  376.     if (hookobj == nil) then
  377.         return
  378.     end
  379.    
  380.     hooktbl[ID_TO_INDEX][name] = nil
  381.    
  382.     local lastobj = hookobj[HOOK_PREV]
  383.     local nextobj = hookobj[HOOK_NEXT]
  384.     local priority = hookobj[HOOK_PRIORITY]
  385.     local lookup = hooktbl[PRIORITY_LOOKUP]
  386.    
  387.     if (lastobj == nil) then
  388.         hooktbl[HOOK_ROOT] = nextobj
  389.        
  390.         -- No previous hook means the hook being removed
  391.         -- is the only one with its priority, and it's the highest
  392.         -- FIXME: JK I'm dumb, check if the lookup object == this hook
  393.        
  394.         -- If the hook is the tail of the priority list
  395.         -- then it must be the only priority of this kind
  396.         -- since this is the root hook
  397.         if (lookup[priority] == hookobj) then
  398.             lookup[priority] = nil
  399.             table_remove(hooktbl[PRIORITY_LIST], 1)
  400.             hooktbl[PRIORITY_COUNT] = hooktbl[PRIORITY_COUNT] - 1
  401.         end
  402.     else
  403.         lastobj[HOOK_NEXT] = nextobj
  404.        
  405.         if (lookup[priority] == hookobj) then
  406.             -- If there's another hook with the same priority
  407.             -- promote it to the tail of the list
  408.             if (lastobj[HOOK_PRIORITY] == priority) then
  409.                 lookup[priority] = lastobj
  410.             else
  411.                 lookup[priority] = nil
  412.                 local count = hooktbl[PRIORITY_COUNT]
  413.                 local prioritylist = hooktbl[PRIORITY_LIST]
  414.                
  415.                 for i = 1, count do
  416.                     if (prioritylist[i] == priority) then
  417.                         table_remove(prioritylist, i)
  418.                     end
  419.                 end
  420.             end
  421.         end
  422.     end
  423.    
  424.     if (nextobj != nil) then
  425.         nextobj[HOOK_PREV] = lastobj
  426.     end
  427. end
  428.  
  429. function hook.Remove(event, name)
  430.     RemoveHook(event, name, MAIN_HOOK_ROOT, MAIN_PRIORITY_LIST, MAIN_PRIORITY_LOOKUP, MAIN_PRIORITY_COUNT, MAIN_ID_TO_INDEX)
  431. end
  432.  
  433. function hook.RemovePreListener(event, name)
  434.     RemoveHook(event, name, PRE_HOOK_ROOT, PRE_PRIORITY_LIST, PRE_PRIORITY_LOOKUP, PRE_PRIORITY_COUNT, PRE_ID_TO_INDEX)
  435. end
  436.  
  437. function hook.RemovePostListener(event, name)
  438.     RemoveHook(event, name, POST_HOOK_ROOT, POST_PRIORITY_LIST, POST_PRIORITY_LOOKUP, POST_PRIORITY_COUNT, POST_ID_TO_INDEX)
  439. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement