I’ve answered this in a another thread:
The gist is to yield conditionally depending on how much time has passed.
local Budget = 1/60 -- seconds
local expireTime = 0
-- Call at start of process.
function ResetTimer()
expireTime = os.clock() + Budget
end
-- Call where appropriate, such as at the top of loops.
function MaybeYield()
if os.clock() >= expireTime then
wait() -- insert preferred yielding method
ResetTimer()
end
end
Choosing a budget depends on how much you already have going on in your game. A budget of 1/60 basically consumes the entire frame. Something like 1/60 * 0.25 would spend 25% of the frame doing your work, leaving the remaining time for the rest of the game. You could also set the budget dynamically, but that would be more complicated.
Also worth noting that the implementations of wait, spawn, and delay were recently changed to have more sensible throttling.