🚀 07 — Ottimizzazione e Debug
INFO
L’ottimizzazione e il debug sono competenze essenziali per sviluppare risorse FiveM performanti e stabili. La console F8, resmon e il profiler sono gli strumenti principali per diagnosticare colli di bottiglia e migliorare il codice.
| Obiettivo | Dettagli |
|---|---|
| Cosa imparerai | - Usare resmon e profiler per diagnosticare problemi di performance - Applicare pattern di ottimizzazione (Wait condizionale, cache locale, DistanceSquared) - Gestire errori con pcall e creare debug output efficaci |
| Prerequisiti | Tutti i file precedenti (02-06) |
Console F8
La console di FiveM (F8 in gioco) è il tuo primo strumento di debug.
flowchart TD A[F8 in gioco] --> B{Comandi utili} B --> C[_devTools - Apre Chrome DevTools per NUI] B --> D[resmon - Mostra uso risorse in tempo reale] B --> E[profiler - Profiling avanzato] B --> F[net_graph - Mostra statistiche rete] B --> G[dbg_... - Vari comandi di debug] B --> H[reload <risorsa> - Ricarica risorsa]
Comandi Console Essenziali
| Comando | Descrizione |
|---|---|
resmon | Mostra il consumo CPU di ogni risorsa |
profiler | Profiling dettagliato di chiamate native |
net_graph 1 | Mostra ping, pacchetti, tick rate |
_devTools | Apre DevTools per debugging NUI |
reload mia_risorsa | Ricarica una risorsa senza riavviare il gioco |
refresh | Ricarica tutte le risorse |
start/stop mia_risorsa | Avvia/ferma una risorsa |
Identificare Colli di Bottiglia
Resmon (Resource Monitor)
F8 → resmon
Resmon mostra:
- CPU ms — quanto tempo CPU spende ogni risorsa per frame
- Chiamate native — quante native chiama al secondo
- Memoria — uso memoria Lua
Target: ogni risorsa sotto 1-2ms per frame. Risorse con 5ms+ sono problematiche.
Pattern che Uccidono le Performance
WARNING
Loop senza
Waitsono la causa numero 1 di lag e crash in FiveM. Un thread bloccante consuma il 100% di un core CPU e blocca l’intero runtime. AncheWait(0)è fondamentale — cede lo scheduler al frame successivo.
-- DA EVITARE: loop senza Wait
Citizen.CreateThread(function()
while true do
-- Nessuna pausa → 100% CPU
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
-- ...
end
end)
-- CORRETTO: Wait appropriato
Citizen.CreateThread(function()
while true do
Citizen.Wait(0) -- minimo: cede 1 frame
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
-- ...
end
end)-- DA EVITARE: polling continuo quando non serve
Citizen.CreateThread(function()
while true do
Citizen.Wait(0)
local ped = PlayerPedId()
local veicolo = GetVehiclePedIsIn(ped, false)
if veicolo ~= 0 then
local speed = GetEntitySpeed(veicolo)
-- aggiorna HUD velocità ogni frame
end
end
end)
-- CORRETTO: cambia Wait in base allo stato
Citizen.CreateThread(function()
while true do
local ped = PlayerPedId()
local veicolo = GetVehiclePedIsIn(ped, false)
if veicolo ~= 0 then
local speed = GetEntitySpeed(veicolo)
-- aggiorna HUD
Citizen.Wait(100) -- 10 fps per HUD veicolo
else
Citizen.Wait(1000) -- controlla solo ogni 1s se non in veicolo
end
end
end)Pattern di Thread: Wait(0) vs Wait(N)
TIP
Strategia a livelli di Wait: usa
Wait(0)solo per input e UI (ogni frame),Wait(100)per HUD e controlli (10 fps),Wait(1000+)per check sporadici. Meno chiamate = migliori performance.
| Wait | Quando usare | Costo CPU |
|---|---|---|
Wait(0) | Ogni frame (60fps) | Alto — solo per UI o input |
Wait(50) | 20 fps | Medio — aggiornamenti frequenti |
Wait(100) | 10 fps | Basso — HUD, controlli |
Wait(1000) | 1 fps | Minimo — check sporadici |
Wait(5000) | Ogni 5s | Quasi nullo — pulizie, sync |
-- Strategia a livelli
Citizen.CreateThread(function()
while true do
-- Ogni frame: solo input
if IsControlJustPressed(0, 38) then
-- azione
end
Citizen.Wait(0)
end
end)
Citizen.CreateThread(function()
while true do
-- Ogni 200ms: check posizione
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
-- ...
Citizen.Wait(200)
end
end)
Citizen.CreateThread(function()
while true do
-- Ogni 5s: sync dati
TriggerServerEvent("sync:posizione")
Citizen.Wait(5000)
end
end)Cache Locale vs Polling Frequente
-- DA EVITARE: chiamare native costose ripetutamente
Citizen.CreateThread(function()
while true do
Citizen.Wait(100)
local ped = PlayerPedId()
local x, y, z = table.unpack(GetEntityCoords(ped))
-- Calcola distanza da più punti ogni volta
local dist1 = #(vector3(x, y, z) - punto1)
local dist2 = #(vector3(x, y, z) - punto2)
local dist3 = #(vector3(x, y, z) - punto3)
if dist1 < 10 then
-- ...
end
end
end)
-- OTTIMIZZATO: usa variabili locali, calcola solo serve
local puntiInteresse = {
{ x = 100, y = 200, z = 30, raggio = 10, label = "Punto A" },
{ x = 500, y = 600, z = 40, raggio = 10, label = "Punto B" },
{ x = -300, y = -400, z = 50, raggio = 10, label = "Punto C" },
}
Citizen.CreateThread(function()
while true do
Citizen.Wait(500) -- check meno frequente
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local px, py, pz = coords.x, coords.y, coords.z
for _, punto in ipairs(puntiInteresse) do
local dx, dy, dz = px - punto.x, py - punto.y, pz - punto.z
local dist = math.sqrt(dx*dx + dy*dy + dz*dz)
if dist < punto.raggio then
print("Vicino a:", punto.label)
end
end
end
end)Distance Check
-- Utility distanza veloce (senza sqrt se non serve)
local function DistanceSquared(x1, y1, z1, x2, y2, z2)
local dx = x2 - x1
local dy = y2 - y1
local dz = z2 - z1
return dx*dx + dy*dy + dz*dz
end
local function IsVicino(coords, target, raggio)
return DistanceSquared(coords.x, coords.y, coords.z,
target.x, target.y, target.z) < raggio * raggio
end
-- Uso: DistanceSquared evita il sqrt() costoso se non serve
Citizen.CreateThread(function()
local centro = vector3(0, 0, 0)
local raggio = 50
while true do
Citizen.Wait(1000)
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
if IsVicino(coords, centro, raggio) then
print("Sei nel raggio di 50m dal centro")
end
end
end)Evitare String Manipulation Pesante
-- DA EVITARE: concatenazione in loop
local function GeneraTestoLungo(items)
local risultato = ""
for _, item in ipairs(items) do
risultato = risultato .. item.nome .. " x" .. item.quantita .. "\n"
end
return risultato
end
-- MEGLIO: table.concat
local function GeneraTestoLungo(items)
local parti = {}
for _, item in ipairs(items) do
parti[#parti + 1] = item.nome .. " x" .. item.quantita
end
return table.concat(parti, "\n")
end
-- DA EVITARE: pattern regex complessi in loop
for _, linea in ipairs(molteLinee) do
local match = string.match(linea, "complesso%d+(%a+)%d+")
end
-- MEGLIO: operazioni più semplici o pre-compilate
local pattern = string.find -- pattern sempliceDebug con Print
-- client.lua — print di base
print("Questo appare nella console F8 del client")
-- Con colori (codici ^0-^9)
print("^2Verde") -- colore 2 = verde
print("^1Rosso") -- colore 1 = rosso
print("^3Giallo") -- colore 3 = giallo
print("^4Blu") -- colore 4 = blu
print("^5Ciano") -- colore 5 = ciano
print("^6Viola") -- colore 6 = viola
print("^7Bianco") -- colore 7 = bianco (default)
print("^8Arancione") -- colore 8 = arancione
print("^9Grigio") -- colore 9 = grigio-- server.lua — print sul server console
print("^2[INFO] ^7Risorsa avviata")
print("^1[ERRORE] ^7Qualcosa è andato storto")
print("^8[WARN] ^7Attenzione a questa cosa")
-- Debug con source
AddEventHandler("playerJoining", function()
print(string.format(
"^5[DEBUG] ^7Giocatore ^3%s^7 (#^6%d^7) sta entrando",
GetPlayerName(source), source
))
end)Print di tabelle
-- Lua non ha un print per tabelle built-in
local function PrintTable(t, indent)
indent = indent or 0
local prefix = string.rep(" ", indent)
for k, v in pairs(t) do
if type(v) == "table" then
print(prefix .. tostring(k) .. ":")
PrintTable(v, indent + 1)
else
print(prefix .. tostring(k) .. ": " .. tostring(v))
end
end
end
-- Uso
local dati = { nome = "Marco", denaro = 5000, pos = { x = 100, y = 200, z = 30 } }
PrintTable(dati)
-- In FiveM c'è anche json.encode per debug veloce
print(json.encode(dati)) -- converte in JSONDebug Output e Log su File
Server log
FiveM scrive automaticamente i print() del server in logs/server_log.txt.
Log custom
-- server.lua — log su file
local function LogToFile(messaggio, livello)
livello = livello or "INFO"
local timestamp = os.date("%Y-%m-%d %H:%M:%S")
local riga = string.format("[%s] [%s] %s", timestamp, livello, messaggio)
print(riga)
-- Se vuoi scrivere su file specifico (usa con cautela!)
-- local file = io.open("resources/mia_risorsa/log.txt", "a")
-- if file then
-- file:write(riga .. "\n")
-- file:close()
-- end
endDebug toggle
TIP
Un flag
Config.Debugti permette di attivare o disattivare tutti i debug print con una singola modifica. Ricorda di impostarlo afalseprima del deploy in produzione.
-- config.lua
Config.Debug = false -- true per debug mode
-- client.lua
local function DebugPrint(...)
if Config.Debug then
local args = { ... }
for i, v in ipairs(args) do
args[i] = tostring(v)
end
print("^5[DEBUG] ^7" .. table.concat(args, " "))
end
end
-- Uso
DebugPrint("Posizione giocatore:", GetEntityCoords(PlayerPedId()))
-- Non stampa niente se Config.Debug = falseUso di pcall per Error Handling Robusto
-- Proteggi blocchi critici con pcall
Citizen.CreateThread(function()
while true do
Citizen.Wait(0)
local successo, errore = pcall(function()
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local veicolo = GetVehiclePedIsIn(ped, false)
if veicolo ~= 0 then
local modello = GetEntityModel(veicolo)
local carburante = GetVehicleFuelLevel(veicolo)
local portiere = GetVehicleDoorLockStatus(veicolo)
-- Calcoli vari...
end
end)
if not successo then
print("^1[ERRORE CRITICO] ^7" .. tostring(errore))
-- Non far crashare il thread
end
end
end)Error Handler Centralizzato
-- error_handler/client.lua
local ErrorCount = {}
local ErrorCooldown = {}
local function SafeCall(fn, ...)
local args = { ... }
local successo, errore = pcall(fn, table.unpack(args))
if not successo then
local trace = debug.traceback()
local chiave = tostring(errore)
-- Cooldown per evitare spam di errori identici
local now = GetGameTimer()
if ErrorCooldown[chiave] and now - ErrorCooldown[chiave] < 5000 then
return false
end
ErrorCooldown[chiave] = now
ErrorCount[chiave] = (ErrorCount[chiave] or 0) + 1
print(string.format(
"^1[ERRORE #%d] ^7%s\n^5%s",
ErrorCount[chiave], tostring(errore), trace
))
end
return successo
end
-- Uso
SafeCall(function()
-- codice che potrebbe fallire
error("test errore")
end)pcall con chiamate native rischiose
-- Alcune native possono crashare se chiamate con parametri invalidi
local function SafeGetEntityCoords(entity)
local successo, coords = pcall(GetEntityCoords, entity)
if successo and coords then
return coords
end
return vector3(0, 0, 0)
end
local function SafeDoesEntityExist(entity)
local successo, esiste = pcall(DoesEntityExist, entity)
return successo and esiste or false
endListe di Controllo Eventi
Resmon
F8 → resmon 1 mostra dettaglio per risorsa
F8 → resmon 0 nasconde
Cosa cercare in resmon:
- CPU ms alti (3+) → risorsa da ottimizzare
- Native calls alti → troppe chiamate, batch o riduci
- Memory crescente → memory leak (tabelle globali che crescono)
Profiler
F8 → profiler
F8 → profiler record avvia registrazione
... gioca per 30 secondi ...
F8 → profiler stop ferma e mostra report
Il profiler mostra:
- Numero di chiamate per ogni native
- Tempo speso in ogni native
- Eventuali native chiamate inutilmente
Checklist di Ottimizzazione
flowchart TD A[Ho un problema di performance?] --> B[Usa resmon] B --> C[Quale risorsa consuma più CPU?] C --> D{Wait è ottimale?} D -->|No| E[Aumenta Wait\nUsa Wait condizionale] D -->|Sì| F{Chiamate native eccessive?} F -->|Sì| G[Raggruppa native\nUsa cache locale] F -->|No| H{Manipolazione stringhe?} H -->|Sì| I[Usa table.concat\nevita pattern in loop] H -->|No| J{Memory leak?} J -->|Sì| K[Cancella tabelle globali\nRimuovi event listener inutilizzati] J -->|No| L[Usa profiler per approfondire]
Lista di Controllo Rapida
- Ogni thread ha un
Wait()appropriato? - Usi
Wait(0)solo quando necessario (input, UI)? - I loop cambiano
Waitin base allo stato? - Eviti chiamate native inutili?
- Usi cache locale per dati che non cambiano spesso?
-
table.concatinvece di..per stringhe lunghe? -
pcallattorno a codice critico? - Controlli
DoesEntityExistprima di usare entità ? - Usi
ModelRequest+HasModelLoadedprima di creare entità ? - Hai disattivato debug in produzione (
Config.Debug = false)? -
resmonmostra tutto sotto 1-2ms per risorsa? - Eventi registrati hanno
AddEventHandlerdopoRegisterNetEvent? - Rimuovi event listener temporanei con
RemoveEventHandler? - Non fai query SQL in loop stretti?
-
DistanceSquaredinvece di#(vector - vector)per confronti veloci?
Strumenti Aggiuntivi
Strumento di terze parti
| Strumento | Descrizione |
|---|---|
| cfx-riim | Tabella Cheat Engine per debug FiveM |
| FiveM Server Browser | Vedi CPU/RAM del server |
Chrome DevTools (F8 → _devTools) | Debug NUI, rete, console |
Aliases utili per la console F8
Puoi creare alias nei tuoi script per debug rapido:
-- client.lua — comandi debug
RegisterCommand("debugme", function()
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local veicolo = GetVehiclePedIsIn(ped, false)
print("=== DEBUG INFO ===")
print("Ped:", ped)
print("Pos:", string.format("%.2f, %.2f, %.2f", coords.x, coords.y, coords.z))
print("In veicolo:", veicolo ~= 0)
print("Salute:", GetEntityHealth(ped))
print("Armatura:", GetPedArmour(ped))
print("==================")
end, false)
RegisterCommand("nui_debug", function()
-- Forza apertura DevTools NUI
ExecuteCommand("_devTools")
end, false)QUESTION
Riflessione: Hai eseguito
resmonsulla tua risorsa? Ogni risorsa dovrebbe stare sotto 1-2ms per frame. Se vedi picchi, controlla i loop conWaitinsufficiente o chiamate native eccessive.
Link Utili
- Risoluzione Problemi — problemi comuni e soluzioni
- State Bags e Routing Buckets — sincronizzazione e instancing
- FiveM Docs — Scripting Best Practices
- FiveM Natives Reference
Torna alla: Indice Generale