Script Performance on FiveM: resmon, Threads and Wait() Explained

When players complain about stutter or low frame rates, scripts are one of the first things to check. A single resource running heavy code every frame can cost every player on the server performance. The good news is that most problems come from a few patterns that are easy to spot once you know what to look for.

Reading resmon

The resource monitor is built into the client. Open the F8 console in game and run resmon 1 (or resmon true) to show it, and run it again with 0 to hide it. It lists each running resource with how much CPU time it uses per frame, in milliseconds, along with its memory use.

Threads and Wait()

Lua scripts in FiveM run loops inside threads, created with CreateThread (also written Citizen.CreateThread). Inside a loop, Wait(ms) pauses that thread and lets the game carry on. Wait(0) means "run again next frame", so a while true do loop with Wait(0) runs its body on every single frame. That is sometimes needed, for example to draw a marker or text, which has to be drawn every frame to stay visible. The mistake is running that work every frame when nobody is nearby.

The sleep pattern

The usual fix is to let the loop sleep longer when the player is far away and only speed up when they get close:

CreateThread(function()
    while true do
        local sleep = 1000
        local coords = GetEntityCoords(PlayerPedId())
        if #(coords - Config.ShopLocation) < 10.0 then
            sleep = 0
            -- draw marker / show prompt here
        end
        Wait(sleep)
    end
end)

Far from the shop, this loop runs about once a second instead of every frame. Close to it, it runs every frame only for as long as that is actually needed. Note the use of #(a - b) for distance, which works directly on vectors.

Other client-side habits

Server-side habits

The server has no frame rate to protect, but a busy server thread can still cause lag for everyone. Loops on the server rarely need to run more often than once a second or so. Avoid querying the database inside a loop for every player; batch the work or save on a timer instead. Store values that many scripts need, such as a player's job, in memory or state bags rather than looking them up repeatedly.

Checking a script before you install it

If you can read the code, search it for Wait(0) and look at what each of those loops does. Every-frame loops that only draw near a location are fine; every-frame loops that run all the time, everywhere, are a warning sign. This is one of the practical reasons to pick open source code, whether you are browsing QBCore resources or ESX scripts. Test new resources on a local server with resmon open before they reach your players.