I use listeners a lot. And it’s come to a point where I am slightly worried if I am using too many. How exactly do they work? Are they like while true do loops with extra steps? I.
See, now this isn’t much of a problem, until you realize you have hundreds upon hundreds of listeners. And I am sure hundreds upon hundreds of while true do loops is definitely not healthy for the game.
Now, I could have one script and one listener, however that would require me to create a mega-script with possibly thousands of lines of code. I definitely don’t want to do that.
Is there a solution for this? Or am I overestimating the resource intensiveness of listeners?
Now I don’t know for sure, but events are abstracted into while true loops and will callback on whatever function you connect to it; the part I’m unsure about is if these loops are independent from one another or not - but I’m pretty sure they are all bundled up in the same loop in some module that handles the events.
The event code is basically the mega script that handles all of that for you so I wouldn’t worry about it.
The only way for your code to check properties changing is to check them every iteration, which needs a loop (unless you want to hard code it to check less often than the framerate). There’s no way around this, listeners do the same thing because in code, we only see them as listeners but they are just abstracted checkers.
That’s a relief to hear, but I’m slightly unsettled with a “pretty sure”.
There’s not actually a problem with my code, it’s working perfectly fine. I’m just wondering if there is a better way to do it. I’m also going to assume that you meant attributes and not properties.
(I may have read the post wrong, please correct me if that’s the case)
I didn’t mean to imply a problem with the code - all I meant to say is, the only way to detect changes to those properties is to use a loop or to use an event (which is just setting up the loop somewhere else for you already). Properties and attributes, all the same I would assume (I know there are some minor implementation details with attributes but those should be negligible). It’s true that cluttering code with events is not great but in the end they represent a handful of ifs in a loop. If your game is slowing down it is probably from something else.
Also worth considering if you are trying to squeeze in performance, disconnecting events when you are not expecting anything from them for significant chunks of time.
I could do that, but that’d require me to connect another attribute listener for game.Workspace:GetAttribute(“inSession”) to be set to false. Which is unideal of course, but I still do appreciate the replies!
Another question in relation to my side note — which would be more performance wise?
The Attribute listener (as implemented now)
A single Bindable Event - each script listens for the same event, and if the parameters match then continue with the code. Problem with this one is, each listener will fire, and that’d probably cause some performance issues.
A Bindable for each event - each script listens for their own bindable. The problem here is that it’s highly inconvienient due to it requiring me to copy + paste the bindable in every single script.
Are you talking about events? Like signals and stuff?
Well almost definitely it’s not all going to be while loops checking over and over again, that would just be silly. No need to worry about how much performance impact there will be because there will likely be little to no impact whatsoever.
I don’t know the specifics of how Roblox handles their events but I am familiar with how events are usually made.
Usually, you have an object that has a few methods like :Fire() and :Connect(). When you call :Connect() with a function as an argument, the object just stores the function in a table then when someone calls :Fire() all the functions in the table are executed.
This means no loops wherever in this system and not very much performance impact.
Here is a sample signal class.
-- Signal
-- Stephen Leitnick
-- Based off of Anaminus' Signal class: https://gist.github.com/Anaminus/afd813efc819bad8e560caea28942010
--[[
signal = Signal.new()
signal:Fire(...)
signal:Wait()
signal:WaitPromise()
signal:Destroy()
signal:DisconnectAll()
connection = signal:Connect(functionHandler)
connection.Connected
connection:Disconnect()
connection:IsConnected()
--]]
local Promise
local Connection = {}
Connection.__index = Connection
function Connection.new(signal, connection)
local self = setmetatable({
_signal = signal;
_conn = connection;
Connected = true;
}, Connection)
return self
end
function Connection:Disconnect()
if (self._conn) then
self._conn:Disconnect()
self._conn = nil
end
if (not self._signal) then return end
self.Connected = false
local connections = self._signal._connections
local connectionIndex = table.find(connections, self)
if (connectionIndex) then
local n = #connections
connections[connectionIndex] = connections[n]
connections[n] = nil
end
self._signal = nil
end
function Connection:IsConnected()
if (self._conn) then
return self._conn.Connected
end
return false
end
Connection.Destroy = Connection.Disconnect
--------------------------------------------
local Signal = {}
Signal.__index = Signal
function Signal.new()
local self = setmetatable({
_bindable = Instance.new("BindableEvent");
_connections = {};
_args = {};
_threads = 0;
_id = 0;
}, Signal)
return self
end
function Signal.Is(obj)
return (type(obj) == "table" and getmetatable(obj) == Signal)
end
function Signal:Fire(...)
local id = self._id
self._id = self._id + 1
self._args[id] = {#self._connections + self._threads, {n = select("#", ...), ...}}
self._threads = 0
self._bindable:Fire(id)
end
function Signal:Wait()
self._threads = self._threads + 1
local id = self._bindable.Event:Wait()
local args = self._args[id]
args[1] = args[1] - 1
if (args[1] <= 0) then
self._args[id] = nil
end
return table.unpack(args[2], 1, args[2].n)
end
function Signal:WaitPromise()
return Promise.new(function(resolve)
resolve(self:Wait())
end)
end
function Signal:Connect(handler)
local connection = Connection.new(self, self._bindable.Event:Connect(function(id)
local args = self._args[id]
args[1] = args[1] - 1
if (args[1] <= 0) then
self._args[id] = nil
end
handler(table.unpack(args[2], 1, args[2].n))
end))
table.insert(self._connections, connection)
return connection
end
function Signal:DisconnectAll()
for _,c in ipairs(self._connections) do
if (c._conn) then
c._conn:Disconnect()
end
end
self._connections = {}
self._args = {}
end
function Signal:Destroy()
self:DisconnectAll()
self._bindable:Destroy()
end
function Signal:Init()
Promise = require(script.Parent.Promise)
end
return Signal
The roblox event system does handle an internal loop that constantly checks for a change.
This is just a wrapper for BindableEvent.
But maybe this is what you are interested in: When an event fires, a new task is added to the task scheduler. If at any given time there are too many tasks, many of them will be relegated to the next game step. of course you also need to be worried about how intensive the tasks are. that will also cause the tasks to be relegated. If too many tasks are relegated this will cause the player to notice some performance issues.
However, Robox is quite powerful so this should not be a cause for worry. At least not until you notice a performance problem. In such a case you could do microprofiling to detect the problem.