What's the use case?
Have you ever created a remote function / event and wanted to prevent how quickly players can call it? Then this module is for you!
This module is incredibly simple to use- it only has one public function:
function PlayerThrottler.createThrottler(delayTime: number?): (Player) -> (boolean)
This returns a function that is intended to be called inside the OnServerEvent function that you wish to throttle on a per-player basis.
Copy & Paste the code below in a new module script (server-side) called PlayerThrottler:
-- PlayerThrottler
-- Author: velstorm
local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local Throttlers = {}
local CLEANUP_THROTTLERS_DELAY = 15
local DEBUG = false
type ThrottleEntry = {
PlayersLastCalledTime: {[Player]: number},
ThrottleDelay: number,
}
function shouldThrottle(player: Player, id: string): boolean
assert(player:IsA("Player"), "[PlayerThrottler]: Parameter player must be of type Player.")
assert(Throttlers[id], "[PlayerThrottler]: Paramater functionKey (" .. id .. ") must be registered with registerThrottler first.")
local thisThrottleEntry: ThrottleEntry = Throttlers[id]
local thisPlayerLastCalledTime: number = thisThrottleEntry.PlayersLastCalledTime[player]
if not thisPlayerLastCalledTime then
if DEBUG then
print("[PlayerThrottler]: " .. player.Name .. " called " .. id .. " for the first time.")
end
thisThrottleEntry.PlayersLastCalledTime[player] = time()
return false
end
if time() > thisPlayerLastCalledTime + thisThrottleEntry.ThrottleDelay then
if DEBUG then
print("[PlayerThrottler]: " .. player.Name .. " called " .. id .. " without throttling.")
end
thisThrottleEntry.PlayersLastCalledTime[player] = time()
return false
else
if DEBUG then
warn("[PlayerThrottler]: " .. player.Name .. " call got throttled for " .. id .. ".")
end
return true
end
end
local PlayerThrottler = {}
----- Public Functions -----
function PlayerThrottler.createThrottler(delayTime: number?): (Player) -> (boolean)
assert(delayTime ~= nil, "[PlayerThrottler]: Parameter delayTime cannot be nil")
local guid = HttpService:GenerateGUID()
local newThrottleEntry: ThrottleEntry = {
PlayersLastCalledTime = {},
ThrottleDelay = delayTime
}
Throttlers[guid] = newThrottleEntry
if DEBUG then
print("[PlayerThrottler]: A new throttler is registered with id " .. guid .. ".")
end
return function(player: Player)
return shouldThrottle(player, guid)
end
end
----- Initialization -----
local function cleanupThrottlers()
for _, tbl: ThrottleEntry in Throttlers do
for player: Player, lastCalledTime in tbl.PlayersLastCalledTime do
if time() > lastCalledTime + tbl.ThrottleDelay then
tbl.PlayersLastCalledTime[player] = nil
end
end
end
if DEBUG then
print("[PlayerThrottler]: Throttling table was cleaned. New tbl: ", Throttlers)
end
end
-- We dont want to remove player from all entries on leaving since this may
-- allow the player to call a function quicker than a throttle time specified
-- if they join back to the same server quickly enough.
coroutine.wrap(function()
while true do
task.wait(CLEANUP_THROTTLERS_DELAY)
cleanupThrottlers()
end
end)()
return PlayerThrottler
Example:
The code below shows the remote event “DoThingRemote” being limited to one call every five seconds for each player.
-- Server Script
local DoThingRemote = ReplicatedStorage.DoThingRemote
-- Require the PlayerThrottler you created.
local PlayerThrottler = require(ServerScriptService.PlayerThrottler)
-- Get a new throttler function that returns true if the event doesn't need to be throttled.
-- In this example, the throttle debounce time per-player is 5 seconds.
local shouldThrottle = PlayerThrottler.createThrottler(5)
DoThingRemote.OnServerEvent:Connect(function(player)
-- shouldThrottle(player) returns true if the player has called this remote event in the last 5 seconds.
if shouldThrottle(player) then
return
end
-- Your code
end)
Just wanted to make this since I think it’s a common pattern. If it’s convenient for you to use then it’s for you. If not, then it’s not for you!
Please let me know if you have any comments/suggestions. I’ll fix bugs as soon as I am aware.
Thanks!