Architecture
L'architecture de GWEN repose sur une division fondamentale : TypeScript pour la logique de jeu, Rust/WASM pour la performance. Ce guide vous explique les deux couches, comment elles communiquent, et comment le modèle Entity-Component-System (ECS) rassemble tout.
Les deux couches
Cœur Rust/WASM
Le cœur de GWEN est un module WebAssembly pré-compilé (gwen_core.wasm) écrit en Rust. Cette couche gère tout ce qui doit être rapide :
- Moteur ECS — Stocke les entités, les composants et gère les requêtes efficacement
- Tableaux de composants — Les données des composants vivent dans la mémoire linéaire de WASM en disposition Structure-of-Arrays (SoA) pour l'efficacité du cache
- Physique — La simulation physique (via Rapier) s'exécute dans WASM
- Primitives mathématiques — Les mathématiques vectorielles, matricielles et les quaternions pour le chemin critique
Vous n'écrivez jamais de Rust. Le module WASM arrive pré-compilé dans les paquets npm (@gwenjs/core, @gwenjs/physics2d, etc.).
Couche TypeScript
Toute la logique de jeu que vous écrivez vit en TypeScript. Cela inclut :
- Systèmes — Fonctions qui lisent et écrivent les données de composants chaque frame
- Graphe de scène — Acteurs, préfabriqués et gestion de scène
- Cycle de vie du plugin —
mount(),onStart(),onUpdate(),onDestroy(),unmount() - Outils Vite — Serveur de développement, HMR, bundling
La couche TypeScript appelle WASM pour interroger les entités, lire les données de composants et appliquer les mises à jour physiques.
Le pont WASM
La communication entre TypeScript et WASM se fait via la mémoire partagée et les appels de fonctions. Il n'y a pas de sérialisation ; au lieu de cela, la mémoire linéaire de WASM est exposée à TypeScript via SharedArrayBuffer et les vues TypedArray.
┌──────────────────────────────────────┐
│ TypeScript Code │
│ - defineSystem() │
│ - useQuery() │
│ - Position.x[entityId] = 10 │
└──────────────┬───────────────────────┘
│ Accès direct à la mémoire (pas de copie)
┌──────────────┴───────────────────────┐
│ SharedArrayBuffer │
│ ┌────────────────────────────────┐ │
│ │ Mémoire linéaire WASM │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ Position.x: [1, 2, 3] │ │ │
│ │ │ Position.y: [4, 5, 6] │ │ │
│ │ └──────────────────────────┘ │ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────┘Quand vous écrivez Position.x[entityId] = 10 dans un système, vous écrivez directement dans la mémoire de WASM sans surcharge. Pas de marshaling, pas d'allocations, pas de garbage collection.
Aperçu de l'ECS
GWEN utilise le modèle Entity-Component-System (ECS) pour organiser les données et la logique du jeu :
Entités
Une entité est un ID entier qui regroupe les composants associés.
// En interne, une entité est juste un nombre
const playerId = 42Il n'y a pas de hiérarchie d'héritage, pas de hiérarchie de classes. Une entité est juste un conteneur.
Composants
Un composant est une structure de données typée contenant des données pures — aucune logique, aucune méthode.
import { defineComponent, Types } from '@gwenjs/core'
export const Position = defineComponent({
name: 'Position',
schema: { x: Types.f32, y: Types.f32 },
})
export const Health = defineComponent({
name: 'Health',
schema: { current: Types.i32, max: Types.i32 },
})Plusieurs composants s'attachent à la même entité pour la décrire. Un joueur peut avoir Position, Health, Velocity, et PlayerTag.
Systèmes
Un système est une fonction qui s'exécute chaque frame sur toutes les entités correspondant à une requête. Les systèmes lisent et écrivent les données de composants.
import { defineSystem, useQuery, onUpdate } from '@gwenjs/core/system'
import { Position, Velocity } from './components'
export const MovementSystem = defineSystem(() => {
const entities = useQuery([Position, Velocity])
onUpdate((dt) => {
for (const id of entities) {
Position.x[id] += Velocity.x[id] * dt
Position.y[id] += Velocity.y[id] * dt
}
})
})La requête [Position, Velocity] retourne tous les ID d'entités qui ont les deux composants. Le système met à jour leurs positions en fonction de la vélocité.
Pourquoi l'ECS ?
Pas d'héritage, pas de maux de tête du polymorphisme. Juste des données + logique. Les systèmes sont des fonctions pures qui lisent et écrivent les données. Cela rend GWEN :
- Rapide — Disposition de la mémoire efficace pour le cache (SoA) et exécution data-parallel
- Flexible — Composez n'importe quelle combinaison de composants ; ajoutez de nouveaux systèmes à tout moment
- Testable — Les systèmes ne dépendent pas d'une hiérarchie de classes ; ce sont juste des fonctions
Cycle de vie du plugin
Les moteurs GWEN chargent des plugins, qui se montent et se démontent pendant le cycle de vie du jeu :
Boot
↓
engine.start()
↓
mount() sur chaque plugin
↓
Charger la scène initiale
↓
onStart() sur chaque acteur → onStart() sur chaque système
↓
Boucle de jeu :
- onUpdate(dt) sur chaque système
- Rendu (via votre renderer)
↓
onDestroy() sur chaque acteur → onDestroy() sur chaque système
↓
Décharger la scène
↓
unmount() sur chaque plugin
↓
ArrêtLes systèmes enregistrent les callbacks pendant leur phase de configuration (defineSystem(() => { ... })). Ces callbacks se déclenchent pendant les étapes de cycle de vie appropriées.
Flux de données : De TypeScript à WASM et retour
Voici comment un frame typique s'exécute :
1. Le code TypeScript crée une nouvelle entité
→ Appel de la fonction WASM : spawn(components...)
→ WASM alloue l'ID de l'entité, initialise les données de composant
2. Le frame commence
→ TypeScript appelle useQuery([Position, Velocity])
→ La requête retourne un tableau d'ID d'entités correspondantes
→ Le code TypeScript itère et lit/écrit les données de composant
→ Les données vivent dans la mémoire de WASM ; TypeScript y accède via la vue TypedArray
3. Tick physique
→ Le moteur physique WASM (Rapier) s'exécute
→ Met à jour les composants Rigidbody et Transform
4. Rendu
→ TypeScript lit Position, Rotation, etc.
→ Passe au renderer (Babylon.js, Three.js, Canvas 2D, etc.)
5. Fin du frame
→ Synchronisation de l'état physique et graphique
→ Le frame suivant commenceL'idée clé : pas de copie de données. TypeScript accède directement à la mémoire de WASM. Votre boucle de jeu est efficace en mémoire.
Prochaines étapes
- Le moteur — Créer et configurer votre premier moteur GWEN.
- Composants — Définir les structures de données que votre jeu utilisera.
- Systèmes — Écrire les systèmes qui donnent vie aux composants.
- Structure du projet — Voir comment un vrai projet GWEN organise les systèmes et les composants.