Runtime
JunkService
Clean-up scopes for players, characters, rounds and instances, delayed destruction, and a leak detector that tells you what is piling up.
Getting it
local JunkService = Engine:GetService("JunkService")Overview
JunkService organises clean-up around scopes. A scope is a Bin with a name, a parent and counters: you add connections, instances, threads and callbacks to it, and destroying the scope cleans up all of them, including child scopes. JunkService hands out ready-made scopes for the lifetimes every game has.
local JunkService = Engine:GetService("JunkService")
Players.PlayerAdded:Connect(function(player)
local scope = JunkService:GetPlayerScope(player)
scope:Connect(player.Chatted, function(message)
logChat(player, message)
end)
scope:Add(task.spawn(function()
while true do
task.wait(60)
givePlaytimeReward(player)
end
end))
end)
-- When the player leaves, their scope is destroyed and all of the above stops.The leak detector counts what every scope holds and warns you when a scope or a watched table keeps growing, which is usually the first sign of a memory leak.
Scopes
CreateScope
JunkService:CreateScope(name: string?, parent: Scope?): ScopeA new scope. With a parent, it becomes a child that is destroyed with the parent.
GetPlayerScope
JunkService:GetPlayerScope(player: Player): ScopeThe scope for a player, created on first use. It is destroyed automatically when the player leaves.
GetCharacterScope
JunkService:GetCharacterScope(character: Model): ScopeThe scope for a character model, created on first use. When the character is destroyed, JunkService forgets the scope so the next character gets a fresh one, but it does not destroy the old scope for you. Destroy it yourself (for example on Humanoid.Died), or use BindToInstance(character) when you want clean-up to happen automatically.
GetRoundScope
JunkService:GetRoundScope(): ScopeOne shared scope for the current round. Destroy it at the end of the round; the next call creates a new one.
local function endRound()
JunkService:GetRoundScope():Destroy() -- removes every round prop, effect and connection
endBindToInstance
JunkService:BindToInstance(instance: Instance): ScopeA new scope that is destroyed when instance is destroyed.
SetDestroyDelay
JunkService:SetDestroyDelay(instance: Instance, seconds: number)Destroys instance after seconds (if it still has a parent). Calling it again for the same instance replaces the old delay. Like Debris:AddItem, but cancellable and counted in the leak report.
Scope
| Member | Description |
|---|---|
:Add(item, cleanupMethod?) | Adds an item and returns it (same rules as Bin:Add). |
:Connect(signal, fn) | Connects and adds the connection. |
:Remove(item), :RemoveNoClean(item) | Take an item out, with or without cleaning it. |
:Extend(name?): Scope | A child scope. |
:AttachTo(instance): Scope | Destroy this scope when instance is destroyed. |
:GetChildren(), :FindChild(name) | Child scopes. |
:GetStats(): Stats | Counts for this scope and, recursively, its children. |
:Clean() | Cleans everything but keeps the scope. |
:Destroy() | Fires Destroying, cleans everything, destroys child scopes. |
Name, Parent, Destroyed, Size | Read-only. |
InstanceCount, ConnectionCount, GenericCount | What the scope holds right now. |
Destroying: Signal | Fires before the scope cleans up. |
Leak detection
StartLeakWatch
JunkService:StartLeakWatch(config: { interval: number?, connectionThreshold: number?, instanceThreshold: number? }?)Scans every scope on an interval and fires LeakDetected when a scope holds more instances or connections than the threshold, or a watched table grew by more keys than allowed since the last scan. You can also switch it on with JunkService.LeakWatchEnabled = true, which uses the configured defaults.
JunkService.LeakDetected:Connect(function(report)
for _, suspect in report.Suspects do
warn("Possible leak:", suspect)
end
end)
JunkService:StartLeakWatch({ interval = 60 })WatchTable
JunkService:WatchTable(t: table, label: string)Adds a table of your own (a cache, a registry) to the leak scan. Watching does not keep the table alive.
GetReport
JunkService:GetReport(): LeakReportRuns a scan right now and returns the report.
| Field | Description |
|---|---|
Timestamp | When the scan ran (os.clock()). |
TotalInstances, TotalConnections, TotalGeneric | Totals over every live scope. |
ScopeCount | Number of live scopes. |
PendingDestroys | Instances waiting on SetDestroyDelay. |
Scopes | { Name, InstanceCount, ConnectionCount, GenericCount } per scope. |
WatchedTables | { Label, Size, GrowthSinceLastScan } per watched table. |
Suspects | Human-readable descriptions of what crossed a threshold. |
LeakDetected
JunkService.LeakDetected: Signal<LeakReport>LeakWatchEnabled
JunkService.LeakWatchEnabled: booleanWhether the periodic scan is running. Off until you turn it on.
Configuration
DefaultConfiguration/Services/JunkService4 settings| Setting | Default | What it does |
|---|---|---|
| DefaultLeakWatchInterval | 30 | Seconds between automatic leak scans. |
| DefaultConnectionThreshold | 500 | Per-scope connection count that flags a suspect. |
| DefaultInstanceThreshold | 1000 | Per-scope instance count that flags a suspect. |
| DefaultWatchedTableGrowthThreshold | 200 | Keys added to a watched table between scans that flags a suspect. |
Types
export type Scope
export type LeakReport
export type LeakWatchConfig = { interval: number?, connectionThreshold: number?, instanceThreshold: number? }