Saltar al contenido principal

Los paquetes compartidos

El editor, el runtime y tu propio código están construidos con los mismos paquetes, y varios de ellos están a tu disposición. Cuáles depende de dónde corra tu código.

PaqueteTe daEn un scriptEn un pluginEn una extensión de componente
@was/ecsentities, componentes, el mundo
@was/enginelas clases de componente
@was/signalsel sistema de reactividad
@was/svdtesquemas y validación
@was/utilsutilidades pequeñas
@was/uila biblioteca de componentes del editor
@was/iconsel juego de iconos del editor
reactReact 19
Los scripts obtienen reactividad a través de ctx.effect

Un script no puede importar el paquete de signals directamente: ctx.effect es el mismo mecanismo con la vida gestionada por ti, así que un efecto muere con su objeto en vez de fugarse.

@was/signals — el sistema de reactividad

Es aquello sobre lo que se construye toda la plataforma: valores que saben quién los lee, de modo que un cambio actualiza exactamente lo que dependía de él y nada más.

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); // se vuelve a ejecutar solo cuando cambia score
});

batch(() => {
score.value += 1;
score.value += 1; // los efectos corren una vez, no dos
});

stop();
FunciónHace
signal(v)un valor que se puede observar; se lee y escribe .value
computed(fn)un valor derivado, recalculado solo cuando cambia lo que lee
effect(fn)corre ahora, y de nuevo cuando cambia lo que leyó; devuelve un stop
batch(fn)agrupar escrituras para que los observadores corran una vez, al final
untracked(fn)leer sin convertirse en dependencia
ref(obj)hacer reactivo un objeto entero, hasta sus hojas
snapshot(obj)una copia llana, no reactiva
raw(obj)el objeto subyacente, sin seguimiento
readonly(obj)una vista que no se puede escribir

Para paneles de React hay un compañero con useSignal, useComputed, useSignalEffect y useLiveSignal, de modo que un componente se vuelve a renderizar desde una signal sin cableado alguno.

Por qué importa aunque nunca lo importes

Es lo que hace que el editor y el runtime se comporten como lo hacen: nada sondea, nada se rerenderiza por si acaso, y un panel se actualiza porque cambiaron los datos, no porque alguien se lo dijera. → Por qué este motor

@was/ui — los componentes del editor

Los paneles de plugins y las extensiones de componente se pueden construir con la misma biblioteca que usa el editor, así que parecen nativos en vez de una página web incrustada. Unos treinta componentes:

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

Más @was/icons para el juego de iconos.

La hoja de estilos viene precompilada

Los paneles usan un conjunto fijo de clases utilitarias que se entrega con la biblioteca. Los valores arbitrarios como text-[13px] no están; usa un style en línea para tamaños fuera de la escala.

@was/ecs — el modelo del mundo

Entity, Component, Space. Un plugin lee y modifica una escena con exactamente la misma API que usa el motor por dentro; no existe una «API de plugins» aparte y más débil.

Space — el mundo

LlamadaDevuelve
getEntity(id)una entity, o nada
hasEntity(id)si existe
getEntities()todas
queryEntities(A, B, …)cada entity que lleve todos esos componentes
makeQueryEntities(A, B, …)la misma consulta, prearmada, para uso repetido
createEntity(id?, components?)una entity nueva, añadida al mundo
addEntity(…e) · removeEntity(…e)meter una o sacarla
getSystem(S) · hasSystem(S)llegar a un sistema
execute()ejecutar un fotograma
Lo que hay que usar es queryEntities

Está respaldado por un índice, así que pedir «todo lo que tenga una luz y un transform» es barato. getEntities() no es lo mismo: te entrega el mundo entero y te obliga a filtrar.

Entity — una cosa del mundo

LlamadaHace
getComponent(Type)un componente, o null
getComponents(A, B)varios a la vez, en ese orden
hasComponent(A, B)si los lleva todos
addComponent(…c)añadir; añadir un tipo que ya tiene se ignora
removeComponent(…c)quitar, por clase o por instancia
component(fn)ejecutar fn por cada componente, ahora y en el futuro
clone() · clean()copiarla, o dejarla pelada
.id · .componentssu id, y sus componentes indexados por tipo

Component — los datos

LlamadaHace
.x o get('x')leer un campo; dentro de un efecto eso además lo suscribe
$datatodo ello como objeto llano
$rawDatael objeto guardado, sin suscribir — de solo lectura
update({ … })la única forma de escribir
updateAt(path, value)escribir dentro de un componente grande sin revalidarlo entero
version(field?)un contador que avanza al cambiar algo, para depender de un campo sin leerlo
reset(data?)volver a los valores por defecto
clone()una copia
version() es el truco detrás del rendimiento con listas grandes

Leer un valor grande te suscribe a cada hoja que hay dentro. Depender en su lugar de su contador de versión significa que te enteras de que cambió sin vigilarlo entero, que es como un panel sobrevive a una lista de veinte mil elementos.

@was/engine — las clases de componente

Cada tipo de componente como clase: TransformComponent, MaterialComponent, RigidBodyComponent y los demás. Importas la clase y se la pasas a getComponent, hasComponent o query.

La lista completa con cada campo: referencia de componentes.

@was/svdt — esquemas

La capa de validación con la que se describen los componentes. Compilada en vez de interpretada, que es por lo que analizar no cuesta nada en el camino de la animación.

import { s, compile } from '@was/svdt';

const schema = s.object({ speed: s.f64(1), name: s.string('') });
const codec = compile(schema); // compila una vez, al declarar — nunca en cada llamada
const value = codec.parse(input);

Describir una forma

Números: s.f32 s.f64 s.i8 s.u8 s.i16 s.u16 s.i32 s.u32, cada uno con un valor por defecto. Escalares: s.bool, s.string, s.color, s.literal, s.enum, s.unknown, s.ref. Vectores: s.vec2, s.vec3, s.mat4. Compuestos: s.object, s.variant, s.array, s.record, s.union, s.preprocess, s.lazy.

Encadenables sobre cualquiera de ellos: .default(v), .optional(), .nullable(), .min(n), .max(n), .int().

Qué te da un códec compilado

LlamadaHace
parse(input)validar y normalizar, lanzando error con una entrada mala
safeParse(input)lo mismo, devolviendo éxito o error en vez de lanzar
parseAt(path, v)validar un campo sin tocar el resto
equals(a, b)comparación profunda, generada para esta forma
diff(a, b)qué cambió
apply(target, p)aplicar un diff
invert(p)invertir uno — la base de deshacer
pack · unpackhacia y desde una forma binaria compacta

Y para inspeccionar un esquema en lugar de datos: introspect, keys, requiredKeys.

@was/utils

Cosas pequeñas que usa todo el mundo: pick, omit, assign, keys, values, entries, capitalize, basename, extname, dispose (pegar funciones de limpieza entre sí) y las utilidades de MIME que hay detrás de la validación de subidas.


Siguiente: Glosario