Skip to content

Instantly share code, notes, and snippets.

@RealEthanPlayzDev
Last active July 17, 2023 16:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RealEthanPlayzDev/c66c91006d75fc89c43171a372587bdb to your computer and use it in GitHub Desktop.
Save RealEthanPlayzDev/c66c91006d75fc89c43171a372587bdb to your computer and use it in GitHub Desktop.
RESignal - RadiatedExodus's Luau-based signal implementation (V2 AND V3 ARE NOT BACKWARDS COMPATIBLE)
--!strict
--[[
File name: RESignal.luau
Author: RadiatedExodus (RealEthanPlayzDev/ItzEthanPlayz_YT)
Created at: February 17, 2022
Version: 3.0.0
A signal implementation in Luau
--]]
local task = task
if (not task) then
local function CustomSpawn(f: ((...any) -> (...any)) | thread, ... : any)
local Thread = if (typeof(f) == "thread") then f else coroutine.create(f)
coroutine.resume(Thread, ...)
return Thread
end
task = { spawn = CustomSpawn, defer = CustomSpawn } :: any
end
--// Enums
local SignalBehavior = table.freeze({
Async = "SignalBehavior.Async";
Deferred = "SignalBehavior.Deferred";
Synced = "SignalBehavior.Synced";
})
export type SignalBehavior = "SignalBehavior.Async" | "SignalBehavior.Deferred" | "SignalBehavior.Synced";
--// A mapping of functions handling signal fire behaviors
local SignalBehaviorHandler = {
[SignalBehavior.Async] = function(callback: (...any) -> (...any), ... : any)
task.spawn(callback, ...)
end;
[SignalBehavior.Deferred] = function(callback: (...any) -> (...any), ... : any)
task.defer(callback, ...)
end;
[SignalBehavior.Synced] = function(callback: (...any) -> (...any), ... : any)
local Ret = {pcall(callback, ...)}
local Success = table.remove(Ret, 1)
if not Success then
task.spawn(error, Ret[1])
end
end;
}
local RESignal = {}
RESignal.__index = RESignal
RESignal.__tostring = function() return "RESignal" end
RESignal.__metatable = "This metatable is locked"
local function constructor_RESignal(behavior: SignalBehavior?): RESignal
return setmetatable({
SignalBehavior = behavior or SignalBehavior.Async;
__FirstConnection = nil :: RESignalConnection?;
}, RESignal)
end
export type RESignal = typeof(constructor_RESignal())
local RESignalConnection = {}
RESignalConnection.__index = RESignalConnection
RESignalConnection.__tostring = function() return "RESignalConnection" end
RESignalConnection.__metatable = "This metatable is locked"
local function constructor_RESignalConnection(f: (...any) -> (...any)): RESignalConnection
return setmetatable({
Function = f;
Signal = nil :: RESignal?;
Connected = true;
__NextConnection = nil :: RESignalConnection?;
}, RESignalConnection)
end
export type RESignalConnection = typeof(constructor_RESignalConnection(function() end))
--// RESignalConenction
function RESignalConnection.Disconnect(self: RESignalConnection)
assert(self.Signal, "Signal not set")
if (self.Signal) then
self.Connected = false
if (self.Signal) and (self.Signal.__FirstConnection == self) then --// TODO: Remove the self.Signal check when Luau typecheck is actually fixed
self.Signal.__FirstConnection = self.__NextConnection
else
local Previous = self.Signal.__FirstConnection
while (Previous) and (Previous.__NextConnection ~= self) do
Previous = Previous.__NextConnection
end
if (Previous) then
Previous.__NextConnection = self.__NextConnection
end
end
end
return
end
function RESignalConnection.Reconnect(self: RESignalConnection)
assert(self.Signal, "connection destroyed, cannot reconnect")
if (not self.Connected) and (not self.__NextConnection) and (self.Signal) then
if (self.Signal.__FirstConnection) then
self.__NextConnection = self.Signal.__FirstConnection
end
self.Connected = true
self.Signal.__FirstConnection = self
end
end
function RESignalConnection.Destroy(self: RESignalConnection)
if (self.Connected) then
self:Disconnect()
end
self.Signal = nil
return
end
--// RESignal
function RESignal.Connect<T...>(self: RESignal, f: (T...) -> ()): RESignalConnection
local Connection = constructor_RESignalConnection(f)
Connection.Signal = self
if (self.__FirstConnection) then
Connection.__NextConnection = self.__FirstConnection
end
self.__FirstConnection = Connection
return Connection
end
function RESignal.Once<T...>(self: RESignal, f: (T...) -> ()): RESignalConnection
local Connection; Connection = self:Connect(function(... : T...)
Connection:Disconnect()
return f(...)
end)
return Connection
end
function RESignal.Wait<T...>(self: RESignal)
local CThread = coroutine.running()
self:Once(function(... : T...)
return coroutine.resume(CThread, ...)
end)
return coroutine.yield()
end
function RESignal.Fire<T...>(self: RESignal, ... : T...)
local Connection = self.__FirstConnection
while Connection do
if Connection.Connected then
SignalBehaviorHandler[self.SignalBehavior](Connection.Function, ...)
end
Connection = Connection.__NextConnection
end
return
end
function RESignal.DisconnectAll(self: RESignal, lazy: boolean?)
if (lazy) and (self.__FirstConnection) then
self.__FirstConnection.Connected = false
self.__FirstConnection = nil
else
local Connection = self.__FirstConnection
while Connection do
local OC = Connection
Connection.Connected = false
Connection = Connection.__NextConnection
OC.__NextConnection = nil
end
end
end
--// Static export
return setmetatable({
new = constructor_RESignal;
SignalBehavior = SignalBehavior;
}, {
__call = function(_, ... : any)
return constructor_RESignal(...)
end
})
local RESignal = require(game.ReplicatedStorage.RESignal)
local FastSignal = require(game.ReplicatedStorage.FastSignal)
local GoodSignal = require(game.ReplicatedStorage.GoodSignal)
--// RESignal
local resignal_obj_ntmode = RESignal.new(RESignal.SignalBehavior.NewThread)
local resignal_obj_deferredmode = RESignal.new(RESignal.SignalBehavior.Deferred)
local resignal_obj_syncedmode = RESignal.new(RESignal.SignalBehavior.Synced)
--// FastSignal
local fastsignal_obj = FastSignal.new()
--// GoodSignal
local goodsignal_obj = GoodSignal.new()
--// Roblox BindableEvent
local rbxbindableevent = Instance.new("BindableEvent")
local ConnectAmount = 1
local ConnectOnceAmount = 1
local WaitAmount = 1
local FireAmount = 5
local function FireArgs()
return "aaaaa", "vvv", workspace, 128381298, false, Enum.KeyCode.X, math.random(1,100), math.random(1,100), math.random(1,100)
end
for i = 1, ConnectAmount do
resignal_obj_ntmode:Connect(function(...)
print("RESignal NewThread - Connect() "..i.." ", ...)
end)
resignal_obj_deferredmode:Connect(function(...)
print("RESignal Deferred - Connect() "..i.." ", ...)
end)
resignal_obj_syncedmode:Connect(function(...)
print("RESignal Synced - Connect() "..i.." ", ...)
end)
rbxbindableevent.Event:Connect(function(...)
print("Roblox BindableEvent - Connect() "..i.." ", ...)
end)
fastsignal_obj:Connect(function(...)
print("FastSignal - Connect() "..i.." ", ...)
end)
goodsignal_obj:Connect(function(...)
print("GoodSignal - Connect() "..i.." ", ...)
end)
end
for i = 1, ConnectOnceAmount do
resignal_obj_ntmode:ConnectOnce(function(...)
print("RESignal NewThread - ConnectOnce() "..i.." ", ...)
end)
resignal_obj_deferredmode:ConnectOnce(function(...)
print("RESignal Deferred - ConnectOnce() "..i.." ", ...)
end)
resignal_obj_syncedmode:ConnectOnce(function(...)
print("RESignal Synced - ConnectOnce() "..i.." ", ...)
end)
local rbe_cn; rbe_cn = rbxbindableevent.Event:Connect(function(...)
rbe_cn:Disconnect()
print("Roblox BindableEvent - ConnectOnce() "..i.." ", ...)
end)
local fastsignal_cn; fastsignal_cn = fastsignal_obj:Connect(function(...)
fastsignal_cn:Disconnect()
print("FastSignal - ConnectOnce() "..i.." ", ...)
end)
local goodsignal_cn; goodsignal_cn = goodsignal_obj:Connect(function(...)
goodsignal_cn:Disconnect()
print("GoodSignal - ConnectOnce() "..i.." ", ...)
end)
end
for i = 1, WaitAmount do
task.spawn(function()
print("RESignal NewThread - Wait() "..i.." ", resignal_obj_ntmode:Wait())
end)
task.spawn(function()
print("RESignal Deferred - Wait() "..i.." ", resignal_obj_deferredmode:Wait())
end)
task.spawn(function()
print("RESignal Synced - Wait() "..i.." ", resignal_obj_syncedmode:Wait())
end)
task.spawn(function()
print("Roblox BindableEvent - Wait() "..i.." ", rbxbindableevent.Event:Wait())
end)
task.spawn(function()
print("FastSignal - Wait() "..i.." ", fastsignal_obj:Wait())
end)
task.spawn(function()
print("GoodSignal - Wait() "..i.." ", goodsignal_obj:Wait())
end)
end
local resignalbench_nt, resignalbench_deferred, resignalbench_synced, rbxbindbench, fastsignalbench, goodsignalbench = {}, {}, {}, {}, {}, {}
for i = 1, FireAmount do
warn("------ INDEX "..i.." ------")
local fa = FireArgs()
local resignal_nt_st = os.clock()
resignal_obj_ntmode:Fire(fa)
local resignal_nt_et = os.clock()
table.insert(resignalbench_nt, resignal_nt_et-resignal_nt_st)
print("RESignal NewThread connections: ", resignal_obj_ntmode.__connections)
local resignal_deferred_st = os.clock()
resignal_obj_deferredmode:Fire(fa)
local resignal_deferred_et = os.clock()
table.insert(resignalbench_deferred, resignal_deferred_et-resignal_deferred_st)
print("RESignal Deferred connections: ", resignal_obj_deferredmode.__connections)
local resignal_synced_st = os.clock()
resignal_obj_syncedmode:Fire(fa)
local resignal_synced_et = os.clock()
table.insert(resignalbench_synced, resignal_synced_et-resignal_synced_st)
print("RESignal Synced connections: ", resignal_obj_syncedmode.__connections)
local rbxbind_st = os.clock()
rbxbindableevent:Fire(fa)
local rbxbind_et = os.clock()
table.insert(rbxbindbench, rbxbind_et-rbxbind_st)
local fastsignal_st = os.clock()
fastsignal_obj:Fire(fa)
local fastsignal_et = os.clock()
table.insert(fastsignalbench, fastsignal_et-fastsignal_st)
print("FastSignal connections: ", fastsignal_obj)
local goodsignal_st = os.clock()
goodsignal_obj:Fire(fa)
local goodsignal_et = os.clock()
table.insert(goodsignalbench, goodsignal_et-goodsignal_st)
print("GoodSignal connections: ", goodsignal_obj._handlerListHead)
warn("------ END INDEX "..i.." ------")
end
local s = "\n\nFire benchmark results:\nRESignal NewThread = {"
for index, time in ipairs(resignalbench_nt) do
s ..= string.format("\n [%d] = %s", index, time)
end
s ..= "\n}\nRESignal Deferred = {"
for index, time in ipairs(resignalbench_deferred) do
s ..= string.format("\n [%d] = %s", index, time)
end
s ..= "\n}\nRESignal Synced = {"
for index, time in ipairs(resignalbench_synced) do
s ..= string.format("\n [%d] = %s", index, time)
end
s ..= "\n}\nRoblox BindableEvent = {"
for index, time in ipairs(rbxbindbench) do
s ..= string.format("\n [%d] = %s", index, time)
end
s ..= "\n}\nFastSignal = {"
for index, time in ipairs(fastsignalbench) do
s ..= string.format("\n [%d] = %s", index, time)
end
s ..= "\n}\nGoodSignal = {"
for index, time in ipairs(goodsignalbench) do
s ..= string.format("\n [%d] = %s", index, time)
end
s ..= "\n}\n\n"
warn(s)
--!strict
--[[
File name: RESignal.luau
Author: RadiatedExodus (RealEthanPlayzDev/ItzEthanPlayz_YT)
Created at: February 17, 2022
Version: 2.0.0
A signal implementation in Luau
--]]
--// Enum SignalBehavior
local SignalBehavior = {
NewThread = "SignalBehavior.NewThread";
Deferred = "SignalBehavior.Deferred";
Synced = "SignalBehavior.Synced";
}
table.freeze(SignalBehavior)
--// A mapping of functions handling signal fire behaviors
local SignalBehaviorHandler = {
[SignalBehavior.NewThread] = function(callback: (...any) -> (...any), ... : any)
task.spawn(callback, ...)
end;
[SignalBehavior.Deferred] = function(callback: (...any) -> (...any), ... : any)
task.defer(callback, ...)
end;
[SignalBehavior.Synced] = function(callback: (...any) -> (...any), ... : any)
local Ret = {pcall(callback, ...)}
local Success = table.remove(Ret, 1)
if not Success then
task.spawn(error, Ret[1])
end
end;
}
--// Class RESignalConnection
local RESignalConnection = {}
RESignalConnection.__index = RESignalConnection
RESignalConnection.__tostring = function() return "RESignalConnection" end
RESignalConnection.__metatable = "This metatable is locked"
--// Function for connecting this RESignalConnection to it's origin RESignal, useful for reconnecting disconnected RESignalConnection(s)
function RESignalConnection.Connect(self: RESignalConnection)
if self.Connected then return end
self.__RESignal.__ConnectedConnections[self] = true
self.Connected = true
return
end
--// Function for disconnected this RESignalConnection from a RESignal
function RESignalConnection.Disconnect(self: RESignalConnection)
if not self.Connected then return end
self.__RESignal.__ConnectedConnections[self] = nil
self.Connected = false
return
end
--// Constructor function
local function constructor_RESignalConnection(resignal: RESignal, f: (...any) -> ())
local Connection = setmetatable({
Connected = true;
__Function = f;
__RESignal = resignal;
}, RESignalConnection)
resignal.__ConnectedConnections[Connection] = true
return Connection
end
--// Class RESignal
local RESignal = {}
RESignal.__index = RESignal
RESignal.__tostring = function() return "RESignal" end
RESignal.__metatable = "This metatable is locked"
--// Calls all callbacks connected to this RESignal via connected RESignalConnection(s)
function RESignal.Fire(self: RESignal, ... : any)
assert(typeof(SignalBehaviorHandler[self.__SignalBehavior]) == "function", "invalid SignalBehavior")
for connection, connected in pairs(self.__ConnectedConnections) do
if not connected then continue end
SignalBehaviorHandler[self.__SignalBehavior](connection.__Function, ...)
end
return
end
--// Connects a function, returning a RESignalConnection object
function RESignal.Connect(self: RESignal, f: (...any) -> ())
assert(not self.__Destroyed, "signal destroyed")
return constructor_RESignalConnection(self, f)
end
--// Connects a function once, once the next ``RESignal:Fire()`` is called, the connection gets immediately disconnected
function RESignal.ConnectOnce(self: RESignal, f: (...any) -> ())
assert(not self.__Destroyed, "signal destroyed")
local Connection; Connection = self:Connect(function(...)
Connection:Disconnect()
return f(...)
end)
return Connection
end
--// Yields the current thread until the next ``RESignal:Fire()`` is called
function RESignal.Wait(self: RESignal)
assert(not self.__Destroyed, "signal destroyed")
local CurrentThread = coroutine.running()
self:ConnectOnce(function(...)
coroutine.resume(CurrentThread, ...)
return
end)
return coroutine.yield()
end
--// Disconnects all RESignalConnection(s) connected to this RESignal object
function RESignal.DisconnectAll(self: RESignal)
for connection, connected in pairs(self.__ConnectedConnections) do
if not connected then continue end
connection:Disconnect()
end
return
end
--// Function for setting the signal behavior on a existing RESignal
function RESignal.SetSignalBehavior(self: RESignal, signalbehavior: string)
self.__SignalBehavior = signalbehavior
return
end
--// Destroyer function
function RESignal.Destroy(self: RESignal)
if self.__Destroyed then return end
self.__Destroyed = true
self:DisconnectAll()
return
end
--// Constructor function
local function constructor_RESignal(signalbehavior: string?)
return setmetatable({
__Destroyed = false;
__ConnectedConnections = {};
__SignalBehavior = if signalbehavior then signalbehavior else SignalBehavior.NewThread;
}, RESignal)
end
--// Luau types
export type RESignal = typeof(constructor_RESignal())
export type RESignalConnection = typeof(constructor_RESignalConnection(constructor_RESignal(), function() end))
return setmetatable({
new = constructor_RESignal;
SignalBehavior = SignalBehavior;
}, { __call = function(_, ...) return constructor_RESignal(...) end })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment