FiveM CreateThread e Wait(0) explicados: por que seu script custa ms

Como CreateThread, Wait e SetTimeout funcionam no Lua FiveM, por que Wait(0) em cada loop aparece no resmon, e por que um while loop sem Wait congela o jogo.

Você escreve um loop para verificar algo, e o jogo para de responder, ou resmon mostra seu recurso em alguns ms enquanto você está parado:

lua
while true do
    -- no Wait here: the game never gets control back
    local ped = PlayerPedId()
end

Este artigo explica como as threads e Wait funcionam no Lua FiveM, o que Wait(0) realmente custa, e como escrever loops que permanecem baratos.

Como as threads funcionam no FiveM

Lua no FiveM é cooperativo. Seu script não é executado em paralelo com o jogo: ele é executado quando o tempo de execução o chama, e deve devolver o controle. Duas coisas tornam isso possível:

  • Event handlers e callbacks são executados uma vez, do início ao fim, e depois retornam.
  • Threads criadas com CreateThread são pequenas coroutines que o agendador retoma novamente e novamente. Uma thread devolve o controle quando chama Wait.
lua
CreateThread(function()
    while true do
        Wait(1000)
        print('one second later')
    end
end)

CreateThread e Citizen.CreateThread são a mesma função, e assim são Wait e Citizen.Wait. Use qual você preferir, mas seja consistente em um script.

Um while loop sem Wait congela o jogo

Se uma thread nunca chama Wait, o agendador nunca recupera o controle. No cliente, o jogo para de desenhar quadros até o loop terminar, e no servidor, cada outro script aguarda o seu.

lua
CreateThread(function()
    while true do
        -- missing Wait: this blocks the whole client
    end
end)

O mesmo acontece com um repeat … until ou um long for loop que faz trabalho pesado. Cada loop que pode executar por um tempo precisa de um Wait dentro dele. Se um script congela o jogo na inicialização, procure por um loop assim primeiro.

O que Wait(0) custa

Wait(0) significa: pause até o próximo quadro (cliente) ou o próximo tick (servidor), e depois continue. O loop é executado novamente em cada quadro, portanto a 60 fps seu corpo é pago 60 vezes por segundo.

lua
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)

Um loop assim custa pouco. Dez deles, em trinta recursos, somam alguns ms de tempo do cliente, que é exatamente o que resmon mostra como ms alto. O custo não é o Wait(0) em si, é tudo que você repete a cada quadro.

Wait apenas tão curto quanto você precisa

A maioria das verificações não precisa ser executada a cada quadro. Pergunte qual é o propósito do loop:

O que o loop faz Espera razoável
Desenhar um marcador ou texto, lidar com um pressionamento de tecla 0 (apenas enquanto o jogador está próximo)
Verificar uma distância para decidir se algo está próximo 250 a 1000
Sondagem de um estado que muda raramente (trabalho, veículo) 1000 ou mais, ou use um evento

O truque usual é duas velocidades: um loop lento que verifica a distância, e um rápido apenas enquanto o jogador está dentro da área.

lua
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)

Longe da zona, a thread acorda uma vez por segundo. Perto dela, ela desenha a cada quadro. Resmon cai para quase nada para a maioria dos jogadores.

Dica: antes de chegar a um loop mais rápido, pergunte se um evento ou uma sacola de estado pode informar quando o valor muda. Nenhum loop é mais barato que qualquer Wait.

SetTimeout: executar algo uma vez, depois

Se você apenas precisa fazer algo uma vez após um atraso, você não precisa de uma thread com um Wait:

lua
SetTimeout(5000, function()
    print('five seconds later')
end)

SetTimeout(ms, callback) executa o callback uma vez após o atraso e não bloqueia nada. Também existe no servidor. Para uma tarefa repetida, uma thread com Wait é a ferramenta certa, pois SetTimeout não se repete por conta própria.

Aguardando dentro de event handlers

Um event handler também pode chamar Wait, porque FiveM o executa em sua própria coroutine. Tenha cuidado no servidor: após um Wait, o source global não aponta mais para o jogador que disparou o evento. Salve-o primeiro (veja eventos de cliente e servidor):

lua
RegisterNetEvent('my_script:server:buy', function()
    local src = source
    Wait(500)
    print(('player %s bought something'):format(src))
end)

Interrompendo uma thread

Uma thread termina quando sua função retorna. Para interromper um loop de fora, use um sinalizador que o loop verifica:

lua
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)

Todas as threads de um recurso param automaticamente quando o recurso para, portanto você apenas precisa disso para parar um loop enquanto o recurso continua em execução.

Checklist

Sintoma Correção
Jogo congela quando um script inicia Um loop while true do não tem Wait: adicione um
Recurso mostra ms constantes no resmon enquanto ocioso Substitua Wait(0) por uma verificação de distância e um wait mais longo
Precisa executar algo uma vez após um atraso SetTimeout(ms, fn) em vez de uma thread
source está errado após um Wait no servidor Salve local src = source antes do Wait
Sondagem de um valor que raramente muda Use um evento ou uma sacola de estado em vez de um loop
Citizen.Wait ou Wait? Mesma função, escolha um estilo

Respostas rápidas

O que Wait(0) faz no FiveM?

Ele pausa a thread atual até o próximo quadro no cliente, ou o próximo tick do servidor. O loop é executado novamente a cada quadro, portanto tudo dentro dele é pago 60 ou mais vezes por segundo.

Citizen.Wait é diferente de Wait?

Não. Wait e Citizen.Wait são a mesma função, e assim são CreateThread e Citizen.CreateThread. Os nomes curtos são apenas aliases.

Wait paralisa todo o jogo?

Não. Ele apenas pausa a thread que a chama, e as outras threads e o jogo continuam em execução. Um loop que nunca chama Wait é o que bloqueia tudo.

Scripts sem esse problema

Advanced BoostingBoosting de veículos pelo tablet: contratos da classe D à S+, crews e fila ao vivo.Ver script →CCTV Security CamerasCâmeras posicionáveis, um tablet com várias telas ao vivo e fotos impressas como prova.Ver script →Mic PhoneUm celular dobrável que abre em tablet e chega ao celular de verdade do jogador.Ver script →

Continue lendo