As indicated by @Quenty above, a thread yielded by WaitForChild is put into the same, budgeted queue as threads yielded by wait
, spawn
, and delay
. The only difference is that the WaitForChild thread is not scheduled with a delay. After being added to the queue, it will run as soon as WaitingScriptsJob gets around to resuming it.
Imagine an AddThreadToScheduler
function that receives a thread along with a number indicating the duration to wait before the thread should be resumed. Within wait
, spawn
, and delay
, the call might look like this:
DefaultWaitTime = 0.03
if duration < DefaultWaitTime then
duration = DefaultWaitTime
end
AddThreadToScheduler(thread, duration)
Whereas WaitForChild would look like this:
AddThreadToScheduler(thread, 0)
The MicroProfiler will let us see how this works. Let’s use the following LocalScript, put under ReplicatedFirst, to be run with Play Solo:
game.ReplicatedFirst:RemoveDefaultLoadingScreen()
wait(5) -- Give the game some time to load and settle down.
-- May also disable Players.CharacterAutoLoads and Chat.LoadDefaultChat to
-- reduce clutter.
local function DoSomeWork(ms)
local t = tick()
repeat until tick()-t >= ms/1000
end
local RunService = game:GetService("RunService")
RunService.Stepped:Connect(function()
debug.profilebegin("STEPPED")
DoSomeWork(2)
debug.profileend()
end)
RunService:BindToRenderStep("BIND", 0, function()
debug.profilebegin("BIND")
DoSomeWork(2)
debug.profileend()
end)
RunService.RenderStepped:Connect(function()
debug.profilebegin("RENDER")
DoSomeWork(2)
debug.profileend()
end)
RunService.Heartbeat:Connect(function()
debug.profilebegin("HEARTBEAT")
DoSomeWork(2)
debug.profileend()
end)
while true do
debug.profilebegin("WAIT")
DoSomeWork(2)
debug.profileend()
wait()
end
Ctrl+F6
will open the profiler. The script will produce a profile that looks similar to this:
Look for RENDER, BIND, STEPPED, HEARTBEAT, and WAIT, as defined the in the script. These are the LocalScript doing work in various locations.
From what can be seen, BIND and RENDER always run in the render step on the Main thread. BIND, which allows a priority to be set, runs first. The bit of activity after BIND is the default camera script doing some work, which runs after BIND because it has a later priority. RenderStepped is designated as having the latest priority, so RENDER runs after all bound render functions.
WAIT, STEPPED, and HEARTBEAT run in one of the several worker threads each frame. Once rendering has finished, WaitngScriptsJob starts. It is not visible in the first frame because it is doing almost no work. Remember that wait()
has a minimum delay of 0.03 seconds, so it resumes at least every other frame. It can be seen in the second frame because WAIT is running.
Following that is simulation. The Stepped event is dependent on the simulation being active, so it runs here. The bit of activity following STEPPED is physics simulation. Finally, HEARTBEAT starts running. The remainder of the time is spent idling to sync to the next frame. Another physics step may also occur here.
The DevHub has more information about the MicroProfiler:
My previous post has the benchmark script I used to produce the data. This data was pasted into LibreOffice Calc and rendered as a chart. Your preferred spreadsheet program should be able to do something similar.