CreateThread i Wait(0) w FiveM wyjaśnione: dlaczego twój skrypt kosztuje ms

Jak CreateThread, Wait i SetTimeout działają w Lua FiveM, dlaczego Wait(0) w każdej pętli pojawia się w resmon i dlaczego pętla while bez Wait zamraża grę.

Piszesz pętlę, aby coś sprawdzić, a gra przestaje odpowiadać, lub resmon pokazuje twój zasób przy kilku ms, gdy stoiszesz w miejscu:

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

Ten artykuł wyjaśnia, jak wątki i Wait działają w Lua FiveM, co Wait(0) naprawdę kosztuje i jak pisać pętle, które pozostają tanie.

Jak wątki działają w FiveM

Lua w FiveM jest kooperatywna. Twój skrypt nie działa równolegle z grą: uruchamia się, gdy runtime go wywołuje, i musi oddać kontrolę. Dwie rzeczy to umożliwiają:

  • Handlery zdarzeń i callbacki uruchamiają się raz, od początku do końca, potem wracają.
  • Wątki utworzone za pomocą CreateThread to małe korutyny, które scheduler wznawia znowu i znowu. Wątek oddaje kontrolę, gdy wywołuje Wait.
lua
CreateThread(function()
    while true do
        Wait(1000)
        print('one second later')
    end
end)

CreateThread i Citizen.CreateThread to ta sama funkcja, i podobnie Wait i Citizen.Wait. Używaj któregokolwiek chcesz, ale bądź konsekwentny w jednym skrypcie.

Pętla while bez Wait zamraża grę

Jeśli wątek nigdy nie wywoła Wait, scheduler nigdy nie odzyskuje kontroli. Na kliencie gra przestaje rysować ramki, aż pętla się skończy, a na serwerze każdy inny skrypt czeka na twój.

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

To samo dzieje się z repeat … until lub długą pętlą for, która wykonuje ciężkie prace. Każda pętla, która może działać przez chwilę, potrzebuje Wait wewnątrz. Jeśli skrypt zamraża grę na starcie, najpierw poszukaj pętli jak ta.

Co kosztuje Wait(0)

Wait(0) oznacza: wstrzymaj się do następnej ramki (klient) lub następnego ticka (serwer), potem kontynuuj. Pętla uruchamia się znowu na każdej ramce, więc przy 60 fps jej główna część jest opłacana 60 razy na sekundę.

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)

Jedna pętla jak ta kosztuje mało. Dziesięć z nich, między trzydziestoma zasobami, sumuje się do kilku ms czasu klienta, co dokładnie to jest, co resmon pokazuje jako wysokie ms. Koszt to nie sam Wait(0), to wszystko, co powtarzasz każdą ramkę.

Wait tylko tak krótko, jak musisz

Większość testów nie musi uruchamiać się każdą ramkę. Zapytaj, do czego służy pętla:

Co robi pętla Rozsądne czekanie
Rysuj marker lub tekst, obsługuj naciśnięcie klawisza 0 (tylko gdy gracz jest blisko)
Sprawdź odległość, aby zdecydować, czy coś jest blisko 250 do 1000
Sonduj stan, który rzadko się zmienia (praca, pojazd) 1000 lub więcej, lub użyj zdarzenia

Zwykła sztuczka to dwie prędkości: powolna pętla, która sprawdza odległość, i szybka tylko podczas gdy gracz jest w obszarze.

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)

Daleko od strefy wątek budzi się raz na sekundę. Blisko niej rysuje każdą ramkę. Resmon spada na prawie nic dla większości graczy.

Porada: zanim sięgniesz po szybszą pętlę, zapytaj, czy zdarzenie lub torebka stanu może ci powiedzieć, kiedy wartość się zmienia. Brak pętli w ogóle jest tańszy niż jakikolwiek Wait.

SetTimeout: uruchom coś raz, później

Jeśli musisz coś zrobić tylko raz po opóźnieniu, nie potrzebujesz wątku z Wait:

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

SetTimeout(ms, callback) uruchamia callback raz po opóźnieniu i nic nie blokuje. Istnieje również na serwerze. Dla powtarzającego się zadania wątek z Wait to właściwe narzędzie, ponieważ SetTimeout nie powtarza się automatycznie.

Czekanie wewnątrz handlerów zdarzeń

Handler zdarzenia może również wywoływać Wait, ponieważ FiveM uruchamia go w swojej własnej korutynie. Bądź ostrożny na serwerze: po Wait, globalny source już nie wskazuje na gracza, który wyzwolił zdarzenie. Zapisz to najpierw (przeczytaj zdarzenia klient i serwer):

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

Zatrzymanie wątku

Wątek kończy się, gdy jego funkcja wraca. Aby zatrzymać pętlę z zewnątrz, użyj flagi, którą pętla sprawdza:

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)

Wszystkie wątki zasobu zatrzymują się automatycznie, gdy zasób się zatrzyma, więc musisz to tylko, aby zatrzymać pętlę podczas gdy zasób nadal działa.

Lista kontrolna

Objaw Naprawa
Gra zamraża się, gdy skrypt się uruchamia Pętla while true do nie ma Wait: dodaj jedną
Zasób pokazuje stałą ms w resmon w bezczynności Zastąp Wait(0) sprawdzeniem odległości i dłuższym czekaniem
Potrzebuję uruchomić coś raz po opóźnieniu SetTimeout(ms, fn) zamiast wątku
source jest zły po Wait na serwerze Zapisz local src = source przed Wait
Sondowanie wartości, która rzadko się zmienia Zamiast pętli użyj zdarzenia lub torebki stanu
Citizen.Wait czy Wait? Ta sama funkcja, wybierz jeden styl

Szybkie odpowiedzi

Co robi Wait(0) w FiveM?

Wstrzymuje bieżący wątek do następnej ramki na kliencie lub następnego ticka na serwerze. Pętla następnie uruchamia się ponownie na każdej ramce, więc wszystko w środku jest opłacane 60 lub więcej razy na sekundę.

Czy Citizen.Wait różni się od Wait?

Nie. Wait i Citizen.Wait to ta sama funkcja, i podobnie CreateThread i Citizen.CreateThread. Krótkie nazwy to tylko aliasy.

Czy Wait zatrzymuje całą grę?

Nie. Wstrzymuje tylko wątek, który go wywołuje, a inne wątki i gra działają dalej. Pętla, która nigdy nie wywołuje Wait, to to, co blokuje wszystko.

Skrypty bez tego problemu

Advanced BoostingBoosting pojazdów z tabletu: kontrakty od klasy D do S+, ekipy i kolejka na żywo.Zobacz skrypt →CCTV Security CamerasKamery do rozstawienia, tablet z podglądem wielu kamer na żywo i drukowane zdjęcia jako dowody.Zobacz skrypt →Mic PhoneSkładany telefon, który rozkłada się w tablet i działa też na prawdziwym telefonie gracza.Zobacz skrypt →

Czytaj dalej