LoopUtil Module

This is a small and easy to use module that makes it easier to create 2 different types of loops.

Time Distribution

This will fire a function x number of times over y number of seconds. The function will return a RBXScriptConnection, and calling :Disconnect() on the connection will stop the loop from running

local LoopUtil = require(game:GetService("ReplicatedStorage"):WaitForChild("LoopUtil"))

-- Fire this function 100 times over 3 seconds
local Connection = LoopUtil.TimeDistribution(3, 100, function()
    print("Fire")
end)

-- Stop running after 1 second
task.delay(1, function()
    Connection:Disconnect()
end)

Interval

This will fire a function every x amount of seconds without accumulating time error like in a normal while true do wait() loop. If the interval is less than the frame time, it will fire the function multiple times in a single frame so the time between function call could be different. This function also returns RBXScriptConnection object and calling :Disconnect() on it will stop the loop from running

local LoopUtil = require(game:GetService("ReplicatedStorage"):WaitForChild("LoopUtil"))

-- Fire this function every 2.5 seconds
LoopUtil.Interval(2.5, function()
    print("Fire")
end)

-- Fire this function 250 times every second
local Connection = LoopUtil.Interval(1/250, function()
    print("Fire")
end)

-- Stop running after 1 second
task.delay(1, function()
    Connection:Disconnect()
end)

Source Code

This should be copy and pasted into a ModuleScript and placed where ever you need it

local RunService = game:GetService("RunService")

local Module = {}

local function GetIterationsDue(StartTime, Time, Iterations)
	return if (Time > 0) then math.floor(Iterations * math.clamp((os.clock() - StartTime) / Time, 0, 1)) else Iterations
end

function Module.TimeDistribution(Time: number, Iterations: number, Function: () -> nil)
	local StartTime = os.clock()
	local IterationsDue = GetIterationsDue(StartTime, Time, Iterations)
	local IterationsFired = IterationsDue
	
	if (IterationsDue > 0) then
		for Key = 1, IterationsDue do
			task.spawn(Function)
		end
	end
	
	if (IterationsFired >= Iterations) then
		return
	end
	
	local UpdateConnection; UpdateConnection = RunService.PreSimulation:Connect(function()
		IterationsDue = GetIterationsDue(StartTime, Time, Iterations)
		
		if (IterationsDue > IterationsFired) then
			for Key = 1, IterationsDue - IterationsFired do
				task.spawn(Function)
			end

			IterationsFired = IterationsDue
			
			if (IterationsFired >= Iterations) then
				if (UpdateConnection) then
					UpdateConnection:Disconnect()
					UpdateConnection = nil
				end
				
				return
			end
		end
	end)
	
	return UpdateConnection
end

function Module.Interval(Interval: number, Function: () -> nil)
	local StartTime = os.clock()
	local IterationsDue = 0
	local IterationsFired = 0
	
	return RunService.PreSimulation:Connect(function()
		IterationsDue = math.floor((os.clock() - StartTime) / Interval)
		
		if (IterationsDue > IterationsFired) then
			for Key = 1, IterationsDue - IterationsFired do
				task.spawn(Function)
			end
			
			IterationsFired = IterationsDue
		end
	end)
end

return Module
5 Likes

For example, waiting the beats per second after minutes will cause the loop to become out of sync with the song. Using this module would prevent that drift?

1 Like

Yep sounds like something this was made for