FiveM CreateThread et Wait(0) expliqués : pourquoi votre script coûte des ms

Comment CreateThread, Wait et SetTimeout fonctionnent dans Lua FiveM, pourquoi Wait(0) dans chaque boucle apparaît dans resmon, et pourquoi une boucle while sans Wait gèle le jeu.

Vous écrivez une boucle pour vérifier quelque chose, et le jeu cesse de répondre, ou resmon montre votre resource à quelques ms alors que vous êtes immobile :

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

Cet article explique comment les threads et Wait fonctionnent dans Lua FiveM, ce que Wait(0) coûte vraiment, et comment écrire des boucles qui restent peu coûteuses.

Comment les threads fonctionnent dans FiveM

Lua dans FiveM est coopératif. Votre script ne s'exécute pas en parallèle avec le jeu : il s'exécute quand le runtime l'appelle, et il doit redonner le contrôle. Deux choses rendent cela possible :

  • Les gestionnaires d'événements et les callbacks s'exécutent une fois, du début à la fin, puis retournent.
  • Les threads créés avec CreateThread sont de petites coroutines que le planificateur reprend encore et encore. Un thread redonne le contrôle quand il appelle Wait.
lua
CreateThread(function()
    while true do
        Wait(1000)
        print('one second later')
    end
end)

CreateThread et Citizen.CreateThread sont la même fonction, et il en va de même pour Wait et Citizen.Wait. Utilisez celui que vous préférez, mais soyez cohérent dans un script.

Une boucle while sans Wait gèle le jeu

Si un thread n'appelle jamais Wait, le planificateur n'obtient jamais le contrôle. Sur le client, le jeu cesse de dessiner les frames jusqu'à ce que la boucle se termine, et sur le serveur, chaque autre script attend le vôtre.

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

La même chose se produit avec une boucle repeat … until ou une longue boucle for qui effectue un travail lourd. Chaque boucle qui peut s'exécuter pendant un certain temps a besoin d'un Wait à l'intérieur. Si un script gèle le jeu au démarrage, cherchez d'abord une boucle comme celle-ci.

Ce que coûte Wait(0)

Wait(0) signifie : pause jusqu'à la frame suivante (client) ou le tick suivant (serveur), puis continue. La boucle s'exécute à nouveau à chaque frame, donc à 60 fps, son corps est payé 60 fois par seconde.

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)

Une boucle comme celle-ci coûte peu. Dix d'entre elles, réparties sur trente resources, s'accumulent à plusieurs ms de temps client, ce qui est exactement ce que resmon montre comme ms élevé. Le coût n'est pas le Wait(0) lui-même, c'est tout ce que vous répétez à chaque frame.

Wait uniquement aussi court que vous en avez besoin

La plupart des vérifications n'ont pas besoin de s'exécuter à chaque frame. Demandez-vous à quoi sert la boucle :

Ce que fait la boucle Attente raisonnable
Dessiner un marqueur ou du texte, gérer une pression de touche 0 (seulement quand le joueur est proche)
Vérifier une distance pour décider si quelque chose est proche 250 à 1000
Interroger un état qui change rarement (job, véhicule) 1000 ou plus, ou utiliser un événement

L'astuce habituelle est deux vitesses : une boucle lente qui vérifie la distance, et une rapide uniquement quand le joueur est à l'intérieur de la zone.

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)

Loin de la zone, le thread se réveille une fois par seconde. Près d'elle, il dessine à chaque frame. Resmon tombe à presque rien pour la plupart des joueurs.

Conseil : avant de chercher une boucle plus rapide, demandez-vous si un événement ou un state bag peut vous dire quand la valeur change. Pas de boucle du tout est moins cher que n'importe quel Wait.

SetTimeout : exécuter quelque chose une fois, plus tard

Si vous n'avez besoin de faire quelque chose qu'une seule fois après un délai, vous n'avez pas besoin d'un thread avec un Wait :

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

SetTimeout(ms, callback) exécute le callback une fois après le délai et ne bloque rien. Il existe aussi sur le serveur. Pour une tâche répétitive, un thread avec Wait est le bon outil, puisque SetTimeout ne se répète pas d'lui-même.

Attendre à l'intérieur des gestionnaires d'événements

Un gestionnaire d'événements peut aussi appeler Wait, car FiveM l'exécute dans sa propre coroutine. Soyez prudent sur le serveur : après un Wait, le source global ne pointe plus vers le joueur qui a déclenché l'événement. Sauvegardez-le d'abord (voir événements client et serveur):

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

Arrêter un thread

Un thread se termine quand sa fonction retourne. Pour arrêter une boucle de l'extérieur, utilisez un drapeau que la boucle vérifie :

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)

Tous les threads d'une resource s'arrêtent d'eux-mêmes quand la resource s'arrête, donc vous n'avez besoin de cela que pour arrêter une boucle pendant que la resource continue de s'exécuter.

Liste de contrôle

Symptôme Correction
Le jeu gèle quand un script démarre Une boucle while true do n'a pas de Wait : en ajouter un
La resource montre des ms constantes dans resmon au repos Remplacer Wait(0) par une vérification de distance et une attente plus longue
Besoin d'exécuter quelque chose une seule fois après un délai SetTimeout(ms, fn) au lieu d'un thread
source est faux après un Wait sur le serveur Sauvegarder local src = source avant le Wait
Interroger une valeur qui change rarement Utiliser un événement ou un state bag au lieu d'une boucle
Citizen.Wait ou Wait ? Même fonction, choisissez un style

Réponses rapides

Que fait Wait(0) dans FiveM ?

Il pause le thread actuel jusqu'à la frame suivante sur le client, ou le tick serveur suivant. La boucle s'exécute ensuite à chaque frame, donc tout à l'intérieur coûte 60 fois ou plus par seconde.

Citizen.Wait est-il différent de Wait ?

Non. Wait et Citizen.Wait sont la même fonction, et il en va de même pour CreateThread et Citizen.CreateThread. Les noms courts ne sont que des alias.

Est-ce que Wait arrête le jeu entier ?

Non. Il ne pause que le thread qui l'appelle, et les autres threads et le jeu continuent de s'exécuter. Une boucle qui n'appelle jamais Wait est ce qui bloque tout.

Des scripts sans ce problème

Advanced BoostingDu boosting de véhicules piloté par tablette : contrats de classe D à S+, crews et file d’attente en direct.Voir le script →CCTV Security CamerasDes caméras à placer, une tablette multi-vues en direct et des photos imprimées comme preuves.Voir le script →Mic PhoneUn téléphone pliable qui se déplie en tablette et se prolonge jusqu’au vrai téléphone du joueur.Voir le script →

À lire aussi