table.pack creates a table from all the given parameters, and table.unpack will “unpack” a table.
So, for example:
local function testFunc(...: any?): nil
--we don't know what's contained in ...
--let's gather it into a table
local extraData = {...}
--[[now we need to run our callback
we use table.unpack so each variable in our table gets distributed to a variable in the parameters
so if it takes 3 parameters and our table has 3 elements each of the parameters receives one of the elements
but we also need to store the results. We don't know how many results we are going to get,
so we can store them in a table using table.pack and assign it to results
]]
local results = table.pack(
self.callback(
table.unpack(extraData)
)
)
end
These functions are useful for within a bindable as it allows for one generic method. We don’t know how many parameters we are going to receive, or how many we are going to get. We could just do return callback(table.unpack(extraData)), but that’s harder to read and understand. Therefore, we send all the parameters needed for the callback to to the callback, and return all the parameters the callback returns.
We only use one callback for this, just like a BindableFunction. It wouldn’t work very well otherwise.
What do you mean by this part? Do you mean I don’t put the brackets after the function name? If I did that, it would run the function and pass the return result of the function, instead of the function itself.
type CustomEvent = typeof(setmetatable({}:: {Callback: (any?) -> (any?),Invoke: (Event,any?) -> (any?),Connect: (Event,any?) -> (any?),Invoked: boolean},{}:: Event))
local CustomEvent = {}
CustomEvent.__index = CustomEvent
-- All Events will be stored here.
CustomEvent.Events = {}
-- Class Constructor
function CustomEvent.new(name: string): Event
local self = {}
setmetatable(self, CustomEvent)
self.Callback = nil -->> Any Functions/Arguments to be executed/passed!
self.Invoke = self:Invoke() -- These doesn't work. lol
self.Connect = self:Connect()
self.Invoked = false -->> Tells us if this Event has fired.
CustomEvent.Events[name] = self
return self
end
--waiting for a signal to exist
function CustomEvent:WaitForInvoke(EventName: string): Event
local Signal: Event
local attempt = 0 --Times waited for a Signal.
repeat
Signal = self.Events[EventName] -->> Signal is equal to the Event given.
attempt += 1
task.wait(1)
if attempt == 5 then -->> If we have waited for a Signal for more than 5 seconds then warn.
warn("Yielded for Event: "..EventName.."5+".."seconds")
end
until
Signal or attempt == 60
return Signal
end
--Replicating RBXScriptSignal's :Connect Function
function CustomEvent:Connect(callback: (any?) -> (any?)): any? --The given Callback/Function should not return a value, stated by the -> (nil)
-- Add an Asynchronous version of the given Callback to be ran.
self.Callback = callback
return self.Callback
end
--Fires Event
function CustomEvent:Invoke(...: any?): any?
local Arguments = table.unpack({...})
local results = self.Callback(Arguments) -->> Calls Callback Function, and unpacks all Arguments and sends to it.
return results or table.unpack(results) --Returns any results returned by the given Callback.
end
export type Event = {
["Connect"]: (self: Event, any?) -> (any?), -->> Connect Function, will pass in the Event/self and any other arguments. and will not return a value.
["Invoke"]: (self: Event, any?) -> (any?), -->> Fire Function, will pass in the Event/self and any other arguments. and will not return a value.
["Callback"]: (any?) -> (any?), -->> Callbacks, so basically Arguments? Functions?
["Invoked"]: boolean --When Signal/Event is Fired this will go true. Making any Receiving Scripts with :WaitForEvent execute their code.
}
return CustomEvent
This part is wrong. Use table.pack to gather ..., use table.pack to store the results of the callback. Use table.unpack on ... only when invoking the callback, and return table.unpack(results). Otherwise, you will only get one of your variables.
sorry bro but i am not reading all that, why are you doing type annotation like that anyway? I thought it’d be easier to type annotate using a table of each element, like you did at the bottom.
This code will cause a script timeout. You said
I think I misunderstood.
We pass functions as parameters/arguments to the :Connect function. These functions are then run with parameters/arguments passed upon :Fire(). We don’t include the brackets when calling them, otherwise that would run them and pass the return value. The function name holds a reference to the function itself, which is also why you can pass an anonymous function to :Connect and it will still run.
I just put that there to see if the script would recognize something, but it didn’t do anything and i just forgot to remove it before sending the script.
yeah, assigning a member of self to the return value of it’s function is not a great idea and would certainly cause a logic error; would be undetectable without monitoring behaviour of the object, though.
function CustomEvent:Invoke(...: any?): any?
local Arguments = table.pack({...})
local results = self.Callback(table.unpack(Arguments)) -->> Calls Callback Function, and unpacks all Arguments and sends to it.
return results or table.unpack(results) --Returns any results returned by the given Callback.
end
Another thing is, how could i improve this base system u gave me In the future? with additions that would be good for it, i could research some stuff and add them but i feel like asking u would be better right now.
The new code still isn’t quite right. Here’s fixed code:
function CustomEvent:Invoke(...: any?): any?
local extraArgs = {...}
local results = table.pack(self.Callback(table.unpack(extraArgs))
return table.unpack(results)
end
The best way to improve it would be to add more methods of RBXScriptSignal and then optimise it. I made that example in a few minutes, but I then spent the time making my own module for it and optimising it (won’t send here sorry, it’s quite important to my game) and it works really well now. Type annotation is very important especially for the function preview when calling methods from this class, it serves as a good reminder of what is needed. I type annotated pretty much everything in mine.
Note that table.pack adds a field with the key n. This is the median of number values within the table.
Interesting, but why table.pack the self.Callback plus the rest? of the arguments?
From what i’ve noticed on yours u don’t add results or table.unpack(results).
Because on mine the output would either be a table or just 1 argument.
table.packing im assuming is just putting every returned value into a table to then be returned with table.unpack?
Theres no variables separating anything so it’s quite hard to digest.
And to add more methods i would just see what methods RBXScriptSignal has and put it in the Module?
And how would i optimise something like this, would be nice if i had something to have reference of about the optimisation part.
function CustomEvent:Invoke(...: any?): any? -->> ... is all arguments passed into :Invoke.
local Arguments = {...}
local ReturnedValues: any? = self.Callback(table.unpack(Arguments))
local results: any = table.pack(ReturnedValues)
return table.unpack(results)
end
Would have to rename some variables but this feels just better to look at lol
When you call a function, the return results are stored in assigned variables. Unstored values are discarded. For example:
local function getNumbers()
return math.random(1, 10), math.random(1, 10)
end
local rand = getNumbers() --> this would store one of the numbers. The other one is not stored and is discarded.
local rand, rand2 = getNumbers() --> both generated numbers are stored
local rand, rand2, rand3 = getNumbers() --> rand3 is nil because there was not a third number returned
When you store the return results, you are only storing the first one. However, if we pass the return results straight to table.pack(), none of them will get discarded and it can all be stored as a table with only one variable holding a reference to it. We can then re-distribute the return results to the variables that requested them by using return table.unpack(results). This will return all the results, the same way all of the results were passed to table.pack(). This allows us to account for any amount of extra parameters through one function.
--Invoke
local Result = TestEvent:Invoke(1,2,3)
print(Result) -->> Would print 3
--Receive
TestEvent:Connect(function(1,2) -->> no mention of 3
return 1 + 2
end)
This wouldn’t discard any variables but since i didn’t mention them in this function, tthey will be “discarded” kind of.
even though they are still passed but just not mentioned.