Debounce Module

Debounce Module

Hey everyone! I was looking for some good modules out there that replace the tedious implementation of debounces, but I couldn’t really find too many that that were light weight and fit my taste. So I decided to make my own!

Constructors

Debounce.new(debounceTime, instantlyReady?)

Creates a new debounce object, with a optional argument that by default is set to true that controls whether or not the debounce is ready right off the bat.

Example:

local debounce = Debounce.new(5)

if debounce:IsReady() then
     print("Hello World!") -- Output: Hello World!
     debounce:Reset()
end

wait(1)

if debounce:IsReady() then
     print("Hello Second World!") -- Output:
end

wait(4)

if debounce:IsReady() then
     print("Hello Third World!"). -- Output: Hello Third World!
end

Example 2:

--[[
      In this second example we set the optional second flag to false to make
      the debounce not ready on creation
]]
local debounce = Debounce.new(5, false)

if debounce:IsReady() then
     print("Hello World!") -- Output: 
end

wait(1)

if debounce:IsReady() then
     print("Hello Second World!") -- Output:
end

wait(4)

if debounce:IsReady() then
     print("Hello Third World!") -- Hello Third World!
end

Debounce.Wrap(func, debounceTime, instantlyReady?)

Wraps a function so it can only be called if the time elapsed is greater then the debounce time

Example:

local function example(text)
    print(text)
end

local debounceExample = Debounce.Wrap(example, 2)
debounceExample("Hello World!") -- Output: Hello World!
debounceExample("test") -- Output: 
wait(2)
debounceExample("test 2") -- Output: test2

Example 2:

local function example()
    print("Hello World")
end

local exampleWrapper = Debounce.Wrap(example, 2)
exampleWrapper() -- Output: Hello World!

-- This is equivalent to...

local exampleDebounce = Debounce.new(2)
if exampleDebounce:IsReady() then
     example() -- Output: Hello World!
     exampleDebounce:Reset()
end

Debounce.SmartWrap(func, debounceTime, instantlyReady?)

Similar to Debounce.Wrap but instead of always reseting the timer, it only resets when the wrapped function returns true

Example:

local function example(boolean)
    print("worked")
    return boolean
end

local debounceExample = Debounce.SmartWrap(example, 2)
debounceExample(false) -- Output: worked
debounceExample(true) -- Output:  worked
debounceExample(true) -- Output:
wait(2)
debounceExample(true) -- Output: worked

Example 2:

local function example(boolean)
    print("worked")
    return boolean
end

local exampleWrapper = Debounce.SmartWrap(example, 2)
exampleWrapper(true) -- Output: worked

-- This is equivalent to...

local exampleDebounce = Debounce.new(2)
if exampleDebounce:IsReady() then
     local success = example(true) -- Output: worked
     if success then
          exampleDebounce:Reset()
     end
end

Methods

Debounce:IsReady()

Returns true if the debounce cooldown is ready

Debounce:GetRemainingTime()

Returns the amount of time left in the debounce’s cooldown

Debounce:Reset()

Resets the debounces remaining cooldown time

Caveats

  • This module uses the time() function which gets the time since the start of the game. This could be sometimes problematic in roblox studio because the time() function only starts ticking once the roblox studio instance is fully loaded, which can cause some problems in studio if you need debounces to start running the second studio starts.

Module

local Debounce = {}
Debounce.__index = Debounce

function Debounce.new(cooldown, instantlyReady)
	local self = setmetatable({}, Debounce)
	
	instantlyReady = instantlyReady == nil or instantlyReady

	self.cooldown = cooldown
	self.PreviousDebounceReset = instantlyReady and time() - cooldown or time()

	return self
end

-- Wraps a function with a debounce, internally calling reset and is ready before the function is called
function Debounce.Wrap(func, cooldown, instantlyReady)
	local debounce = Debounce.new(cooldown, instantlyReady)
	return function(...)
		if debounce:IsReady() then
			debounce:Reset()
			return func(...)
		end
	end
end

-- Same as Debounce.Wrap but only resets the debounce when the given function returns true
function Debounce.SmartWrap(func, cooldown, instantlyReady)
	local debounce = Debounce.new(cooldown, instantlyReady)
	return function(...)
		if debounce:IsReady() then
			local success = func(...)
			if success then
				debounce:Reset()
			end
		end
	end
end

-- Checks if the remaining time to the next available debounce is 0
function Debounce:IsReady()
	if self:GetRemainingTime() == 0 then
		return true
	end
	return false
end

-- Gets the remaining time to next debounce
function Debounce:GetRemainingTime()
	return math.max(self.PreviousDebounceReset + self.cooldown - time(), 0)
end

-- Restarts the debounce timer
function Debounce:Reset()
	self.PreviousDebounceReset = time()
end

return Debounce
8 Likes

nice work on the module. I’ll def use it

1 Like

In my opinion, this just overcomplicates a simple problem, wouldn’t it be better to just store the time a function was last called and check if the time that has passed since the last (successful) call; not to mention, there will probably be metamethod call overhead too even though it’ll be really small.

Note: You could even yield in a function call and use a book instead instead of storing the time which is probably even more computationally cheaper.

10 Likes

I like the thought taken with this module, but it seems a bit redundant, because it’s actually more steps then setting up a debounce like this. A debounce like this is simply enough to prevent too many things from firing at once.

local db = false

if not db then
   db = true

   task.wait(3)
   db = false
end

I always type this once when I need it, and I get desirable results. Navigating to the module, requiring it, then using these built-in functions could have all been done easier, and a lot faster with the method I stated above (There are many ways of setting up a debounce, not saying the one I made will always work in every situation!)

1 Like

This isn’t really that use full bc you can just make a normal debounce and it works but still hard work i respect it

Even though you did hard work, people using your module is doing more hard work. How about a simple function

local function debounce(time, activatePerTime, func)
   local currentTime = tick()
   local activated = 0
   return function(...)
      if tick() - currentTime > time then
         activated = 0
         currentTime = tick()
      end
      activated = activated + 1
      if activated > activatePerTime then
         return "ratelimited"
      end
      func(...)
   end
end
local deb = debounce(5, 10, function(args)
   print(args)
end)
for i = 1, 15 do
   deb("yay")
end -- prints only 10 yays

but thanks for contributing

1 Like