TweenService and Debris Wrapper

So a while back I created two modules that wrapped the TweenService and Debris. You just use them in place of the direct service itself and it adds some additional functionality. I’ve been using them for a while so I figured why not just post them to the devforum to anyone that needs to use them so here you go.

--example on how to use it (debris)
local Debris = game:GetService("Debris")
local part = workspace.Part
Debris:AddItem(part, 10) --this is the only function of debris afaik
--now with the wrapper:
local Debris = require(path_to_module) --instead of getservice you just require the module, duh
local part = workspace.Part --whatever instance
Debris:AddItem(part, 10) --scheduled to be destroyed in 10 seconds
Debris:PauseItem(part) --indefinitely pauses
Debris:UnpauseItem(part)
Debris:PauseItemForDuration(part, 2) --pauses for 2 seconds (just adds two seconds to its lifetime, can be used to shorten lifetime), extendlifetime may be a better name
Debris:IsAlive(part) --returns 1: whether or not the item is alive (true for still in queue, false for has been destroyed, or nil for the item not being in queue), and 2: the item's remaining lifetime (-1 for has been destroyed and nil for not in queue)
Debris:RemoveItem(part) --hallelujah!

Otherwise, for the tweenservice wrapper, it functions (and is used) identically to the regular tweenservice without any additional functionalities. It simply destroys the tweens once they have completed. The original reason I created this module was because I read somewhere that tweens aren’t necessarily gc’d correctly, or something of the sort. However, after a 2 second dive through a few devforum posts, this appears to be unnecessary since it isn’t actually true from what I’ve once again read. This module is definitely unnecessary, however I’ve included it anyways just for the hell of it. Please don’t pester me about it.

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

local Debris = game:GetService("Debris")

game:GetService("RunService").Heartbeat:Connect(function(dt)
	for item, lifespan in pairs(Queue) do
		if item and item.Parent and lifespan > 0 then
			if lifespan - dt <= 0 then
				Queue[item] = nil
				item:Destroy()
			else
				Queue[item] -= dt
			end
		end
	end
end)

return setmetatable(
	{
		AddItem = function(self: any, item: Instance, lifetime: number?)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			Queue[item] = lifetime ~= nil and lifetime < 1 / 60 and 1 / 60 or lifetime ~= nil and lifetime or 10
		end,
		addItem = function(self: any, item: Instance, lifetime: number?)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			Queue[item] = lifetime ~= nil and lifetime < 1 / 60 and 1 / 60 or lifetime ~= nil and lifetime or 10
		end,
		RemoveItem = function(self: any, item: Instance)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			if not Queue[item] then warn(item.ClassName..' "'..item.Name..'" could not be found in debris.') return end
			Queue[item] = nil
		end,
		PauseItem = function(self: any, item: Instance)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			if not Queue[item] then warn(item.ClassName..' "'..item.Name..'" could not be found in debris.') return end
			Queue[item] = -math.abs(Queue[item])
		end,
		UnpauseItem = function(self: any, item: Instance)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			if not Queue[item] then warn(item.ClassName..' "'..item.Name..'" could not be found in debris.') return end
			Queue[item] = math.abs(Queue[item])
		end,
		PauseItemForDuration = function(self: any, item: Instance, duration: number)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			if not Queue[item] then warn(item.ClassName..' "'..item.Name..'" could not be found in debris.') return end
			Queue[item] += Queue[item] / math.abs(Queue[item]) * duration
		end,
		IsAlive = function(self: any, item: Instance): (boolean?, number?)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			return Queue[item] and true or item.Parent ~= nil and nil, Queue[item] or not item.Parent and -1 or nil
		end
	},
	{
		__index = Debris,
		__newindex = Debris,
	}
) :: {
	AddItem: (self: any, item: Instance, lifetime: number?) -> (),
	addItem: (self: any, item: Instance, lifetime: number?) -> (),
	RemoveItem: (self: any, item: Instance) -> (),
	PauseItem: (self: any, item: Instance) -> (),
	UnpauseItem: (self: any, item: Instance) -> (),
	PauseItemForDuration: (self: any, item: Instance, duration: number) -> (),
	IsAlive: (self: any, item: Instance) -> (boolean?, number?)
} & Debris
TweenService Source
local TweenService = game:GetService("TweenService")

return setmetatable(
	{
		Create = function(self, ...)
			local Tween = TweenService:Create(...)
			Tween.Completed:Once(function()
				Tween:Destroy()
			end)
			return Tween
		end
	}, {
		__index = TweenService,
		__newindex = TweenService,
	}
) :: TweenService

This was my first community resources post so thank you for reading. If you have any optimizations (I’m more specifically thinking about the runservice loop in the debris module), please let me know. Enjoy!

7 Likes
Debris Source v2
local Queue: {[Instance]: {[number]: thread | number}} = {}
local Debris = game:GetService("Debris")

function Destroy(item)
	local info, time = Queue[item], time()
	if time - info[2] < info[3] - 1 / 60 then
		info[3] -= time - info[2]
		info[1], info[2] = task.delay(info[3], Destroy, item), time
	else
		Queue[item] = nil
		item:Destroy()
	end
end

return setmetatable(
	{
		AddItem = function(self: any, item: Instance, lifetime: number?)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			lifetime = lifetime ~= nil and lifetime < 1 / 60 and 1 / 60 or lifetime ~= nil and lifetime or 10
			Queue[item] = {task.delay(lifetime, Destroy, item), time(), lifetime}
		end,
		addItem = function(self: any, item: Instance, lifetime: number?)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			lifetime = lifetime ~= nil and lifetime < 1 / 60 and 1 / 60 or lifetime ~= nil and lifetime or 10
			Queue[item] = {task.delay(lifetime, Destroy, item), time(), lifetime}
		end,
		RemoveItem = function(self: any, item: Instance)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			if not Queue[item] then warn(item.ClassName..' "'..item.Name..'" could not be found in debris.') return end
			task.cancel(Queue[item][1])
			Queue[item] = nil
		end,
		PauseItem = function(self: any, item: Instance)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			if not Queue[item] then warn(item.ClassName..' "'..item.Name..'" could not be found in debris.') return end
			task.cancel(Queue[item][1])
			Queue[item][3] -= time() - Queue[item][2]
			Queue[item][1], Queue[item][2] = nil, time()
		end,
		UnpauseItem = function(self: any, item: Instance)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			if not Queue[item] then warn(item.ClassName..' "'..item.Name..'" could not be found in debris.') return end
			Queue[item][1] = task.delay(Queue[item][3], Destroy, item)
		end,
		PauseItemForDuration = function(self: any, item: Instance, duration: number)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			if not Queue[item] then warn(item.ClassName..' "'..item.Name..'" could not be found in debris.') return end
			Queue[item][3] += duration
		end,
		IsAlive = function(self: any, item: Instance): (boolean?, number?)
			assert(typeof(item) == "Instance", "Unable to cast value to Object")
			return Queue[item] and true or item.Parent ~= nil and nil, Queue[item] and Queue[item][3] - time() + Queue[item][2] or not item.Parent and -1 or nil
		end
	},
	{
		__index = Debris,
		__newindex = Debris,
	}
) :: {
	AddItem: (self: any, item: Instance, lifetime: number?) -> (),
	addItem: (self: any, item: Instance, lifetime: number?) -> (),
	RemoveItem: (self: any, item: Instance) -> (),
	PauseItem: (self: any, item: Instance) -> (),
	UnpauseItem: (self: any, item: Instance) -> (),
	PauseItemForDuration: (self: any, item: Instance, duration: number) -> (),
	IsAlive: (self: any, item: Instance) -> (boolean?, number?)
} & Debris

Changes:

  • Removed heartbeat loop > replaced with threads
4 Likes
TweenService Source v2
local TweenService = game:GetService("TweenService")

return setmetatable(
    {
        Create = function(self, ...)
            local Tween = TweenService:Create(...)
            Tween.Completed:Once(function()
                Tween:Destroy()
            end)
            return Tween
        end,
        TweenModel = function(self, model: Model, tweenInfo: TweenInfo, propertyTable: {["CFrame"]: CFrame} | {["Position"]: Vector3})
            local CFrameVal = Instance.new("CFrameValue")
            local Tween = self.Create(self, CFrameVal, tweenInfo, {Value = propertyTable.CFrame or CFrame.new(propertyTable.Position) * model:GetPivot().Rotation})
            CFrameVal.Value, CFrameVal.Parent = model:GetPivot(), Tween
            CFrameVal:GetPropertyChangedSignal("Value"):Connect(function()
                model:PivotTo(CFrameVal.Value)
            end)
            return Tween
         end,
    }, {
        __index = TweenService,
        __newindex = TweenService,
    }
) :: {
    TweenModel: (self, model: Model, tweenInfo: TweenInfo, propertyTable: {["CFrame"]: CFrame} | {["Position"]: Vector3}) -> Tween
} & TweenService

Changes:

  • Added capability to tween models
    Example:
local TweenService = require(path_to_module)
local model = workspace.Model
local Tween = TweenService:TweenModel(model, TweenInfo.new(3, Enum.EasingStyle.Bounce, Enum.EasingDirection.In, 5, true), {CFrame = CFrame.new(123, 78, 456) * CFrame.Angles(math.rad(60), math.rad(90), math.rad(120))})
Tween.Completed:Once(function()
   print("Completed!")
end)
Tween:Play()
2 Likes

Uploaded publically available modules for both of these.
Debris:

TweenService:

1 Like

nice and super cool ModuleScripts