GarbageMan / cleanup module

Hey everyone,

I made GarbageMan, a typed cleanup / lifecycle manager for Roblox Luau.

It is made for managing temporary runtime objects such as connections, instances, promises, threads, tweens, UI objects, hitboxes, projectiles, tools, NPC controllers and other resources that need to be cleaned up when a system ends.

The main idea is simple:

Create a scope, add resources to that scope, then clean or destroy the scope when the system is done.

I originally made this for my own projects because cleanup code can get messy pretty fast once a system grows. Manually disconnecting a few connections is fine at first, but once you have UI, tools, character controllers, hitboxes, effects, promises, tweens and temporary instances all living together, it becomes easy to forget something.

GarbageMan gives each system a clear ownership scope.

Links: Documents · GitHub · Latest Release · RBXM · Benchmarks


Quick Example

local GarbageMan = require(path.to.GarbageMan)

local scope = GarbageMan.new("Weapon")

scope:Connect(tool.Activated, function()
	print("Activated")
end)

local part = scope:Construct(Instance, "Part")
part.Parent = workspace

scope:Add(function()
	print("Weapon cleaned")
end)

scope:Destroy("Weapon unequipped")

Features
  • Typed Luau API
  • Tracks Roblox instances, connections, functions, threads, promises and custom cleanup objects
  • Supports custom cleanup methods like "Destroy", "Disconnect", "Cancel" or "Clean"
  • Tagged resources with Replace() and Get()
  • Object cleanup with Remove()
  • Tag cleanup with RemoveTag()
  • Ownership release with Drop() and DropTag()
  • Child scopes with Extend() and Adopt()
  • Signal helpers with Connect(), Once(), DestroyOnSignal() and ReplaceConnection()
  • Tween helper with ReplaceTween()
  • Temporary resource cleanup with AddTemporary()
  • Delayed cleanup with CleanAfter() and DestroyAfter()
  • RenderStep cleanup with Render()
  • Promise cleanup with AddPromise()
  • Instance lifecycle binding with BindTo() and BindToAncestry()
  • Deferred cleanup with CleanDeferred() and DestroyDeferred()
  • Batched cleanup with CleanBatched() and DestroyBatched()
  • Lifecycle hooks with OnDestroying() and OnDestroyed()
  • Debug summaries, leak warnings, failed cleanup tracking and optional add tracebacks
  • Optional cleanup profiling through GarbageMan.configure()
Installation

Wally

[dependencies]
GarbageMan = "virtualdesign0/garbageman@0.1.4"

Rojo

The repository includes default.project.json, which mounts the module as:

ReplicatedStorage
  GarbageMan
    Cleanup
    Promise
    Types

Usage inside Studio:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local GarbageMan = require(ReplicatedStorage.GarbageMan)

Manual Install

Download the .rbxm file from Latest Release and put it inside ReplicatedStorage.

What it supports

GarbageMan can track and clean:

  • RBXScriptConnection
  • Instance
  • functions
  • threads
  • promise-like objects
  • tweens through "Cancel"
  • custom objects with Destroy, Disconnect, Cancel, or Clean
  • tagged resources
  • temporary resources
  • child scopes
  • delayed cleanup
  • deferred cleanup
  • batched cleanup
  • lifecycle callbacks
  • debug summaries
  • leak warnings
  • failed cleanup tracking
  • optional add tracebacks
  • optional cleanup profiling
Why use this instead of Maid or Trove?

GarbageMan follows the same general cleanup-scope idea as Maid and Trove-style utilities, so if you already use one of those and it works well for your project, you do not need to switch just for the sake of switching.

The main reason I made GarbageMan is because I wanted a cleanup utility that also handled a few larger-system cases in one place.

GarbageMan may be useful if you want:

  • tagged resource replacement with Replace() and Get()
  • final destroy semantics with Destroy()
  • reusable cleanup with Clean()
  • temporary resources with AddTemporary()
  • delayed cleanup with CleanAfter() and DestroyAfter()
  • child scopes with Extend() and Adopt()
  • signal helpers like Connect(), Once() and DestroyOnSignal()
  • tween replacement with ReplaceTween()
  • batched cleanup for larger scopes
  • lifecycle hooks with OnDestroying() and OnDestroyed()
  • debug summaries
  • leak warnings
  • failed cleanup tracking
  • optional add tracebacks
  • optional cleanup profiling

So the difference is not really “Maid/Trove but renamed”.

The goal is more like:

Keep the simple cleanup-scope workflow, but add extra lifecycle, debugging and ownership tools for systems that grow larger over time.

For example, in a small script, a normal Maid/Trove-style cleanup object is usually enough.

But in systems like weapons, NPC controllers, round managers, UI screens, projectiles, hitboxes or player session objects, I often want tags, child scopes, delayed cleanup, destroy reasons, leak warnings and debug summaries without building those parts again every time.

That is the reason GarbageMan exists.

Clean vs Destroy

Clean() removes the current resources, but the scope can still be reused.

local scope = GarbageMan.new("Round")

scope:Add(connection)
scope:Clean()

scope:Add(newConnection) -- valid

Destroy() is final. After a scope is destroyed, methods like Add, Replace, Connect or Extend should not be used anymore.

local scope = GarbageMan.new("Weapon")

scope:Add(connection)
scope:Destroy("Weapon unequipped")

scope:Add(otherConnection) -- error

Use Clean() for reusable lifetimes, like refreshing UI or restarting a round.

Use Destroy() when the system is finished for good, like a weapon being unequipped, an NPC despawning, a projectile expiring, or a player leaving.

Tagged resources

Tags are useful when only one resource of a certain type should exist at once.

For example, keeping only one active tween:

scope:Replace("Tween:Current", tween, "Cancel")

local currentTween = scope:Get("Tween:Current")

scope:RemoveTag("Tween:Current")

If the same tag is replaced later, the old resource is cleaned automatically.

Temporary resources

You can add one resource and let GarbageMan clean only that resource after a delay.

scope:AddTemporary(hitbox, 0.25)
scope:AddTemporary(sound, 5)
scope:AddTemporary(trail, 2)

The scope itself stays alive.

This is useful for temporary hitboxes, VFX parts, sounds, trails, dropped items, short UI effects and projectiles.

Batched cleanup

For larger systems, cleanup can be split across scheduler steps.

scope:DestroyBatched("Round ended", 50)

This can help when a scope owns many objects and you do not want to clean everything in one immediate sweep.

Debugging

GarbageMan includes debug helpers for checking active scopes, tracked resources, failed cleanup info and possible leaks.

Example debug summary:

for _, summary in GarbageMan.Debug.getSummary() do
	print(
		summary.name,
		summary.size,
		summary.age,
		summary.destroyed
	)
end

You can also enable optional debug behavior:

GarbageMan.configure({
	tracebacks = true,
	captureAddTracebacks = true,
	leakWarnings = true,
	profiling = true,
})

These options are mainly useful while testing or debugging leaks. I would not keep expensive debug options enabled everywhere in normal gameplay code.


Benchmarks

These were run in Roblox Studio on the server runtime. Each case was run 7 times and the median result is shown.

[GarbageManBenchmark] GarbageMan | Add function + Clean      | n=10000 | median=1.659 ms
[GarbageManBenchmark] GarbageMan | Add Destroy table + Clean | n=10000 | median=8.001 ms
[GarbageManBenchmark] GarbageMan | Connect + Clean           | n= 5000 | median=12.296 ms
[GarbageManBenchmark] GarbageMan | Replace tag               | n=10000 | median=9.083 ms
[GarbageManBenchmark] GarbageMan | Add traceback capture     | n= 1000 | median=0.388 ms

Full benchmark notes are here:

Benchmarks


Good use cases

GarbageMan is meant for lifetimes like:

  • one UI screen
  • one weapon
  • one NPC
  • one projectile
  • one temporary effect
  • one round
  • one player session object

It is similar in spirit to Maid or Trove-style utilities, but it also includes tagged replacement, final destroy semantics, temporary resources, batched cleanup, debug summaries and leak tracking tools.

The repository includes source files, tests, benchmarks, Rojo project files, Wally support and release assets.

Feedback and bug reports are welcome.

6 Likes