Aller au contenu principal

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.

PaquetVous apporteDans un scriptDans un pluginDans une extension de composant
@was/ecsentities, composants, le monde
@was/engineles classes de composants
@was/signalsle système de réactivité
@was/svdtschémas et validation
@was/utilsde petits utilitaires
@was/uila bibliothèque de composants de l'éditeur
@was/iconsle jeu d'icônes de l'éditeur
reactReact 19
Les scripts obtiennent la réactivité via ctx.effect

Un 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();
FonctionFait
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.

Pourquoi cela compte même si vous ne l'importez jamais

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.

La feuille de styles est préconstruite

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

AppelRenvoie
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
C'est vers queryEntities qu'il faut se tourner

Il 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

AppelFait
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 · .componentsson id, et ses composants indexés par type

Component — les données

AppelFait
.x ou get('x')lire un champ ; dans un effet, cela y abonne aussi
$datale tout comme objet simple
$rawDatal'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 listes

Lire 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é

AppelFait
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 · unpackvers 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