Perth

Untitled

Sep 26th, 2021
5,058
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --- Lua-side duplication of the API of events on Roblox objects.
  2. -- Signals are needed for to ensure that for local events objects are passed by
  3. -- reference rather than by value where possible, as the BindableEvent objects
  4. -- always pass signal arguments by value, meaning tables will be deep copied.
  5. -- Roblox's deep copy method parses to a non-lua table compatable format.
  6. -- @classmod Signal
  7.  
  8. local HttpService = game:GetService("HttpService")
  9.  
  10. local ENABLE_TRACEBACK = false
  11.  
  12. local Signal = {}
  13. Signal.__index = Signal
  14. Signal.ClassName = "Signal"
  15.  
  16. --- Constructs a new signal.
  17. -- @constructor Signal.new()
  18. -- @treturn Signal
  19. function Signal.new()
  20.     local self = setmetatable({}, Signal)
  21.  
  22.     self._bindableEvent = Instance.new("BindableEvent")
  23.     self._argMap = {}
  24.     self._source = ENABLE_TRACEBACK and debug.traceback() or ""
  25.  
  26.     -- Events in Roblox execute in reverse order as they are stored in a linked list and
  27.     -- new connections are added at the head. This event will be at the tail of the list to
  28.     -- clean up memory.
  29.     self._bindableEvent.Event:Connect(function(key)
  30.         self._argMap[key] = nil
  31.  
  32.         -- We've been destroyed here and there's nothing left in flight.
  33.         -- Let's remove the argmap too.
  34.         -- This code may be slower than leaving this table allocated.
  35.         if (not self._bindableEvent) and (not next(self._argMap)) then
  36.             self._argMap = nil
  37.         end
  38.     end)
  39.  
  40.     return self
  41. end
  42.  
  43. --- Fire the event with the given arguments. All handlers will be invoked. Handlers follow
  44. -- Roblox signal conventions.
  45. -- @param ... Variable arguments to pass to handler
  46. -- @treturn nil
  47. function Signal:Fire(...)
  48.     if not self._bindableEvent then
  49.         warn(("Signal is already destroyed. %s"):format(self._source))
  50.         return
  51.     end
  52.  
  53.     local args = table.pack(...)
  54.  
  55.     -- TODO: Replace with a less memory/computationally expensive key generation scheme
  56.     local key = HttpService:GenerateGUID(false)
  57.     self._argMap[key] = args
  58.  
  59.     -- Queues each handler onto the queue.
  60.     self._bindableEvent:Fire(key)
  61. end
  62.  
  63. --- Connect a new handler to the event. Returns a connection object that can be disconnected.
  64. -- @tparam function handler Function handler called with arguments passed when `:Fire(...)` is called
  65. -- @treturn Connection Connection object that can be disconnected
  66. function Signal:Connect(handler)
  67.     if not (type(handler) == "function") then
  68.         error(("connect(%s)"):format(typeof(handler)), 2)
  69.     end
  70.  
  71.     return self._bindableEvent.Event:Connect(function(key)
  72.         -- note we could queue multiple events here, but we'll do this just as Roblox events expect
  73.         -- to behave.
  74.  
  75.         local args = self._argMap[key]
  76.         if args then
  77.             handler(table.unpack(args, 1, args.n))
  78.         else
  79.             error("Missing arg data, probably due to reentrance.")
  80.         end
  81.     end)
  82. end
  83.  
  84. --- Wait for fire to be called, and return the arguments it was given.
  85. -- @treturn ... Variable arguments from connection
  86. function Signal:Wait()
  87.     local key = self._bindableEvent.Event:Wait()
  88.     local args = self._argMap[key]
  89.     if args then
  90.         return table.unpack(args, 1, args.n)
  91.     else
  92.         error("Missing arg data, probably due to reentrance.")
  93.         return nil
  94.     end
  95. end
  96.  
  97. --- Disconnects all connected events to the signal. Voids the signal as unusable.
  98. -- @treturn nil
  99. function Signal:Destroy()
  100.     if self._bindableEvent then
  101.         -- This should disconnect all events, but in-flight events should still be
  102.         -- executed.
  103.  
  104.         self._bindableEvent:Destroy()
  105.         self._bindableEvent = nil
  106.     end
  107.  
  108.     -- Do not remove the argmap. It will be cleaned up by the cleanup connection.
  109.  
  110.     setmetatable(self, nil)
  111. end
  112.  
  113. return Signal
RAW Paste Data