Rate limiting a function [Beginner]

If you have a function which is connected to certain events such as InputChanged, you’ll notice that they fire a whole lot. Maybe you don’t need it to be so frequent; but how to slow it down? Rate limiting a function is pretty easy:

  1. We decide how long to wait between calls
  2. We store the time whenever we call the function.
  3. When we call the function, we check if time - old_time is larger or equal to the wait period.

Create a modulescript for the scripting.


Encapsulation

To do this, we’ll encapsulate the function in a table. The table will keep track of the data we need. We’ll use a metatable to set up the call, so that we can call the table and it’ll call the function for us.

When we create our table, we’ll need to pass the function and the cooldown time.

local function Ratelimit(func, cooldown)
	local prev_T = os.clock() -- This'll store the exact time of the last call. For now, just set it to os.clock().

	local self = {} -- This'll be our table. It can access prev_T, because they're in the same scope.

	return self -- We return the table, so that we can use it to call.
end

Metatables

Metatables are extensions of tables. They allow us to add functionality to tables. We're going to use a __call metatable. This essentially makes the table callable: that means we can use it like a function, and it does something.
local function Ratelimit(func, cooldown)
	local prev_T = os.clock() 

	local self = setmetatable({},{__call = function(t, ...) 
      --[['t' represents the table itself. We may need to pass arguments to our table. 
      We add ... as a parameter to account for that. 
      It ensures we can add a varying amount of parameters.
      Whatever we write in this function, will happen when we call the table.]]
	end})

	return self
end

Rate limiting

So now for the actual rate limiting. Every time we try to call the function, we'll check if the elapsed time since last call is larger than the cooldown time. That means we've waiting longer than the cooldown, and are ready to call again. When we successfully call, we set the prev_T to the current time (os.clock()). Then we call the function and pass on our parameters (...)
local function Ratelimit(func, cooldown)
	local prev_T = os.clock() 

	local self = setmetatable({},{__call = function(t, ...)
		if os.clock()-prev_T  >= cooldown then
			prev_T = os.clock()
			func(...)
		end
	end})

	return self
end

return Ratelimit -- Then we return the function, so we can use it in other scripts.

Example use

Let's say we have something which, whenever the mouse is moved, it prints it's position on the screen. But we don't need it to refresh so frequently, so we'll rate limit it. It also prints whatever parameters it receives as input.
-- MODULES
local RateLimiter = require(game.ReplicatedStorage.RateLimiter) -- Require our module, so we can use it.

-- SERVICES
local UserInputService = game:GetService('UserInputService') 


local function PrintMousePosition(...) -- The function we're going to rate limit.
	print(...)
	print(UserInputService:GetMouseLocation())
end

-- INSTANCES
local PrintMousePos = RateLimiter(PrintMousePosition,0.3) -- The RateLimiter table. This is what we call from now on, instead of the function. The 0.3 is our cooldown timer.

UserInputService.InputChanged:Connect(function(inputObject, gameProcessedEvent)
	if inputObject.UserInputType == Enum.UserInputType.MouseMovement then
		PrintMousePos('My variables', 123, false)
	end
end)

--[[ As we can see, there's roughly 0.3s between every call. 
16:44:29.286  My variables 123 false  -  Client - LocalScript:44
16:44:29.286  402, 579  -  Client - LocalScript:45
16:44:29.601  My variables 123 false  -  Client - LocalScript:44
16:44:29.602  546, 342  -  Client - LocalScript:45
16:44:29.902  My variables 123 false  -  Client - LocalScript:44
16:44:29.902  490, 4  -  Client - LocalScript:45
]]
6 Likes