Created
January 25, 2022 18:22
-
-
Save deter0/e467f8d4658374d7280b089596f69aaa to your computer and use it in GitHub Desktop.
Type Safe Signal Class
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 | |
--@Signal Class (deter) | |
--Example: | |
-- local mySig = Signal.new(); | |
-- mySig:Connect(function(a: number) | |
-- print("A"); | |
-- return 1; | |
-- end) --> Connection type is infered as `(a:number) -> number` | |
-- mySig:Connect(function(a:number) | |
-- print("B") | |
-- return 1; | |
-- end) --> Fine | |
-- mySig:Connect(function(a:string) | |
-- return ""; | |
-- end) --> Incorrect since the function provided has type `(a:string) -> string` not `(a:number) -> number` | |
-- mySig:Dispatch(); --> Find due to luau bug | |
-- mySig:Dispatch("A"); --> Incorrect | |
-- mySig:Dispatch(1); --> Correct and intended | |
local Signal = {}; | |
function Signal.new<P, R>() | |
type T = (P) -> (R); | |
type self = typeof(Signal.new()); | |
return setmetatable({ | |
ConnectionPool = {} :: {T}, | |
Connect = function(self: self, Callback: T) | |
self.ConnectionPool[#self.ConnectionPool + 1] = Callback; | |
end, | |
Dispatch = function(self:self, ...:P) | |
local args: {P} = {...}; | |
for _, Callback in ipairs(self.ConnectionPool) do | |
xpcall(function() | |
(Callback :: T)(unpack(args) :: P); | |
end, coroutine.wrap(function(Err) | |
error(Err); | |
end)); | |
end | |
end, | |
-- NOTE(deter): Unsafe meaning that if any function in `ConnectionPool` throws an error it will not fire the rest of the functions. | |
-- This is probably faster as it gets rid of any `coroutine.wrap`, `xpcall`, `unpack` calls. | |
DispatchUnsafe = function(self:self, ...:P) | |
for _, Callback in ipairs(self.ConnectionPool) do | |
(Callback :: T)(...); | |
end | |
end, | |
}, {__index = Signal}); | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Rant about luau generics
Inferred generics are a horrible idea all in all. I don't know why they just wouldn't make them required or any if they're not provided. Why do they have to break programming traditions this code could've been like every other luau class written but it had to be changed because all the other methods I tried wouldn't comply with generic types. They could've simple added a
Signal.new<() -> ()>();
and that would've been perfect! Zero complaints. But for whatever reason that I do not know they decided that generics should be inferred. This makes it way harder to write code that is type safe but it really makes you think that weather it's worth having to change the way you write code instead of just doingx :any
I don't know I could be wrong about how I wrote this but I tried like 10 methods and this is the only one that worked. If anyone knows a better way to write generic classes, please tell me.