Instance.IsDestroyed and Instance.Destroyed event

This sounds like a use case for weak tables.

3 Likes

I’ve never been able to use weak tables correctly.
What do I do to make the array delete itself and its references if nothing is pointing to the table?

Values will automatically be collected when they are no longer in use.

local weak = {__mode="v"}
local tab = setmetatable({}, weak)

You can also use k and kv where k makes the ‘key’ weak and v makes the ‘value’ weak.

2 Likes

That works, however, it will still be easier to have an event so we don’t have to continually poll for when it’s removed from the table.

Could you provide some tangible code examples of what you’re doing now vs what you would do with this feature?

Currently:

spawn( function ( )

while script and script.Parent and wait( ) do end

-- Cleansup admin stuff such as tables ( Destroying a modulescript doesnt stop code running, this line prevents me from moving my module into nil for safer storage )

end )

With this:

script.Destroyed:connect( function ( )

-- Cleansup admin stuff

end )

What defines an object as being in use or not? Is it just idle time?

No strong references* are made to the object.

* any reference that isn’t weak

2 Likes

Figures. So if the RBXScriptConnection is still connected, then it won’t be collected?

Destroy disconnects any connections.

It doesn’t delete the userdata that was allocated to represent the connection. The userdata does consume memory, I’ve been able to measure it.

I found a way to work around this issue, but it was really tricky for me to isolate that it was happening in the first place.

I was having trouble correctly detecting that an object was destroyed because the ancestorychanged connection was getting killed before I could check.

If you don’t already know, quick way to print when it’s destroyed:

function PrintDestroyed( Obj )
   spawn( function ( ) local a, b = Obj.Name, setmetatable( { Obj }, { __mode="v" } )
   while b[ 1 ] and wait( ) do end
   print( a .. " Destroyed" ) end )
end

Also, the event userdata wouldn’t gc until any references to it are cleared however it wouldn’t stop the object the event is for being gc’d as it has no references to the event object ( I assume ).

I have cleaned up your code, it was a bit confusing because of the way you structured it. I also believe you needed to dereference obj otherwise it will never print?

local collectV = {__mode='v'}
local function printDestroyed(obj)
  spawn(function ()
    local a, b = obj.Name, setmetatable({obj}, collectV)
    obj = nil
    repeat wait() until not b[1]
    print(a, "destroyed")
  end)
end
1 Like

Please know that weak tables are not reliable for this. Just because a weak reference is collected does not mean the referent is no longer in memory. Observe:

-- Create a weak table.
local a = setmetatable({}, {__mode="v"})

-- Create an instance an add it to the workspace.
-- We'll even give it some time to ensure that it's there.
local v = Instance.new("BoolValue")
v.Name = "UniqueName"
v.Parent = workspace
wait(1)

-- Ensure the only reference to the instance is weak.
a[1] = v
v = nil

-- Wait for reference to be collected.
local t = tick()
while #a > 0 do wait() end
print("ref collected after", tick()-t, "seconds")
print(workspace:FindFirstChild("UniqueName"), "still exists")

This happens because the Garbage Collector is not aware of the internal Roblox state. The GC only collected the userdata pointing to the internal state of the instance, so the internal state is still there. The game tree doesn’t maintain a set of strong references inside the Lua state that allow the GC to know that the instance still exists. The same applies to Connections; because the GC isn’t aware of the internal reference of a Connection to its instance, weak references to a Connection will be collected immediately.

5 Likes

That explains why I was having trouble making weaktables work with this. I figured there was something else going on behind the scenes that I wasn’t accounting for.
I don’t really want to use weaktables anyway, Roblox should just be accounting for this somehow.

Would it be infeasible for Roblox to just null out the userdata representation of connections if there was a reference to one in an array, or is that too unsafe? That would probably fix the problem I was having.

I ended up fixing this issue by isolating the userdata from the rest of my code with a proxy that keeps track of when the object gets destroyed.

local function isParentUnlocked(inst)
	if not inst.Parent then
		coroutine.yield()
		local unlocked = pcall(function ()
			local parent = inst.Parent
			local f = Instance.new("Folder")
			inst.Parent = f
			coroutine.yield()
			inst.Parent = nil
		end)
		return unlocked
	else
		return not inst:IsA("Terrain") 
	end
end
--
local function makeWeakConnection(object,signalName,func)
	local signal = object[signalName]
	local connection = signal:Connect(func)
	local proxy = { Connected = true }
	local ancestrySignal
	
	function proxy:Disconnect()
		if connection then
			connection:Disconnect()
			self.Connected = false
		end
	end
	
	local function onAncestoryChanged()
		if not (object:IsDescendantOf(game) and isParentUnlocked(object)) then
			proxy:Disconnect()
			ancestrySignal:Disconnect()
			proxy = nil
			connection = nil
			signal = nil
			object = nil
			ancestrySignal = nil
		end
	end
	
	ancestrySignal = object.AncestryChanged:Connect(onAncestoryChanged)
	
	return proxy
end
1 Like

AFAIK, the reference would gc as nothing was using the reference?

The anonymous function to spawn is still able to reference it.

Oh, is that a thing?

I thought if it wasn’t used it would GC.

Crap. Time to rewrite a lot of stuff.

Check this post out too. Looks like a similar request (that even has the same name).

Edit: I’ve been trying to figure out if the module I posted in that thread still works. It shows some very strange behavior in studio, but it seems to work perfectly online. In studio, it will only detect when instances are garbage-collected from nil if the instance and the detection code are run as soon as the game starts. Online it doesn’t matter and it will detect when an instance is garbage-collected regardless of when it is ran. :Destroy() detection works fine in both studio and online.

1 Like

I’d like Instance.IsDestroyed to be implemented, it would be very helpful

11 Likes