Last active
July 17, 2023 16:05
-
-
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)
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
--!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 | |
}) |
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
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) |
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
--!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