CreateThread de FiveM y Wait(0) explicados: por qué tu script cuesta ms

Cómo funcionan CreateThread, Wait y SetTimeout en Lua de FiveM, por qué Wait(0) en cada bucle aparece en resmon, y por qué un bucle while sin Wait congela el juego.

Escribes un bucle para verificar algo, y el juego deja de responder, o resmon muestra tu recurso con algunos ms mientras te quedas quieto:

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

Este artículo explica cómo funcionan los hilos y Wait en Lua de FiveM, lo que realmente cuesta Wait(0), y cómo escribir bucles que permanezcan económicos.

Cómo funcionan los hilos en FiveM

Lua en FiveM es cooperativo. Tu script no se ejecuta en paralelo con el juego: se ejecuta cuando el tiempo de ejecución lo llama, y debe devolver el control. Dos cosas hacen que sea posible:

  • Los controladores de eventos y las devoluciones de llamada se ejecutan una vez, de principio a fin, y luego regresan.
  • Los hilos creados con CreateThread son pequeñas corrutinas que el programador reanuda una y otra vez. Un hilo devuelve el control cuando llama a Wait.
lua
CreateThread(function()
    while true do
        Wait(1000)
        print('one second later')
    end
end)

CreateThread y Citizen.CreateThread son la misma función, al igual que Wait y Citizen.Wait. Usa la que prefieras, pero sé consistente en un script.

Un bucle while sin Wait congela el juego

Si un hilo nunca llama a Wait, el programador nunca recupera el control. En el cliente, el juego deja de dibujar fotogramas hasta que el bucle termina, y en el servidor, todos los demás scripts esperan el tuyo.

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

Lo mismo ocurre con un repeat … until o un for largo que hace trabajo pesado. Cada bucle que puede ejecutarse por un tiempo necesita un Wait dentro de él. Si un script congela el juego al iniciar, busca primero un bucle como este.

Qué cuesta Wait(0)

Wait(0) significa: pausa hasta el siguiente fotograma (cliente) o el siguiente tick (servidor), y luego continúa. El bucle se ejecuta nuevamente en cada fotograma, así que a 60 fps su cuerpo se paga 60 veces 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)

Un bucle como este cuesta poco. Diez de ellos, en treinta recursos, suman varios ms de tiempo del cliente, que es exactamente lo que resmon muestra como alto ms. El costo no es el Wait(0) en sí, es todo lo que repites en cada fotograma.

Espera solo lo que necesites

La mayoría de los controles no necesitan ejecutarse cada fotograma. Pregúntate para qué es el bucle:

Qué hace el bucle Espera razonable
Dibujar un marcador o texto, manejar una pulsación de tecla 0 (solo mientras el jugador esté cerca)
Verificar una distancia para decidir si algo está cerca 250 a 1000
Sondear un estado que cambia raramente (trabajo, vehículo) 1000 o más, o usa un evento

El truco habitual es dos velocidades: un bucle lento que verifica la distancia, y uno rápido solo mientras el jugador está dentro del á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)

Lejos de la zona, el hilo se despierta una vez por segundo. Cerca de ella, dibuja en cada fotograma. Resmon baja a casi nada para la mayoría de los jugadores.

Consejo: antes de que recurras a un bucle más rápido, pregúntate si un evento o una bolsa de estado puede decirte cuándo cambia el valor. Ningún bucle es más barato que cualquier Wait.

SetTimeout: ejecuta algo una vez, más tarde

Si solo necesitas hacer algo una vez después de un retraso, no necesitas un hilo con un Wait:

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

SetTimeout(ms, callback) ejecuta la devolución de llamada una vez después del retraso y no bloquea nada. También existe en el servidor. Para una tarea repetida, un hilo con Wait es la herramienta correcta, ya que SetTimeout no se repite por sí solo.

Esperando dentro de controladores de eventos

Un controlador de eventos también puede llamar a Wait, porque FiveM lo ejecuta en su propia corrutina. Ten cuidado en el servidor: después de un Wait, el source global ya no apunta al jugador que activó el evento. Guárdalo primero (ver eventos del cliente y el servidor):

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

Deteniendo un hilo

Un hilo termina cuando su función regresa. Para detener un bucle desde afuera, usa un indicador que el bucle 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)

Todos los hilos de un recurso se detienen por sí solos cuando el recurso se detiene, así que solo necesitas esto para detener un bucle mientras el recurso continúa ejecutándose.

Lista de verificación

Síntoma Solución
El juego se congela cuando un script se inicia Un bucle while true do no tiene Wait: agrega uno
El recurso muestra ms constantes en resmon mientras está inactivo Reemplaza Wait(0) con una verificación de distancia y una espera más larga
Necesita ejecutar algo una vez después de un retraso SetTimeout(ms, fn) en lugar de un hilo
source es incorrecto después de un Wait en el servidor Guarda local src = source antes de Wait
Sondear un valor que raramente cambia Usa un evento o una bolsa de estado en lugar de un bucle
¿Citizen.Wait o Wait? Misma función, elige un estilo

Respuestas rápidas

¿Qué hace Wait(0) en FiveM?

Pausa el hilo actual hasta el siguiente fotograma en el cliente, o el siguiente tick del servidor. Luego el bucle se ejecuta nuevamente en cada fotograma, por lo que todo lo que está dentro de él se paga 60 o más veces por segundo.

¿Es Citizen.Wait diferente de Wait?

No. Wait y Citizen.Wait son la misma función, al igual que CreateThread y Citizen.CreateThread. Los nombres cortos son solo alias.

¿Detiene Wait todo el juego?

No. Solo detiene el hilo que lo llama, y los otros hilos y el juego continúan ejecutándose. Un bucle que nunca llama a Wait es lo que bloquea todo.

Scripts que evitan este problema

Advanced BoostingBoosting de vehículos desde una tablet: contratos de clase D a S+, crews y cola en vivo.Ver script →CCTV Security CamerasCámaras colocables, una tablet con vista múltiple en vivo y fotos como prueba.Ver script →Mic PhoneUn móvil plegable que se abre en tablet y llega al móvil real del jugador.Ver script →

Sigue leyendo