Created
February 26, 2023 21:45
-
-
Save Retinalogic/a340af7f9d55523965c806cedbc8825e to your computer and use it in GitHub Desktop.
Lua-side duplication of the API of events on Roblox objects. Signals are needed for to ensure that for local events objects are passed by reference rather than by value where possible, as the BindableEvent objects always pass signal arguments by value, meaning tables will be deep copied. Roblox's deep copy method parses to a non-lua table compat…
This file contains 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
--- Lua-side duplication of the API of events on Roblox objects. | |
-- Signals are needed for to ensure that for local events objects are passed by | |
-- reference rather than by value where possible, as the BindableEvent objects | |
-- always pass signal arguments by value, meaning tables will be deep copied. | |
-- Roblox's deep copy method parses to a non-lua table compatable format. | |
-- @classmod Signal | |
local HttpService = game:GetService("HttpService") | |
local ENABLE_TRACEBACK = false | |
local Signal = {} | |
Signal.__index = Signal | |
Signal.ClassName = "Signal" | |
--- Constructs a new signal. | |
-- @constructor Signal.new() | |
-- @treturn Signal | |
function Signal.new() | |
local self = setmetatable({}, Signal) | |
self._bindableEvent = Instance.new("BindableEvent") | |
self._argMap = {} | |
self._source = ENABLE_TRACEBACK and debug.traceback() or "" | |
-- Events in Roblox execute in reverse order as they are stored in a linked list and | |
-- new connections are added at the head. This event will be at the tail of the list to | |
-- clean up memory. | |
self._bindableEvent.Event:Connect(function(key) | |
self._argMap[key] = nil | |
-- We've been destroyed here and there's nothing left in flight. | |
-- Let's remove the argmap too. | |
-- This code may be slower than leaving this table allocated. | |
if (not self._bindableEvent) and (not next(self._argMap)) then | |
self._argMap = nil | |
end | |
end) | |
return self | |
end | |
--- Fire the event with the given arguments. All handlers will be invoked. Handlers follow | |
-- Roblox signal conventions. | |
-- @param ... Variable arguments to pass to handler | |
-- @treturn nil | |
function Signal:Fire(...) | |
if not self._bindableEvent then | |
warn(("Signal is already destroyed. %s"):format(self._source)) | |
return | |
end | |
local args = table.pack(...) | |
-- TODO: Replace with a less memory/computationally expensive key generation scheme | |
local key = HttpService:GenerateGUID(false) | |
self._argMap[key] = args | |
-- Queues each handler onto the queue. | |
self._bindableEvent:Fire(key) | |
end | |
--- Connect a new handler to the event. Returns a connection object that can be disconnected. | |
-- @tparam function handler Function handler called with arguments passed when `:Fire(...)` is called | |
-- @treturn Connection Connection object that can be disconnected | |
function Signal:Connect(handler) | |
if not (type(handler) == "function") then | |
error(("connect(%s)"):format(typeof(handler)), 2) | |
end | |
return self._bindableEvent.Event:Connect(function(key) | |
-- note we could queue multiple events here, but we'll do this just as Roblox events expect | |
-- to behave. | |
local args = self._argMap[key] | |
if args then | |
handler(table.unpack(args, 1, args.n)) | |
else | |
error("Missing arg data, probably due to reentrance.") | |
end | |
end) | |
end | |
--- Wait for fire to be called, and return the arguments it was given. | |
-- @treturn ... Variable arguments from connection | |
function Signal:Wait() | |
local key = self._bindableEvent.Event:Wait() | |
local args = self._argMap[key] | |
if args then | |
return table.unpack(args, 1, args.n) | |
else | |
error("Missing arg data, probably due to reentrance.") | |
return nil | |
end | |
end | |
--- Disconnects all connected events to the signal. Voids the signal as unusable. | |
-- @treturn nil | |
function Signal:Destroy() | |
if self._bindableEvent then | |
-- This should disconnect all events, but in-flight events should still be | |
-- executed. | |
self._bindableEvent:Destroy() | |
self._bindableEvent = nil | |
end | |
-- Do not remove the argmap. It will be cleaned up by the cleanup connection. | |
setmetatable(self, nil) | |
end | |
return Signal |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment