Debounce Function Class

DEBOUNCE FUNCTION CLASS
Important
When testing the debounce function in my own project. I noticed that arguments were not being passed. Though I tested it, it was tested with a function that did not require arguments. I forgot the three dots “” I have updated the code accordingly to account for arguments.

local DebounceFunction = {}

function DebounceFunction.new(func, debounceTime)
	local debounce = false
	local debounceTime = debounceTime
	return function(...)
		if debounce then return end
		debounce = true
		func(...)
		coroutine.wrap(function()
			wait(debounceTime)
			debounce = false
		end)()
	end
end

return DebounceFunction

In order to use this and create a debounce function, there are two preameters

DebounceFunction.new(func: function, debounceTime: number)

– simple example

x = DebounceFunction.new(function() print("hi") end, 1)

Conclusion
In summary, this is a simple utility module to make debounce functions look less like a mess.

Important Notes
The debounce handling is handled in another thread. In other words, the function supplied in the first will cause yielding whilst the debounce timer will not cause any yielding.

This has been tested, and it works. If there are any issues or suggestions, please leave a reply.
I recommend using this after having learned what a debounce function is and how to implement it.

4 Likes

Awesome! This should help me in some of my projects!! :smiley:

  1. avoid messing with function environments using getfenv() or setfenv(), disables some luau optimizations

The use of any of these functions performs a dynamic deoptimization, marking the affected environment as “impure”. The optimizations are only in effect on functions with “pure” environments - because of this, the use of loadstring / getfenv / setfenv is not recommended. Note that getfenv deoptimizes the environment even if it’s only used to read values from the environment.

  1. don’t use wait() or spawn(), whole article written about it here https://eryn.io/gist/3db84579866c099cdd5bb2ff37947cec, some more context/explanatino provided here Coroutines V.S. Spawn()... Which one should I use? - #8 by evaera

Similarly, avoid using spawn() or delay() as they use the same internal mechanics as wait() . Uses of spawn() are generally better served with coroutine.wrap() and coroutine.resume() of the coroutine library.

  1. with the use of metatables, you’re making a simple task unnecessarily complicated. by adding a ton of extra instructions, you’re making the code less performant and more difficult to maintain. the methods I show below don’t even need a cleanup function

simpler and more performant solutions below

local function debounce(callback)
    local running = false

    return function(...)
        if running then return end

        running = true
        callback(...)
        running = false
    end
end

local function timedDebounce(callback, limit)
    local previous = 0

    return function(...)
        local current = os.clock()
        if current - previous <= limit then return end

        previous = current
        callback(...)
    end
end

return { debounce, timedDebounce }

sources
(1) Performance - Luau
(2) Task Scheduler | Roblox Creator Documentation

2 Likes

This looks like a lazy attempt at showing off your code by overcomplicating it. I have no clue why you’re excessively using rawget/rawset and metatable magic other than a childish attempt to assert your knowledge dominance to inexperienced programmers who have found this thread.

You’re also using spawn instead of coroutines which is known to cause yielding issues.

I can write the same class without using setfenv and getfenv, and therefore make use of the Luau optimizations.

local dbClass = {}
dbClass.__index = class

function dbClass.new(callback, debounceTime)
	local self = setmetatable({
		["func"] = callback
	}, dbClass)
	coroutine.wrap(function()
		wait(debounceTime)
		if self.func then
			self.func()
		else
			warn("Not running debounce callback because cleanup was called prior to the debounceTime being finished")
		end
	end)()
	return self
end

function dbClass:clean()
	self.func = nil
end
2 Likes

The main goal is to return a function rather than a table. As such, objects are unnecessary in this context. I have simplified it down and will update the post accordingly.

local DebounceFunction = {}

function DebounceFunction.new(func, debounceTime)
	local debounce = false
	local debounceTime = debounceTime
	return function()
		if debounce then return end
		debounce = true
		func()
		coroutine.wrap(function()
			wait(debounceTime)
			debounce = false
		end)()
	end
end

return DebounceFunction