🧵 02 — Eventi e Thread
INFO
FiveM utilizza thread virtuali cooperativi tramite il runtime Citizen e un sistema di eventi per la comunicazione tra client, server e altre risorse. Gli eventi sono il pilastro della comunicazione in qualsiasi server FiveM.
| Obiettivo | Dettagli |
|---|---|
| Cosa imparerai | - Creare e gestire thread con Citizen.CreateThread e Citizen.Wait- Comunicare con eventi locali e di rete ( TriggerEvent, TriggerServerEvent, TriggerClientEvent)- Pattern di loop condizionali e best practices per performance |
| Prerequisiti | Conoscenze base di Lua, familiarità con l’ecosistema FiveM |
Thread in FiveM
FiveM non è Lua puro: gira su Citizen, il runtime di Cfx.re. Citizen fornisce threading cooperativo tramite Citizen.CreateThread e Citizen.Wait.
flowchart LR A[Citizen.CreateThread] --> B[Avvia thread virtuale] B --> C{Esecuzione} C -->|Citizen.Wait(N)| D[Sospeso per N ms] D --> C C -->|Fine funzione| E[Thread termina]
Citizen.CreateThread
Crea un thread virtuale. Più thread condividono lo stesso thread OS — sono coroutine gestite dal runtime.
-- client.lua
Citizen.CreateThread(function()
while true do
-- Questo loop gira ogni secondo
print("Thread attivo:", GetGameTimer())
Citizen.Wait(1000)
end
end)Citizen.Wait
Sospende il thread corrente per N millisecondi. Fondamentale: senza Wait, un loop blocca tutto il client.
WARNING
Loop senza Wait bloccano il client. Un loop infinito senza
Citizen.Waitfa crashare FiveM o causa timeout. Aggiungi sempre una pausa, anche minima comeWait(0).
-- Come NON fare — loop bloccante
Citizen.CreateThread(function()
while true do
-- SENZA Wait: FiveM si blocca, crash o timeout
end
end)
-- Come fare — loop con pausa
Citizen.CreateThread(function()
while true do
-- Questo gira ogni 100ms (bilanciato tra performance e reattivitĂ )
Citizen.Wait(100)
end
end)Pattern: loop condizionale
TIP
Usa un loop condizionale per bilanciare performance e reattivitĂ : aumenta il
Waitquando il codice non serve e riducilo quando è attivo. Questo riduce il carico CPU senza sacrificare la reattività .
-- client.lua — loop solo quando serve
local mostraHUD = false
Citizen.CreateThread(function()
while true do
if mostraHUD then
-- Disegna HUD solo se attivo
local ped = PlayerPedId()
local salute = GetEntityHealth(ped)
local pos = GetEntityCoords(ped)
print(string.format("HP: %d | Pos: %.1f, %.1f, %.1f",
salute, pos.x, pos.y, pos.z))
Citizen.Wait(500)
else
-- Se non serve, aspetta piĂą a lungo e risparmia CPU
Citizen.Wait(1000)
end
end
end)Pattern: timer singolo (SetTimeout)
-- Esegue codice UNA VOLTA dopo N ms
Citizen.SetTimeout(5000, function()
print("Sono passati 5 secondi")
end)
-- Alternativa con CreateThread
Citizen.CreateThread(function()
Citizen.Wait(5000)
print("Sono passati 5 secondi")
end)Eventi
Gli eventi sono il sistema di messaggistica di FiveM. Permettono la comunicazione client ↔ client, client ↔ server, e server ↔ server.
flowchart TD subgraph Client C1[TriggerEvent\nlocale] C2[TriggerServerEvent\n→ server] C3[TriggerLatentServerEvent\n→ server con buffer] end subgraph Server S1[TriggerEvent\nlocale] S2[TriggerClientEvent\n→ client specifico] S3[TriggerLatentClientEvent\n→ client con buffer] end C2 --> S{Server} S2 --> C{Client}
RegisterNetEvent
Registra un evento perché possa essere ricevuto. Sia client che server lo usano.
-- client.lua
RegisterNetEvent("myresource:riceviMessaggio")
AddEventHandler("myresource:riceviMessaggio", function(mittente, testo)
print("Messaggio da", mittente, ":", testo)
SetNotificationTextEntry("STRING")
AddTextComponentSubstringPlayerName(testo)
DrawNotification(false, false)
end)AddEventHandler
Collega una funzione a un evento. Alternativa moderna:
RegisterNetEvent("myresource:apriMenu", function()
-- Usando RegisterNetEvent + funzione anonima direttamente
-- (equivale a AddEventHandler)
end)
-- Con AddEventHandler esplicito
RegisterNetEvent("myresource:apriMenu")
AddEventHandler("myresource:apriMenu", function()
print("Menu aperto")
end)TriggerEvent (locale)
Innesca un evento solo sulla macchina corrente (client o server).
-- client.lua — entrambi gli eventi sono sullo stesso client
RegisterNetEvent("mioEvento:locale")
AddEventHandler("mioEvento:locale", function(msg)
print("Ricevuto localmente:", msg)
end)
TriggerEvent("mioEvento:locale", "Ciao dal client stesso")TriggerServerEvent
Manda un evento al server dal client.
-- client.lua
RegisterCommand("compra", function(source, args)
local item = args[1]
local quantita = tonumber(args[2]) or 1
-- Manda richiesta al server
TriggerServerEvent("negozio:acquista", item, quantita)
end, false)-- server.lua
RegisterNetEvent("negozio:acquista")
AddEventHandler("negozio:acquista", function(item, quantita)
local source = source -- IMPORTANTE: source è disponibile solo dentro l'handler
print("Giocatore", source, "vuole", quantita, "x", item)
-- Verifica e processa
-- ...
end)TriggerClientEvent
Manda un evento dal server a uno o piĂą client.
-- server.lua
RegisterCommand("annuncio", function(source, args)
local messaggio = table.concat(args, " ")
-- A tutti i giocatori
TriggerClientEvent("chat:addMessage", -1, {
color = { 255, 255, 0 },
args = { "ANNUNCIO", messaggio }
})
end, true) -- true = solo admin-- server.lua — target specifico
TriggerClientEvent("giocatore:teletrasporta", targetSource, x, y, z)TriggerLatentServerEvent / TriggerLatentClientEvent
INFO
Latent events dividono i dati in pacchetti piĂą piccoli per evitare disconnessioni su payload grandi (es. immagini, tabelle di grandi dimensioni). Usali quando un singolo evento supera qualche KB.
Per dati grandi (es. immagini, tabelle enormi). Usano buffer a pacchetti.
-- client.lua
TriggerLatentServerEvent("upload:immagine", 128000, datiImmagine)Eventi nativi di FiveM
| Evento Client | Descrizione |
|---|---|
onClientResourceStart | Risorsa client avviata |
onClientResourceStop | Risorsa client fermata |
playerSpawned | Giocatore spawnato (dopo selezione personaggio) |
| Evento Server | Descrizione |
|---|---|
playerJoining | Giocatore in fase di connessione |
playerDropped | Giocatore disconnesso |
onResourceStart | Risorsa server avviata |
onResourceStop | Risorsa server fermata |
rconCommand | Comando RCON inviato |
Esempio: playerJoining
-- server.lua
AddEventHandler("playerJoining", function(playerSource, oldIds, deferredJoin)
-- deferredJoin: permette di ritardare l'ingresso (es. per caricare dati)
print("Giocatore", playerSource, "sta entrando...")
-- Caricare dati dal database
-- deferredJoin.defer()
-- deferredJoin.done({ ... })
end)Esempio: playerDropped
-- server.lua
AddEventHandler("playerDropped", function(reason)
local source = source
print("Giocatore", source, "ha lasciato. Motivo:", reason)
-- Salvare dati nel database
local ped = GetPlayerPed(source)
local pos = GetEntityCoords(ped)
exports["oxmysql"]:execute(
"UPDATE giocatori SET ultima_posizione_x = ?, ultima_posizione_y = ?, ultima_posizione_z = ? WHERE identifier = ?",
{ pos.x, pos.y, pos.z, GetPlayerIdentifier(source) }
)
end)Esempio: onResourceStart
-- server.lua
AddEventHandler("onResourceStart", function(resourceName)
if resourceName == GetCurrentResourceName() then
print("Risorsa", resourceName, "avviata correttamente")
end
end)
-- client.lua
AddEventHandler("onClientResourceStart", function(resourceName)
if resourceName == GetCurrentResourceName() then
print("Risorsa client avviata")
end
end)Esempi completi
Sistema di notifica via evento
-- notify/client.lua
RegisterNetEvent("notify:send")
AddEventHandler("notify:send", function(tipo, messaggio, durata)
durata = durata or 3000
if tipo == "success" then
SetNotificationTextEntry("STRING")
AddTextComponentSubstringPlayerName("âś… " .. messaggio)
DrawNotification(true, false)
elseif tipo == "error" then
SetNotificationTextEntry("STRING")
AddTextComponentSubstringPlayerName("❌ " .. messaggio)
DrawNotification(true, false)
elseif tipo == "info" then
SetNotificationTextEntry("STRING")
AddTextComponentSubstringPlayerName("ℹ️ " .. messaggio)
DrawNotification(false, false)
end
-- Alternativa con ESX (se disponibile)
-- ESX.ShowNotification(messaggio, tipo)
end)-- notify/server.lua
RegisterCommand("notifica", function(source, args)
local messaggio = table.concat(args, " ")
TriggerClientEvent("notify:send", source, "info", messaggio)
end, false)Timer countdown con thread
-- client.lua
local countdownAttivo = false
RegisterNetEvent("countdown:avvia")
AddEventHandler("countdown:avvia", function(secondi)
if countdownAttivo then return end
countdownAttivo = true
Citizen.CreateThread(function()
local rimasti = secondi
while rimasti > 0 do
print("Tempo rimasto:", rimasti, "secondi")
TriggerEvent("chat:addMessage", {
args = { "COUNTDOWN", tostring(rimasti) .. " secondi rimasti" }
})
Citizen.Wait(1000)
rimasti = rimasti - 1
end
print("Countdown finito!")
TriggerServerEvent("countdown:finito", source)
countdownAttivo = false
end)
end)Evento con callback (promise pattern)
-- client.lua
local function richiediDatiGiocatore()
local promise = Citizen.Promise.new()
-- Crea un evento temporaneo per la risposta
local eventName = "dati:risposta_" .. math.random(100000, 999999)
RegisterNetEvent(eventName)
AddEventHandler(eventName, function(dati)
promise:resolve(dati)
RemoveEventHandler(eventName)
end)
TriggerServerEvent("dati:richiedi", eventName)
return promise
end
-- Uso con await
Citizen.CreateThread(function()
local dati = Citizen.Await(richiediDatiGiocatore())
print("Dati ricevuti:", json.encode(dati))
end)-- server.lua
RegisterNetEvent("dati:richiedi")
AddEventHandler("dati:richiedi", function(eventRisposta)
local source = source
-- Prepara dati
local dati = {
nome = "Marco",
denaro = 500,
livello = 3
}
TriggerClientEvent(eventRisposta, source, dati)
end)Cancellazione eventi
-- Rimuovere un handler specifico
local handler = AddEventHandler("evento:temporaneo", function()
print("Questo gira solo una volta")
RemoveEventHandler(handler)
end)
-- Rimuovere tutti gli handler di un evento
-- Non esiste una funzione built-in; bisogna tenerne tracciaQUESTION
Riflessione: Quanti thread stai creando nella tua risorsa? Ricorda che ogni thread, anche in idle, consuma risorse dello scheduler. Potresti consolidarne alcuni?
Best Practices
| Regola | Perché |
|---|---|
| Sempre RegisterNetEvent prima di AddEventHandler | Senza, l’evento non viene registrato per la ricezione |
| Non abusare di TriggerServerEvent | Ogni evento server è una richiesta di rete — limita a dati necessari |
| Source è disponibile solo dentro l’handler | Non salvare source in variabili globali — potrebbe cambiare |
| Usa eventi specifici | player:dati meglio di player — evita collisioni |
| Prefissa i tuoi eventi | miaRisorsa:azione — evita conflitti con altre risorse |
| Wait sempre nei loop | Senza, client si blocca o crasha |
| Wait(0) vs Wait(N) | Wait(0) cede allo scheduler (1 frame); Wait(N) sospende per N ms |
TIP
Prossimo passo: 03 - Native Functions
Torna alla: Indice Generale