games / fivem
Callbacks client-serveur : le pattern promise-like
Explication
Ce que vous allez apprendre
- Comprendre la limite "fire and forget" des events classiques
- Construire un système de callback manuel avec un identifiant de requête unique
- Nettoyer systématiquement un callback résolu pour éviter une fuite mémoire
- Utiliser la syntaxe moderne
lib.callback.awaitd'ox_lib - Choisir entre un event simple et un callback selon le besoin réel
Dans quel contexte ?
Un joueur ouvre son interface bancaire et doit voir son solde exact s'afficher immédiatement. Un simple event ne suffit pas ici : il faut poser une vraie question au serveur ("quel est mon solde ?") et attendre sa réponse précise, pas juste annoncer un fait. Cette leçon comble ce manque avec un pattern de callback, avant de montrer sa version moderne et simplifiée via ox_lib.
| Approche | Complexité | Usage recommandé |
|---|---|---|
| Callback manuel (id + table) | Verbeux, pédagogique | Comprendre le mécanisme sous-jacent |
lib.callback (ox_lib) | Concis, basé sur coroutines | Utilisation en production |
Piège fréquent
Un callback enregistré mais jamais nettoyé après réception de sa réponse reste en mémoire indéfiniment. Sur un serveur qui tourne des jours ou des semaines, cette fuite mémoire lente finit par dégrader les performances de façon perceptible.
Le trou dans le système d'events
Les events, vus en leçon 4, fonctionnent sur un modèle "fire and forget" (déclencher et oublier) : on envoie une information, mais rien ne garantit ni n'organise une réponse en retour. C'est parfaitement adapté pour annoncer un fait ("j'ai cliqué"), mais totalement insuffisant pour poser une vraie question et attendre sa réponse précise, comme "quel est mon solde actuel ?". C'est ce manque que le pattern de callback client-serveur vient combler.
L'astuce : un identifiant de requête unique
Comment le client sait-il quelle réponse correspond à quelle question, si plusieurs demandes sont envoyées presque en même temps ? La solution repose sur un identifiant unique généré à chaque appel : le client se souvient localement de quelle fonction doit être exécutée pour tel identifiant, l'envoie au serveur avec sa question, et le serveur renvoie ce même identifiant accompagné de sa réponse — permettant au client de retrouver et d'exécuter la bonne fonction en attente, même si d'autres questions sont en cours en parallèle.
Pourquoi nettoyer les callbacks résolus
Un détail facile à négliger mais important sur un serveur qui tourne des jours ou des semaines : chaque callback enregistré occupe de la mémoire tant qu'il n'est pas explicitement supprimé après avoir reçu sa réponse. Oublier ce nettoyage crée une fuite mémoire lente mais réelle, qui finit par dégrader les performances sur une session longue.
La solution moderne : les coroutines
Ce système manuel, bien qu'instructif pour comprendre le mécanisme sous-jacent, est verbeux à écrire à chaque fois. Des bibliothèques comme ox_lib exploitent les coroutines de Lua (une fonctionnalité qui permet de "mettre en pause" une fonction en attendant un résultat, sans bloquer le reste du programme) pour offrir une syntaxe await bien plus lisible, qui masque toute cette mécanique tout en gardant le même principe de fonctionnement.
Commandes & code
Callbacks client-serveur
Les events sont "fire and forget" : pour obtenir une réponse (ex: solde du compte), il faut un système de callback dédié.
-- CLIENT : pattern manuel avec identifiant de requête unique (avant les libs modernes)
local callbacks = {}
local callbackId = 0
function TriggerServerCallback(name, cb, ...)
callbackId = callbackId + 1
local id = callbackId
callbacks[id] = cb
TriggerServerEvent('technologik:serverCallback', name, id, ...)
end
RegisterNetEvent('technologik:clientCallbackResult', function(id, ...)
if callbacks[id] then
callbacks[id](...)
callbacks[id] = nil -- nettoyage : évite une fuite mémoire sur callbacks jamais résolus
end
end)-- SERVEUR : registre de handlers et relai vers le bon client
local serverCallbacks = {}
function RegisterServerCallback(name, handler)
serverCallbacks[name] = handler
end
RegisterNetEvent('technologik:serverCallback', function(name, id, ...)
local src = source
if serverCallbacks[name] then
serverCallbacks[name](src, function(...)
TriggerClientEvent('technologik:clientCallbackResult', src, id, ...)
end, ...)
end
end)
RegisterServerCallback('getPlayerMoney', function(source, cb)
local money = GetUserMoney(GetPlayerIdentifierByType(source, 'license'))
cb(money)
end)-- Utilisation côté client
TriggerServerCallback('getPlayerMoney', function(money)
print('Argent du joueur: ' .. money)
end)-- Version moderne avec ox_lib (recommandé en production, syntaxe await lisible)
-- SERVEUR
lib.callback.register('technologik:getPlayerMoney', function(source)
local identifier = GetPlayerIdentifierByType(source, 'license')
return GetUserMoney(identifier) -- valeur de retour = réponse envoyée automatiquement
end)
-- CLIENT
local money = lib.callback.await('technologik:getPlayerMoney', false)
print('Argent: ' .. money)Résumé
- Un event seul ne renvoie jamais de résultat : il faut un identifiant de requête pour router la réponse.
- Toujours nettoyer les callbacks résolus pour éviter des fuites mémoire sur session longue.
ox_lib'slib.callbackremplace avantageusement l'implémentation manuelle en production.
Exercices pratiques
Mission : traquer une fuite mémoire dans l'interface bancaire
Objectif : Diagnostiquer une fuite mémoire causée par des callbacks jamais nettoyés, puis migrer un callback manuel vers ox_lib.
Contexte
L'interface bancaire NUI de technologik_bank utilise le pattern de callback manuel de la leçon pour afficher le solde du joueur. Après plusieurs semaines de fonctionnement continu, txAdmin signale une consommation mémoire du serveur qui grimpe lentement sans jamais redescendre. En inspectant le code, tu remarques que le handler technologik:clientCallbackResult ne contient aucune ligne callbacks[id] = nil.