Instance.Destroyed Event

The Instance.Destroyed event would fire once after all other events have disconnected, then it will disconnect itself. It would fire when the Instance can no longer be parented to or interact with the game world. It would allow for ‘extensions’ of existing Instances without messy or inefficient cleanup code.

It is very common that I have an ‘extended’ instance that needs to terminate some running code when the actual instance it uses in the hierarchy is deleted. Currently there is no signal for when an Instance is destroyed other than checking behavior after it is parented to nil. This is a messy solution to a simple problem.

Some examples:

  • A controls display GUI that needs to listen to UserInputService.LastInputTypeChanged in order to update the displayed control. When the actual GUI object is destroyed it can disconnect the LastInputTypeChanged event automatically by connecting to the Instance.Destroyed event on the GUI.
  • A custom effects/particles system based on existing ROBLOX Instances. When the Instances it’s based on are destroyed it can disconnect any RenderStepped events it’s connected to, remove itself from effects index tables, etc.
  • Special objects with unique behavior in the game world that are listening for user events or other events not directly related to the Instance they are based on. For example, a physical keyboard that watches for user presence to open up a keyboard GUI. It needs to stop watching for user presence when the keyboard model is destroyed.

Most of these apply when a single script such as a ModuleScript is handling these objects. With normal Scripts or LocalScripts parented to such objects, connections and programming will automatically be disconnected and stopped. It is often more efficient and easier to manage such systems when all the programming is contained within one place instead of being duplicated into each object. We need a way to know when to disconnect and stop programming in these sort of systems.

44 Likes

For now, can’t you do something like this?:

Instance.Parent.ChildRemoved:connect(function(Obj)
	if Obj == Instance then
		--item got destroyed
	end
end)

But yeah, an official event for this would be nice to have.

You can listen to AncestryChanged and check if the instance is still in the DataModel:

instance.AncestryChanged:connect(function()
    if not instance:IsDescendantOf(game) then
        -- item was removed
    end
end)

Or you can listen to game.DescendantRemoving.

4 Likes

This is technically not the same behavior, as de-parenting from world is different than pure GC.

The core use of this is, as Corecii said, extending existing ROBLOX objects, which currently requires listening to ancestry changed and has several edge cases associated with it.

3 Likes

I realize they are not equivalent, but I don’t see use cases for detecting when an object is destroyed that isn’t satisfied by detecting when the object is removed from the DataModel. If you want to handle the object being re-added, listen to DataModel.DescendantAdded.

1 Like

I made a workaround for now that should work in almost all cases.

Here is a module. It’s named MainModule so it should be requireable by ID.
Here is the source on Github Gist.

(Edit: This will no longer prevent garbage collection of instances)

It takes advantage of the fact that when an object is destroyed all events are disconnected. It uses coroutine.yield to push execution to the end of the Lua execution cycle then checks if the event is connected. If it’s not connected, it runs a given function. If it’s still connected but in nil, it checks the connection every RunService Step because it can be destroyed then without firing AncestryChanged.

Overall, I would call this solution a bit of a hack. It’s messy and takes advantage of weird quirks in the ROBLOX engine. By abstracting it out to a module it can be fixed as needed and replaced by an actual, official solution if or when it is added.


This is generally true, but I prefer any ‘extended’ instances I make to work without having to restrict their usage further than I would a normal ROBLOX instance. If a normal ROBLOX Instance can be parented to nil then back and still work, I’d like to cleanly and easily accomplish the same with my own ‘extensions’.

2 Likes

In some cases, it’s nice to not GC and then reconstruct your Lua side objects upon temporary nil parenting, especially if these objects construct components themselves, or store state in some way.

Sometimes construction and deconstruct costs can be high enough that this could be messy.

I think an event like this helps encapsulate state better (i.e. I want to clean-up only when this object is destroyed, not when it’s in/out of the datamodel).

1 Like

It’d still be really useful to have this feature (seems like a huge missing part of the API) to avoid having to resort to hacky unstable ways of telling when objects have been destroyed for sure (as opposed to being temporarily orphaned) before performing irreversible clean-up work.

2 Likes

Ya bump still useful

I was just searching for something like this, yes, it would really help in certain cases.

This is something I’m looking for. I keep having to handle edge cases and it creates a ton of unpredictability in my codebase

6 Likes

This is a feature I’m looking for because I occasionally parent things to nil temporarily but don’t want my destruction detection code to think it’s destroyed. Is an event like this planned for the near future?

Yes please!

This needs to be implemented as having a workaround that doesn’t serve the same purpose is quite annoying!

And as @EncodedLua stated, the current workaround can give false positives.

2 Likes

I would still really like this. One example of when AncestryChanged won’t work is if the Parent of an object is never set, e.g. creating an instance, connecting an AncestryChanged, and then destroying it, will not fire the AncestryChanged event.

2 Likes

I’d like to see this event added. Reliance on AncestryChanged means that I cannot parent objects to nil without worrying that they’ll be classed as destroyed by my own code.

2 Likes