Skip to main content

Fluix

Every object ready before demand arrives.

Adaptive, demand-smoothed object pooling for Roblox.

Fluix is a per-instance generic object pool. It tracks acquisition demand with an exponential moving average, pre-warms and gradually shrinks the pool to match real usage, and exposes hot/cold priority tiers, cross-pool borrowing, per-object TTL, and lifecycle signals — all with zero allocations on the acquire/release hot path.


One File. One Require.

Drop Fluix into ReplicatedStorage and require it from any script.

local Fluix = require(ReplicatedStorage.Fluix)

Basic Pool

local BulletPool = Fluix.new({
Factory = function() return Bullet.new() end,
Reset = function(b) b:Reset() end,
MinSize = 32,
})

BulletPool:Seed(20)

local bullet = BulletPool:Acquire(function(b)
b.Position = spawnPos
b.Velocity = direction * speed
end)

BulletPool:Release(bullet)

Adaptive Sizing

Fluix measures acquisitions-per-window using an exponential moving average (EMA). The Heartbeat pre-warms the cold pool toward EMA × Headroom, then gradually shrinks it when demand drops — after ShrinkGraceSeconds of sustained surplus.

local Pool = Fluix.new({
Factory = function() return Part.new() end,
Reset = function(p) p.Parent = nil end,
MinSize = 16,
Headroom = 3.0, -- target = EMA × 3
ShrinkGraceSeconds = 5.0, -- wait 5s before evicting surplus
Alpha = 0.2, -- slower EMA smoothing
})

Hot/Cold Tiers

When HotPoolSize > 0, a dedicated sub-pool of the most-recently-used objects is maintained. Acquire drains hot first (O(1) pop), then cold. Release refills hot first.

local Pool = Fluix.new({
Factory = function() return ExplosionEffect.new() end,
Reset = function(e) e:Reset() end,
MinSize = 64,
HotPoolSize = 8,
})

Signals

Every pool exposes a Signals table of VeSignal connections:

SignalParametersFires when
OnAcquireobj: TObject left the pool
OnReleaseobj: TObject returned to the pool
OnMissobj: TFactory was called (pool was empty)
OnGrowadded, totalPool expanded
OnShrinkremoved, totalPool contracted
Pool.Signals.OnMiss:Connect(function(obj)
warn("Pool miss — consider raising MinSize or Headroom")
end)

Cross-Pool Borrowing

On a factory miss, Fluix checks BorrowPeers in order before allocating a new object. The borrowed object is tracked as live in the borrowing pool and released normally.

local BulletPool = Fluix.new({ Factory = ..., Reset = ..., BorrowPeers = { FragPool } })

-- Or register dynamically:
BulletPool:RegisterPeer(FragPool)
FragPool:RegisterPeer(BulletPool)

Per-Object TTL

Set TTL to automatically reclaim objects that are held too long. Fluix's Heartbeat force-resets and returns any live object that exceeds its TTL.

local Pool = Fluix.new({
Factory = function() return StatusEffect.new() end,
Reset = function(e) e:Deactivate() end,
TTL = 10, -- reclaim after 10 seconds
})

Lifecycle Control

pool:Pause()     -- disconnect Heartbeat (preserves state)
pool:Resume() -- reconnect Heartbeat
pool:Drain() -- evict all pooled objects; live objects unaffected
pool:ReleaseAll() -- force-return every live object at once
pool:Destroy() -- tear down the pool entirely

Configuration Reference

FieldTypeDefaultDescription
Factory() -> TrequiredAllocates a fresh object
Reset(T) -> ()requiredClears an object before re-pooling
MinSizenumber8Cold pool floor
MaxSizenumber256Hard cold-pool ceiling
Alphanumber0.3EMA smoothing coefficient (0–1)
Headroomnumber2.0Target multiplier over EMA
SampleWindownumber0.5Demand window in seconds
PrewarmBatchSizenumber16Max allocs per Heartbeat tick
ShrinkGraceSecondsnumber3.0Surplus duration before eviction
IdleDisconnectWindowsnumber6Idle windows before dormancy
HotPoolSizenumber0Hot sub-pool capacity (0 = off)
TTLnumbernilLive object time limit in seconds
MissRateThresholdnumbernilMiss rate warn threshold
OnOverflow(T) -> ()nilCalled when pool is full on Release
BorrowPeers{ Pooler }{}Pools to borrow from on miss