When a Part falls out of the world, you would expect it to be Destroyed and have it’s connections disconnected and the other stuff Destroy does, however, as you can see, this isn’t the case
(Test performed by dropping a part out of the workspace)
The print statement is not fired if you instead Destroy the part explicitly
This creates a severe memory leak for places that rely on lots of parts falling through the world that have events on them.
Expected behavior
I expect the part to be destroyed the same that calling Destroy on it would when it falls out of the world.
This is a weird engine quirk, reviving parts that fell out of the world is definitely a hack. While I’m all for backwards compatibility, this is one of those places where I’m skeptical of it because of the memory leak. StreamingEnabled is likely the only place where destroying the part is a bad idea for the time being (even though it would be easy to code around).
I’m hoping to get engineer input here 2 years later because StreamingEnabled is a little more developed instead of reviving a dead thread when the Destroying event didn’t exist
The fix right now is just explicity calling Destroy when AncestryChanged fires with Parent = nil but this also feels really hacky, but it does work for characters and players if done right.
I thought this had been fixed along with the introduction of Instance.Destroying! This behaviour doesn’t make any sense, and feels unintended (like, why is it called FallenPartsDestroyHeight if it doesn’t :Destroy anything?)
Definitely seems like a significant memory leak. +1.
I had no idea this behavior existed and it’s probably causing some unknown slow leaks in our games.
This is probably true for the overwhelming majority of devs as well.
Leaving a landmine like this in the engine is a horrible idea just for the sake of backwards compatibility.
Reminds me of this exploit almost nobody knew about (except exploiters) and was leaving vulnerabilities in nearly every game:
I stumbled upon this by complete accident by doing the tomfoolery that I usually do in Studio. I was just trying to show someone that Destroyed didn’t fire when an instance fell out of the world and then it spiraled into me testing loads of internal removals to check if Roblox is destroying them properly (it wasn’t)
Interestingly it seems to be the older behaviours that still rely on this. It’s not only parts falling out the workspace, it happens to characters respawning and players leaving as well (the player one is quite well known). GUIs being removed or re-apply descriptions do correctly destroy instances
If you want the raw code that I use to test if instances are being removed properly, I like using this (wont work on players, you need to modify it a bit)
local part = workspace.Part
local e = part:GetPropertyChangedSignal("Name"):Connect(function()
print("oh no it didnt clean up properly")
end)
part.Destroyed:Connect(function()
print("yay it called Destroy")
end)
part.AncestryChanged:Connect(function(_, p)
if not p then
task.wait(1) -- allows Roblox's collector to cycle
print(e.Connected) -- should return false
p.Name = "oh no" -- the event above should not fire if this is destroyed
end
end)
I noticed this the other day as well. My old character model stayed grouped even after I died, and none of the parts inside seemed to be destroyed as they were still parented to the model, inside nil. This behavior might also differ from client-server.