Aller au contenu principal

PARTIE III : Réseau et synchronisation

Chapitre 6 : Infrastructure réseau déterministe

6.1 Introduction : le réseau comme source d’incertitude

Le réseau est, par nature, l’antithèse du déterminisme. La latence varie imprévisiblement (jitter), les paquets arrivent dans le désordre ou sont perdus, et la bande passante est partagée avec des flux incontrôlables. L’infrastructure réseau d’un moteur déterministe doit masquer cette incertitude tout en préservant la cohérence de l’état synchronisé entre toutes les machines.1

Pour atteindre des latences sous-millisecondes requises par la VR multijoueur, LplKernel implémente un Kernel Bypass réseau inspiré de l’architecture DPDK (Data Plane Development Kit). Au lieu de subir le coût des interruptions matérielles et des multiples copies en mémoire de la pile TCP/IP classique, le pilote réseau (ex: Intel 8254x ou i217) mappe directement ses anneaux de descripteurs matériels (Descriptor Rings) dans une zone mémoire physique contiguë (allouée via le Buddy Allocator sous forme de MBUFs). Le CPU scrute ces anneaux en boucle (Polling), ce qui réalise un transfert Zero-Copy DMA absolu. Couplé au Receive Side Scaling (RSS), la carte réseau hache elle-même les paquets UDP entrants et les distribue matériellement sur les différents cœurs CPU du système SMP.

Avant ce bypass complet, le moteur a déjà éprouvé la même philosophie sous Linux, avec son module noyau dédié. Un hook Netfilter posé en NF_INET_PRE_ROUTING intercepte les paquets UDP du port de jeu et répond NF_DROP : choix assumé, le paquet intercepté ne remonte jamais la pile TCP/IP, il n’existe que pour la simulation. Le hook écrit directement dans un anneau de réception logé dans une mémoire partagée mmap’ée entre le noyau et le processus moteur (têtes et queues atomiques), et l’expéditeur est identifié par son couple adresse/port source extrait de l’en-tête IP, sans socket par client. À l’émission, la logique est symétrique et regroupée : le moteur remplit l’anneau TX puis réveille un thread noyau d’un seul ioctl, un « kick » qui expédie N paquets d’un coup, bien moins cher que N appels sendto(). Et parce qu’on ne développe pas toujours avec un module noyau sous la main (WSL, intégration continue), un repli socket UDP classique reste disponible derrière la même interface : indispensable pour itérer vite.

6.2 Modèles de synchronisation approfondis

6.2.1 Lockstep déterministe

Le Lockstep est le modèle le plus ancien et le plus strict. Tous les clients transmettent leurs inputs pour le tick TT ; chaque client attend d’avoir reçu les inputs de tous les autres clients avant de simuler le tick TT. La simulation étant déterministe (Chapitre 3), l’état résultant est identique sur toutes les machines.

L’avantage : la bande passante est minimale, seuls les inputs transitent, pas l’état du monde (quelques octets par tick au lieu de kilooctets). D’où son usage dans les RTS (StarCraft, Age of Empires), où l’on simule des milliers d’unités, ce qui rend la synchronisation d’état impraticable.

L’inconvénient : la latence perçue est celle du joueur le plus lent. Si un joueur a un ping de 200 ms, tous les joueurs subissent 200 ms de délai.

6.2.2 Rollback Netcode (GGPO)

Le Rollback Netcode, formalisé par la bibliothèque GGPO (Good Game Peace Out) pour les jeux de combat, est un mécanisme sophistiqué en trois temps :

  1. Prédiction : Le client simule immédiatement en utilisant le dernier input connu de l’adversaire (ou une prédiction basée sur l’historique).
  2. Réception : Lorsqu’un input distant arrive (potentiellement en retard), le client vérifie s’il diffère de la prédiction.
  3. Rollback et resimulation : Si l’input réel diffère de la prédiction, le client revient en arrière au tick où la divergence a eu lieu, applique l’input correct, puis resimule tous les ticks jusqu’au tick courant.
void RollbackNetcode::ProcessRemoteInput(uint32_t tick, Input remoteInput) {
    if (tick < m_currentTick) {
        // Input en retard — rollback nécessaire
        RestoreSnapshot(tick);            // Restaurer l'état au tick T
        m_inputBuffer[tick] = remoteInput; // Corriger l'input
        // Resimulation rapide : du tick T au tick courant
        for (uint32_t t = tick; t < m_currentTick; ++t) {
            SimulateFixedStep(m_inputBuffer[t]);
            SaveSnapshot(t + 1);
        }
    }
}

Ce mécanisme exige :

  • Snapshots rapides : La sauvegarde/restauration de l’état doit être quasi-instantanée. Les allocateurs Arena (Chapitre 2) sont idéaux : un memcpy du bloc complet suffit.
  • Simulation rapide : La resimulation de N ticks doit être plus rapide que le temps réel. Les systèmes ECS (Chapitre 4) avec itération SoA permettent de resimuler 8+ ticks dans le budget d’une frame.
  • Déterminisme absolu : La resimulation doit produire exactement le même état qu’une simulation directe.

Le module réseau du moteur réserve d’ailleurs sa place à cette stratégie de rollback aux côtés du transport et du protocole. La gestion des sessions y suit une leçon apprise dans le module noyau : un SpinLock crée de la contention dès que les lectures dominent, et le suivi des sessions est précisément un cas « beaucoup de lectures, peu d’écritures ». La structure visée est donc du RCU (Read-Copy-Update) : les lecteurs traversent la table des sessions sans jamais prendre de verrou, et les mises à jour publient une nouvelle version atomiquement.

Le Lockstep et le Rollback sont les deux extrémités d’un même axe : l’un ne transmet que les inputs et paie la latence du plus lent, l’autre prédit et resimule et paie le coût de la resimulation. Aucun des deux ne tient à l’échelle d’un MMORPG : des milliers d’entités, des centaines de joueurs, des cartes gigantesques, des pings hétérogènes. Entre ces deux bornes s’étend tout un spectre de techniques que la production AAA a affinées depuis vingt ans, et que ce chapitre recense parce que chacune éclaire un compromis. LplPlugin vise par défaut le MMORPG et le FullDive, mais rien n’oblige un moteur à choisir une seule stratégie : de même qu’il conserve à la fois le broadcast complet et le broadcast par zone d’intérêt et laisse le développeur (ou, à terme, une heuristique adaptative) trancher, il gagne à connaître tout l’éventail.

6.2.3 Serveur autoritaire, prédiction et réconciliation

Le modèle qui domine le FPS et le MMO depuis Quake est le serveur autoritaire à prédiction client. Le serveur exécute la simulation de référence à pas fixe. Le client, lui, pour masquer le délai aller-retour, applique immédiatement ses propres inputs (prédiction), puis corrige son état lorsqu’un instantané autoritaire arrive (réconciliation). L’articulation canonique en est la série de Gabriel Gambetta.2

Un détail d’implémentation de LplPlugin illustre pourquoi ce modèle économise beaucoup de bande passante : la vélocité n’est jamais transmise. L’instantané ne porte que la position, la taille et les points de vie, trente-deux octets par entité, parce que la vélocité est une fonction pure des inputs, recalculée à chaque tick par une formule identique sur le serveur et sur le client (computeMovementVelocity : WASD modulé par la concentration neurale, la composante verticale préservée pour la gravité). Ce qui se déduit ne se transmet pas : seul voyage sur le fil ce qui intègre, et donc dérive, à savoir la position. C’est le même principe qui, dans le Lockstep, fait ne transiter que les inputs.

La réconciliation impose une contrainte d’identité souvent sous-estimée : le client doit ranger chaque entité sous l’identifiant que le serveur lui a donné, sinon un instantané ultérieur pour la même entité ne la retrouve pas et en crée un double à chaque tick. LplPlugin fait porter cette identité par le smart-handle générationnel de l’ECS (EntityId, un couple génération/slot empaqueté sur 32 bits, décrit au Chapitre 4) : le client adopte l’identifiant du serveur tel quel, ce qui rend la résolution en O(1)O(1) et fait que le même instantané met l’entité à jour au lieu de la dupliquer.

6.2.4 Interpolation d’instantanés et dead reckoning

Le client ne peut prédire que sa propre entité, dont il connaît les inputs. Pour toutes les autres, il ne reçoit que des instantanés périodiques et doit combler l’intervalle. Deux techniques complémentaires, formalisées par Yahn Bernier chez Valve et éprouvées dans le moteur Source3 4 :

  • L’interpolation fait afficher au client le passé, avec un retard d’interpolation (typiquement 100 ms), et interpole les positions entre les deux derniers instantanés reçus. Le mouvement des autres joueurs reste fluide même quand les instantanés sont espacés ou qu’un paquet manque.
  • Le dead reckoning fait extrapoler la trajectoire à partir de la dernière vitesse connue, plutôt que d’attendre le prochain instantané. Transmettre une vitesse quantifiée pour les entités distantes devient alors un choix payant, non pour la simulation autoritaire mais pour lisser le rendu entre deux corrections. Le compromis est classique : l’extrapolation réduit la latence perçue mais produit des embardées à corriger quand la trajectoire réelle diverge de la prédite.

6.2.5 Compression delta et baseline acquittée

Envoyer l’état complet à chaque tick est un gaspillage : d’un tick à l’autre, la majeure partie de l’état ne bouge pas. Le modèle réseau de Quake III, puis de DOOM III, encode chaque instantané comme un delta contre une baseline acquittée : le client accuse réception du dernier instantané reçu, et le serveur n’envoie plus que la différence par rapport à cette version précise.5 Les champs inchangés ne coûtent qu’un bit d’absence. Le AoiBroadcast de LplPlugin fait déjà un delta d’entrée/sortie de zone (apparition, disparition, mise à jour). L’étape suivante, proprement AAA, consiste à y ajouter l’acquittement de baseline pour ne jamais renvoyer ce que le client a déjà confirmé.

6.2.6 Gestion d’intérêt (Area of Interest)

Le broadcast complet est en O(clients×N)O(\text{clients} \times N) : chaque client reçoit l’état de toutes les entités. C’est le mur qui interdit les grands mondes. La gestion d’intérêt le brise en n’envoyant à chaque client que les entités pertinentes pour lui, d’ordinaire celles proches de son avatar. Concrètement, LplPlugin en implémente une forme par grille spatiale (AoiBroadcast) : une requête de rayon sur la partition de Morton renvoie le voisinage, et le flux devient O(clients×voisins)O(\text{clients} \times \text{voisins}). Les raffinements connus forment une gradation :

  • La grille de cellules, la partition uniforme la plus simple, celle du moteur.
  • Les bulles ou anneaux concentriques réalisent un network LOD, où la cadence et la précision décroissent avec la distance : le proche à pleine fréquence et pleine précision, le lointain rafraîchi rarement et quantifié plus grossièrement.
  • Les requêtes d’intérêt (query-based interest), popularisées par SpatialOS d’Improbable, où chaque client déclare une requête (un volume, un filtre de composants) et ne reçoit que ce qui y répond.6

6.2.7 Pertinence, priorité et budget de bande passante

La gestion d’intérêt dit qui est visible. La réplication par pertinence d’Unreal Engine dit, en plus, combien de bande passante chaque entité mérite.7 Chaque acteur accumule une priorité (distance, dans le champ de vision ou non, temps écoulé depuis son dernier envoi). À chaque tick, par client, le serveur trie les acteurs pertinents et envoie les plus prioritaires jusqu’à épuisement d’un budget de bande passante mesuré. S’y ajoutent une fréquence de mise à jour propre à chaque entité (NetUpdateFrequency) et la dormance (dormancy) : une entité au repos ne consomme plus aucun trafic tant qu’elle ne bouge pas. C’est le mécanisme le plus directement adaptatif du lot, et le candidat naturel pour que LplPlugin ajuste seul son débit à la bande passante réelle de chaque client.

6.2.8 Distribution : sharding spatial et server meshing

Toutes les techniques précédentes optimisent ce qu’un seul serveur envoie. Passé un certain nombre d’entités simulées, c’est la simulation elle-même qu’il faut distribuer. Le sharding spatial découpe le monde en zones prises en charge par des processus ou des machines distincts. Le server meshing y ajoute le transfert transparent d’une entité d’un serveur à l’autre quand elle franchit une frontière, de sorte que le joueur ne perçoit pas les coutures. SpatialOS l’a industrialisé autour d’une couche de simulation distribuée. Star Citizen le porte à grande échelle avec une couche de réplication qui persiste l’état des entités et le diffuse aux serveurs et aux clients concernés.8 C’est la seule voie réaliste vers des cartes gigantesques, et elle figure à la feuille de route du moteur.

6.2.9 Compensation de latence

Dans un jeu autoritaire, une action de visée arrive au serveur avec le retard du client : au moment où le serveur la traite, les cibles ont bougé. La compensation de latence (lag compensation), également formalisée par Bernier, fait rembobiner au serveur l’état du monde jusqu’à l’instant que le client voyait réellement, valide le tir dans ce passé, puis reprend le fil du présent.3 Elle repose sur le même historique d’instantanés que la détection de désynchronisation (§6.4) et le rollback (§6.2.2) : conserver quelques dizaines de ticks d’état permet aussi bien de rembobiner pour un tir que de comparer un hash divergent.

6.2.10 Les murs de charge : un ordre de grandeur

Combien de clients un serveur tient-il vraiment ? Deux plafonds répondent, calculés sur les constantes réelles du moteur : un tick à 144 Hz, un drain de 256 paquets par tick sur un seul thread, une charge utile de 1400 octets, 32 octets par entité sur le fil.

Le premier mur est celui de la réception, et il est artificiel. pumpNetwork draine au plus maxPacketsPerTick paquets par tick, toutes instances confondues, sur un seul thread :

256×144=36864 paquets/s256 \times 144 = 36\,864 \text{ paquets/s}

Traduit en clients, selon leur cadence d’envoi :

Les clients envoientPlafond avant accumulation
1 input/tick (144/s)~256 clients
30 Hz~1 230 clients
10 Hz~3 690 clients

Au-delà, rien ne bloque : la socket est non bloquante et renvoie EAGAIN. Le problème est pire qu’un blocage, parce qu’il est muet. Les paquets débordent le tampon de réception du noyau (net.core.rmem_max) et le système d’exploitation les jette sans que rien ne le signale. Vous perdez des inputs et vous désynchronisez à l’aveugle. C’est précisément ce que le compteur de contre-pression du serveur rend visible.

Le second mur est celui de l’émission, et c’est le vrai tueur, parce qu’il est algorithmique. Le broadcast complet envoie la même charge à chaque session : il est en O(clients×fragments)O(\text{clients} \times \text{fragments}), autrement dit en O(N2)O(N^2) à l’échelle du serveur. Avec un millier d’entités, un instantané pèse une trentaine de kilooctets, soit environ vingt-quatre fragments par client et par tick :

ClientsDébit sortantVerdict
100~3,6 Gbit/stenable
1 000~36 Gbit/ssature un lien 10 GbE
10 000~360 Gbit/simpossible

Le tableau de datagrammes lui-même finit par coûter : à un million de clients, ce sont des centaines de mégaoctets de métadonnées reconstruites à chaque tick. Le regroupement d’envois (sendBatch, recvmmsg) n’y change rien. Il fait passer N appels système à N/64, un facteur constant qui déplace le mur de réception de « centaines » à « milliers », mais ne touche pas au O(N2)O(N^2) de l’émission. Regrouper des appels système sur un algorithme quadratique, c’est cirer le pont du Titanic.

Le vrai chemin vers l’échelle empile des étages, chacun nommé dans ce livre :

ÉtageCe qu’il débloqueFacteur
recvmmsg + cap de réception configurable + compteur de contre-pressionréception mono-thread×5 à ×10§6.1
RSS : la carte hache l’UDP sur N files, donc N cœursbrise le mur de la socket unique×cœurs§6.1
Gestion d’intérêt et deltacasse le O(N2)O(N^2) en O(Nk)O(N \cdot k)×100 et plus§6.2.6
Sharding per-CPU / per-NUMAlocalité, sans contention×cœursChapitre 2
Kernel bypass (DPDK, io_uring) ou datagrammes QUIC9supprime le syscall par paquet×constant élevé§6.1

Un seul de ces étages compte vraiment pour viser le million : la gestion d’intérêt. Sans elle, ni le regroupement ni le RSS ne sauvent un broadcast quadratique. Le RSS donne la réception, la gestion d’intérêt donne l’émission, le sharding donne les cœurs.

Un spectre, pas un dogme

Ces techniques ne s’excluent pas : un MMORPG moderne empile serveur autoritaire, prédiction et réconciliation, interpolation et dead reckoning, delta acquitté, gestion d’intérêt, budget de pertinence, server meshing et compensation de latence. Chacune répond à un mur précis : latence perçue, bande passante, nombre d’entités, taille de carte. Les moteurs de production les combinent selon leur genre : Overwatch marie un ECS et son netcode10, Photon Quantum pousse un ECS déterministe à prédiction et rollback pour de grands nombres d’entités11, et Rocket League fait tourner toute sa physique en rollback12.

TechniqueMur adresséRéférence type
LockstepBande passante (RTS)Age of Empires5
Rollback (GGPO)Latence (petits comptes)jeux de combat
Prédiction / réconciliationLatence perçueGambetta2
Interpolation / dead reckoningFluidité entre instantanésValve Source3 4
Delta acquittéBande passanteQuake III / DOOM III5
Gestion d’intérêtNombre d’entitésSpatialOS6
Pertinence + budgetBande passante adaptativeUnreal7
Server meshingTaille de carteStar Citizen8
Compensation de latenceÉquité de viséeValve3

Le moteur n’en fige aucune : à l’image du couple Broadcast / AoiBroadcast déjà présent, chaque stratégie est une option activable, et l’ambition à terme est une sélection adaptative : le moteur choisit selon la charge, la carte et le profil réseau de chaque client. Le déterminisme absolu de l’état autoritaire (Chapitre 3) reste, in fine, le socle : il est ce qui permet à la réconciliation, au rollback et à la détection de désynchronisation de comparer deux machines octet pour octet.

6.3 Sérialisation : le Bitstream

6.3.1 Pourquoi pas memcpy ?

La sérialisation naïve par memcpy d’une structure C++ sur le réseau est problématique :

  • Padding : Le compilateur insère des octets de bourrage pour l’alignement, gaspillant de la bande passante.
  • Endianness : Un serveur big-endian (rare mais possible en embarqué) et un client little-endian interpréteront les octets différemment.
  • Portabilité : La taille des types (int, long, pointeurs) varie entre les architectures.

6.3.2 Le Bitstream

Un Bitstream est un tampon binaire qui sérialise les données bit par bit, sans padding ni gaspillage :

class Bitstream {
    std::vector<uint8_t> m_buffer;
    size_t m_bitOffset = 0;

public:
    void WriteBits(uint32_t value, uint8_t numBits) {
        for (int i = numBits - 1; i >= 0; --i) {
            size_t byteIndex = m_bitOffset / 8;
            size_t bitIndex = m_bitOffset % 8;
            if (byteIndex >= m_buffer.size())
                m_buffer.push_back(0);
            if (value & (1u << i))
                m_buffer[byteIndex] |= (1u << (7 - bitIndex));
            ++m_bitOffset;
        }
    }

    uint32_t ReadBits(uint8_t numBits) {
        uint32_t result = 0;
        for (int i = numBits - 1; i >= 0; --i) {
            size_t byteIndex = m_bitOffset / 8;
            size_t bitIndex = m_bitOffset % 8;
            if (m_buffer[byteIndex] & (1u << (7 - bitIndex)))
                result |= (1u << i);
            ++m_bitOffset;
        }
        return result;
    }
};

6.3.3 Bit-packing et quantization

Pour minimiser la bande passante, les données sont compressées sémantiquement :

  • Bit-packing : un booléen utilise 1 bit (pas 8). Un angle de rotation (0-360°) quantifié sur 10 bits donne une résolution de 0.35°, suffisante pour le gameplay, avec 10 bits au lieu de 32.
  • Quantization : Les positions flottantes sont converties en entiers avec une résolution fixe. Une position dans un monde de 1000m, quantifiée sur 16 bits, donne une résolution de 15 mm.
  • Delta compression : Seules les différences par rapport à l’état précédent sont envoyées. Si une entité n’a pas bougé, 0 bits sont transmis pour sa position.13

6.4 State Hashing et détection de désynchronisation

6.4.1 Le hash d’état

Pour détecter les désynchronisations entre le serveur et les clients (dues à un bug, une corruption mémoire ou une tentative de triche), l’état de la simulation est hashé à chaque tick. Le hash est calculé sur les données critiques : positions, vitesses, états d’entités.

uint64_t HashGameState(const GameState& state) {
    uint64_t hash = 0xcbf29ce484222325ULL; // FNV-1a offset basis
    for (const auto& entity : state.entities) {
        // Hash chaque composant déterministe
        hash ^= HashBytes(&entity.position, sizeof(entity.position));
        hash *= 0x100000001b3ULL; // FNV-1a prime
        hash ^= HashBytes(&entity.velocity, sizeof(entity.velocity));
        hash *= 0x100000001b3ULL;
    }
    return hash;
}

Les clients transmettent périodiquement leur hash au serveur. Si un hash client diverge du hash serveur pour le même tick, une désynchronisation est détectée. Le serveur peut alors forcer une resynchronisation complète ou bloquer le client suspect.

6.4.2 Diagnostic de désynchronisation

Lorsqu’une désynchronisation est détectée, le système enregistre les snapshots complets des états client et serveur au tick divergent. On peut alors faire un diagnostic post-mortem : comparer chaque composant pour identifier la source de la divergence (erreur d’arrondi, race condition, bug logique).

6.5 Le Replay System

Le Command Pattern (Chapitre 4) transforme chaque input joueur en un objet sérialisable. En enregistrant la séquence chronologique de toutes les commandes de tous les joueurs, le moteur peut rejouer une partie complète :

struct ReplayFrame {
    uint32_t tick;
    uint8_t  playerIndex;
    Input    input;  // Sérialisé en Bitstream
};

std::vector<ReplayFrame> replayLog;

// Enregistrement
void RecordInput(uint32_t tick, uint8_t player, Input input) {
    replayLog.push_back({tick, player, input});
}

// Rejeu
void Replay(const std::vector<ReplayFrame>& log) {
    GameState state = InitialState();
    for (const auto& frame : log) {
        while (state.tick < frame.tick)
            SimulateFixedStep(state, {});  // Ticks vides
        ApplyInput(state, frame.playerIndex, frame.input);
    }
}

Le replay est rendu possible par le déterminisme : les mêmes inputs reproduisent identiquement les mêmes résultats. C’est un outil de débogage puissant et un mécanisme anti-triche (le serveur peut rejouer l’enregistrement pour vérifier la validité des actions signalées).

Dans le moteur, ce mécanisme a son module dédié, la sérialisation d’état et le replay déterministe : chaque composant qui participe à l’état autoritaire implémente une interface de sérialisation, un enregistreur capture les instantanés (StateSnapshot) et le flux d’inputs, et un lecteur rejoue la session à l’identique. C’est la brique commune du diagnostic de désynchronisation (§6.4.2), du futur rollback et des tests de régression : une partie enregistrée est un test reproductible.

6.6 Communication inter-threads : SPSC lock-free

Le thread réseau (réception de paquets) et le thread de simulation ne doivent jamais se bloquer mutuellement via un mutex. La solution, c’est la queue SPSC lock-free (Single-Producer, Single-Consumer) détaillée au Chapitre 2 (Ring Buffer).

Le flux est :

Figure 6.1 : Les deux queues SPSC entre réseau et simulation. Deux threads, deux queues à sens unique, aucun verrou. Le thread réseau reçoit les paquets UDP et pousse les inputs décodés dans la première queue, que le thread de simulation draine. Ce dernier produit l’état et le pousse dans la seconde queue, que le thread réseau draine à son tour pour l’émission. Chaque queue n’a donc qu’un producteur et qu’un consommateur, d’où le sigle SPSC, et c’est précisément cette contrainte qui autorise l’absence de mutex.

Chaque queue est un Ring Buffer avec des atomiques acquire/release. Le thread réseau ne bloque jamais le thread de simulation, et vice versa : une propriété décisive pour tenir le pas de temps fixe sous les 16.67 ms.

Un dernier choix d’architecture mérite mention : client et serveur partagent une seule et même classe réseau. La duplication (un chemin client, un chemin serveur) finit toujours en dérive de protocole, chaque copie évoluant de son côté ; une implémentation unique, et header-only, élimine la dérive et les problèmes d’ordre de link du même coup.

6.7 Synthèse

ComposantTechnologieRôle
SérialisationBitstream + bit-packingCompression maximale des paquets
SynchronisationRollback Netcode + Lockstep (hybride)Latence minimale + déterminisme
IntégritéState Hashing (FNV-1a)Détection de désynchronisation
DiagnosticReplay System (Command Pattern)Rejeu déterministe + anti-triche
ConcurrenceSPSC Lock-Free Ring BuffersZéro contention entre threads

Notes de bas de page du chapitre 6


Footnotes

  1. Glenn Fiedler, « Networking for Game Programmers », série d’articles de référence sur les architectures réseau pour les jeux multijoueur.

  2. Gabriel Gambetta, « Fast-Paced Multiplayer » : série d’articles de référence sur la prédiction client et la réconciliation avec un serveur autoritaire. 2

  3. Yahn W. Bernier (Valve), « Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization », GDC 2001 : interpolation d’entités et compensation de latence. 2 3 4

  4. Valve Developer Community, « Source Multiplayer Networking » : interpolation, prédiction et compensation de latence dans le moteur Source. 2

  5. Fabien Sanglard, « Quake 3 Source Code Review: Network model », et J.M.P. van Waveren, « The DOOM III Network Architecture » : encodage delta d’instantanés contre une baseline acquittée. Le Lockstep des RTS est décrit par Paul Bettner & Mark Terrano, « 1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond », GDC 2001. 2 3

  6. Improbable, documentation SpatialOS : gestion d’intérêt par requêtes (query-based interest) dans une simulation distribuée. 2

  7. Epic Games, documentation Unreal Engine sur la réplication réseau : pertinence des acteurs (relevancy), priorité, NetUpdateFrequency, NetCullDistance et dormance. 2

  8. Cloud Imperium Games, présentations techniques « Server Meshing » de Star Citizen : couche de réplication persistante et transfert d’entités entre serveurs. 2

  9. QUIC, RFC 9000, et « An Unreliable Datagram Extension to QUIC », RFC 9221 : datagrammes non fiables sur QUIC.

  10. Timothy Ford (Blizzard), « Overwatch Gameplay Architecture and Netcode », GDC 2017 : combinaison d’un ECS et du netcode.

  11. Photon Quantum (Exit Games), documentation technique : ECS déterministe à prédiction et rollback, dimensionné pour un grand nombre d’entités.

  12. Jared Cone (Psyonix), « It IS Rocket Science! The Physics of Rocket League Detailed », GDC 2018 : physique réseau en rollback et interpolation.

  13. Glenn Fiedler, « Reading and Writing Packets » et « Serialization Strategies », détails sur le bit-packing et la sérialisation optimisée.