Explorer
KNOW-ANT-FIVEM-006

FiveM - Iteration ESX.GetPlayers() sans cache : charge serveur inutile

Domaine
backend
Type
anti-pattern
Priorité
P3

Parent : [[INDEX-FIVEM-BACKEND]]

FiveM - Itération ESX.GetPlayers() sans cache : charge serveur inutile

Problème

Itération sur TOUS les joueurs pour compter les policiers à chaque callback, sans cache. Sur un serveur à 100+ joueurs, cette opération est exécutée des centaines de fois par minute.

-- rm_trainheist/server.lua (PROBLEMATIQUE)
ESX.RegisterServerCallback('trainheist:checkPoliceCount', function(source, cb)
    local policeCount = 0
    local players = ESX.GetPlayers()
    for i = 1, #players do
        local player = ESX.GetPlayerFromId(players[i])
        for k, v in pairs(Config.dispatchJobs) do
            if player.job.name == v then
                policeCount = policeCount + 1
            end
        end
    end
    cb(policeCount >= Config.requiredPoliceCount)
end)

Conséquences

  • O(n*m) à chaque callback : n joueurs × m jobs dispatch
  • Pas de cache : le compte est recalculé même si aucun joueur n'a changé de job depuis 10 secondes
  • Charge serveur : impact sur les tick rates, latence augmentée

Solution

Correct

-- Cache avec invalidation
local policeCountCache = 0
local lastCacheUpdate = 0
local CACHE_TTL = 5000 -- 5 secondes

function GetPoliceCount()
    local now = GetGameTimer()
    if now - lastCacheUpdate < CACHE_TTL then
        return policeCountCache
    end
    
    local count = 0
    for _, playerId in ipairs(ESX.GetPlayers()) do
        local xPlayer = ESX.GetPlayerFromId(playerId)
        if xPlayer and Config.dispatchJobs[xPlayer.job.name] then
            count = count + 1
        end
    end
    
    policeCountCache = count
    lastCacheUpdate = now
    return count
end

-- Invalidation sur changement de job
RegisterNetEvent('esx:setJob')
AddEventHandler('esx:setJob', function(job)
    lastCacheUpdate = 0 -- Force recalcul au prochain appel
end)

-- Usage dans le callback
ESX.RegisterServerCallback('trainheist:checkPoliceCount', function(source, cb)
    cb(GetPoliceCount() >= Config.requiredPoliceCount)
end)

Alternative : Event-driven

-- Mise à jour proactive plutôt que reactive
local onlinePolice = {}

AddEventHandler('esx:setJob', function(source, job, lastJob)
    if Config.dispatchJobs[job.name] then
        onlinePolice[source] = true
    else
        onlinePolice[source] = nil
    end
end)

AddEventHandler('playerDropped', function()
    onlinePolice[source] = nil
end)

function GetPoliceCount()
    local count = 0
    for _ in pairs(onlinePolice) do
        count = count + 1
    end
    return count
end

Incorrect

-- Double boucle dans le event handler (encore pire)
RegisterNetEvent('robbery:attempt')
AddEventHandler('robbery:attempt', function(storeId)
    local cops = 0
    for _, id in ipairs(ESX.GetPlayers()) do
        local p = ESX.GetPlayerFromId(id)
        for _, job in ipairs({'police', 'sheriff', 'fbi'}) do
            if p.job.name == job then cops = cops + 1 end
        end
    end
    if cops < 3 then return end
    -- ... même logique dans 10 events différents
end)

Détection

Rechercher :

ESX.GetPlayers\(\)\n.*for.*#.*players
for.*ESX.GetPlayers.*do.*job

Références

  • loffe_robbery/server.lua L38-45
  • rm_trainheist/server.lua L24-32