I am reporting an engine issue about the CollectionService. In my real project, I’m using the CollectionService to populate certain tags with certain objects. It is important to note that the objects used to populate can themselves have tags which require population. I found an issue where tagged children who were created through tag population ie cloned by a handler of a different GetInstanceAddedSignal signal would be detected twice by the GetInstanceAddedSignal signal. I created a test project to be able to see the bug without all of the core code of the main game. Here is the project structure and all related scripts.
Workspace:
Part: OriginalCreator (Tagged Populater)
ReplicatedStorage:
Part: CloneableCreator (Tagged Populater)
Part: CreatedPart (Tagged CreatedPart)
ServerScriptService:
Script: TestScript (Source code attached)
TestScript.lua
-- /// Services ///
local CollectionService = game:GetService("CollectionService")
-- /// Helpers ///
local function run_for_each_tagged_part(tag, cb)
local function wrapped_cb(part)
if workspace:IsAncestorOf(part) then
cb(part)
end
end
-- Listen for this tag being applied to objects
CollectionService:GetInstanceAddedSignal(tag):Connect(wrapped_cb)
-- Also detect any objects that already have the tag
for _, object in pairs(CollectionService:GetTagged(tag)) do
wrapped_cb(object)
end
end
-- /// Setup some listeners ///
run_for_each_tagged_part("Populater", function(part)
print("Populating creator named " .. part:GetFullName())
game.ReplicatedStorage.CreatedPart:Clone().Parent = part
end)
run_for_each_tagged_part("CreatedPart", function(part)
print("Found a part that got created and is a descendant of the workspace named " .. part:GetFullName())
end)
-- /// Spawn a new creator ///
print("Cloning game.ReplicatedStorage.CloneableCreator and moving to workspace")
game.ReplicatedStorage.CloneableCreator:Clone().Parent = workspace
Current output:
(Logic error is in italics)
Populating creator named Workspace.OriginalCreator
Found a part that got created and is a descendant of the workspace named Workspace.OriginalCreator.CreatedPart
Cloning game.ReplicatedStorage.CloneableCreator and moving to workspace
Populating creator named Workspace.CloneableCreator Found a part that got created and is a descendant of the workspace named Workspace.CloneableCreator.CreatedPart Found a part that got created and is a descendant of the workspace named Workspace.CloneableCreator.CreatedPart
Desired output:
Populating creator named Workspace.OriginalCreator
Found a part that got created and is a descendant of the workspace named Workspace.OriginalCreator.CreatedPart
Cloning game.ReplicatedStorage.CloneableCreator and moving to workspace
Populating creator named Workspace.CloneableCreator
Found a part that got created and is a descendant of the workspace named Workspace.CloneableCreator.CreatedPart Found a part that got created and is a descendant of the workspace named Workspace.CloneableCreator.CreatedPart
What the program does:
Setups up two listeners for two tags: Populater and CreatedPart (Note: These events only run on descendants of the workspace):
The Populater tag handler copies a part tagged with CreatedPart located in the ReplicatedStorage and reparents it to the detected part
The CreatedPart logs the part’s existence to the console
The listeners detect a part in the workspace already tagged as a Populater and populates it.
The listeners detect the created part in the workspace.OriginalCreator tagged as a CreatedPart and correctly logs it’s existence to the console once.
A part tagged as a Populater is coppied from ReplicatedStorage and moved to the workspace
This part gets detected by the Populater tag listener where it is populated once.
However, the created part in the workspace.ClonedCreator is detected twice by the CreatedPart tag listener and it’s existence is logged out twice even though it’s only created once. The issue lies here.
What I know
Cloning the CreatedPart into the Workspace or Workspace.OriginalCreator fixes the double detection of the CreatedPart.
Adding a slight wait, running the code with breakpoints or spawning a new thread before cloning the CreatedPart fixes this.
Thanks,
Riley
EDIT:
I found an even easier way to replicate this bug. All it requires is the following script in the ServerScriptService.
local CollectionService = game:GetService("CollectionService")
CollectionService:GetInstanceAddedSignal("Registered"):Connect(function(part)
print("InstanceAddedSignal called for " .. part:GetFullName())
end)
game.DescendantAdded:Connect(function(part)
print("DescendantAdded called for " .. part:GetFullName())
CollectionService:AddTag(part, "Registered")
end)
Instance.new("Part").Parent = workspace
Current output:
…
DescendantAdded called for workspace.part InstanceAddedSignal called for workspace.part
InstanceAddedSignal called for workspace.part
…
Desired output:
…
DescendantAdded called for workspace.part
InstanceAddedSignal called for workspace.part InstanceAddedSignal called for workspace.part
…
What the program does:
Setups up an event listener for when an instance is tagged as Registered.
Setups up a game.DescendantAdded listener for when an instance is added to the DataModel / game.
I am also experiencing this issue, I’ve attached a place containing a script that reproduces the bug (you’ll find it under ServerScriptService). CSBug.rbxl (17.0 KB)
Wow I did not expect to find a thread that is 3 years old on the same exact issue that I experienced…
I have added up to date code using task* and also provide a rbxl file
Code
local CollectionService = game:GetService("CollectionService")
local Test = workspace.Test
do
local function setTag(instance: Instance)
CollectionService:AddTag(instance, "Test")
end
for _, descendant: Instance in ipairs(Test:GetDescendants()) do
task.spawn(setTag, descendant)
end
--[[
Case 1: setting workspace.SignalBehavior to Deferred will fix it
Case 2: adding task.wait before adding a tag will fix it
Case 3: using task.defer before adding a tag will fix it
therefor it seems like waiting a frame before adding a tag will solve our problem
--]]
Test.DescendantAdded:Connect(function(descendant: Instance)
-- Case 2:
--task.wait()
setTag(descendant)
-- Case 3:
task.defer(CollectionService.AddTag, CollectionService, descendant, "Test")
end)
end
local function doForTagged(tag: (string), callback: (Instance) -> ()): RBXScriptConnection
for _, instance: Instance in ipairs(CollectionService:GetTagged(tag)) do
task.spawn(function()
print("for ipairs-", instance:GetFullName())
callback(instance)
end)
end
return CollectionService:GetInstanceAddedSignal(tag):Connect(function(instance: Instance)
print("GetInstanceAddedSignal-", instance:GetFullName())
callback(instance)
end)
end
local cache = {}
doForTagged("Test", function(instance)
if not cache[instance] then
cache[instance] = instance
--print("1st:", instance:GetFullName())
else
--! print if GetInstanceAddedSignal got triggered twice
print("2nd:", instance:GetFullName())
end
end)
task.spawn(function()
local model = Instance.new("Model")
while true do
local model = Instance.new("Model")
model.Name = game.HttpService:GenerateGUID()
model.Parent = Test
task.wait(1)
end
end)
I have an issue with GetInstanceRemovedSignal() firing twice. When a part is destroyed, the function is called twice (though it is in a more complex environement so there’s possibly something else doing something??? idk). If I can’t find a nice fix for it that’ll be annoying
Edit: it was caused by connecting two tags to my tag removed function, so when the part was destroyed, both tags were removed, thus firing twice
I am also experiencing this issue and cannot find a consistent workaround.
Edit: Oops, I had a script set to the localscript runcontext in the starterplayer, which caused it to be duplicated, which is why I thought it was firing twice
Edit: my issue was fixed a while ago, linking it more clearly to help others:
old reply
I think I’m also having this issue. GetInstanceAddedSignal is firing twice in all of my code that use it.
Here's a specific script with relevant code. *(A local script under StarterGui. )*
local CollectionService = game:GetService("CollectionService")
local TweenService = game:GetService("TweenService")
local ToggleLoadingScreenRemoteEvent = game.Workspace.RemoteEventsFolder.UI.ToggleLoadingScreen
local PlayerEnteredAWorld_RemoteEvent = game.Workspace.RemoteEventsFolder.PlayerEnteredAWorld_RemoteEvent
local teleportPlayer_ClientToServer = game.Workspace.RemoteEventsFolder["TeleportPlayer(ClientToServer)"]
local SetGameData_RemoteEvent = game.Workspace.RemoteEventsFolder["Data System"].SetGameData
local objectsLoadedIn_Table = {}
local tagForThisScript = "Portals"
local localPlayer = game.Players.LocalPlayer
local function makeObjectWhatItShouldBeFunction(objectModel_v)
if objectModel_v ~= nil then
local PortalPart
local WherePlayerGoesPart
local TeleportingSound
local debounce = false
local whatWorldDidThePlayerEnter
local success, errorMessage = pcall(function()
PortalPart = objectModel_v.portalBit
WherePlayerGoesPart = objectModel_v.EndTeleport
TeleportingSound = objectModel_v.TeleporterPortalSound
whatWorldDidThePlayerEnter = objectModel_v.WhereDoesThisPortalTakeThePlayer_StringValue.Value
end)
--print(success,errorMessage)
if success then
PortalPart.Touched:Connect(function(hit)
if game.Players:GetPlayerFromCharacter(hit.Parent) == localPlayer then
if debounce == false then
debounce = true
local teleportGoalCFrame = WherePlayerGoesPart.CFrame
local shouldLocationBeStreamedIn = true
teleportPlayer_ClientToServer:FireServer(teleportGoalCFrame,shouldLocationBeStreamedIn)
if objectModel_v.Parent.Name == "World1_Grasslands" then
-- this means the portal is from grasslands tutorial to hub world
local whatDataIsBeingSet = "hasPlayerCompletedTheTutorialOnThisSaveFile"
local whatShouldTheDataBeSetTo = true
SetGameData_RemoteEvent:FireServer(whatDataIsBeingSet,whatShouldTheDataBeSetTo)
end
--TeleportingSound:Play()
local toggleOnOrOff = true
ToggleLoadingScreenRemoteEvent:FireServer(toggleOnOrOff)
PlayerEnteredAWorld_RemoteEvent:FireServer(whatWorldDidThePlayerEnter)
print("Portal Test!!")
task.wait(1.85)
toggleOnOrOff = false
ToggleLoadingScreenRemoteEvent:FireServer(toggleOnOrOff)
debounce = false
end
end
end)
--
end
end
end
-- to get future parts:
CollectionService:GetInstanceAddedSignal(tagForThisScript):Connect(function(newpart: Part)
--print("Check GetInstanceAddedSignal!")
print(newpart:GetFullName())
if table.find(objectsLoadedIn_Table, newpart) == nil then
table.insert(objectsLoadedIn_Table, newpart)
--print(newpart:GetFullName())
makeObjectWhatItShouldBeFunction(newpart)
wait(0.5)
end
end)
-- to remove parts as they are unloaded:
CollectionService:GetInstanceRemovedSignal(tagForThisScript):Connect(function(oldpart: Part)
--print("Check GetInstanceRemovedSignal!")
local index = table.find(objectsLoadedIn_Table, oldpart)
if index then
table.remove(objectsLoadedIn_Table, index)
end
end)
The models tagged are all Atomic; I’m using StreamingEnabled in my game.
This doesn’t seem to apply/work for me. Do you mean in a CoreScript? I assume not. My implementation does not use :AddTag(). *see code from dropdown above
I had the same problem but with CollectionService:GetTagged(). I don’t know how to properly use task.defer so this is my solution
local AllTags = CollServ:GetTagged("Absolute")
local Tags = {}
table.move(AllTags, 1, #AllTags/2, 1, Tags)
for i,v in ipairs(Tags) do
print(i,v)
AbsoluteButton(v)
end
It moves half of the table to another table and it works. Don’t worry about not working functions, it deletes only clones(somehow). If it works, don’t touch it lol.
Edit: It doesn’t work. It fires one time but the code I put in it doesn’t work. So I will write a script that activates all connections when GUI is active and disconnects them when GUI is hidden to compensate double connections.
Edit2: Wait a minute if code doesn’t work then it means there is no double connections. It might be visual bug
Hi, could you confirm if this bug is still happening? I’m not able to reproduce it with the code you’ve provided, using both deferred and immediate signal behavior. There’s been a few fixes recently related to tag adding and removal, so it’s possible this bug has been fixed indirectly.
Edit: actually, this issue might still be a thing for others. I guess waiting for the OP is key here, hence why you replied to them. By bad. I had notifications on for this thread. My specific issue had to deal with something that was fixed quite a while ago. See my old reply for details.
my old reply
Hi, you can close this bug out. In short, it’s not a bug. More info can be found from these quotes: