Task Library - Now Available!

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.

5 Likes

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?

4 Likes

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.

13 Likes

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 :slight_smile:

7 Likes

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)
18 Likes

This is likely just slight variation in framerate or from you printing the result. This kind of difference is often negligible and not that important.

6 Likes

Here’s a really easy to understand use case:

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.

35 Likes

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.

7 Likes

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?

3 Likes

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.

6 Likes

Did you even read the post? lol This is

4 Likes

I love how you can pass additional arguments after the function, I hated this kind of syntax:

delay(10, function() SpawnEnemies(5) end)

this is much cleaner:

task.delay(10, SpawnEnemies, 5)

awesome library!

15 Likes

What’s the performance cost of task.spawn()? Some relative comparisons would be very useful. For example, how many math operations can I do in the same time, or what sort of memory allocation does it incur?

13 Likes

spawn and task.spawn called on a function both create a new coroutine, so there’s no raw performance benefit in that regard. However task.spawn gives you the option of passing a coroutine instead if you want to manage things more closely yourself including potentially reusing coroutines for better perf.

The task library is a low level library, it does what you tell it to do. If you use task.spawn and/or task.wait to resume 100,000 threads in a frame it will dutifully try to run the 100,000 threads that frame (though there’s no guarantee the client / server in question can actually handle that without lagging / running out of memory).

22 Likes

Is it exactly equivalent in terms of performance as game:GetService(“RunService”).Heartbeat:Wait()?

7 Likes

Hey there :wave:t5:, quick question, how does task.wait(n) look like in lua, and how does wait(n) look like in lua?

5 Likes

This creates a lot more simplicity for programmers looking to create accurate solutions to wait(). I’m always excited to see improvement, especially in an area like this!

To my understanding, some of the built-in instances/functions use the wait and delay functions. Will these eventually use the task library?

6 Likes

Swap the two, you’ll get opposite results. It’s exactly the same.

10 Likes

That’s a great update, finally.
It would be cool if you added

function task.debounce(event, duration, callback, ...)

for example

-- Triggered by touched event only after 3 seconds passed of the last call
local function PartTouched(debounce, HitPart, RandomData)
	if RandomData then
		print("The part",HitPart)
		-- the cooldown of the debounce refresh after being consumed properly
		debounce.consume()
	end
end
task.debounce(part.Touched, 3, PartTouched, RandomData)

Thank you!
It would be really helpful for beginners

12 Likes

The latter would be faster due to task.wait() being built into the roblox engine (and therefore runs on C++ rather then lua).

8 Likes