Explorer
KNOW-ANT-FIVEM-003

Global state abuse - Éviter les variables globales

Domaine
backend
Type
anti-pattern
Priorité
P2

Parent : [[INDEX-FIVEM-BACKEND]]

Anti-Pattern : Global State Abuse (FiveM)

Erreur à éviter

Utiliser des variables globales pour stocker l'état des joueurs/systemes, créant des conflits et des bugs difficiles à tracer.

Exemple incorrect

-- ❌ DANGER - Variables globales
-- global.lua (horrible)
isDead = false  -- Global modifié par tout le monde !
currentHealth = 200
playerInventory = {}
closestPlayer = nil

citizen.CreateThread(function()
    while true do
        -- Problème : tout le monde partage la même variable
        if isDead then
            -- Revival logic - mais isDead est global !
        end
        Wait(1000)
    end
end)

-- Dans un autre script
RegisterNetEvent('playerDied')
AddEventHandler('playerDied', function()
    isDead = true  -- Modifie pour tout le monde !
end)

Pourquoi c'est dangereux

  • Conflits : 2 joueurs modifient la même variable
  • Race conditions : Un thread écrase la valeur d'un autre
  • Debugging nightmare : Impossible de savoir qui a modifié quoi
  • Memory leaks : Variables jamais nettoyées

Bonne pratique

-- ✅ CORRECT - Tables locales par player
-- state.lua
local PlayerStates = {}

function GetPlayerState(playerId)
    if not PlayerStates[playerId] then
        PlayerStates[playerId] = {
            isDead = false,
            health = 200,
            inventory = {},
            lastPosition = nil,
            metadata = {}
        }
    end
    return PlayerStates[playerId]
end

function SetPlayerState(playerId, key, value)
    local state = GetPlayerState(playerId)
    state[key] = value
    -- Trigger event pour synchronisation si nécessaire
    TriggerEvent('wasabi_state:changed', playerId, key, value)
end

-- Utilisation
RegisterNetEvent('wasabi_ambulance:playerDied')
AddEventHandler('wasabi_ambulance:playerDied', function()
    local src = source
    SetPlayerState(src, 'isDead', true)
    SetPlayerState(src, 'deathTime', os.time())
end)

-- Cleanup quand le joueur quitte
AddEventHandler('playerDropped', function()
    local src = source
    PlayerStates[src] = nil  -- Nettoyage mémoire
end)

Pattern : State Manager

-- wasabi_statemanager.lua
local StateManager = {}
local states = {}

function StateManager:InitPlayer(playerId)
    states[playerId] = {
        medical = {
            isDead = false,
            injuries = {},
            bloodLevel = 100
        },
        position = {
            last = nil,
            current = nil
        },
        actions = {
            lastHeal = 0,
            lastRevive = 0
        }
    }
end

function StateManager:Get(playerId, category, key)
    if not states[playerId] then return nil end
    if not states[playerId][category] then return nil end
    return states[playerId][category][key]
end

function StateManager:Set(playerId, category, key, value)
    if not states[playerId] then
        self:InitPlayer(playerId)
    end
    if not states[playerId][category] then
        states[playerId][category] = {}
    end
    states[playerId][category][key] = value
end

function StateManager:Cleanup(playerId)
    states[playerId] = nil
end

-- Export pour usage externe
exports('GetStateManager', function() return StateManager end)

Migration depuis global

-- ❌ AVANT (global)
isDead = true

-- ✅ APRÈS (encapsulé)
local PlayerData = exports['wasabi_core']:GetPlayerData(source)
PlayerData.medical.isDead = true
exports['wasabi_core']:SavePlayerData(source, PlayerData)

Règles d'or

  1. Aucune variable globale pour l'état mutable
  2. Tables locales indexées par player ID
  3. Cleanup automatique sur playerDropped
  4. Exports/API pour accès contrôlé

Références

  • Pattern WASABI : Gestion d'état médical
  • Lua best practices : Local vs Global
  • ESX PlayerData pattern