CollectionService :GetTagged

Is calling CollectionService :GetTagged() repeatedly “bad”? Should I be caching it somewhere instead?

You should never need to call it more than once for the same tag afaik. Why do you need to?

1 Like

It’s no worse to call it often than any other method, really-it’s simply to improve performance a bit. If you’re not calling it incredibly often it should be fine. Ideally you should only call it if there’s a possibility that the list of tagged items will have changed since the last call.

But, there’s a possibility that the list has changed. Also considering the collection service have tags, I feel it’ll be best to assume there’s a table with associated references contained internally.

Calling CollectionService:GetTagged is no worst than getting a variable set to a table. It’s not like its traversing the workspace to find the tagged items.

With that being said I’m unsure if setting a variable outside the scope of a function that’s expecting the list to be updated if something was changed prior to the variable being set, would return an updated table with the tagged instances.

It might be best to experiment with this :wink:

The most efficient way to stay up to date on tagged instances is the following:

local tagged = CollectionService:GetTagged("Tagged")

CollectionService:GetInstanceAddedSignal("Tagged"):Connect(function(new_instance)
    table.insert(tagged, new_instance)
end)

CollectionService:GetInstanceRemovedSignal("Tagged"):Connect(function(removed_instance)
    for i=1,#tagged do
        if tagged[i] == removed_instance then
             table.remove(tagged, i)
             break
        end
    end
end)

Then, at any moment, you can assume the tagged table is always up to date, no need to call :GetTagged()

disclaimer:

I don’t know the backend of :GetTagged(). It may very well be just as efficient as doing this^

3 Likes

This becomes pretty intensive once you reach 1000s of instances since you’ll have to iterate through them all when the last item needs to be removed. If the 10 most recently tagged items were to be removed, and there’s currently 1000 tagged items, then you’ll need to iterate through ~10000 entries total!

An improvement on this is to use an array in addition to a map with maps the tagged instance to its index in the array since looking up its index in the map is much faster than iterating through the array.

local TAG = "Tagged"
local tagged = CollectionService:GetTagged(TAG)
-- Build a mapping of which instance maps to which index in the tagged array.
-- This is a one-time cost.
local idxMap = {}
for i = 1, #tagged do
	idxMap[tagged[i]] = i
end

CollectionService:GetInstanceAddedSignal("Tagged"):Connect(function(newInstance)
	-- Append the instance to the array.
	local idx = #tagged+1
	tagged[idx] = newInstance
	-- Maintain the mapping.
	idxMap[newInstance] = idx
end)

CollectionService:GetInstanceRemovedSignal("Tagged"):Connect(function(removedInstance)
	-- Keep track of the last item in the tagged array.
	-- We will re-insert this into the index where removedInstance was.
	local lastIdx = #tagged
	local lastTagged = tagged[lastIdx]
	-- We can look up where the removedInstance is in the array quickly using the map.
	local removedIdx = idxMap[removedInstance]
	-- Remove that instance from both the array and the map.
	tagged[removedIdx] = nil
	idxMap[removedInstance] = nil
	-- If the removed instance wasn't the last instance in the array, then
	-- move the last instance in the array to where the removed instance was.
	if lastTagged ~= removedInstance then
		tagged[lastIdx] = nil
		tagged[removedIdx] = lastTagged
		-- Again, make sure to maintain the mapping.
		idxMap[lastTagged] = removedIdx
	end
end)

Note: It’s very important that you do not modify the tagged array when you want to use it (e.g. by inserting or removing entries yourself), otherwise the mapping falls apart!

8 Likes