Skip to content

Construire un monde ouvert dans le navigateur, partie 2 : la physique dans un Worker et la crainte de la latence des commandes

Par Oleg Sidorkin, CTO et cofondateur de Cinevva

Vous découvrez la série ? Consultez le guide de la série. Il explique ce qu’est un spike et contient les liens vers toutes les parties.

Quand on développe des jeux multijoueurs dans le navigateur depuis assez longtemps, on finit toujours par entendre ce débat.

« La physique dans un Worker offre une architecture propre. La physique sur le thread principal semble plus sûre. »

Les deux peuvent être vrais. Ce qui compte, ce sont les sensations de contrôle et la latence avec de vraies commandes.

Le spike 2 a été conçu pour répondre à cette question avec des mesures, pas des opinions.

Ouvrir le spike 2 dans un nouvel onglet ↗ · Voir le code source

Nous avons réutilisé le terrain du spike 1 et intégré Rapier dans un Worker de module dédié. L’état des commandes était envoyé au Worker à chaque frame, la simulation y était exécutée, puis la position faisant autorité était renvoyée au moteur de rendu.

Les métriques clés étaient la latence entre la commande et le mouvement visible, ainsi que la durée des étapes de simulation physique. Nous avons également surveillé les saccades pendant les déplacements normaux, les accélérations en sprint et les enchaînements de sauts.

Le résultat a dépassé nos attentes. Avec la structure et la cadence de nos messages, la frontière du Worker n’était pas le principal facteur de latence. Les commandes semblaient toujours immédiates, et c’était la seule chose qui comptait pour les joueurs.

L’une des difficultés de cette phase concernait le risque de mauvaise interprétation. Après un résultat concluant, les équipes ont souvent tendance à généraliser à l’excès et à considérer que la question architecturale est définitivement réglée. Ce n’est pas le cas. Nous n’avons validé qu’un scénario concret et qu’un profil matériel. Les spikes suivants devaient encore réévaluer nos hypothèses lorsque la charge du GPU et du streaming a augmenté.

Ce spike nous a également permis d’améliorer notre processus. Nous avons commencé à afficher par défaut la télémétrie des temps d’exécution dans le HUD de nos spikes interactifs. Les discussions de l’équipe sont alors passées de « il y a quelque chose qui cloche » à « ce chemin a ajouté 1,2 ms ».

Dans la partie 3, nous abordons les expériences moins spectaculaires qui nous ont évité de coûteuses surprises par la suite : charge liée à la diffusion, contraintes mobiles et fiabilité de la génération des comportements.

Technologies mentionnées dans ce chapitre

Rapier. Un moteur physique écrit en Rust qui se compile en WebAssembly pour être utilisé dans le navigateur. Il gère les corps rigides, les colliders, les articulations, les contrôleurs de personnage et le lancer de rayons avec des performances 2 à 3 fois inférieures à celles du code natif. Pour les mondes ouverts, Rapier fournit des contrôleurs de personnage joueur — déplacement sur le terrain, franchissement de marches et glissement sur les pentes —, la collision des objets, le lancer de rayons pour les interactions et les volumes déclencheurs. Consultez la documentation de Rapier et notre guide des technologies 3D pour navigateur consacré à la physique.

Web Workers. Des threads de navigateur qui exécutent du JavaScript — ou du Wasm — en dehors du thread principal. Exécuter la simulation physique dans un Worker signifie qu’un appel lourd à world.step() ne bloque pas le rendu. À chaque frame, le thread principal envoie l’état des commandes au Worker via postMessage, puis reçoit en retour les positions faisant autorité. Le coût en latence correspond aux deux trajets des messages — environ 0,1 à 0,5 ms chacun sur ordinateur. L’avantage est que le thread de rendu n’est jamais bloqué par la détection des collisions. Les objets transférables — transfert d’ArrayBuffer — éliminent le coût de copie des grands tableaux de positions.

WebAssembly (Wasm). Un format d’instructions binaire qui s’exécute dans les navigateurs à une vitesse proche du code natif. Rapier, Havok et Recast se compilent tous en Wasm. Avec Rapier-Wasm, une étape de simulation physique prend généralement entre 0,5 et 2 ms pour quelques centaines de corps, contre 5 à 15 ms pour un équivalent en JavaScript. Les modules Wasm sont chargés sous forme de fichiers .wasm récupérés avec le code de liaison JavaScript. Consultez la spécification WebAssembly.

Latence entre la commande et l’affichage. Le délai entre l’appui sur une touche et le changement visuel qui en résulte à l’écran. Pour qu’un mouvement semble « immédiat », ce délai doit rester inférieur à environ 80 ms. Dans une configuration où la physique s’exécute dans un Worker, les délais de la chaîne s’additionnent :

L=tinput+2tmsg+tstep+trender+tvsync

Événement keydown sur le thread principal, postMessage envoyé au Worker puis renvoyé (2tmsg), étape de simulation physique, application de la nouvelle position par le moteur de rendu, puis synchronisation verticale suivante. Chaque terme est faible pris isolément — environ 0,1 à 0,5 ms par trajet de message sur ordinateur —, mais ils s’additionnent. C’est pourquoi nous avons mesuré L directement au lieu de nous fier au schéma d’architecture.


Partie 2 sur 12.
Précédent : Partie 1 — Nous avons commencé par essayer de tout casser
Suivant : Partie 3 — Les spikes peu spectaculaires qui nous ont sauvés
Guide de la série : /fr/blog/2026-02-25-open-world-browser-series-guide