Thread
Threads are designed to execute loops or delays without blocking the game. A thread exists until its code has finished executing or it is manually terminated using Kill.
All threads run sequentially (synchronously) on the main thread. Therefore, there is no need to worry about writing thread-safe code.
If you use an infinite loop inside a thread, you must call Thread.Pause() (even Thread.Pause(0) is sufficient). Failing to do so will freeze the entire game or server, as the thread will never yield control back to the main thread. In Lua and JavaScript the runaway protection stops such a thread after 5 seconds and shuts the resource down; in Squirrel nothing steps in.
JavaScript: threads are async functions
In Lua and Squirrel a thread is a coroutine, and Thread.Pause suspends it. JavaScript has no
coroutines, so a thread is an async function and Thread.Pause returns a promise: the
thread suspends where you await it.
Thread.Create(async () => {
while (true) {
await Thread.Pause(1000);
Chat.AddMessage("One second later.");
}
});
Everything else is the same: the thread starts on the next tick, wakes up on the first tick after
the pause, and Thread.Kill ends it. A few consequences of the promise model:
- Forgetting
awaitmeans no pause at all.Thread.Pause(1000);on its own returns immediately, the loop spins, and the runaway protection stops the resource after 5 seconds. - A thread function that is not
async(or that never awaits) runs to its end in one go and is finished right after. - A killed thread is simply never resumed. Code after the
awaitit is waiting on does not run, and neither does afinallyblock or acatcharound it. Thread.PauseandThread.WaitUntilcan be awaited in anyasyncfunction, not only in a thread. Awaiting them in an event handler works and is a convenient way to delay something; such a wait belongs to no thread, soThread.Killcannot cut it short.- An
asyncevent handler is not awaited by the game: whatever the handler returns, and anyEvents.Cancel, has to happen before its firstawait.
Quick Reference
| Name | Type | Description |
|---|---|---|
| Create | Shared | Creates a new thread with a function that is executed asynchronously. |
| Pause | Shared | Pauses the current thread for the specified duration. |
| WaitUntil | Shared | Pauses the current thread until a condition is met. |
| Kill | Shared | Stops the specified thread. |
| IsAlive | Shared | Returns whether the specified thread is currently alive and running. |
Timing
Threads are driven by the script tick. A thread never runs between two ticks, and everything below follows from that:
- The server ticks about every 10 ms, plus however long the tick itself takes.
- The client ticks once per rendered frame: roughly 16 ms at 60 FPS, 33 ms at 30 FPS, and much longer while the game stutters or streams.
Thread.Pause(ms) therefore means at least ms, never exactly ms. The thread wakes up on the
first tick after the time has elapsed, so a pause is always rounded up to the next tick.
Do not use Thread.Pause to measure time. Adding up the values you passed to it does not give
you elapsed milliseconds, because the error accumulates with every iteration and is different on every
machine. Thread.Pause(1) in a loop is not a millisecond counter: on the server it advances in
steps of roughly 10 ms, on the client in frames.
To measure time, read a clock instead. System.GetTime is the clock the
threads themselves run on and is available on both sides:
- Lua
- JavaScript
- Squirrel (deprecated)
Thread.Create(function()
local start = System.GetTime()
while System.GetTime() - start < 5000 do
Thread.Pause(0)
end
Chat.AddMessage("Five seconds are really over.")
end)
Thread.Create(async () => {
const start = System.GetTime();
while (System.GetTime() - start < 5000) {
await Thread.Pause(0);
}
Chat.AddMessage("Five seconds are really over.");
});
Thread.Create(function() {
local start = System.GetTime();
while (System.GetTime() - start < 5000) {
Thread.Pause(0);
}
Chat.AddMessage("Five seconds are really over.");
});
Thread.Pause(0) means "give up the rest of this tick and continue on the next one". It is the
right call inside a loop that has to run every frame. A smaller value buys you nothing, because
the next tick is the earliest the thread can continue either way.
Shared Functions
Create
Creates a thread with a function that is executed on the next script tick.
int threadId = Thread.Create(function threadFunc, any ...)
The function does not start running inside Thread.Create, not even its first line. It runs
for the first time on the next tick, so anything the calling code does afterwards still happens
first. This also holds for a thread created from inside another thread.
Threads have no parent. A thread created from inside another thread runs on its own, and killing the one that created it does not affect it.
Example:
- Lua
- JavaScript
- Squirrel (deprecated)
Events.Subscribe("sessionInit", function()
Thread.Create(function()
-- Get player model hash key.
local playerModel = Game.GetHashKey("M_Y_MULTIPLAYER")
-- Request model.
Game.RequestModel(playerModel)
-- Wait until model loaded.
while not Game.HasModelLoaded(playerModel) do
Game.RequestModel(playerModel)
Thread.Pause(0)
end
-- Change player model.
Game.ChangePlayerModel(Game.GetPlayerId(), playerModel)
-- Release model.
Game.MarkModelAsNoLongerNeeded(playerModel)
end)
end)
Events.Subscribe("sessionInit", () => {
Thread.Create(async () => {
// Get player model hash key.
const playerModel = Game.GetHashKey("M_Y_MULTIPLAYER");
// Request model.
Game.RequestModel(playerModel);
// Wait until model loaded.
while (!Game.HasModelLoaded(playerModel)) {
Game.RequestModel(playerModel);
await Thread.Pause(0);
}
// Change player model.
Game.ChangePlayerModel(Game.GetPlayerId(), playerModel);
// Release model.
Game.MarkModelAsNoLongerNeeded(playerModel);
});
});
Events.Subscribe("sessionInit", function() {
Thread.Create(function() {
// Get player model hash key.
local playerModel = Game.GetHashKey("M_Y_MULTIPLAYER");
// Request model.
Game.RequestModel(playerModel);
// Wait until model loaded.
while (!Game.HasModelLoaded(playerModel)) {
Game.RequestModel(playerModel);
Thread.Pause(0);
}
// Change player model.
Game.ChangePlayerModel(Game.GetPlayerId(), playerModel);
// Release model.
Game.MarkModelAsNoLongerNeeded(playerModel);
});
});
Pause
Pauses the current thread for at least the specified duration. See Timing.
Thread.Pause(int milliseconds)
In JavaScript the function returns a promise: await Thread.Pause(int milliseconds).
In Lua and Squirrel this function can only be used within a running thread. Calling it anywhere
else raises a script error. In JavaScript it can be awaited in any async function, see
above.
The comment in the example below is a simplification: the message is sent no sooner than every 5 seconds, and the small overshoot of each round adds up over time. If a task has to happen at an exact wall-clock moment, compare against a clock instead of relying on the pause.
Example:
- Lua
- JavaScript
- Squirrel (deprecated)
Events.Subscribe("scriptInit", function()
Thread.Create(function()
while true do
Thread.Pause(5000)
-- This message is sent every 5 seconds.
Chat.AddMessage("5 seconds are over.")
end
end)
end)
Events.Subscribe("scriptInit", () => {
Thread.Create(async () => {
while (true) {
await Thread.Pause(5000);
// This message is sent every 5 seconds.
Chat.AddMessage("5 seconds are over.");
}
});
});
Events.Subscribe("scriptInit", function() {
Thread.Create(function() {
while (true) {
Thread.Pause(5000);
// This message is sent every 5 seconds.
Chat.AddMessage("5 seconds are over.");
}
});
});
WaitUntil
Pauses the current thread until the condition returns true. The condition is called once per
script tick, starting with the next tick, so this is the same as a loop that calls
Thread.Pause(0) until the condition holds, only shorter.
Thread.WaitUntil(function condition)
In JavaScript the function returns a promise: await Thread.WaitUntil(function condition).
Like Pause, in Lua and Squirrel this function can only be used within a running thread.
If the condition raises an error, the error is raised in the waiting thread, at the WaitUntil
call, where you can catch it like any other error.
Example:
- Lua
- JavaScript
- Squirrel (deprecated)
Thread.Create(function()
local model = Game.GetHashKey("SULTANRS")
Game.RequestModel(model)
Thread.WaitUntil(function()
return Game.HasModelLoaded(model)
end)
Chat.AddMessage("Model loaded.")
end)
Thread.Create(async () => {
const model = Game.GetHashKey("SULTANRS");
Game.RequestModel(model);
await Thread.WaitUntil(() => Game.HasModelLoaded(model));
Chat.AddMessage("Model loaded.");
});
Thread.Create(function() {
local model = Game.GetHashKey("SULTANRS");
Game.RequestModel(model);
Thread.WaitUntil(function() {
return Game.HasModelLoaded(model);
});
Chat.AddMessage("Model loaded.");
});
Kill
Stops the specified thread. It never runs again: whatever it was waiting for, the code after that
point is not executed. IsAlive returns false right away.
Thread.Kill(int threadId)
A thread may kill itself. The code that called Thread.Kill keeps running until the thread's
next Thread.Pause or Thread.WaitUntil, which is where it stops for good.
IsAlive
Returns whether the specified thread is currently alive and running.
bool alive = Thread.IsAlive(int threadId)