Update [1.0.9]
- Addressed bug where wait() or wait(x) where x <= 0 would not yield the thread. cc @LiISk8rBoi
According to my benchmarking the module of CloneTrooper mentioned in the main thread performs better than this one.
This loops us back to the previous debate - code performs and scales differently on different machines. There is no one absolute, and Iām not that interested in results that could be biased depending on what machine it was ran.
Either way, since both utilize custom task schedulers they are already optimized out well enough. The only difference you would see between the two would be when running both > 10^7 times, which would never occur in any productive code.
Also, sorry for being rude in the previous reply. I was rather heated towards the reply.
Thatās a satisfactory reply. Thanks.
I have found a bug (in Roblox).
When you use this module, you can not stop it from the outside, i.e deleting/disabling the script will not stop it.
Also when you are using custom wait modules the Script Performance tab does not show the usage % of it.
We can fix this by checking if a script is deleted or disabled in loops etc. This bug is probably because of how Roblox works internally.
EDIT: Tested it again, it stopped this time. The outcome is variable, or it is dependent on the code in the loop.
I meanā¦ Iām unsure as to why youād wanna do that, but a quick workaround would be:
local Yielded = {}
local function WrappedWait(n)
Yielded[coroutine.running()] = true
local a,b = RBXWait(n)
Yielded[coroutine.running()] = nil
return a, b
end
local function UnyieldAll()
for Thread in next, Yielded do
coroutine.resume(Thread)
end
table.clear(Yielded)
end
I had some rare use cases of disabling the script (with a loop inside) from the outside, but they were a long time ago and were perhaps just bad coding practice.
But the script performance one is still kind of relevant when measuring efficiency of scripts.
Amazing module! Could you also add an custom implementation of the DebrisService AddItem function?
Already done!
Hey, Iām having an issue with the module. It seems whenever I use multiple wait() at the same time, all of them will wait for the amount of time that was longest. I dumped the source code into a module called Wait2 and then ran this code:
local t = tick()
local wait2 = require(game.ReplicatedStorage.Modules.Wait2)
coroutine.wrap(function()
wait2(3)
end)()
wait2(1)
print(tick() - t)
The print will always spout a number around 3.
The code used in the module:
local heap = {}
local currentSize = 0
function insert(yieldTime, data)
currentSize += 1
local start = time()
heap[currentSize] = {
pos = start - yieldTime,
data = data,
time = start
}
-- bubble up
local pos = currentSize
local parentIdx = math.floor(pos/2)
local currentIdx = pos
while currentIdx > 1 and start-heap[parentIdx].pos < start-heap[currentIdx].pos do
heap[currentIdx], heap[parentIdx] = heap[parentIdx], heap[currentIdx]
currentIdx = parentIdx
parentIdx = math.floor(parentIdx/2)
end
end
function extractMin()
if currentSize < 2 then
heap[1] = nil
currentSize = 0
return
end
heap[1], heap[currentSize] = heap[currentSize], nil
-- sink down
local k = 1
local start = time()
while k < currentSize do
local smallest = k
local leftChildIdx = 2*k
local rightChildIdx = 2*k+1
if leftChildIdx < currentSize and start-heap[smallest].pos < start-heap[leftChildIdx].pos then
smallest = leftChildIdx
end
if rightChildIdx < currentSize and start-heap[smallest].pos < start-heap[rightChildIdx].pos then
smallest = rightChildIdx
end
if smallest == k then
break
end
heap[k], heap[smallest] = heap[smallest], heap[k]
k = smallest
end
currentSize -= 1
end
game:GetService('RunService').Stepped:Connect(function()
local PrioritizedThread = heap[1]
if not PrioritizedThread then
return
end
local start = time()
-- while true do loops could potentially trigger script exhaustion, if you were to have >50k yields for some reason...
for _ = 1, 10000 do
local YieldTime = start - PrioritizedThread.time
if PrioritizedThread.data[2] - YieldTime <= 0 then
extractMin()
coroutine.resume(PrioritizedThread.data[1], YieldTime, start)
PrioritizedThread = heap[1]
if not PrioritizedThread then
break
end
else
break
end
end
end)
return function(Time)
Time = (type(Time) ~= 'number' or Time <= 0) and 0.001 or Time
insert(Time, {coroutine.running(), Time})
return coroutine.yield()
end
Hey! I just updated the source. Let me know if the issue still occurs!
cc. @mi54321
I thought it used time()
? I do prefer os.clock()
, at least in my experience it seems to go a bitā¦ not as ācorrectā? You canāt get small precise numbers from it, it will just give you 0, not sure, was a problem for me.
I also saw people doing wait functions by getting the delta time from Heartbeat as well, to the point where Iām not sure what is better :P
local function wait(n: number?)
if not n then
return runService.Heartbeat:Wait()
else
local lasted = 0
repeat
lasted += runService.Heartbeat:Wait()
until lasted >= n
return lasted
end
end
It has been switched back to os.clock()
. Itās pretty easy to edit the source anyway.
You would be creating a new loop that calls a yielding function (Heartbeat:Wait()
) for every single wait
call - this module stores all threads to yield and updates all of them in the same loop, thus making it much faster.
Thanks, Iāll probably be using this in my next module. (session locking, so thereās auto save loops which are pretty ew) I even tried making my own version based on yours.
local runService = game:GetService("RunService")
local running = coroutine.running
local c_yield = coroutine.yield
local c_resume = coroutine.resume
local c_create = coroutine.create
local os_clock = os.clock
local beat = runService.Heartbeat
if false and runService:IsClient() then
beat = runService.RenderStepped
end
local yields = {}
beat:Connect(function(delta)
local Clock = os_clock()
for i = 1, #yields do
local data = yields[i]
local spent = Clock - data[1]
if spent >= data[2] then
yields[i] = yields[#yields]
yields[#yields] = nil
c_resume(data[3], spent)
end
end
end)
return function(n: number?)
if not n then n = 0 end
local thread = running()
yields[#yields + 1] = {os_clock(), n, thread}
return c_yield()
end
Tried using some tips people gave me, Iām still not sure if I should be using RenderStepped when itās a client but yeah :D
Amazing! I love this. Great job.
am using Artificial Heartbeat
function CreateArtificialHB(name:string?,fps:number?,parent:instance?)
local ArtificialHB = Instance.new("BindableEvent")
ArtificialHB.Name = "ArtificialHB" .. tostring(math.random(1,100))
if name then
pcall(function()
ArtificialHB.Name = name
end)
end
if parent then
pcall(function()
ArtificialHB.Parent = parent
parent:WaitForChild(ArtificialHB.Name)
end)
end
if not fps or fps < 1 then
fps = 60
end
frame = 1/fps or 1/60
local tf = 0
local allowframeloss = false
local tossremainder = false
local lastframe = tick()
ArtificialHB:Fire()
local connection = game:GetService("RunService").Heartbeat:Connect(function(s,p)
tf = tf + s
if tf >= frame then
if allowframeloss then
ArtificialHB:Fire()
lastframe = tick()
else
for i = 1,math.floor(tf/frame) do
ArtificialHB:Fire()
end
lastframe = tick()
end
if tossremainder then
tf = 0
else
tf = tf - frame * math.floor(tf/frame)
end
end
end)
return ArtificialHB,connection,function(number:number?)
if not number or number < 1 then
ArtificialHB.Event:Wait()
return true
end
for _ = 1,number do
ArtificialHB.Event:Wait()
end
return true;
end
end
local ArtificialHB,Signal,Await = CreateArtificialHB("ArtificialHB",60)
while true do
--ur stuff
Await()
end
Hey,
thanks for amazing resource but I have experienced problem the custom wait does not working in ModuleScripts inside ServerScriptService is it intended?
In what way is it not working? could you show me the code? Itās been working for me and many others, so please thoroughly verify that this is not an issue on your end. If it is indeed an issue on our end, please make a reproduction script which reproduces the bug in as few lines as possible.