Les paquets partagés
L'éditeur, le runtime et votre propre code sont bâtis à partir des mêmes paquets, et plusieurs d'entre eux vous sont accessibles. Lesquels dépend de l'endroit où votre code tourne.
| Paquet | Vous apporte | Dans un script | Dans un plugin | Dans une extension de composant |
|---|---|---|---|---|
@was/ecs | entities, composants, le monde | ✓ | ✓ | ✓ |
@was/engine | les classes de composants | ✓ | ✓ | ✓ |
@was/signals | le système de réactivité | — | ✓ | ✓ |
@was/svdt | schémas et validation | ✓ | ✓ | — |
@was/utils | de petits utilitaires | ✓ | ✓ | — |
@was/ui | la bibliothèque de composants de l'éditeur | — | ✓ | ✓ |
@was/icons | le jeu d'icônes de l'éditeur | — | ✓ | ✓ |
react | React 19 | — | ✓ | ✓ |
ctx.effectUn script ne peut pas importer le paquet signals directement — ctx.effect est le même mécanisme,
avec la durée de vie gérée pour vous : un effet meurt avec son objet au lieu de fuir.
@was/signals — le système de réactivité
C'est ce sur quoi toute la plateforme est bâtie : des valeurs qui savent qui les lit, si bien qu'un changement met à jour exactement ce qui en dépendait, et rien d'autre.
import { signal, computed, effect, batch, untracked } from '@was/signals';
const score = signal(0);
const doubled = computed(() => score.value * 2);
const stop = effect(() => {
render(score.value); // relancé seulement quand score change
});
batch(() => {
score.value += 1;
score.value += 1; // les effets tournent une fois, pas deux
});
stop();
| Fonction | Fait |
|---|---|
signal(v) | une valeur observable ; on lit et écrit .value |
computed(fn) | une valeur dérivée, recalculée seulement quand ce qu'elle lit change |
effect(fn) | tourne maintenant, et à chaque changement de ce qu'il a lu ; renvoie un arrêt |
batch(fn) | grouper des écritures pour que les observateurs tournent une fois, à la fin |
untracked(fn) | lire sans devenir une dépendance |
ref(obj) | rendre un objet entier réactif, jusqu'à ses feuilles |
snapshot(obj) | une copie simple, non réactive |
raw(obj) | l'objet sous-jacent, sans suivi |
readonly(obj) | une vue qu'on ne peut pas écrire |
Pour les panneaux React, il existe un compagnon avec useSignal, useComputed, useSignalEffect
et useLiveSignal : un composant se re-rend depuis un signal sans aucun câblage.
C'est ce qui fait que l'éditeur et le runtime se comportent comme ils le font : rien n'interroge en boucle, rien ne se re-rend par précaution, et un panneau se met à jour parce que la donnée a changé, pas parce qu'on le lui a dit. → Pourquoi ce moteur
@was/ui — les composants de l'éditeur
Les panneaux de plugins et les extensions de composant peuvent être bâtis avec la bibliothèque même qu'utilise l'éditeur : ils ont donc l'air natifs plutôt que d'une page web embarquée. Une trentaine de composants :
Accordion · Avatar · Button · Card · Checkbox · Chip · CloseButton
DimensionInput · Draggable · Dropdown · Flag · Icon · IconButton · Input
Menu · Modal · Outside · Panel · Popover · Portal · Render · Scroll
Search · Section · Segmented · Select · Skeleton · Switcher · Tabs
Toast · Toolbar · Tooltip
Plus @was/icons pour le jeu d'icônes.
Les panneaux utilisent un jeu fixe de classes utilitaires livré avec la bibliothèque. Les valeurs
arbitraires comme text-[13px] n'y sont pas — utilisez un style en ligne pour les tailles hors
échelle.
@was/ecs — le modèle du monde
Entity, Component, Space. Un plugin lit et modifie une scène avec exactement la même API que
le moteur utilise en interne ; il n'existe pas d'« API plugin » séparée et plus faible.
Space — le monde
| Appel | Renvoie |
|---|---|
getEntity(id) | une entity, ou rien |
hasEntity(id) | si elle existe |
getEntities() | toutes |
queryEntities(A, B, …) | chaque entity portant tous ces composants |
makeQueryEntities(A, B, …) | la même requête, préconstruite, pour un usage répété |
createEntity(id?, components?) | une nouvelle entity, ajoutée au monde |
addEntity(…e) · removeEntity(…e) | en mettre une dedans ou l'en sortir |
getSystem(S) · hasSystem(S) | atteindre un système |
execute() | exécuter une image |
queryEntities qu'il faut se tournerIl s'appuie sur un index : demander « tout ce qui a une lumière et un transform » coûte peu.
getEntities() n'est pas la même chose — il vous tend le monde entier et vous laisse filtrer.
Entity — une chose du monde
| Appel | Fait |
|---|---|
getComponent(Type) | un composant, ou null |
getComponents(A, B) | plusieurs d'un coup, dans cet ordre |
hasComponent(A, B) | si elle les porte tous |
addComponent(…c) | ajouter ; ajouter un type qu'elle a déjà est ignoré |
removeComponent(…c) | retirer, par classe ou par instance |
component(fn) | exécuter fn pour chaque composant, maintenant et à l'avenir |
clone() · clean() | la copier, ou la dépouiller |
.id · .components | son id, et ses composants indexés par type |
Component — les données
| Appel | Fait |
|---|---|
.x ou get('x') | lire un champ ; dans un effet, cela y abonne aussi |
$data | le tout comme objet simple |
$rawData | l'objet stocké, sans abonnement — en lecture seule |
update({ … }) | la seule façon d'écrire |
updateAt(path, value) | écrire profondément dans un gros composant sans tout revalider |
version(field?) | un compteur qui avance quand quelque chose change, pour dépendre d'un champ sans le lire |
reset(data?) | retour aux valeurs par défaut |
clone() | une copie |
version() est l'astuce derrière les performances sur les grandes listesLire une grande valeur vous abonne à chacune de ses feuilles. Dépendre de son compteur de version signifie que vous apprenez qu'elle a changé sans la surveiller entièrement — c'est ainsi qu'un panneau survit à une liste de vingt mille éléments.
@was/engine — les classes de composants
Chaque type de composant sous forme de classe : TransformComponent, MaterialComponent,
RigidBodyComponent et les autres. Vous importez la classe et la passez à getComponent,
hasComponent ou query.
La liste complète avec chaque champ : référence des composants.
@was/svdt — les schémas
La couche de validation qui décrit les composants. Compilée plutôt qu'interprétée, et c'est pourquoi l'analyse ne coûte rien sur le chemin de l'animation.
import { s, compile } from '@was/svdt';
const schema = s.object({ speed: s.f64(1), name: s.string('') });
const codec = compile(schema); // compiler une fois, à la déclaration — jamais à chaque appel
const value = codec.parse(input);
Décrire une forme
Nombres : s.f32 s.f64 s.i8 s.u8 s.i16 s.u16 s.i32 s.u32, chacun prenant une valeur par défaut.
Scalaires : s.bool, s.string, s.color, s.literal, s.enum, s.unknown, s.ref.
Vecteurs : s.vec2, s.vec3, s.mat4.
Composites : s.object, s.variant, s.array, s.record, s.union, s.preprocess, s.lazy.
Chaînables sur n'importe lequel : .default(v), .optional(), .nullable(), .min(n),
.max(n), .int().
Ce que donne un codec compilé
| Appel | Fait |
|---|---|
parse(input) | valider et normaliser, en levant une erreur si l'entrée est mauvaise |
safeParse(input) | pareil, en renvoyant un succès ou une erreur au lieu de lever |
parseAt(path, v) | valider un champ sans toucher au reste |
equals(a, b) | comparaison profonde, générée pour cette forme |
diff(a, b) | ce qui a changé |
apply(target, p) | appliquer un diff |
invert(p) | l'inverser — la base de l'annulation |
pack · unpack | vers et depuis une forme binaire compacte |
Et pour inspecter un schéma plutôt que des données : introspect, keys, requiredKeys.
@was/utils
Les petites choses dont tout le monde se sert : pick, omit, assign, keys, values,
entries, capitalize, basename, extname, dispose (coller ensemble des fonctions de
nettoyage), et les utilitaires MIME derrière la validation des imports.
Suite : Glossaire