Shared

Debounce

Cooldown wrappers for functions. Two variants: a global cooldown for a single shared timer, and a per-key cooldown where each unique key (typically a player) has its own independent timer.

Import local Debounce = RoExpress.Debounce
Both wrappers return true when the function was called and false when it was blocked by the cooldown. You can ignore the return value — it's there if you need it for feedback or logging.

Debounce.default

local wrapped = Debounce.default(fn)

Wraps fn with a simple toggle-based debounce state. Each time the returned function is called, the internal debounce value alternates between true and false. The current state is passed as the first argument to fn.

Parameter Type Description
fn (Debounce: boolean, ...) → () Function invoked on every call. The first argument is the current debounce state.

Returns a function with the same arguments as fn (excluding the prepended debounce state). Calling the returned function toggles the internal state, invokes fn, and returns true.

Debounce.fn

local wrapped = Debounce.fn(fn, cooldown)

Wraps fn with a single shared cooldown. All calls share the same timer — the function can only fire once per cooldown seconds regardless of who calls it.

Parameter Type Description
fn (...) → () The function to wrap
cooldown number Minimum seconds between calls

Returns a wrapped function with the same arguments as fn plus a boolean return indicating whether the call went through.

local announceWin = Debounce.fn(function(winner)
    Bridge.Emit("round.win", { winner = winner })
end, 5)

announceWin(player)  -- fires
announceWin(player)  -- blocked, returns false
task.wait(5)
announceWin(player)  -- fires again

Debounce.key

local wrapped = Debounce.key(fn, cooldown)

Wraps fn with a per-key cooldown. The first argument of every call is used as the key — each unique key gets its own independent timer. This is the standard pattern for per-player cooldowns.

Parameter Type Description
fn (key, ...) → () The function to wrap. First arg is the key.
cooldown number Minimum seconds between calls per key
local onShoot = Debounce.key(function(player, data)
    -- player is the key — each player has their own 0.5s cooldown
    fireBullet(player, data.direction)
end, 0.5)

app:Post("/shoot", function(req, res)
    local fired = onShoot(req.player, req.data)
    res:Send({ fired = fired })
end)
For server-side rate limiting use TokenBucket — it supports burst capacity, refill rates, and integrates with the request pipeline. Debounce is better for client-side or lightweight one-liner cooldowns.

fn vs key

Debounce.fn Debounce.key
Timer scope Shared across all callers Independent per first argument
Use case Announcements, global events, single-caller actions Per-player actions: shooting, purchasing, jumping
First argument Passed to fn as-is Used as the cooldown key and passed to fn

See also

TokenBucket | burst-capable server-side rate limiting  ·  Maid | lifecycle cleanup  ·  Tamper | exploit detection for rate abuse