Wanted to test the accuracy to see if task.wait has any benefits. Here’s the results of averaging the delta time of each function 20 times:
task.wait really is EXACTLY Heartbeat:Wait(), only much easier to type out. For anyone asking the differences between wait and task.wait, the answer is accuracy.
You should use defer in cases where you want to run some code after the current code has yielded, terminated, or you simply don’t care about the exact timing of it. Meanwhile the new spawn method will halt the calling thread until the thread it resumes has finished executing. So the main benefit is it won’t halt your code but will still execute almost immediately.
The new spawn method resumes a thread immediately while the old one waited a frame before resuming the callback and was subject to throttling. You can best replicate the old behavior by doing the following:
task.delay(0, function () ... end)
With deferred Lua enabled, events are deferred which is the reason for adding the new spawn method. It allows you to replicate the behavior of fastSpawn when deferred events are disabled.
Also, it kinda runs a bit faster than heartbeat < 0.0000001 nanosecond faster, and will you guys fix the problem thats its very inaccurate the first few seconds of starting the game?
This is a spectacular update!
A long awaited accuracy improvement for wait and an alternative to using coroutines and is speedier inside the call than out!
Really cool, excited for Parallel Lua!!
As someone who uses coroutines a lot to get around the shortcomings of spawn, are there practical differences between task.spawn(f) and coroutine.resume(coroutine.create(f))? (or even coroutine.wrap(f)()?
The time returned by Heartbeat:Wait() will be the time since the last frame while the time returned by task.wait() will be the total time elapsed since that call. In other words, this is to be expected.
You can really see the difference when calling task.wait() from one of our other engine events. For example, the following will result in a much shorter yield time.
Not gonna lie this scared me for a second. I thought by “finished executing” you meant the resumed thread had to be dead before it returned to the calling thread but that is definitely not the case.
Thanks for clearing up the rest of it though, although I still find it hard to believe that you wouldn’t care about execution order of your code but the part about running code after the current code yields or terminates makes perfect sense.
Still don’t get it, could you explain it a bit better? “After the current code has yielded” particularly don’t understand this part, isn’t that how Lua works, it will only continue executing the next line of code once the previous one is finished?
Can we get a way to cancel a delayed task, like having the task functions return some task object that lets us cancel?
It’s a lot less convenient to have to check whether or not a condition is present in the scheduled task. If instead of cancelling a task you check if state is A, sometimes you’ll have state go from A to B then back to A, and now two delayed tasks will both see state as A and run twice in total. You have to use something like identity tables / functions which is gross.
Cancelling is the only thing I think is missing for this, but it’s a pretty big thing to miss.
Kudos to Find All / Replace All.
I was able to port my waits and coroutines with minimal effort.
My game uses routines to schedule NPC movement. I noticed things are a lot faster with this new system.
Running wait without throttling makes a huge difference with the volume of NPCs my game runs.
On top I noticed my error handling functions execute all the time rather than some of the time preventing NPCs from breaking
When using the task library, threads are resumed through the Roblox scheduler (rather than resuming the raw coroutines yourself). This provides a number of benefits:
Engine-level error handling
Support for continuations (fixes issues such as this)
object.ChildAdded:Connect(function(child)
if someCondition(child) then
--> Kaboom! 😢
child:Destroy()
--> Destroys on the same frame that the child was added!
-- (there was no non-hacky way to accomplish this before)
task.defer(child.Destroy, child)
end
end)
I’m expecting to task.spawn to be much more widely used, but I think you’ll find that use cases for task.defer do come up reasonably frequently, mostly in cases where the engine calls your Lua code in the middle of some process, but you need to respond to that process after it completes.
task.wait() should be used in place of wait(). wait() is not based off of Heartbeat, where task.wait() is. wait() and delay() are old methods which have now been deprecated and shouldn’t be used in new projects, as these new methods are more reliable and better. task.delay() also uses heartbeat whereas delay() doesn’t making it more accurate.
Will some of these deprecate syntax functions/events down the road like spawn(), delay(time,func), etc? Is the task library meant to be a replacement for some of those functions/events?
I’ve found a bug with this, or atleast unintended behavior:
local camera = workspace.CurrentCamera do
local desiredType = Enum.CameraType.Scriptable
local start = os.clock()
local retries = 30
while camera.CameraType ~= desiredType and os.clock() < (start + 5) do
retries -= 1
wait()
camera.CameraType = desiredType
end
warn("(DEBUG)", "Setting camera type to", desiredType.Name, "took:", string.format("%.2f", os.clock() - start) .. "(s)")
end
While using wait(), this code sets the camera type to scriptable as intended, and warns a number anywhere from 2 to 3 as usual.
However, swapping the wait() for task.wait() causes the camera type to not change, and the warned number will be from 0.00 to 0.06.