Architecturer des serveurs Zero-Waste : Analyse de la proposition d'hibernation pour l'optimisation des serveurs Fortnite
Tout développeur multijoueur finit par se heurter au même mur financier : votre infrastructure serveur brûle du cash pour simuler du vide. Lorsque vous lancez un Dedicated Server pour un Battle Royale à grande échelle, un jeu de survie ou un MMO, les cycles CPU sont massivement gaspillés dans des calculs d'inactivité. Vous payez des tarifs de Cloud Computing premium pour calculer la gravité de rochers que personne ne regarde, traiter la navigation IA d'ennemis sans cibles et maintenir des World States dans des secteurs totalement dépourvus d'activité de joueurs.
Récemment, une proposition technique fascinante a fait surface dans la communauté Unreal Engine, adressée à la direction d'Epic Games. La thèse centrale ? L'échelle massive de Fortnite nécessite une transition d'un modèle d'hébergement centralisé à maintenance élevée vers un modèle d'infrastructure « Zero-Waste ». L'auteur soutient qu'en éliminant le gaspillage de simulation, Epic pourrait réduire ses Operating Expenses (Opex) de 60 à 70 %, ce qui leur permettrait théoriquement de ramener le prix de la monnaie premium à un MSRP de 1,99 $ pour 1 000 V-Bucks.
Bien que la faisabilité économique du prix des V-Bucks soit un débat pour les designers de monétisation, le pilier technique de cette proposition — Sector Physics Hibernation (SGH) — est une leçon magistrale d'architecture serveur moderne.
Dans cette analyse de l'industrie, nous allons décortiquer la mécanique de la proposition d'hibernation pour l'optimisation des serveurs Fortnite, explorer le fonctionnement du Logic-Side Culling dans Unreal Engine 5 et démontrer comment vous pouvez implémenter une infrastructure Zero-Waste dans vos propres titres multijoueurs.
Les mathématiques du gaspillage de simulation
Pour comprendre pourquoi la Sector Physics Hibernation est nécessaire, il faut regarder les chiffres brutaux des Dedicated Servers de jeux.
Prenez une carte standard de Battle Royale de 100 km². Au début d'un match, 100 joueurs sont parachutés sur divers points d'intérêt. Dans les 5 premières minutes, 50 % des joueurs sont éliminés, et les survivants convergent vers une Safe Zone qui rétrécit.
À la 10e minute, plus de 70 % de la surface totale de la carte ne contient aucun joueur actif. Pourtant, dans une configuration Authoritative Server standard, le Dedicated Server continue de faire « ticker » l'intégralité du World State à 30 Hz.
- Physics Calculations : Les Rigid Bodies, les environnements destructibles et la balistique sont toujours suivis en mémoire.
- Actor Ticking : Des milliers d'instances d'
AActorappellent leur fonctionTick()30 fois par seconde. - NavMesh Processing : L'IA errante ou les obstacles dynamiques continuent d'interroger le Navigation Mesh.
Si vous faites tourner vos serveurs sur des instances AWS c5.2xlarge, vous payez environ 0,34 $ par heure et par machine. Si une seule machine ne peut héberger que deux instances de jeu de 100 joueurs parce que le CPU est au maximum pour calculer du vide, votre scalabilité est sévèrement limitée.
La proposition suggère qu'en récupérant cet overhead CPU gaspillé, les développeurs peuvent soit packager 5 à 6 instances de jeu sur le même matériel (réduisant les factures serveur d'environ 60 %), soit rediriger cette puissance de calcul récupérée pour booster le Tick Rate global du serveur de 30 Hz à 60 Hz+, garantissant une Hit Registration parfaite et un gameplay ultra-fluide.
Deep Dive : Sector Physics Hibernation dans UE5
La solution technique proposée repose sur l'exploitation du système World Partition existant d'Unreal Engine 5, mais en détournant son cas d'utilisation principal de la gestion de la mémoire côté client vers la gestion du CPU côté serveur.
Le problème des Dedicated Servers par défaut
Par défaut, le World Partition d'UE5 charge et décharge des cellules pour le client en fonction de leur distance par rapport à la source de streaming (la caméra du joueur). C'est excellent pour maintenir une utilisation mémoire basse et des framerates élevés côté client.
Cependant, le Dedicated Server charge généralement toute la carte en mémoire pour maintenir son autorité. Si un sniper tire une balle à travers une vallée, ou si un événement global se déclenche, le serveur a besoin des données de collision et des Actor States immédiatement disponibles pour valider l'action. Charger et décharger des données dynamiquement depuis le disque sur le serveur (Level Streaming) est souvent trop lent et provoque des saccades massives, ruinant le Tick Rate.
La solution SGH : Logic-Side Culling
Au lieu de décharger le secteur de la mémoire (ce qui cause des goulots d'étranglement IO), la Sector Physics Hibernation propose des CPU-Sleep States.
Le secteur reste en RAM, mais tous les Ticks, calculs de physique et mises à jour d'état sont mis en pause de manière agressive. Lorsqu'une cellule de la Spatial Grid d'un secteur détecte zéro entité active (joueurs, véhicules de joueurs ou projectiles actifs), le serveur suspend l'allocation CPU pour cette grille spécifique.
Implémenter un Hibernation Manager en C++
Pour construire cela dans Unreal Engine, vous avez besoin d'un sous-système qui surveille les cellules de la Spatial Grid et bascule dynamiquement l'état de Tick des acteurs qu'elles contiennent. Voici un prototype architectural simplifié d'un SectorHibernationManager.
#include "SectorHibernationManager.h"
#include "EngineUtils.h"
#include "GameFramework/Actor.h"
#include "GameFramework/PlayerController.h"
void USectorHibernationManager::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
HibernationCheckInterval = 2.0f; // Vérification toutes les 2 secondes
TimeSinceLastCheck = 0.0f;
GridCellSize = 10000.0f; // Cellules de grille de 100m
}
void USectorHibernationManager::Tick(float DeltaTime)
{
TimeSinceLastCheck += DeltaTime;
if (TimeSinceLastCheck >= HibernationCheckInterval)
{
EvaluateSectors();
TimeSinceLastCheck = 0.0f;
}
}
void USectorHibernationManager::EvaluateSectors()
{
UWorld* World = GetWorld();
if (!World) return;
// Étape 1 : Mapper les positions des joueurs actifs aux cellules de la grille
TSet<FIntVector> ActiveCells;
for (FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator)
{
APlayerController* PC = Iterator->Get();
if (PC && PC->GetPawn())
{
FVector PlayerPos = PC->GetPawn()->GetActorLocation();
FIntVector CellCoord = FIntVector(
FMath::FloorToInt(PlayerPos.X / GridCellSize),
FMath::FloorToInt(PlayerPos.Y / GridCellSize),
FMath::FloorToInt(PlayerPos.Z / GridCellSize)
);
// Marquer cette cellule et les cellules adjacentes comme actives (zone tampon)
MarkAdjacentCellsActive(CellCoord, ActiveCells);
}
}
// Étape 2 : Parcourir les acteurs hibernables et basculer le tick
for (TActorIterator<AActor> ActorItr(World); ActorItr; ++ActorItr)
{
AActor* Actor = *ActorItr;
// Ignorer les acteurs d'infrastructure essentiels
if (!Actor->ActorHasTag(FName("Hibernatable"))) continue;
FVector ActorPos = Actor->GetActorLocation();
FIntVector ActorCell = FIntVector(
FMath::FloorToInt(ActorPos.X / GridCellSize),
FMath::FloorToInt(ActorPos.Y / GridCellSize),
FMath::FloorToInt(ActorPos.Z / GridCellSize)
);
bool bShouldBeActive = ActiveCells.Contains(ActorCell);
if (bShouldBeActive && !Actor->IsActorTickEnabled())
{
// Réveil
Actor->SetActorTickEnabled(true);
Actor->SetActorEnableCollision(true);
}
else if (!bShouldBeActive && Actor->IsActorTickEnabled())
{
// Mise en veille
Actor->SetActorTickEnabled(false);
// Optionnel : Réduire la collision aux requêtes simples pour économiser du temps sur le thread physique
Actor->SetActorEnableCollision(false);
}
}
}
La complexité de la phase de « Réveil »
Le code ci-dessus illustre le concept de base, mais le véritable défi technique réside dans la phase de réveil. Si un joueur tire avec un fusil de précision à haute vélocité vers un secteur en veille, le projectile traversera la limite avant que la boucle d'évaluation de 2 secondes ne le détecte.
Si le secteur se réveille après l'arrivée de la balle, vous subirez une désynchronisation catastrophique. La balle pourrait passer à travers un véhicule en hibernation parce que sa collision était désactivée. Ce phénomène est étroitement lié aux problèmes détaillés dans notre guide sur The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It, où les écarts de timing entre l'état du serveur et la prédiction du client brisent complètement la simulation.
Pour résoudre ce problème, l'infrastructure Zero-Waste nécessite des Predictive Wake-Ups. Au lieu de simplement suivre les positions des joueurs, le serveur doit calculer les vecteurs de vélocité de tous les projectiles actifs et véhicules rapides. Si un vecteur intersecte une cellule de grille en veille, le serveur doit instantanément forcer un événement de réveil sur cette cellule spécifique avant l'arrivée de l'objet.
Orchestrer des serveurs Zero-Waste à l'échelle
Implémenter le Logic-Side Culling dans votre moteur de jeu n'est que la moitié de la bataille. L'autre moitié est l'orchestration de l'infrastructure.
Si votre Dedicated Server UE5 réduit dynamiquement son empreinte CPU de 60 %, votre environnement d'hébergement serveur doit être assez intelligent pour reconnaître cette baisse de consommation de ressources et packager de nouvelles instances de jeu sur le même nœud matériel.
Construire cette orchestration vous-même nécessite une quantité immense d'ingénierie DevOps. Vous devriez déployer des clusters Kubernetes, configurer Agones pour la gestion du cycle de vie des serveurs de jeu, écrire des métriques de scaling personnalisées basées sur l'utilisation CPU et gérer des Load Balancers pour router les joueurs vers les bonnes instances. C'est facilement 4 à 6 mois de travail d'infrastructure dédié — du temps pris directement sur le développement de votre jeu.
Avec horizOn, ces services d'orchestration Backend sont préconfigurés. La plateforme gère le packaging dynamique d'instances, l'Auto-Scaling basé sur la charge serveur en temps réel et les pipelines de déploiement automatisés pour vos builds de Dedicated Server. En laissant un Backend-as-a-Service spécialisé gérer l'infrastructure, vous pouvez livrer votre jeu multijoueur au lieu de passer six mois à vous battre avec des manifestes Kubernetes.
De plus, lorsque vous packager plus d'instances sur un seul nœud, vous augmentez le risque de problèmes de Noisy Neighbor affectant votre thread réseau. Sécuriser votre Netcode contre ces goulots d'étranglement est critique, un sujet que nous couvrons largement dans The Uefn Server Performance Exploit Explained Hard Armoring Your Unreal Engine Netcode.
Best Practices pour une architecture multijoueur Zero-Waste
Que vous construisiez un Battle Royale de 100 joueurs ou un jeu de survie en monde ouvert persistant, l'implémentation de l'hibernation et des techniques Zero-Waste nécessite une discipline architecturale stricte. Voici cinq bonnes pratiques éprouvées pour garantir que l'Opex de votre serveur reste bas sans sacrifier l'expérience joueur :
1. Découpler le Game State de la Tick Loop
Le plus grand ennemi de la performance serveur est le polling continu de données. N'utilisez jamais Tick() pour vérifier si un événement doit se produire. Passez entièrement à une Event-Driven Architecture. Si un feu de camp doit s'éteindre après 5 minutes, ne le faites pas ticker à chaque frame pour soustraire du temps. Utilisez un Timer Delegate qui se déclenche exactement une fois après 300 secondes. Cela permet à l'acteur du feu de camp de rester complètement endormi pendant 4 minutes et 59 secondes.
2. Implémenter un NetCullDistanceSquared agressif
Unreal Engine détermine quels acteurs répliquer à quels clients en fonction de NetCullDistanceSquared. Beaucoup de développeurs laissent les valeurs par défaut, forçant le serveur à sérialiser et compresser des données pour des acteurs situés à des centaines de mètres d'un joueur. Auditez vos distances de Cull. Une arme au sol n'a pas besoin d'être répliquée au-delà de 5 000 unités (50 mètres). Calculez le rayon minimum absolu requis pour votre Gameplay Loop et appliquez-le strictement.
3. Utiliser des Spatial Hash Grids pour des lookups en O(1)
Lors du calcul des acteurs à mettre en veille, itérer sur chaque acteur du monde (TActorIterator) devient un goulot d'étranglement en soi si vous avez 100 000 entités. Implémentez une Spatial Hash Grid. Lorsqu'un acteur se déplace, il met à jour sa position dans la Hash Map. Cela permet à votre Hibernation Manager d'interroger « Qu'y a-t-il dans la cellule X ? » avec une complexité temporelle en O(1), rendant l'évaluation de l'hibernation virtuellement gratuite pour le CPU.
4. Utiliser des Buffer Zones pour des réveils fluides
Ne mettez jamais un secteur en hibernation juste à la limite de la vision d'un joueur. Maintenez toujours une « Buffer Zone » de secteurs actifs d'au moins une cellule de large autour de toute entité active. Si vos cellules font 100 mètres de large et qu'un joueur est dans la cellule A, toutes les cellules adjacentes (une grille 3x3) doivent rester actives. Cela garantit que si un joueur sprinte soudainement à travers une frontière, la cellule de destination est déjà initialisée et ticke déjà.
5. Profiler régulièrement vos builds de Dedicated Server
Ne devinez pas ce qui consomme votre CPU. Utilisez Unreal Insights dans un environnement Dedicated Server packagé avec une charge simulée. Regardez spécifiquement les timings du GameThread. Si vous voyez que Physics ou TickTime dominent le graphe du thread alors que les joueurs sont immobiles, votre logique d'hibernation échoue. La télémétrie est le seul moyen de valider que votre architecture Zero-Waste fonctionne en réalité, pas seulement en théorie.
L'avenir de l'Opex serveur
La proposition de la communauté Fortnite met en lumière une vérité critique : le standard actuel de l'industrie consistant à forcer la performance serveur avec du Cloud Compute coûteux n'est pas viable. À mesure que les mondes s'agrandissent et que le nombre de joueurs augmente, le scaling linéaire des coûts d'infrastructure videra lentement les budgets Live-Ops.
La Sector Physics Hibernation, le Logic-Side Culling et le Dynamic Instance Packing ne sont plus seulement des optimisations pour les studios AAA ; ce sont des nécessités de survie pour les jeux multijoueurs de toutes tailles. En adoptant un état d'esprit Zero-Waste tôt dans votre cycle de développement, vous garantissez que la rentabilité de votre jeu évolue parallèlement à sa base de joueurs.
Si vous êtes prêt à implémenter le scaling dynamique de serveurs sans le casse-tête DevOps, essayez horizOn gratuitement ou consultez la doc API pour voir à quel point l'infrastructure multijoueur peut être fluide.