PlayerDebounce | Efficent debounce module!

This is my first community resources post so please give me any feedback you can!
Sorry for the code not being in a github thing I don’t know how to do that unfortunately :frowning:

playerDebounce is module that handles debounces efficently for you games.
You can use this module to control the same debounces on different scripts and also set different channels for debounces (for example a “swordDebounce” channel for sword debounces!).

Features

  • Easy API
  • Reverse lookup system for efficent player removal.
  • Automatic debounce cleanup with and option to disable it.
  • Robust error handling.
  • Flexible design for different types of games.
  • Able to use different channels for debounces.

Formats
This shows how things are stored in the dictionaries.

  • Channel format.
    [UserId] = debounceValue

  • Channels format.
    [ChannelName] = Channel format

  • Reverse lookup format
    [UserId] = {[ChannelName], [ChannelName]} ← Channels that player is removed from automatically get removed from the lookup.

API:

  • .init(autoRemove: boolean) can be called anytime when you want to change the AutoRemoveDebounce value.

  • createChannel(channelName: string, values: {}? | nil) should be called when you want to create a channel for debounces. You can also provide default values for the channel with the channel format listed above

  • getDebounceFromChannel(channelName: string, userId: string) should be called when you want to access a debounce from a channel. Provide the channel name and userId to the function.

  • setDebounce(channelName: string, userId: string, debounce: number) should be called when you want to add a debounce to a user on a channel. Provide the channel name, userId and debounce to succesfully set the debounce. If you have AutoRemoveDebounced set to true then it will automatically remove the debounce from the channel once the debounce time has passed.

  • removeDebounce(channelName: string, userId: string) should be called when you want to remove a debounce from a user on a channel. Provide the channel name and userId and the function will remove the debounce from the player on the channel.

  • channelExists(channelName: string) can be called when you want to check if a channel already exists. Returns a boolean.

  • printCache() prints everything : P

Constants

  • InitMessage: string gets printed everytime .init is called.

  • AutoRemoveDebounced: boolean Determines if debounces are automatically removed from the channel once the debounce time has passed. (Not really a constant :P)

Code:

-- You do NOT have to give any credit but do not claim this as your OWN.

--[[
	Handles playerDebounces on different channels.
]]

--// ------------ CONSTANTS ------------ \\--
local InitMessage = "Hello from playerDebounce!"
local AutoRemoveDebounced = true --// Not really a constant :P

--// ------------ SERVICES ------------ \\--
local Players = game:GetService("Players")

--// ------------ VARIABLES ------------ \\--
local channels: {[string]: {[string]: number}} = {}
local reverselookup: {[string]: {[string]: boolean}} = {}

--// ------------ FUNCTIONS ------------ \\--
local playerAdded = function(player: Player)
	--//
end

local playerRemoved = function(player: Player)
	--// If removed player does exists in a channel then we can terminate them from the channel.
	local userID = tostring(player.UserId)
	--// Check if reverse lookup has the player registered to it.
	if reverselookup[userID] then
		--// Player has been registered to the reverselookup.
		for _channelName, isJoined: boolean in reverselookup[userID] do
			if channels[_channelName] and isJoined and channels[_channelName][userID] then
				--// Remove player from channel.
				channels[_channelName][userID] = nil
			end
		end
		--// Remove player from reverselookup.
		reverselookup[userID] = nil
	else
		--// Player has not been registered to the reverselookup. We can use the old fashioned method of looping all channels.
		for _channelName, container in channels do
			for user_ID, non_ in container do
				if userID == user_ID then
					--// Player exists in this channel so remove him.
					container[userID] = nil
				end
			end
		end
	end
end

--// ------------ MODULE ------------ \\--
local handler = {
	init = function(autoRemoveDebounced: boolean)
		--// Script has been initialized.
		AutoRemoveDebounced = autoRemoveDebounced
		print(InitMessage)
	end,
	createChannel = function(channelName: string, values: {}? | nil)
		--// Creates and adds a debounce channel to channels with already provided values if they exist.
		if not channels[channelName] then
			--// This channel does not already exist. We can move on and create the channel.
			if values then
				--// Default values were provided. We can add them to the created channel.
				channels[channelName] = {}
				for _i, _preValue in values do
					channels[channelName][_i] = _preValue
					if not reverselookup[_i] then
						reverselookup[_i] = {}
					end
					reverselookup[_i][channelName] = true
				end
			else
				--// No default values provided. We can create an empty channel.
				channels[channelName] = {}
			end
		else
			--// This channel already exists.
			warn("This channel by the name: "..channelName.." already exists. Please access the channel instead of creating another one.")
		end
	end,
	getDebounceFromChannel = function(channelName: string, userId: string)
		--// Returns the debounce value for player on the given channel.
		if channels[channelName] then
			--// This channel does exist.
			local value = nil
			value = channels[channelName][userId] or nil
			--// Return value.
			return value
		else
			--// Channel doesn't exist.
			warn("This channel by the name: "..channelName.." does not exist.")
			return nil
		end
	end,
	setDebounce = function(channelName: string, userId: string, debounce: number)
		--// Sets debounce on a channel with provided name.
		if channels[channelName] then
			--// Channel does exist so we can add the debounce to the channel.
			channels[channelName][userId] = debounce
			--// If autoremove then delay a function that removes the debounce.
			task.delay(debounce, function()
				if channels[channelName] and channels[channelName][userId] then
					--// Channel does exist. We can remove the debounce from the user since the time has passed.
					channels[channelName][userId] = nil
					--// Remove channel from players reverselookup
					if reverselookup[userId] and reverselookup[userId][channelName] then
						reverselookup[userId][channelName] = nil
					end
				else
					--// Channel doesn't exist.
					warn("This channel by the name: "..channelName.." does not exist. Error in AutoRemoving debounce.")
				end
			end)
		else
			--// Channel doesn't exist.
			warn("This channel by the name: "..channelName.." does not exist.")
		end
	end,
	removeDebounce = function(channelName: string, userId: string)
		--// Removes debounce from a player on a channel.
		if channels[channelName] and channels[channelName]{userId} then
			--// Channel does exist so we can remove the debounce from the channel.
			channels[channelName][userId] = nil
			--// Remove channel from players reverselookup
			if reverselookup[userId] and reverselookup[userId][channelName] then
				reverselookup[userId][channelName] = nil
			end
		else
			--// Channel doesn't exist.
			warn("This channel by the name: "..channelName.." does not exist. Or player does not have a debounce on the channel.")
		end
	end,
	channelExists = function(channelName: string)
		--// Returns true if channel by the name provided exists.
		if channels[channelName] then
			return true
		end
		return false
	end,
	printCache = function()
		--// Prints everything.
		print(InitMessage, AutoRemoveDebounced, reverselookup, channels)
	end,
}

--// ------------ CONNECTIONS ------------ \\--
Players.PlayerRemoving:Connect(playerRemoved)

--// ------------ RETURN ------------ \\--
return handler

Basic use case:

local debounce = require(script.Parent)
local swordChannel = "SWORD"
local swordDebounce = 0.25

debounce.init(true)
if not debounce.channelExists(swordChannel) then
    debounce.createChannel(swordChannel, nil)
end

local function SwordAttacked(player: Player)
	local userId = tostring(player.UserId)
	--// BLA BLA BLA
	--// CHECK DEBOUNCE
	if debounce.getDebounceFromChannel(swordChannel, userId) then
		--//USER IS CLICKING TOO SOOON STOP!! NOW
		print("Debounce crossed")
	else
		--// DO SWORD STUFF
		--// SET DEBOUNCE
		print("Debounce passed")
		debounce.setDebounce(swordChannel, userId, swordDebounce)
	end
end

--// BLA BLA
--// SwordEvent.OnServer:Connect(SwordAttacked)

Thanks for reading to the end I appreciate that very much. Leave any feedback you have please!
I’m out!

6 Likes

So to my understanding, this is best used on a serverscript to properly validate actions and avoid hackers spoofing cooldowns the are on the client?

1 Like

Yes! Sorry the writing is kind of vague :stuck_out_tongue:

No problem with that. It would be helpful if you described it a little clearer.

Ill be trying it and seeing if it works for my game. Thank you for this resource!

1 Like

I will try to improve the post. Thanks alot!

Update 1:

New function: channelExists(channelName: string): boolean.
Allows you to check if a channel already exists.

I added this because I ran into a issue while using this inside a tool. When I died the script would try to create a channel with the same name that already exists resulting in a warning.

brother im sorry tis is like just some crazy next level bloatware