FiveM CreateThread и Wait(0) объяснены: почему ваш скрипт стоит ms
Как работают CreateThread, Wait и SetTimeout в FiveM Lua, почему Wait(0) в каждом цикле показывается в resmon и почему цикл while без Wait зависает игру.
Вы пишете цикл, чтобы что-то проверить, а игра перестаёт отвечать, или resmon показывает ваш ресурс на несколько ms, пока вы стоите:
while true do
-- no Wait here: the game never gets control back
local ped = PlayerPedId()
endВ этой статье объясняется, как работают потоки и Wait в FiveM Lua, что действительно стоит Wait(0) и как писать циклы, которые остаются дешёвыми.
Как работают потоки в FiveM
Lua в FiveM кооперативна. Ваш скрипт не выполняется параллельно с игрой: он выполняется, когда его вызывает runtime, и должен вернуть управление. Две вещи делают это возможным:
- Обработчики событий и обратные вызовы выполняются один раз от начала до конца, затем возвращаются.
- Потоки, созданные с помощью
CreateThread, — это маленькие сопрограммы, которые планировщик возобновляет снова и снова. Поток возвращает управление, когда вызываетWait.
CreateThread(function()
while true do
Wait(1000)
print('one second later')
end
end)CreateThread и Citizen.CreateThread — это одна и та же функция, а также Wait и Citizen.Wait. Используйте то, что вам нравится, но будьте последовательны в одном скрипте.
Цикл while без Wait зависает игру
Если поток никогда не вызывает Wait, планировщик никогда не получает управление обратно. На клиенте игра перестаёт рисовать кадры до завершения цикла, а на сервере все остальные скрипты ждут вашего.
CreateThread(function()
while true do
-- missing Wait: this blocks the whole client
end
end)То же самое происходит с циклом repeat … until или длинным циклом for, который выполняет тяжёлую работу. Каждый цикл, который может работать какое-то время, нуждается в Wait внутри него. Если скрипт зависает игру при запуске, сначала ищите такой цикл.
Что стоит Wait(0)
Wait(0) означает: пауза до следующего кадра (клиент) или следующего тика (сервер), затем продолжить. Цикл выполняется снова на каждом кадре, поэтому при 60 fps его тело платится 60 раз в секунду.
CreateThread(function()
while true do
Wait(0)
local coords = GetEntityCoords(PlayerPedId())
local dist = #(coords - vector3(215.0, -810.0, 30.7))
if dist < 3.0 then
-- draw a marker, show text...
end
end
end)Один такой цикл стоит мало. Десять из них, в тридцати ресурсах, складываются на несколько ms клиентского времени, что именно resmon показывает высокой ms. Стоимость — не сам Wait(0), а всё, что вы повторяете каждый кадр.
Wait только так коротко, как вам нужно
Большинство проверок не нужно выполнять каждый кадр. Спросите, для чего нужен цикл:
| Что делает цикл | Разумное ожидание |
|---|---|
| Нарисовать маркер или текст, обработать нажатие клавиши | 0 (только пока игрок близко) |
| Проверить расстояние, чтобы решить, близко ли что-то | 250 to 1000 |
| Опросить состояние, которое редко меняется (работа, автомобиль) | 1000 или больше, или используйте событие |
Обычный трюк — две скорости: медленный цикл, который проверяет расстояние, и быстрый только когда игрок находится внутри области.
local zone = vector3(215.0, -810.0, 30.7)
CreateThread(function()
while true do
local sleep = 1000
local dist = #(GetEntityCoords(PlayerPedId()) - zone)
if dist < 10.0 then
sleep = 0
DrawMarker(1, zone.x, zone.y, zone.z - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1.5, 1.5, 0.5, 255, 255, 255, 120, false, false, 2, false, nil, nil, false)
end
Wait(sleep)
end
end)Далеко от зоны поток пробуждается один раз в секунду. Рядом с ней он рисует каждый кадр. Resmon падает почти до нуля для большинства игроков.
Совет: перед тем как обратиться к более быстрому циклу, спросите, может ли событие или state bag сказать вам, когда значение меняется. Вообще никакого цикла — дешевле, чем любой Wait.
SetTimeout: запустить что-то один раз, позже
Если вам нужно сделать что-то только один раз после задержки, вам не нужен поток с Wait:
SetTimeout(5000, function()
print('five seconds later')
end)SetTimeout(ms, callback) запускает обратный вызов один раз после задержки и ничего не блокирует. Он также существует на сервере. Для повторяющейся задачи поток с Wait — правильный инструмент, так как SetTimeout не повторяется сам по себе.
Ожидание внутри обработчиков событий
Обработчик событий также может вызывать Wait, потому что FiveM запускает его в своей собственной сопрограмме. Будьте осторожны на сервере: после Wait глобальный source больше не указывает на игрока, который вызвал событие. Сохраните его в первую очередь (см. события клиента и сервера):
RegisterNetEvent('my_script:server:buy', function()
local src = source
Wait(500)
print(('player %s bought something'):format(src))
end)Остановка потока
Поток заканчивается, когда его функция возвращается. Чтобы остановить цикл снаружи, используйте флаг, который проверяет цикл:
local running = true
CreateThread(function()
while running do
Wait(500)
-- work
end
end)
AddEventHandler('onResourceStop', function(resource)
if resource == GetCurrentResourceName() then
running = false
end
end)Все потоки ресурса автоматически останавливаются, когда ресурс останавливается, поэтому вам это нужно только для остановки цикла, пока ресурс продолжает работать.
Чеклист
| Симптом | Исправление |
|---|---|
| Игра зависает, когда скрипт запускается | Цикл while true do не имеет Wait: добавьте один |
| Ресурс показывает постоянный ms в resmon во время простоя | Замените Wait(0) проверкой расстояния и более длительным ожиданием |
| Нужно запустить что-то один раз после задержки | SetTimeout(ms, fn) вместо потока |
source неправильный после Wait на сервере |
Сохраните local src = source перед Wait |
| Опрос значения, которое редко меняется | Используйте событие или state bag вместо цикла |
Citizen.Wait или Wait? |
Одна и та же функция, выберите один стиль |
Короткие ответы
Что делает Wait(0) в FiveM?
Он приостанавливает текущий поток до следующего кадра на клиенте или следующего тика на сервере. Затем цикл выполняется снова на каждом кадре, поэтому всё внутри платится 60 или более раз в секунду.
Отличается ли Citizen.Wait от Wait?
Нет. Wait и Citizen.Wait — это одна и та же функция, а также CreateThread и Citizen.CreateThread. Короткие названия — просто псевдонимы.
Wait останавливает всю игру?
Нет. Он только приостанавливает поток, который его вызывает, а другие потоки и игра продолжают работать. Цикл, который никогда не вызывает Wait, — вот что блокирует всё.
Скрипты без этой проблемы
Advanced BoostingУгон машин на заказ через планшет: контракты от класса D до S+, команды и живая очередь.Смотреть скрипт →
CCTV Security CamerasУстанавливаемые камеры, планшет с мультиэкраном в реальном времени и распечатанные фото-улики.Смотреть скрипт →
Mic PhoneСкладной телефон, который раскладывается в планшет и работает и на настоящем телефоне игрока.Смотреть скрипт →