This new library is a great replacement of wait and other functions, but I experienced a huge problem with it. If you delete a script that has a loop which utilizes task.wait, that loop will continue to run for an infinite amount of time unless it has special additional checks in place to stop itself. Using .RenderStepped:Wait() or standard wait would stop the loop and thus disabled script wont actually run anymore.
While this makes sense from standpoint of how this new library works, its counterintuitive for people who are used to behavior of standard wait, and if not handled properly it can make stuff lag over time (especially if that loop is located in an object that gets destroyed/respawned a lot, like character or vehicles with scripts in them)
Coming back to this post but… I don’t quite understand the use case of task.defer. Anyone able to explain when it would be appropriate to use? I’m especially keen on this because I want to get started on working with Deferred signals so I’m future-proofed when Immediate signals go poof.
I’ve got the hang of most of the other events but deferred stuff confuses me heavily even after checking out the diagram and everything from the other thread. I’m not sure when and where I should use it or what kinds of problems it’d help me solve. Is it stupid to defer everything? When does code not in task.defer run? Just in general I don’t really understand deferring stuff.
EDIT: I did find an interesting use of task.defer. It doesn’t help me understand what exactly deferring is or what task.defer is used for, but I tested it out a bit and made a script that prevents a tool from being unequipped. It works in both Immediate and Deferred signal modes.
local Tool = script.Parent
local Character
Tool.Equipped:Connect(function ()
Character = Tool.Parent
end)
Tool.Unequipped:Connect(function ()
if Character then
task.defer(function ()
Tool.Parent = Character
end)
end
end)
The defer method schedules code for resumption at the end of the current (or next if we aren’t currently in one) resumption point. You can think of it as adding something to the back of the queue of threads we are going to resume.
So it’s useful in cases such as the example given by @tnavarts and the one you’ve shared here but it can also be useful for other purposes too.
Scheduling custom events to run later so they don’t affect the current code (we do this with deferred events)
Coalescing state changes so code only runs once
An example of the latter might be a case where you only care about the final value of something and not any intermediary values:
local currentRequestTime = 0
local function performDataSave(requestTime)
if requestTime < currentRequestTime then
return -- a newer data-save was requested
end
-- save data
end
local function queueDataSave()
currentRequestTime = os.clock()
task.defer(performDataSave, currentRequestTime)
end
instance:GetAttributeChangedSignal("Points"):Connect(function ()
queueDataSave()
end)
Of course there are other ways to implement this but this is one such way to do it.
Hey, sorry if I’m missing something in terms of engine implementation detail, but what difference is there in simply calling the Object::Destroy method directly and using task.defer? What engine detail is changed? Going off the comment of it being destroyed instantly, does calling the method directly have a form of delay before the object is destroyed?
Many thanks beforehand.
Adding onto this: does task.defer add some performance benefit over task.spawn / spawn / coroutines? Since it’s stated it doesn’t care for exact timing and all.
Changing the parent of an instance during a parent change operation will result in the following warning being shown and the parent remaining the same.
Something unexpectedly tried to set the parent of <Instance> to NULL while trying to set the parent of <Instance>. Current parent is <Parent>.
When signaling behavior is set to immediate (the default), ancestry changed events fire during the parent change operation so running the code in @tnavarts’s example, where ‘Destroy’ is called directly, will produce the warning above.
By deferring the call to ‘Destroy’, the parent change happens after the parent change operation but still within the same resumption cycle (so almost immediately).
The code doesn’t run immediately but will still be processed before the engine can move onto its next step. It’s not really any different other than it doesn’t block the code which is currently running.
It’s not a “bug” in that coroutine.wrap / coroutine.resume propagate errors in the same way that they do in vanilla Lua, they just don’t have the extra behavior that Roblox adds for convenient debugging in Studio. It makes sense to have a separate library like this if you want the Roblox behaviors so that vanilla Lua code can be brought into the engine from other sources and work as expected.
I see no need for this at all
task.wait() is fine as it is, besides if wait() is deprecated then it WON’T be updated anymore, it is still going to be useable so older games don’t break
I don’t think it’s all evil in all honestly. I use spawn() mainly for its task scheduler functionality which helps performance a lot in looped events such as character leg movement replication, bullet replication and stuff which doesn’t need to be ran instantly
@WallsAreForClimbing and @tnavarts, looking at the spec provide for task.wait() and docs for wait() you can see they do not return the same number of parameters. wait() returns the duration and the total game time, whereas task.wait() is only returning the duration. Devs should be aware of this difference when updating their code.
Is there any reason to not make task.wait return both parameters?
Below is the last 4 lines of the current Character.Animate script for reference. You will get unexpected behaviour assuming they are equivalent and just replace wait() with task.wait().
while Character.Parent ~= nil do
local _, currentGameTime = wait(0.1)
stepAnimate(currentGameTime)
end
changing to task.wait(0.1) will not give you the expected results:
while Character.Parent ~= nil do
local _, currentGameTime = task.wait(0.1)
stepAnimate(currentGameTime)
end
If you task.wait() during the Heartbeat part of the frame, then it will wait until the next Heartbeat (roughly 1/60), but if you task.wait() in an earlier part of the frame (such as on RenderStepped), it won’t even wait a full frame, it will unsuspend on Heartbeat of the same frame.
Very much yes! Is this a solution to deferred events? Will this allow Parallel Luau to exist without breaking all event-handling code across all existing games?
Are you able to disconnect task.delay? I want to disconnect task.delay if a certain condition is met so it doesn’t run and I’m having trouble doing that.