games / fivem
Persistance de données joueur
Explication
Ce que vous allez apprendre
- Retarder proprement une connexion avec
playerConnectinget les "deferrals" - Charger les données d'un joueur une fois en jeu et les garder en cache mémoire
- Sauvegarder les données à la déconnexion (
playerDropped) - Mettre en place une sauvegarde périodique automatique comme filet de sécurité
- Nettoyer le cache mémoire pour éviter une fuite sur une session longue
Dans quel contexte ?
Un serveur RP tourne depuis plusieurs jours sans interruption quand il subit un crash imprévu (bug mémoire, coupure réseau). Sans double filet de sauvegarde, tous les joueurs connectés au moment du crash perdraient leur progression depuis leur dernière déconnexion propre — une catastrophe pour la confiance des joueurs envers le serveur. Cette leçon construit précisément ce filet de sécurité à deux niveaux.
| Événement | Rôle |
|---|---|
playerConnecting + deferrals | Retarde la connexion le temps de charger/valider le profil |
playerDropped | Sauvegarde à la déconnexion (premier filet) |
Sauvegarde périodique (CreateThread + Wait) | Protège contre un crash serveur (second filet) |
Piège fréquent
Ne sauvegarder qu'à la déconnexion semble suffisant, mais un serveur qui plante brutalement ne déclenche jamais cet événement de déconnexion propre : toutes les modifications depuis la dernière sauvegarde seraient alors perdues. D'où la nécessité d'une sauvegarde périodique automatique en complément.
Mettre en pratique la persistance vue précédemment
La leçon sur la base de données a montré comment stocker et lire des informations. Cette leçon applique concrètement cette compétence au cas le plus important d'un serveur RP : ne jamais perdre la progression d'un joueur, ni au moment où il se connecte, ni pendant sa session, ni en cas de crash imprévu du serveur.
Retarder une connexion, pas juste la refuser
Les "deferrals" permettent de mettre en pause temporairement la connexion d'un joueur, le temps de vérifier ou charger ses informations, tout en lui affichant un message clair sur ce qui se passe ("Chargement de votre profil..."). C'est plus élégant qu'un simple refus binaire : cela laisse un délai naturel pour interroger la base de données et, éventuellement, refuser proprement une connexion invalide plutôt que de bloquer l'accès au jeu de façon brutale et sans explication.
Pourquoi garder un cache en mémoire
Relire la base de données à chaque petite action du joueur (chaque déplacement, chaque interaction) serait extrêmement coûteux en performance, puisque chaque requête, même asynchrone, prend un peu de temps. La solution consiste à charger les données une fois à la connexion, les garder en mémoire pendant toute la session, et ne les réécrire en base qu'à certains moments précis — une bien meilleure répartition de la charge entre mémoire (rapide) et base de données (plus lente mais durable).
Le double filet de sécurité
Sauvegarder uniquement à la déconnexion semble suffisant, mais un serveur peut planter brutalement sans jamais déclencher cet événement de déconnexion propre — auquel cas toutes les modifications depuis la dernière sauvegarde seraient perdues. C'est pour cela qu'on combine une sauvegarde à la déconnexion ET une sauvegarde périodique automatique en arrière-plan : le second mécanisme limite les pertes possibles à quelques minutes maximum, même dans le pire des scénarios.
Commandes & code
Persistance de données joueur
Charger les données à la connexion, sauvegarder régulièrement, et ne jamais perdre de progression en cas de crash.
-- Deferrals : bloquer la connexion le temps de charger/valider le profil
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
deferrals.defer()
CreateThread(function()
deferrals.update('Chargement de votre profil...')
Wait(0) -- laisse la boucle deferrals s'initialiser correctement
local src = source
local identifier = GetPlayerIdentifierByType(src, 'license')
if not identifier then
deferrals.done('Identifiant Rockstar introuvable.')
return
end
local userData = MySQL.single.await('SELECT * FROM users WHERE identifier = ?', { identifier })
if not userData then
MySQL.insert.await('INSERT INTO users (identifier, name) VALUES (?, ?)', { identifier, name })
end
deferrals.done() -- autorise la connexion à se poursuivre
end)
end)-- Chargement complet une fois le joueur réellement en jeu
local playerCache = {}
AddEventHandler('playerJoining', function()
local src = source
local identifier = GetPlayerIdentifierByType(src, 'license')
local userData = MySQL.single.await('SELECT * FROM users WHERE identifier = ?', { identifier })
playerCache[src] = userData
end)-- Sauvegarde à la déconnexion : dernier filet avant la perte de session
AddEventHandler('playerDropped', function(reason)
local src = source
SavePlayerData(src)
playerCache[src] = nil -- nettoyage du cache mémoire, sinon fuite sur le long terme
end)
function SavePlayerData(source)
local identifier = GetPlayerIdentifierByType(source, 'license')
local data = playerCache[source]
if not data then return end
MySQL.update.await('UPDATE users SET money = ?, inventory = ? WHERE identifier = ?', {
data.money,
json.encode(data.inventory),
identifier,
})
end-- Sauvegarde périodique : filet de sécurité contre les crashs serveur (perte totale sinon)
CreateThread(function()
while true do
Wait(5 * 60 * 1000) -- toutes les 5 minutes
for _, playerId in ipairs(GetPlayers()) do
SavePlayerData(tonumber(playerId))
end
print('[technologik] Sauvegarde automatique effectuée')
end
end)Résumé
playerConnecting+deferralspermet de refuser/retarder une connexion tant que les données ne sont pas prêtes.- Un cache mémoire côté serveur évite de re-requêter la DB à chaque action du joueur.
- Sauvegarder à la fois à la déconnexion ET périodiquement protège contre les crashs serveur.
Exercices pratiques
Mission : reconstituer le filet de sécurité après un crash
Objectif : Diagnostiquer l'absence de sauvegarde périodique après un crash serveur, puis ajouter ce second filet de sécurité.
Contexte
Le serveur technologik_rp tourne depuis quatre jours quand il subit un crash imprévu (bug mémoire d'une ressource tierce). En consultant les journaux après redémarrage, plusieurs joueurs signalent avoir perdu l'argent et les objets gagnés dans l'heure précédant le crash, alors que le système de sauvegarde à la déconnexion (playerDropped) fonctionne correctement en temps normal.