Les weak_maps de Verse se nettoient-elles automatiquement lorsqu'un joueur quitte dans UEFN ?
En bref
Cet article déconstruit l'idée reçue selon laquelle les weak_maps de Verse nettoient automatiquement les entrées des joueurs lors de leur déconnexion dans UEFN. Il détaille le modèle de persistance sous-jacent et le cycle de vie des objets joueur pour expliquer pourquoi des références obsolètes provoquent des fuites de mémoire et des runtime crashes. Un tutoriel complet montre comment séparer les données temporaires des données persistantes et implémenter une logique de nettoyage manuel robuste. Enfin, il présente comment le SDK de horizOn permet d'externaliser cette gestion complexe vers un backend cloud performant.
Si vous faites confiance à votre assistant de code IA lorsqu'il vous dit que Verse purge automatiquement les données de joueur d'une weak_map à la milliseconde près où ils se déconnectent de votre île Fortnite, vous vous exposez à un grave runtime crash. L'affirmation semble logique : puisqu'il s'agit d'une référence faible, le moteur devrait nettoyer la clé une fois que l'objet joueur subit la Garbage Collection. Mais dans Unreal Editor for Fortnite (UEFN), la réalité est bien plus complexe, et mal comprendre comment le gestionnaire de mémoire de Verse traite le cycle de vie des joueurs conduit à des fuites d'état silencieuses et à des exceptions bloquantes.
Lorsqu'un joueur part, référencer son objet obsolète dans une map peut déclencher le redouté ErrRuntime_WeakMapInvalidKey ou entraîner des crashs généraux de l'île, vous obligeant à mettre en place un protocole de correction de crash de serveur UEFN strict pour garder vos serveurs stables. Pour éviter cela, les développeurs doivent apprendre comment Verse gère la mémoire en interne et comment implémenter des routines de nettoyage faisant autorité.
L'idée reçue : conseils générés par IA vs réalité de Verse
De nombreux développeurs demandent à leurs assistants IA comment gérer les données de map d'un joueur lorsqu'il quitte un match. Un conseil classique généré par IA prétend que le moteur traite la clé du joueur comme une « référence faible » (weak reference) et supprime automatiquement l'entrée du joueur de la map dès son départ. C'est fondamentalement incorrect.
Bien que la weak_map(player, t) de Verse utilise des références faibles comme clés sous le capot pour éviter les cycles de référence forts qui bloqueraient le garbage collector, elle n'effectue pas de nettoyage automatique et immédiat des entrées de la map elles-mêmes. L'entrée — contenant à la fois l'emplacement de la clé et ses données associées — reste allouée dans le conteneur de la map.
Si votre code tente d'accéder à cette clé, de l'évaluer ou de la modifier après le départ du joueur, le runtime de Verse tentera de déréférencer un objet joueur nul ou invalide. Au lieu d'échouer proprement (fail gracefully), le runtime déclenche un crash ou lève une exception non interceptable. Le système s'attend à ce que vous gériez explicitement les transitions de cycle de vie au lieu de vous reposer sur un nettoyage automatique.
Pourquoi les weak maps ne nettoient pas automatiquement les entrées de joueur
Pour comprendre pourquoi cela se produit, nous devons examiner le but d'une weak_map dans UEFN. Contrairement aux environnements de programmation standard où les weak maps sont des caches mémoire temporaires, Verse utilise principalement weak_map(player, t) comme gardien des données persistantes des joueurs.
Persistance entre les sessions de jeu
Lorsque vous utilisez une weak_map(player, t) déclarée au niveau du module (module scope), le moteur lie les valeurs à la base de données cloud persistante d'Epic. Si un joueur quitte le match et revient trois jours plus tard, le moteur fait correspondre son ID de joueur avec la clé de map persistante pour restaurer sa progression.
Si le moteur effaçait automatiquement l'entrée d'un joueur de la map dès qu'il quitte le jeu, la map perdrait toutes ses données persistantes. Les niveaux, les monnaies personnalisées et les objets déverrouillés seraient réinitialisés à zéro chaque fois qu'un joueur se déconnecte ou subit un timeout réseau. Ainsi, la base de données est architecturée pour conserver ces entrées intactes précisément parce qu'elles sont censées survivre aux déconnexions.
La durée de vie limitée des objets Player
Lorsqu'un joueur quitte un match, son objet de session actif dans le playspace est détruit. La référence player physique détenue par votre code Verse devient un handle mort.
Puisque la clé dans la map pointe désormais vers un objet invalide et inactif, interroger la map avec cette référence morte échouera. Le moteur ne scanne pas activement et ne supprime pas les clés mortes de la map en temps réel. Au lieu de cela, il les laisse inertes, c'est pourquoi une gestion manuelle est obligatoire pour éviter l'accumulation de références obsolètes.
Les conséquences : fuites de mémoire, données obsolètes et crashs de serveur
Ne pas nettoyer les entrées des joueurs entraîne trois problèmes distincts qui dégradent les performances du jeu et la stabilité du serveur sur les matchs longs.
- Fuite de données obsolètes (Stale Data Leakage) : Si un joueur part et qu'un autre joueur le rejoint, le nouveau joueur pourrait hériter des données de session de l'ancien joueur si le moteur réutilise les slots internes des joueurs. Cela conduit à des bugs d'état, comme de nouveaux joueurs apparaissant avec un inventaire plein ou des statistiques de match incorrectes.
- Accumulation de mémoire : Bien qu'un simple booléen ou entier prenne un espace négligeable, stocker des structures complexes pour jusqu'à 50 joueurs dans un lobby à haute capacité peut augmenter l'utilisation de la mémoire. Sur une session de serveur de 4 heures, cette accumulation peut dégrader les tick rates du serveur.
- Échecs de recherche (Look-up Failures) : Tenter de requêter le statut d'un joueur inactif ou d'appeler des fonctions sur une référence de joueur morte déclenche immédiatement des runtime crashes.
Atteindre les limites de sauvegarde cloud d'Epic
UEFN impose des limites strictes sur les données persistantes. Vous êtes limité à un maximum de 4 weak_maps persistantes par île, et la taille de l'enregistrement individuel de chaque joueur ne peut pas dépasser 256 Ko de données.
Si vous utilisez une weak_map persistante pour stocker des états de session temporaires, vous gaspillez cet espace précieux en base de données. Chaque mise à jour écrit dans la base de données d'Epic, risquant une pénalité de limitation d'écriture (write-throttling) ou le dépassement de la limite de 256 Ko, ce qui déclenche une erreur d'exécution (runtime error) lors de la tentative d'écriture de données supplémentaires.
Tutoriel étape par étape : gérer les états de session des joueurs en toute sécurité
Pour gérer les états des joueurs sans risquer de fuites de mémoire (memory leaks) ou de surcharge de la base de données, vous devez séparer vos données de session temporaires de vos données cloud persistantes. Les données temporaires doivent être stockées dans des maps standards et non persistantes, que vous devez nettoyer manuellement lors des déconnexions de joueurs.
Étape 1 : Définir la structure de votre état de session (Session State Struct)
Commencez par définir une structure non persistante (non-persistable struct) contenant toutes les variables dont votre joueur a besoin pendant une seule manche ou un match. Ne marquez pas cette classe ou structure avec l'attribut <persistable>.
# Define the transient data structure for active gameplay tracking
player_session_state := struct:
IsMoneyBagFull : logic = false
CurrentGold : int = 0
SpawnTime : float = 0.0
Étape 2 : Établir le device de gestion (Manager Device)
Créez un creative device qui servira de coordinateur. Il contiendra une map mutable et non persistante des joueurs actifs. Étant donné que les maps standards dans Verse sont immutables, nous déclarons la variable de map en tant que var afin de pouvoir la remplacer lorsque les joueurs rejoignent ou quittent la partie.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Playspaces }
using { /Verse.org/Simulation }
# Device handling player lifecycle events and session state mapping
state_manager_device := class(creative_device):
# Non-persistent map for tracking active player sessions
var SessionStates : [player]player_session_state = map{}
Étape 3 : S'abonner aux événements du Playspace
Dans la fonction OnBegin, abonnez-vous aux événements de connexion du playspace. Cela garantit que vous exécutez le code d'initialisation lorsqu'un joueur rejoint et le code de nettoyage lorsqu'il part.
OnBegin<override>()<suspends>:void=
GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)
GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)
# Initialize any players already in the session (useful for UEFN hot-reloading)
for (Player : GetPlayspace().GetPlayers()):
OnPlayerAdded(Player)
Étape 4 : Implémenter la logique d'enregistrement et de nettoyage
Lorsqu'un joueur rejoint, remplissez la map avec son état de session par défaut. Lorsqu'il part, vous devez purger son entrée de la map. Comme Verse ne dispose pas d'une fonction intégrée Map.Remove(), vous devez reconstruire la map en filtrant le joueur qui part. Cela empêche les références obsolètes de stagner en mémoire.
# Triggered when a player connects to the server
OnPlayerAdded(Player: player):void=
if (not SessionStates[Player]):
InitialState := player_session_state{IsMoneyBagFull := false, CurrentGold := 0, SpawnTime := GetEngineTime()}
if (set SessionStates[Player] = InitialState):
Print("Initialized gameplay state for joining player.")
# Triggered when a player disconnects or leaves the game
OnPlayerRemoved(Player: player):void=
Print("Player disconnected. Initiating map cleanup.")
RemovePlayerSession(Player)
# Purges the player's entry by reconstructing the map
RemovePlayerSession(PlayerToRemove: player):void=
var CleanedStates : [player]player_session_state = map{}
for (ActivePlayer -> State : SessionStates):
# Copy all players except the one who left
if (ActivePlayer <> PlayerToRemove):
if (set CleanedStates[ActivePlayer] = State):
# Entry successfully migrated to the cleaned map
set SessionStates = CleanedStates
Print("Successfully removed player session entry from memory.")
En reconstruisant la map lors de la suppression du joueur, vous supprimez complètement la clé de référence. Le garbage collector peut alors récupérer les ressources du joueur sans laisser d'entrées obsolètes dans votre boucle de jeu (game loop).
Si vous souhaitez suivre des télémétries personnalisées lors de ces transitions de cycle de vie, vous devez également prêter attention aux limites telles que la limite de 32 caractères pour les noms d'événements d'analytics dans Verse lorsque vous signalez des durées de session ou des statistiques de monnaie à des backends externes.
Bonnes pratiques pour la gestion d'état dans Verse
- Différencier les données de session des données persistantes : Ne stockez jamais de variables à courte durée de vie (comme la santé actuelle dans le match, le score de la manche ou les positions temporaires) dans une
weak_mappersistante. Conservez les états temporaires dans une map mutable standard enveloppée dans une classe de gestion. - Vérifier l'activité du joueur avec
IsActive: Avant de récupérer ou de modifier les données d'un joueur dans une map, vérifiez s'il est toujours présent dans le playspace à l'aide de la requêteIsActive[]. SiIsActive[]renvoie faux, abandonnez la recherche et déclenchez un événement de nettoyage. - Surveiller la taille des données avec
FitsInPlayerMap: Lors de l'écriture dans uneweak_mappersistante, appelezFitsInPlayerMap()pour confirmer que la mise à jour ne dépassera pas la limite de 256 Ko, évitant ainsi les exceptions à l'exécution. - Regrouper vos maps : Ne créez pas de maps distinctes pour chaque variable. Définissez une seule classe contenant toutes les variables du joueur et associez le joueur à cette classe. Cela minimise votre nombre de maps et respecte la limite de l'île de quatre weak maps persistantes.
Déléguer la complexité à un backend cloud fiable
Gérer le cycle de vie des sessions de joueurs, les limites de base de données et la logique de nettoyage manuel dans Verse peut rapidement devenir complexe. Si vous devez créer une progression cross-session, des inventaires synchronisés globalement ou du matchmaking régional, gérer ces états manuellement nécessite de configurer des webhooks, de mettre à l'échelle des bases de données externes et de gérer la synchronisation de serveur à serveur.
Avec horizOn, ces défis de backend sont gérés automatiquement. En intégrant le SDK de horizOn dans votre serveur de jeu, vous pouvez déléguer la gestion des sessions de joueurs à une base de données cloud dédiée. Lorsqu'un joueur se déconnecte, horizOn déclenche un nettoyage automatique de la session, met à jour les bases de données globales et synchronise les enregistrements d'inventaire entre les instances de serveur sans atteindre les limites de mémoire de 256 Ko de Verse ni risquer des runtime crashes.
Prêt à faire évoluer votre backend UEFN ? Essayez horizOn gratuitement ou consultez la documentation de l'API.