Add ability to check if instance is destroyed

As a Roblox developer, it is currently impossible to cleanly check if an in-game instance was destroyed.

For context, this is different than simply having the Parent property set to nil. An instance that is explicitly destroyed is special because certain aspects of the object become locked down. Currently, the only way to absolutely check if a part is destroyed is by trying to access one of these locked-down properties in a pcall and see what the error is. This is not sufficient.

Example use-case: I want to create a module that acts as a wrapper around an in-game model. The module needs to know when to clean itself up, which should naturally occur when the model is destroyed. However, there’s no way to actually do this. The module could just listen for the Parent to be set to nil, but that’s no guarantee that the model was actually destroyed! Perhaps the developer simply set the Parent to nil temporarily and will add it back.

Addressing this issue would allow developers to properly check if an object is destroyed, thus allowing developers to create object wrappers.


A possible API addition would be a read-only boolean field on all instances called IsDestroyed.

40 Likes

This is probably related? Instance.IsDestroyed and Instance.Destroyed event

2 Likes

Can you have the module clean up all references to the Model when the Model becomes parented to nil? Might help to include a code sample showing what you are trying to do.

1 Like

The problem is that a model parented to nil doesn’t necessarily mean that it’s done being used. For instance, maybe it’s some sort of building that pops in and out of existence. Cleaning up when the parent is set to nil is what I currently do for my own work, but trying to open-source any modules for other people becomes difficult under this model.

This especially becomes true when the wrapper is under component-based design, thus it could be applied to just about any object.

A really simple example: Let’s say I have a wrapper module that changes the color of a part every time a player enters the game:

local Colorizer = {}
Colorizer.__index = Colorizer

function Colorizer.new(part)
	local self = setmetatable({Part = part}, Colorizer)
	self._playerAdded = game.Players.PlayerAdded:Connect(function()
		self:ChangeColor()
	end)
	self:ChangeColor()
	return self
end

function Colorizer:ChangeColor()
	self.Part.Color = Color3.new(math.random(), math.random(), math.random())
end

Now, in the current state, this code is bad because we are making a reference to PlayerAdded forever, which is affecting Part–an instance that isn’t guaranteed to last forever. If Part were destroyed, then the ChangeColor function would throw an error every time a player entered.

So the solution here is to disconnect the event when the part is destroyed:

	-- Within the constructor:
	part:GetPropertyChangedSignal("Parent"):Connect(function()
		if (not part.Parent) then
			self._playerAdded:Disconnect()
		end
	end)

So the problem here is that the Part being parented to nil doesn’t necessarily mean that we’re done with the part. Maybe the part is only temporarily removed and will be reparented? The ChangeColor method will work fine if it’s just parented to nil, but will fail if destroyed.

It would be nice if there was either an event or property to capture the actual destruction of an instance:

	-- Destroyed Event:
	part.Destroyed:Connect(function()
		self._playerAdded:Disconnect()
	end)

	-- Or an IsDestroyed property:
	part:GetPropertyChangedSignal("IsDestroyed"):Connect(function()
		if (part.IsDestroyed) then
			self._playerAdded:Disconnect()
		end
	end)

The big point here is that there is a fundamental behavioral change of instances that are destroyed, and currently there is no way to capture that change.

Sorry, this was longer than expected

9 Likes

I see. Can you achieve what you are trying to achieve by disconnecting when the part is parented to nil, and otherwise adding an event listener like so?

local Colorizer = {}
Colorizer.__index = Colorizer

function Colorizer.new(part)
	local self = setmetatable({Part = part}, Colorizer)
	self:UpdateListeners()
	part:GetPropertyChangedSignal("Parent"):Connect(function()
		self:UpdateListeners()
	end)
	self:ChangeColor()
	return self
end

function Colorizer:UpdateListeners()
	if part.Parent then
		if not self._playerAdded then
			self._playerAdded = game.Players.PlayerAdded:Connect(function()
				self:ChangeColor()
			end)
		end
	else
		self._playerAdded:Disconnect()
	end
end

function Colorizer:ChangeColor()
	self.Part.Color = Color3.new(math.random(), math.random(), math.random())
end

Here’s my attempt.

local function trackDestroyed(instance)
	local connection = instance:GetPropertyChangedSignal("Parent"):Connect(function() end)
	return function()
		return not connection.Connected
	end
end

local part = Instance.new("Part", workspace)
local isDestroyed = trackDestroyed(part)

print(isDestroyed()) -- false
wait(1)
part:Destroy()
print(isDestroyed()) -- true

This relies on the fact that :Destroy() disconnects all connections.

And here’s an event based form:

local RunService = game:GetService("RunService")

local function onDestroyed(instance, callback)
	local connection
	connection = instance:GetPropertyChangedSignal("Parent"):Connect(function()
		RunService.Heartbeat:Wait()
		if not connection.Connected then
			callback()
		end
	end)
end

local part = Instance.new("Part", workspace)
onDestroyed(part, function()
	print("Destroyed!")
end)

wait(1)
part:Destroy()

RunService.Heartbeat:Wait() is necessary because the event is disconnected after it’s fired.

11 Likes

That’s quite a bit of boilerplate/overhead and hackiness with timing for a workaround. I guess it works, but both a destroyed event and a way to check the destroyed state would make this so much cleaner and easier to read and understand.

Here is the feature request for the Destroyed event by the way, it has a lot of support from developers too:

6 Likes

It would be nice if some of these old requests, when fulfilled, would get an update or a link

3 Likes

The .Destroyed event is useful however this is only invoked if Destroy is called on the instance

It’d be nice if the behaviour was more consistent with other ways an instance can be destroyed. Falling out of the world, a character model respawning etc, afaik, these do not trigger the Destroyed event