Last active
August 22, 2021 10:34
-
-
Save Earu/c0bc385077cd166ee4fa8c4f93fbb5b6 to your computer and use it in GitHub Desktop.
POC for a Garry's Mod asynchronous event library.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
MAIN DIFFERENCES WITH THE SYNCHRONOUS HOOK LIBRARY | |
- You cannot add a hook callback to an event that is being executed | |
example: | |
hook.Add("Think", "example", function() | |
hook.Add("Think", "example2", function() end) | |
end) | |
In this case the hook would be added from the next call of the Think event | |
and not in the callback. | |
- Because of the nature of async workflows and coroutines, you cannot run game loop dependent | |
event like "Think", "Tick" or "HUDPaint". They would end up stacking, and stacking, anything | |
lower than the CALL_INTERVAL will never be ran. | |
- This is probably slower than the vanilla hook library. | |
]]-- | |
module ("hook_async", package.seeall) | |
CALL_INTERVAL = 0.1 -- This can be changed depending on how much time you want to pass between each calls | |
local hooks = {} | |
function GetTable() | |
return hooks | |
end | |
local add_queue = {} | |
local mutex = {} | |
local function unlock(event_name) | |
mutex[event_name] = mutex[event_name] - 1 | |
if mutex[event_name] > 0 then return end | |
mutex[event_name] = nil -- this can be here because this part of the code is synchronous, | |
-- meaning the rest of this function will always be run accordingly | |
-- when it happens | |
if not add_queue[event_name] then return end | |
hooks[event_name] = hooks[event_name] or {} | |
for _, callback_data in ipairs(add_queue[event_name]) do | |
hooks[event_name][callback_data.identifier] = callback_data.callback | |
end | |
add_queue[event_name] = nil | |
end | |
local isstring = isstring | |
local isfunction = isfunction | |
function Add(event_name, callback_identifier, callback) | |
if not isstring(event_name) or not isfunction(callback) then return end | |
if mutex[event_name] then | |
add_queue[event_name] = add_queue[event_name] or {} | |
table.insert(add_queue[event_name], { identifier = callback_identifier, callback = callback }) | |
else | |
hooks[event_name] = hooks[event_name] or {} | |
hooks[event_name][callback_identifier] = callback | |
end | |
end | |
function Remove(event_name, callback_identifier) | |
if not isstring(event_name) then return end | |
if hooks[event_name] then | |
hooks[event_name][callback_identifier] = nil | |
end | |
end | |
function Run(event_name, on_finished, ...) | |
return Call(event_name, gmod and gmod.GetGamemode(), on_finished, ...) | |
end | |
local co_create = coroutine.create | |
local co_yield = coroutine.yield | |
local co_resume = coroutine.resume | |
local co_status = coroutine.status | |
local pairs = pairs | |
local IsValid = IsValid | |
function Call(event_name, gm, on_finished, ...) | |
on_finished = isfunction(on_finished) and on_finished or function() end | |
local hook_callbacks = hooks[event_name] | |
if not hook_callbacks then return on_finished() end | |
mutex[event_name] = (mutex[event_name] or 0) + 1 | |
local co = co_create(function(hook_callbacks, ...) | |
local a, b, c, d, e, f | |
for identifier, callback in pairs(hook_callbacks) do | |
if isstring(identifier) then | |
a, b, c, d, e, f = callback(...) | |
else | |
if IsValid(identifier) then | |
a, b, c, d, e, f = callback(identifier, ...) | |
else | |
hook_callbacks[identifier] = nil | |
end | |
end | |
if a ~= nil then | |
on_finished(a, b, c, d, e, f) | |
unlock(event_name) | |
return | |
else | |
co_yield() | |
end | |
end | |
if not gm then | |
unlock(event_name) | |
return on_finished() | |
end | |
local gm_func = gm[event_name] | |
if not gm_func then | |
unlock(event_name) | |
return on_finished() | |
end | |
on_finished(gm_func(gm, ...)) | |
unlock(event_name) | |
end) | |
local function resume(...) | |
local ok, err = co_resume(co, hook_callbacks, ...) | |
if not ok then | |
error(debug.traceback(co, err)) | |
else | |
if co_status(co) == "dead" then return end | |
timer.Simple(CALL_INTERVAL, resume) | |
end | |
end | |
resume(...) | |
return co | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment