1# llimphi-jaula
2
3La cáscara del lado **app**: corre el ciclo de Llimphi dentro de una jaula WASM y
4emite una [`EscenaWire`](../llimphi-wire-escena/LEEME.md) en vez de rasterizar.
5
6Es el gemelo de [`llimphi-wawa`](../llimphi-wawa/LEEME.md), del otro lado de la
7frontera: aquélla recibe y ejecuta, ésta compone y manda.
8
9W4 de [`03_ukupacha/wawa/PLAN-LLIMPHI.md`](../../../03_ukupacha/wawa/PLAN-LLIMPHI.md).
10
11```
12View<Msg> → mount → layout → paint → EscenaWire → postcard → …
13…………………………… frontera WASM ……………………………
14 … → shapear nativo → rasterizar
15```
16
17## Lo que hay que entender de acá
18
19Las dos cáscaras corren el **mismo `llimphi-compositor`** — el mismo mount, el
20mismo layout de taffy, el mismo `render.rs` de 4.000 líneas. Lo único que cambia
21es el alias `Lienzo` y, con él, el `Typesetter`.
22
23Eso es el resultado de W4 que vale decir en voz alta: **no hay un compositor para
24apps y otro para el kernel.** Un widget escrito una vez sirve en Linux, adentro
25del kernel, y adentro de una jaula.
26
27## Las dos cosas que ésta hace y la del kernel no
28
29### Pregunta las medidas
30
31taffy no puede dimensionar una etiqueta sin saber cuánto mide, y medir es
32shapear — justo lo que no debe pasar adentro de la jaula: el 30,3x del intérprete
33se comería el 72% del fotograma (W4.1). Así que el medidor es un **argumento**:
34
35```rust
36let mut jaula = Jaula::new(320.0, 72.0, |p: &Peticion| {
37 // En wawa: un `sys_medir_texto` que cruza a parley del lado nativo.
38 // En un test: un `Typesetter` de parley de verdad.
39 medir_del_otro_lado(p)
40});
41let montado = jaula.fotograma(vista());
42let bytes = postcard::to_allocvec(jaula.escena())?;
43```
44
45Sin medidor no se devuelve cero — un cero silencioso colapsaría el layout entero
46y la pantalla saldría vacía «funcionando». Se estima grosero **y se cuenta**:
47`jaula.medidas_estimadas()` distinto de cero significa que ese layout es
48aproximado.
49
50En wawa, ese medidor ya viene hecho — es [`medidor::wawa`](src/medidor.rs), que
51envuelve el syscall `sys_medir_texto`:
52
53```rust
54let mut jaula = Jaula::new(ancho, alto, llimphi_jaula::medidor::wawa);
55```
56
57`sys_medir_texto` **no está gateado por permiso**: medir texto no es un
58privilegio, es aritmética sobre una fuente, y toda app que dibuje la necesita. Lo
59que sí lleva es cuota, porque el shaping lo paga el kernel fuera del `fuel` de la
60app. Fuera de wasm el medidor devuelve `None` en vez de un número inventado.
61
62### Se queda con el árbol
63
64El `Mounted` no cruza la frontera, así que el hit-test se resuelve de este lado:
65el kernel manda una coordenada y la app contesta qué se tocó.
66
67```rust
68if let Some(msg) = jaula.click(&montado, x, y) {
69 modelo = App::update(modelo, msg);
70}
71```
72
73Es lo contrario de la cáscara del kernel, que **no puede** hacer hit-test
74justamente porque el árbol se quedó acá.
75
76## Por qué está fuera del workspace
77
78Enciende `llimphi-text/wire`, que es un backend excluyente de `vello` (el que usa
79todo el resto del repo por default). La unificación de features de cargo los
80juntaría y dispararía el `compile_error!` que `llimphi-text` tiene puesto para
81delatarlo — es la misma trampa que ya mordió con `llimphi-wawa`.
82
83Que las dos estén afuera no es una molestia: es el diseño. Son dos procesos en
84dos lados de una frontera.
85
86```bash
87cargo test --manifest-path 02_ruway/llimphi/llimphi-jaula/Cargo.toml \
88 --no-default-features --features std
89cargo check --manifest-path 02_ruway/llimphi/llimphi-jaula/Cargo.toml \
90 --target wasm32-unknown-unknown --no-default-features --features libm
91```
92
93## El fixture
94
95Como el encoder y el reproductor no pueden compilarse juntos, la costura del test
96es la misma que la de producción: **bytes**.
97
98```bash
99cargo run --example emitir_fixture --no-default-features --features std
100```
101
102Compone una vista con el compositor de verdad y deja los bytes en
103`../llimphi-wawa/pruebas/fixture_jaula.postcard`, donde los levantan el test
104`la_escena_que_emitio_el_compositor_llega_a_pixeles` **y el arranque del kernel**.
105Si alguien cambia el formato del cable de un solo lado, esos dos lo dicen.
LEEME.md
llimphi-jaula
La cáscara del lado app: corre el ciclo de Llimphi dentro de una jaula WASM y
emite una EscenaWire en vez de rasterizar.
Es el gemelo de llimphi-wawa, del otro lado de la
frontera: aquélla recibe y ejecuta, ésta compone y manda.
W4 de 03_ukupacha/wawa/PLAN-LLIMPHI.md.
View<Msg> → mount → layout → paint → EscenaWire → postcard → …
…………………………… frontera WASM ……………………………
… → shapear nativo → rasterizar
Lo que hay que entender de acá
Las dos cáscaras corren el mismo llimphi-compositor — el mismo mount, el
mismo layout de taffy, el mismo render.rs de 4.000 líneas. Lo único que cambia
es el alias Lienzo y, con él, el Typesetter.
Eso es el resultado de W4 que vale decir en voz alta: no hay un compositor para
apps y otro para el kernel. Un widget escrito una vez sirve en Linux, adentro
del kernel, y adentro de una jaula.
Las dos cosas que ésta hace y la del kernel no
Pregunta las medidas
taffy no puede dimensionar una etiqueta sin saber cuánto mide, y medir es
shapear — justo lo que no debe pasar adentro de la jaula: el 30,3x del intérprete
se comería el 72% del fotograma (W4.1). Así que el medidor es un argumento:
let mut jaula = Jaula::new(320.0, 72.0, |p: &Peticion| {
// En wawa: un `sys_medir_texto` que cruza a parley del lado nativo.
// En un test: un `Typesetter` de parley de verdad.
medir_del_otro_lado(p)
});
let montado = jaula.fotograma(vista());
let bytes = postcard::to_allocvec(jaula.escena())?;
Sin medidor no se devuelve cero — un cero silencioso colapsaría el layout entero
y la pantalla saldría vacía «funcionando». Se estima grosero y se cuenta:
jaula.medidas_estimadas() distinto de cero significa que ese layout es
aproximado.
En wawa, ese medidor ya viene hecho — es medidor::wawa, que
envuelve el syscall sys_medir_texto:
let mut jaula = Jaula::new(ancho, alto, llimphi_jaula::medidor::wawa);
sys_medir_texto no está gateado por permiso: medir texto no es un
privilegio, es aritmética sobre una fuente, y toda app que dibuje la necesita. Lo
que sí lleva es cuota, porque el shaping lo paga el kernel fuera del fuel de la
app. Fuera de wasm el medidor devuelve None en vez de un número inventado.
Se queda con el árbol
El Mounted no cruza la frontera, así que el hit-test se resuelve de este lado:
el kernel manda una coordenada y la app contesta qué se tocó.
if let Some(msg) = jaula.click(&montado, x, y) {
modelo = App::update(modelo, msg);
}
Es lo contrario de la cáscara del kernel, que no puede hacer hit-test
justamente porque el árbol se quedó acá.
Por qué está fuera del workspace
Enciende llimphi-text/wire, que es un backend excluyente de vello (el que usa
todo el resto del repo por default). La unificación de features de cargo los
juntaría y dispararía el compile_error! que llimphi-text tiene puesto para
delatarlo — es la misma trampa que ya mordió con llimphi-wawa.
Que las dos estén afuera no es una molestia: es el diseño. Son dos procesos en
dos lados de una frontera.
cargo test --manifest-path 02_ruway/llimphi/llimphi-jaula/Cargo.toml \
--no-default-features --features std
cargo check --manifest-path 02_ruway/llimphi/llimphi-jaula/Cargo.toml \
--target wasm32-unknown-unknown --no-default-features --features libm
El fixture
Como el encoder y el reproductor no pueden compilarse juntos, la costura del test
es la misma que la de producción: bytes.
cargo run --example emitir_fixture --no-default-features --features std
Compone una vista con el compositor de verdad y deja los bytes en
../llimphi-wawa/pruebas/fixture_jaula.postcard, donde los levantan el test
la_escena_que_emitio_el_compositor_llega_a_pixeles y el arranque del kernel.
Si alguien cambia el formato del cable de un solo lado, esos dos lo dicen.