Problem with destroying instances in a table for visual effects system

So I’m trying to make a system for creating visual effects on the client to save on server memory. Currently it’s having trouble destroying instanced effects that are stored in a table.

The problem is very confusing because trying to print the full name of the instances reveal that the table is The instances and not just a reference to them, which has left me stumped cuz if it has access to instance functions surely it should be able to destroy them?

The table itself is declared like this

local EffectTable = {nil}

Instances are inserted into the table like this

table.insert(EffectTable, ObjectVisual)

And it’s trying to destroy instances like this
(i have double checked to make sure indexing isn’t causing the problem, it isn’t as otherwise it wouldn’t be able to return the correct names through :GetFullName())

EffectTable[ReceivedIndex]:Destroy()

Kinda lost on what to do cuz it honestly feels like an engine bug that it’s just not able to destroy instances this way when other instance functions like :GetFullName() or :GetChildren() work perfectly fine with no issues whatsoever.

local EffectTable = {}


print(ReceivedIndex, EffectTable[ReceivedIndex])
EffectTable[ReceivedIndex]:Destroy()

unfortunately removing nil from the EffectTable declaration had no effect. trying to print as shown here resulted in expected results but the problem wasn’t fixed.

This is a more readable way to store instances by index.

local EffectTable = {}

local testIndex = 1
local testObjectVisual = Instance.new("ParticleEmitter")

--Instead of
table.insert(EffectTable, testObjectVisual)

--Do:
EffectTable[testIndex] = testObjectVisual

As for your issue, can you show the rest of your code?

That is a valid way to handle it but due to my specific case it isn’t really viable as far as I know.

I should probably elaborate on how the surrounding code works.

The visual handler receives a HttpService GUID, a name and an index.
The name and index are used to get the corresponding object.
The GUID is used as a tag for storage as every object needs to be assigned a unique ID so the server can send signals to the client for those effects to be moved, deleted, etc. as other methods I’ve tried have resulted in excessive lag or unreliable indexing.

After the object is cloned it checks if there’s a space in the table where a previous object was deleted and adds to that empty spot, otherwise it just adds a new index to the table. I’m doing it this way as I don’t want a table to just get unlimited new objects as that would probably break something if too many effects are spawned over a client’s lifetime on a server.

The rest of the code is pretty self-explanatory, it receives calls from the server with the GUID and some settings for how an effect object should be changed, then tries to make those changes, however the thing that breaks is trying to use :Destroy() on objects when a call for that is made, as it simply just does nothing nor even errors.

local replicatedStorage = game.ReplicatedStorage
local EffectTable = {}
local IDTable = {""}

local function DoVisualThing(ReceivedIndex:number, ReceivedMode:number, ReceivedPosition:Vector3)
	if EffectTable[ReceivedIndex] ~= nil then
		if ReceivedMode == 1 then
			EffectTable[ReceivedIndex]:Destroy()
			IDTable[ReceivedIndex] = nil
		elseif ReceivedMode == 0 then
			EffectTable[ReceivedIndex].Position = ReceivedPosition
		end
	end
end

replicatedStorage.Remotes.ReceiveVisualData.OnClientEvent:Connect(function(ReceivedIndex:string, ReceivedMode:number, ReceivedPosition:Vector3)
	local IsIndexReal = table.find(IDTable, ReceivedIndex)
	if IsIndexReal ~= nil then
		DoVisualThing(IsIndexReal, ReceivedMode, ReceivedPosition)
	else
		repeat task.wait() until table.find(IDTable, ReceivedIndex)
		IsIndexReal = table.find(IDTable, ReceivedIndex)
		DoVisualThing(IsIndexReal, ReceivedMode, ReceivedPosition)
	end
end)

replicatedStorage.Remotes.InitiateVisualEffect.OnClientEvent:Connect(function(ReceivedID:string,ReceivedName:string,ReceivedIndex:number)
	local ObjectVisual
	if ReceivedIndex == 0 then
		ObjectVisual = game.ReplicatedStorage.VisualReplication.Projectiles:FindFirstChild(ReceivedName):Clone()
		ObjectVisual.Parent = workspace.ProjectileVisuals
	end
	if ObjectVisual ~= nil then
		local IsThereBlank = table.find(IDTable, "")
		if IsThereBlank then
			IDTable[IsThereBlank] = ReceivedID
		else
			table.insert(IDTable, ReceivedID)
		end
		IsThereBlank = table.find(EffectTable, nil)
		if IsThereBlank then
			ObjectVisual.Name = IsThereBlank
			EffectTable[IsThereBlank] = ObjectVisual
		else
			ObjectVisual.Name = #EffectTable + 1
			table.insert(EffectTable, ObjectVisual)
		end
	end
end)

Can you clarify what exactly is the issue?
Is it throwing an error, is it not destroying the instance you want, or is it something else?

I’m a bit confused with the EffectTable and IDTable, why not just use the assigned GUID as the key, and have a single dictionary instead of 2 arrays?
It’ll also be way better for performance because it’ll always be a simple lookup (complexity O(1)), rather than performing a search.

To reply to both:

The issue is that :Destroy() is just not destroying the object instances. As stated previously other instance functions like :GetFullName() or :GetChildren() work perfectly fine it’s just for some reason :Destroy() isn’t destroying.

Also interesting that dictionaries are better for performance, I was under the impression they ate more memory but if that’s not the case then good to know!

I believe you might just be encountering a logical error, does the call to :Destroy() ever happen?

Dictionaries do use more memory because they have keys, but they would actually use less in this case because you’re practically storing the keys in a separate array.

Ah I see that checks out.

Can also confirm that yes the call does happen, I’ve had a few prints from around when the :Destroy() call happens, it just simply doesn’t happen for some reason.

If you could show the target instance and add some debug print messages in a video that might help, it’s very unlikely this is due to an engine bug.

Ok while messing around trying to do that I decided “oh yeah I’ll just set the projectile spawner to spawn only 1 projectile to make it easier to see what’s going on” and then it DID destroy the visual object.

It seems to be when it has to handle multiple visuals that it’s suddenly unable to destroy something. It’s progress but now I’m even more confused lol

That sounds like it’s just not keeping track of the multiple instances or those instances aren’t descendants of the instance that does get destroyed.

I know at the very least that it is storing references to each object since I had a test where I made every object get numbered and it was able to recall individual ones correctly.

And yes they aren’t descendants of the one that does since that wouldn’t really do anything useful for my use case here, me using :GetChildren() was just a test to see if other instance functions would work on the object table if :Destroy() wouldn’t.

It might be useful because you’d only need to make one call to :Destroy() on the parent, rather than potentially hundreds of individual instances.

For removing a whole group at once yes, the thing is in my case here I’m trying to have individual projectile visuals in an attempt to recreate what was done in this video:

Which of course means I can’t mass remove since that would mean projectile visuals would be dropped before they’re meant to disappear.

Ah I thought you meant you have multiple instances for one single effect, how are you keeping track of and removing the projectiles?

The server handles the positional logic and offloads all of the visual work to the client by firing a creation event and subsequent update events from a RunService.Heartbeat loop. The rest is shown in the code I provided earlier.

Try changing it to a single dictionary like I mentioned, I believe it might have to do with your “IsThereBlank” logic, as you can’t really insert or find nil.

1 Like

Took a little while to figure out how to do that since ROBLOX’s own documentation on dictionaries is just Gone for whatever reason.

Can confirm that changing the two tables into a dictionary fixed the issue! There’s still a separate problem to fix with trying to remove entries once their associated object is destroyed but all’s good now, thanks for the answer!