Thats what i said in the title . It creates event WITHOUT using EXTERNAL instances.
Can you elaborate further? What do you exactly mean by “waiting to fire”? Do you mean Delay?
event:Wait()
it would yield the current thread until the event fires. also, how well does this work when a connection yields?
edit on yielding connections: it doesn’t:
however, for simple use cases this is a very nice and easy solution. if you plan on having a simple event in an oop class that will run very often, this would be a good option to consider
Yea i havent really implemented it yet. It currently yields when there are multiple functions connected to it (due to its behaviour), i will update it once i either implement a thing im currently working on, or just coroutine them using task.spawn
i think this module does have potential so i wanna provide my signal class that you could use as a reference when you start on these things:
--!strict
export type Connection<A...> = {
_Callback: (A...) -> (A...),
_NextConnection: Connection<A...>?,
_Signal: Signal<A...>,
_OnlyOnce: boolean,
__index: Connection<A...>,
new: (callback: (any) -> (), connectOnce: boolean, signal: Signal<A...>) -> (Connection<A...>),
Run: (self: Connection<A...>, ...any) -> (),
Disconnect: (self: Connection<A...>) -> ()
}
export type Signal<A...> = {
__index: Signal<A...>,
_Connection: Connection<A...>?,
Fire: (self: Signal<A...>, A...) -> (),
Wait: (self: Signal<A...>, duration: number?) -> (A...),
Connect: (self: Signal<A...>, Callback: (A...) -> ()) -> (Connection<A...>),
Once: (self: Signal<A...>, Callback: (A...) -> ()) -> (Connection<A...>),
Destroy: (self: Signal<A...>) -> (),
}
local ConnectionClass: Connection<any> = {} :: Connection<any>
ConnectionClass.__index = ConnectionClass
function ConnectionClass.new(callback, connectOnce, signal)
return setmetatable({
_Callback = callback,
_NextConnection = nil,
_Signal = signal,
_OnlyOnce = connectOnce,
} :: any, ConnectionClass)
end
function ConnectionClass:Run(...)
task.defer(self._Callback,...)
if self._OnlyOnce then self:Disconnect() end
end
function ConnectionClass:Disconnect()
if self._Signal._Connection == self then
self._Signal._Connection = self._NextConnection
else
local priorConnection = self._Signal._Connection
if priorConnection then
while priorConnection._NextConnection ~= self do
priorConnection = priorConnection._NextConnection
end
priorConnection._NextConnection = self._NextConnection
end
end
end
local SignalClass: Signal<> = {} :: Signal<>
function SignalClass:Fire(...)
local currentlyRunningConnection = self._Connection
while currentlyRunningConnection do
local nextConnection = currentlyRunningConnection._NextConnection
currentlyRunningConnection:Run(...)
currentlyRunningConnection = nextConnection
end
end
function SignalClass:Wait(duration : number?)
local Running = coroutine.running()
local _delay
if duration then
_delay = task.delay(duration, function(thread)
task.defer(thread)
end, Running)
end
self:Once(function(...)
if _delay then task.cancel(_delay) end
task.defer(Running, ...)
end)
return coroutine.yield()
end
function SignalClass:Connect(callback: () -> ())
local connection = ConnectionClass.new(callback, false, self)
connection._NextConnection = self._Connection
self._Connection = connection
return connection
end
function SignalClass:Once(callback: () -> ())
local connection = ConnectionClass.new(callback, true, self)
connection._NextConnection = self._Connection
self._Connection = connection
return connection
end
function SignalClass:Destroy()
self._Connection = nil
setmetatable(self,nil)
end
return {
new = function<C...>()
local self: Signal<C...> = setmetatable({
_Connection = nil,
} :: any, {__index = SignalClass})
return self
end
}
Just a tip, you can use export type Signal = typeof(SignalClass)
so you don’t have to manually the types!
id rather have a type that keeps me in check when writing the class itself but ty
i did some optimizations and successfully cut few microseconds
im having hard time making the wait functions without it making a big impact on the compilation time
local Eventer = {}
---- simplified funcs ----
local ts = task.spawn
local tw = task.wait
local ti = table.insert
local tc = table.clone
local tr = table.remove
local tf = table.find
---- function ----
local function DoEvent(self,...)
for i,v in self.connections do
ts(v.funcs,...)
if v.Once == true then table.remove(self.connections,i) end
end
if self.Fired == true then self.Fired = false end
end
local function Disconnect(self,i)
tr(self.connections,i)
end
---- general code ----
local mt = {
__call = DoEvent
}
export type Event<A...> = {
Fire: (any) -> (nil),
Wait: () -> (nil),
Connect: (Callback: () -> (any)) -> (nil),
Once: (Callback: () -> (any)) -> (nil)
}
local t : Event = {
connections = {},
Fired = false
}
function t:Connect(func)
ti(self.connections,{funcs = func})
local i = tf(self.connections,func)
return {Disconnect = function() tr(self,i) end}
end
function t:Once(func)
ti(self.connections,{
funcs = func,
Once = true
})
end
function t:Fire(...)
self(...)
end
function t:Wait()
self.Fired = true
repeat tw() until self.Fired == false
end
setmetatable(t,mt)
function Eventer.new()
local tab = tc(t)
return tab
end
return Eventer
I did some optimization and this is what i got. Any ideas?
this function seems laggy since it repeats task.wait() until the event is fired.
this is a good start, but as pointed out it may be laggy.
--!nocheck
local Eventer = {}
---- simplified funcs ----
local ts = task.spawn
local tw = task.wait
local ti = table.insert
local tc = table.clone
local tr = table.remove
local tf = table.find
---- general code ----
export type Event<A...> = {
Fire: (any) -> (nil),
Wait: () -> (nil),
Connect: (Callback: () -> (any)) -> (nil),
Once: (Callback: () -> (any)) -> (nil)
}
local t : Event = {{}}
function t:Connect(func)
ti(self[1],func)
return {Disconnect = function() tr(self[1],tf(self[1],func)) end}
end
function t:Once(func)
ti(self[1],-#self[1],func)
end
function t:Fire(...)
for i,v in self[1] do
ts(v,...)
if i < 0 then table.remove(self[1],i) end
end
end
function t:Wait()
local Running = coroutine.running()
local defer = nil
self:Once(function()
if defer ~= nil then task.cancel(defer) end
task.defer(Running)
end)
return coroutine.yield()
end
function Eventer.new()
return tc(t)
end
return Eventer
i revamped some things and took your :Wait() to make my own (not really different from yours)
Somehow now it can be compared to GoodSignal (70% of the time Eventer is faster)
that looks much better, good job!
@kalabgs! I found how to do this. Because of this wonderful solution by @7z99 Yield a coroutine? - #7 by 7z99 you can make a wait function.
Edit: So sorry, I was so hyped that I didn’t realize you already had an answer!
Hey!
Could you edit the original / first post and put an Up-To-Date script?
I’ve really been wanting to do something similar to this module with my own code but I’ve also been stuck thinking about the wait function. I had an idea, what if the connection wait() relied on another API that is versatile enough to provide the built-in wait, like a .Changed:Wait() signal?? (Maybe an instance value that resides underneath the module as a descendant, only created when calling the Wait()
use the task library and coroutine library to yield and resume the thread
Ahhhhhh I see! So task library is a fairly recent feature that lets you halt threads that aren’t necessarily coroutines, plus more, righto-mundo??
yeah, just use coroutine.running to get the current thread, coroutine.yield(running) to pause the thread, and use the connect function to task.spawn(running) to create a wait function
A great ide is to add Event:Wait(timeout: number)
. Should be very easy, just have a variable which is os.time() + timeout
and then in the repeat
, just wait until either the event fires or timeout reaches.