Help with Debris module

So I have this custom debris module which essentially acts identical to the regular debris, except with a few more features. The problem is, is that when the script that initially called the AddItem function is destroyed, the function also stops. This may not seem like an issue at first, but considering that the module is mainly used by scripts inside the player’s character (tools), the scripts could be destroyed at any point. Any ideas on how to circumvent this?

Here’s the module:

local Debris = {}

local Queue: {[Instance]: number} = {}

--TODO: make this work even after the require-er script is destroyed, WITHOUT using bindable events

function Debris:AddItem(item: Instance, lifetime: number?)
	Queue[item] = os.time()
	task.delay(lifetime or 10, function()
		if item and item.Parent and Queue[item] then
			Queue[item] = nil
			item:Destroy()
		end
	end)
end

function Debris:RemoveItem(item: Instance)
	if not Queue[item] then warn(item.ClassName..' "'..item.Name..'" was not found in debris.') return end
	Queue[item] = nil
end

function Debris:IsAlive(item: Instance): (boolean?, number?)
	return Queue[item] and true or item.Parent ~= nil and nil, Queue[item] and os.time() - Queue[item] or not item.Parent and -1 or nil
end

return Debris
1 Like

Sorry for those previous attempts at a fix. This seems to work.

local bEvent = Instance.new("BindableEvent");
bEvent.Parent = script;

local Debris = {}

local Queue: {[Instance]: number} = {}

--TODO: make this work even after the require-er script is destroyed, WITHOUT using bindable events

function Debris:AddItem(item: Instance, lifetime: number?)
	bEvent:Fire(item, lifetime);
end

function Debris:RemoveItem(item: Instance)
	if not Queue[item] then warn(item.ClassName..' "'..item.Name..'" was not found in debris.') return end
	Queue[item] = nil
end

function Debris:IsAlive(item: Instance): (boolean, number)
	return Queue[item] and true or item.Parent and false, Queue[item] and os.time() - Queue[item] or item.Parent and -1
end

bEvent.Event:Connect(function(item, lifetime)
	Queue[item] = os.time()
	task.delay(lifetime or 10, function()
		if item and item.Parent and Queue[item] then
			Queue[item] = nil
			item:Destroy()
		end
	end)
end)

return Debris

Are BindableEvents really the only way? I’m not exactly sure what would be the effect of firing hundreds (thousands at times) of bindable events a second.

sorry, but I’m not aware of any other ways of doing this. Ideally you don’t place scripts inside characters but inside starterplayer, so it’s just one script running the entire time and it’s never destroyed so you don’t have to worry about these sorts of things.

Unfortunately, tools usually have scripts placed inside of them rather than being ran from within the player. I’m not really sure if it’s efficient and time-worthy to get a system in place that has all of your custom items and inventory needs running from a single script inside the player.

Just because that’s usually the case doesn’t mean you have to go with it. In fact, the majority of the time I see cases like this, it’s people just throwing around scripts left and right because they don’t know any better. Now that you’ve made the decision to literally insert a script into every tool that exists in a character, which will also be less efficient than what I mentioned, you can already see the problems arising such as scripts being deleted and threads being cut short. If you had coded it the other way from the beginning, I’m sure it would have been worth your time, and it would have been efficient as well.

Also, not sure why you would be restricted to doing this in one script on the client. You could create a module for each type of tool and just hook up the modules with the tools, so the tool uses code from one module rather than each tool using its own script which is just inefficient and I would say disorganized.

The latter part of what you said is actually exactly what I already do. When the Item.new function is called, it gets the item’s data via its ID, checks the template (item type’s modulescript) associated with it, plugs in the item’s stats into the module, and activates the scripts parented under it.

When the scripts activate, they read the stats stored inside the parent module and do what they’re supposed to do.

I get what you mean by doing the method you mentioned about individual scripts is messy and slow. If you change one you have to go through and change the rest and etc.

With my system, If I change the item’s template the changes automatically take affect in all related items.

The issue isn’t that my system isn’t unorganized or inefficient.

The only problem I’m having is that I can’t make completely independent threads without performance loss (spamming bindables and such) to prevent as you said, my threads being cut short.

1 Like

An idea is to ditch the task library and instead check every frame for if it should be destroyed. I have made a sample module that seems to work.

local RunService = game:GetService("RunService")

local Debris = {}

type QueueInfo = {
	InsertTime: number,
	DestroyTime: number,
}
local Queue: {[Instance]: QueueInfo} = {}

RunService.Heartbeat:Connect(function()
	local currentTime: number = os.clock()

	for item: Instance, queueInfo: QueueInfo in pairs(Queue) do
		if not item.Parent then
			Queue[item] = nil
		elseif queueInfo.DestroyTime <= currentTime then
			Queue[item] = nil
			item:Destroy()
		end
	end
end)

function Debris:AddItem(item: Instance, lifetime: number?)
	local currentTime: number = os.clock()

	local newDestroyTime: number = currentTime + (lifetime or 10)

	local existingQueueInfo: QueueInfo? = Queue[item]
	if existingQueueInfo then
		existingQueueInfo.InsertTime = currentTime
		existingQueueInfo.DestroyTime = math.min(existingQueueInfo.DestroyTime, newDestroyTime)
	else
		Queue[item] = {
			InsertTime = currentTime,
			DestroyTime = newDestroyTime,
		}
	end
end

function Debris:RemoveItem(item: Instance)
	if not Queue[item] then warn(item.ClassName..' "'..item.Name..'" was not found in debris.') return end
	Queue[item] = nil
end

function Debris:IsAlive(item: Instance): (boolean?, number?)
	return Queue[item] and true or item.Parent ~= nil and nil, Queue[item] and os.clock() - Queue[item].InsertTime or not item.Parent and -1 or nil
end

return Debris

A few things about this. First, in order to preserve the behavior of the second return value of Debris:IsAlive, I had to modify what the Queue stores in order to contain both the call time of Debris:AddItem and the time at which the instance should be destroyed.

In the case of Debris:AddItem being called multiple times on the same instance, it will change the call time to the newest call time and will also only change the destroy time if it would make it be destroyed quicker, mirroring the old module.

Finally, I switched it to os.clock internally, as it provides much more accuracy than os.time (which only provides the second). It doesn’t give the UTC time, but that isn’t needed for this module.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.