Timers and tasks
Lesson 13.2
“Do this in three seconds.” “Do this every minute.” “Build this tower one block at a time.” All of these need code that waits. In a game you never wait by stopping everything; you ask the game to call you again later.
The game counts time in ticks: 20 ticks are one second.
Waiting inside a coroutine: cl.spawn and cl.sleep_ticks
The friendliest way to wait is a coroutine, which you met in Coroutines. cl.spawn(fn) starts fn as a coroutine, and inside it cl.sleep_ticks(n) pauses for n ticks while the game goes on:
cl.on("player.join", function(event)
local player = event.player
cl.spawn(function()
cl.sleep_ticks(60) -- 3 seconds
if player:is_valid() then
player:send_message("Don't forget to read the rules!")
end
end)
end)
The code reads from top to bottom, as if it really waited. Underneath, the coroutine yields, and the game resumes it 60 ticks later.
Two things to keep in mind:
- Copy what you need from the event first. The event is gone once the handler returns, and the coroutine runs on after that. Here
local player = event.playercopies the player before the wait. - Check that things still exist after waiting. The player may have left in those 3 seconds;
is_valid()tells you.
cl.sleep_ticks only works inside cl.spawn. Called anywhere else, it raises cl.sleep_ticks must be called inside cl.spawn.
Step-by-step work
Waiting in a loop spreads work over time. A tower that grows one block every half second:
local function grow_tower(world, base, height)
cl.spawn(function()
for i = 1, height do
world:set_block(base + cl.pos(0, i, 0), "minecraft:stone_bricks")
cl.sleep_ticks(10)
end
end)
end
This is also the way to do big jobs without a lag spike: do a slice of the work, sleep one tick, do the next slice.
The scheduler: cl.sched
cl.sched runs a plain function later. It returns a task you can cancel.
-- once, in 5 seconds
local task = cl.sched.run_server(100, function()
cl.entity.server_broadcast("Five seconds have passed.")
end)
-- every minute, starting now
local reminder = cl.sched.repeat_server(0, 1200, function()
cl.entity.server_broadcast("Remember to drink water.")
end)
-- stop it again
reminder:cancel()
| Function | Runs |
|---|---|
cl.sched.run_server(delay, fn) |
Once, after delay ticks, on the server. |
cl.sched.repeat_server(delay, period, fn) |
First after delay ticks, then every period ticks, on the server. |
cl.sched.run_client(delay, fn) |
Once, after delay ticks, in the game client. |
task:cancel() stops a task, and task:is_pending() tells you whether it is still waiting.
The server functions only work while a world runs: in singleplayer, or on a server. A delay of 0 means “on the next tick”.
Which one to use
- A sequence with waits in between (a countdown, an animation, a slow build):
cl.spawnandcl.sleep_ticks. - Something that repeats forever (a reminder, an autosave):
cl.sched.repeat_server. - Something every single tick: an event handler for
tick.server_post. Keep it short, it runs 20 times a second.
Both cl.spawn and the scheduler cost nothing while nothing is waiting.
Pausing
In singleplayer the world stops while the pause menu is open, and so do server ticks. Your timers wait too, just like the game’s own.
Try it
Write a /countdown command that counts down from 5 in the action bar of the player who typed it, one number per second, then shows “Go!”.
Show a solution
cl.on("server.command_register", function()
cl.cmd.register(cl.cmd.literal("countdown"):executes(function(ctx)
local player = ctx:player()
if not player then
ctx:failure("Players only.")
return 0
end
cl.spawn(function()
for n = 5, 1, -1 do
if not player:is_valid() then return end
player:send_action_bar(tostring(n))
cl.sleep_ticks(20)
end
if player:is_valid() then player:send_action_bar("Go!") end
end)
end))
end)
The command returns right away; the coroutine keeps running for the next five seconds. player was copied out of ctx before the first wait, for the same reason as with events.
Recap: cl.spawn with cl.sleep_ticks waits inside a coroutine, cl.sched.run_server and repeat_server call a function later or repeatedly, and 20 ticks are one second.