tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 614Markdown 274TOML 149RON 310606 Definiciones

⎇ main llimphi-cable/src/lib.rs Rust · 849 líneas · 34.8 KB Historia Quién tocó qué crudo

chore: refresco desde el monorepo — 132 miembros, 79 widgets, la galería vuelve y licencia dual d8edd9a9c · Sergio ·

1//! `llimphi-cable` — **el caño entre una app enjaulada y su anfitrión, en Linux.**
2//!
3//! W4 dejó resuelto qué cruza la frontera ([`llimphi_wire_escena`]), quién lo
4//! emite (`llimphi-jaula`) y quién lo pinta (`llimphi-reproductor`). Lo que
5//! faltaba para correr ese mismo cable entre **dos procesos de Linux** —y no
6//! entre una app `.wasm` y el kernel— es el transporte y la vuelta. Esto es eso,
7//! y nada más: enmarca, serializa y ordena.
8//!
9//! ## La forma
10//!
11//! ```text
12//!   huésped (proceso propio)                anfitrión (shuma / pata)
13//!   ────────────────────────                ────────────────────────
14//!   view + layout + geometría
15//!        │  ¿cuánto mide esta cadena?
16//!        ├──────── PedirMedida ──────────►  parley, con SU typesetter
17//!        ◄──────── Medida ───────────────┤  (12 bytes)
18//!        │
19//!        └──────── Cuadro(EscenaWire) ──►   reproducir → píxeles
20//!                                            │
21//!        ◄──────── Puntero / Tamaño ────────┘
22//! ```
23//!
24//! ## El marco, y por qué un cuadro a medias se tira entero
25//!
26//! Cada mensaje va como **largo (`u32` LE) + postcard**. Un lector que se quede
27//! sin bytes a mitad de un marco no puede entregar «lo que llegó»: media escena
28//! es peor que ninguna —se pinta una pantalla que nunca existió— así que el
29//! marco incompleto es un error de lectura y el cuadro se descarta.
30//!
31//! Hay un **tope** ([`MAX_MARCO`]) y no es paranoia de manual: del otro lado hay
32//! un proceso que puede estar roto, y un `u32` de basura pediría alocar 4 GiB
33//! antes de darse cuenta.
34//!
35//! ## El orden, que es la única parte con astucia
36//!
37//! Medir es **síncrono** para el huésped: taffy no puede seguir sin la respuesta.
38//! Pero mientras espera, el anfitrión puede mandarle un clic o un redimensionado.
39//! Si el huésped leyera el primer mensaje que llega y lo tratara como la medida,
40//! interpretaría un evento como tres `f32` y el layout saldría cualquier cosa.
41//!
42//! Por eso [`Huesped::medir`] lee **hasta encontrar la medida** y **encola** todo
43//! lo demás, que se drena después con [`Huesped::eventos`]. Nada se pierde y
44//! nada se confunde. El test `un_evento_que_llega_mientras_se_espera_la_medida_no_se_pierde`
45//! lo fija, con brazo de control.
46
47#![forbid(unsafe_code)]
48
49pub mod vida;
50pub use vida::{Estado, Motivo, Reintentos};
51
52use std::collections::VecDeque;
53use std::io::{self, Read, Write};
54
55use llimphi_entrada::{ImeEvent, KeyEvent, WheelDelta};
56use llimphi_wire_escena::{EscenaWire, MedidaTexto, PeticionMedida};
57use serde::{Deserialize, Serialize};
58
59/// Tope de un marco, en bytes.
60///
61/// 64 MiB: una escena densa anda en decenas de KiB —los patrones de prueba dan
62/// entre 186 y 313 bytes—, así que el tope no es un límite de diseño sino la
63/// defensa contra un largo de basura. Del otro lado hay un proceso que puede
64/// estar roto, y sin esto un `u32` cualquiera pediría alocar 4 GiB.
65pub const MAX_MARCO: usize = 64 * 1024 * 1024;
66
67/// Lo que el huésped le manda al anfitrión.
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub enum DelHuesped {
70    /// Un fotograma listo para pintar.
71    Cuadro(Box<EscenaWire>),
72    /// «¿Cuánto mide esta cadena?» — el huésped queda esperando la respuesta.
73    PedirMedida(Box<PeticionMedida>),
74}
75
76/// Lo que el anfitrión le manda al huésped.
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
78pub enum DelAnfitrion {
79    /// La respuesta a [`DelHuesped::PedirMedida`].
80    ///
81    /// Van los **12 bytes crudos** de [`MedidaTexto`] y no el struct: ese tipo no
82    /// implementa serde a propósito —su representación fija existe para que el
83    /// kernel la escriba sin alocar (`MedidaTexto::BYTES`)—, y agregarle serde
84    /// acá para comodidad de este caño le cambiaría el contrato al otro cable.
85    Medida([u8; MedidaTexto::BYTES]),
86    /// El puntero, **en coordenadas del huésped**.
87    ///
88    /// Dos leyes que se heredan del kernel y no se negocian
89    /// (`wasm/env/presentacion.rs`, `sys_puntero`):
90    ///
91    /// - **El huésped nunca conoce la posición absoluta.** Lo que viaja es
92    ///   local a su tablado, ya traducido.
93    /// - **Sólo viajan los eventos que caen adentro.** Un clic sobre el chrome
94    ///   del anfitrión, sobre otra pestaña o sobre la barra no llega. Lo decide
95    ///   la geometría del anfitrión, no una comprobación del huésped: si fuera
96    ///   una comprobación, sería opcional.
97    ///
98    /// Del lado de Linux las dos salen gratis: el compositor ya entrega las
99    /// coordenadas **locales al nodo** (`View::on_click_at`) y sólo llama al
100    /// handler del nodo que fue golpeado. Va en `f32` y no en los `u16` del
101    /// kernel porque en Llimphi las coordenadas son `f32` en todos lados —
102    /// redondear acá perdería el subpíxel del escalado fraccionario.
103    Puntero { x: f32, y: f32, apretado: bool },
104    /// Una tecla. El huésped la procesa con su propio `on_key`: el cable la
105    /// lleva, no la interpreta.
106    Tecla(KeyEvent),
107    /// Texto compuesto (acentos por dead key, CJK, emoji). Viaja aparte de
108    /// [`DelAnfitrion::Tecla`] porque es otro flujo —Enabled → Preedit* →
109    /// Commit— y aplanarlo perdería la preedición, que es lo que se pinta
110    /// mientras el usuario compone.
111    Ime(ImeEvent),
112    /// La rueda, en líneas lógicas. Convención CSS: positivo = hacia abajo.
113    Rueda(WheelDelta),
114    /// El puntero salió del tablado.
115    ///
116    /// Existe porque su ausencia es un defecto con nombre: sin esto, lo último
117    /// que el huésped sabe del puntero es dónde estaba cuando se fue, y deja
118    /// una fila resaltada para siempre. El anfitrión es el único que puede
119    /// saberlo — el huésped no ve los eventos de afuera, justamente.
120    PunteroSalio,
121    /// El tablado cambió de tamaño.
122    Tamano { ancho: f32, alto: f32 },
123    /// El anfitrión se va. El huésped debería salir en vez de quedarse pintando
124    /// contra un caño muerto.
125    Adios,
126    /// **Andá a este lugar.** La ruta como texto, porque el cable no conoce
127    /// `PathBuf` ni tiene por qué.
128    ///
129    /// Existe porque el anfitrión suele tener su propio navegador al lado del
130    /// tablado —en pata, el árbol de carpetas del sidebar— y sin esto ese árbol
131    /// es decoración: el clic mueve el estado del anfitrión y el huésped, que es
132    /// el que se ve, se queda donde estaba. Los otros eventos son de **entrada
133    /// cruda** (dónde está el dedo, qué tecla); éste es el único que dice una
134    /// intención, y va aparte justamente por eso: mandarlo como una tecla
135    /// sintética sería inventarle al huésped un atajo que no existe.
136    Ir(String),
137    /// **Montá este dispositivo.** El id del nodo de `DispositivosSource`, como
138    /// texto: un disco no es una carpeta, es una fuente sintética con su propio
139    /// espacio de ids, así que [`Self::Ir`] —que habla rutas POSIX— no lo puede
140    /// expresar.
141    ///
142    /// Existe por lo mismo que `Ir`: el anfitrión pinta al lado su propia
143    /// sección de Dispositivos, y sin esto esas filas son decoración.
144    AbrirDispositivo(String),
145    /// **Abrí esta colección.** El id del qutu (un ULID), como texto. Mismo
146    /// motivo que [`Self::AbrirDispositivo`]: una colección tampoco es una
147    /// carpeta.
148    AbrirColeccion(String),
149}
150
151/// Escribe un mensaje enmarcado.
152pub fn escribir<T: Serialize, W: Write>(destino: &mut W, msg: &T) -> io::Result<()> {
153    let cuerpo = postcard::to_allocvec(msg)
154        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
155    if cuerpo.len() > MAX_MARCO {
156        return Err(io::Error::new(
157            io::ErrorKind::InvalidData,
158            format!("marco de {} bytes, tope {MAX_MARCO}", cuerpo.len()),
159        ));
160    }
161    destino.write_all(&(cuerpo.len() as u32).to_le_bytes())?;
162    destino.write_all(&cuerpo)?;
163    destino.flush()
164}
165
166/// Lee un mensaje enmarcado. Bloquea hasta tenerlo **entero**.
167///
168/// Un marco incompleto es un error y no un mensaje a medias: ver la doc del
169/// módulo. `UnexpectedEof` limpio significa que el otro lado cerró.
170pub fn leer<T: for<'de> Deserialize<'de>, R: Read>(origen: &mut R) -> io::Result<T> {
171    let mut largo = [0u8; 4];
172    origen.read_exact(&mut largo)?;
173    let largo = u32::from_le_bytes(largo) as usize;
174    if largo > MAX_MARCO {
175        return Err(io::Error::new(
176            io::ErrorKind::InvalidData,
177            format!("marco de {largo} bytes, tope {MAX_MARCO}"),
178        ));
179    }
180    let mut cuerpo = vec![0u8; largo];
181    origen.read_exact(&mut cuerpo)?;
182    postcard::from_bytes(&cuerpo).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))
183}
184
185/// El extremo del **huésped**: manda cuadros, pide medidas, recibe eventos.
186///
187/// Genérico sobre el flujo para que los tests corran sobre un par de sockets sin
188/// procesos ni archivos — y para que mañana el caño pueda ser otra cosa sin
189/// tocar esta lógica.
190pub struct Huesped<S: Read + Write> {
191    flujo: S,
192    /// Lo que llegó mientras se esperaba una medida. Ver la doc del módulo.
193    pendientes: VecDeque<DelAnfitrion>,
194    /// Cuántas medidas se pidieron. Es el número que el SDD manda medir antes de
195    /// creerle nada al diseño: si un cuadro pide muchas, el trabajo no es
196    /// acelerar el viaje sino cachear.
197    medidas_pedidas: u64,
198}
199
200impl<S: Read + Write> Huesped<S> {
201    pub fn new(flujo: S) -> Self {
202        Self { flujo, pendientes: VecDeque::new(), medidas_pedidas: 0 }
203    }
204
205    /// Cuántas medidas pidió en toda su vida.
206    pub fn medidas_pedidas(&self) -> u64 {
207        self.medidas_pedidas
208    }
209
210    /// Manda un fotograma.
211    pub fn enviar_cuadro(&mut self, escena: &EscenaWire) -> io::Result<()> {
212        escribir(&mut self.flujo, &DelHuesped::Cuadro(Box::new(escena.clone())))
213    }
214
215    /// Pide una medida y **espera** la respuesta, encolando lo que llegue en el
216    /// medio. Ver «El orden» en la doc del módulo.
217    pub fn medir(&mut self, peticion: &PeticionMedida) -> io::Result<MedidaTexto> {
218        self.medidas_pedidas += 1;
219        escribir(&mut self.flujo, &DelHuesped::PedirMedida(Box::new(peticion.clone())))?;
220        loop {
221            match leer::<DelAnfitrion, _>(&mut self.flujo)? {
222                DelAnfitrion::Medida(b) => return Ok(MedidaTexto::de_bytes(&b)),
223                otro => self.pendientes.push_back(otro),
224            }
225        }
226    }
227
228    /// Los eventos que se encolaron mientras se esperaban medidas.
229    ///
230    /// **No lee del caño**: drena lo ya recibido. Leer acá bloquearía el bucle
231    /// del huésped esperando un evento que puede no venir nunca — un asiento
232    /// quieto no manda nada.
233    pub fn eventos(&mut self) -> impl Iterator<Item = DelAnfitrion> + '_ {
234        std::iter::from_fn(move || self.pendientes.pop_front())
235    }
236
237    /// Espera **un** evento del anfitrión, bloqueando. Para el arranque, donde el
238    /// huésped necesita saber su tamaño antes de componer el primer cuadro.
239    pub fn esperar_evento(&mut self) -> io::Result<DelAnfitrion> {
240        if let Some(e) = self.pendientes.pop_front() {
241            return Ok(e);
242        }
243        leer(&mut self.flujo)
244    }
245}
246
247/// El extremo del **anfitrión**: recibe cuadros y pedidos, contesta medidas.
248pub struct Anfitrion<S: Read + Write> {
249    flujo: S,
250}
251
252impl<S: Read + Write> Anfitrion<S> {
253    pub fn new(flujo: S) -> Self {
254        Self { flujo }
255    }
256
257    /// Lee lo próximo que mande el huésped. Bloquea.
258    pub fn recibir(&mut self) -> io::Result<DelHuesped> {
259        leer(&mut self.flujo)
260    }
261
262    /// Contesta una medida.
263    pub fn responder_medida(&mut self, m: MedidaTexto) -> io::Result<()> {
264        escribir(&mut self.flujo, &DelAnfitrion::Medida(m.a_bytes()))
265    }
266
267    /// Atiende **un** mensaje del huésped: guarda el cuadro o contesta la medida.
268    ///
269    /// `medir` es del llamador porque medir necesita un `Typesetter` y este crate
270    /// no dibuja ni mide — la misma línea que separa al reproductor del cable. Es
271    /// la simetría de `llimphi_jaula::medidor::medir_con` del otro lado: el
272    /// marshalling es común, el cruce lo pone quien lo tiene.
273    ///
274    /// **El que contesta tiene que ser el que pinta.** Si el anfitrión midiera
275    /// con un typesetter distinto del que va a shapear, el texto se mediría con
276    /// un juego de fuentes y se pintaría con otro — y eso no mueve el glifo,
277    /// mueve la caja de línea (`SDD-JAULA-LINUX.md`).
278    pub fn atender_uno<F>(&mut self, mut medir: F) -> io::Result<Atendido>
279    where
280        F: FnMut(&PeticionMedida) -> MedidaTexto,
281    {
282        match self.recibir()? {
283            DelHuesped::Cuadro(e) => Ok(Atendido::Cuadro(e)),
284            DelHuesped::PedirMedida(p) => {
285                let m = medir(&p);
286                self.responder_medida(m)?;
287                Ok(Atendido::Medida)
288            }
289        }
290    }
291
292    /// Manda un evento (puntero, tamaño, adiós).
293    pub fn enviar(&mut self, e: &DelAnfitrion) -> io::Result<()> {
294        escribir(&mut self.flujo, e)
295    }
296
297    /// Manda un clic **ya local al tablado**.
298    ///
299    /// Los `(x, y)` son los que `View::on_click_at` entrega: relativos al nodo.
300    /// El anfitrión no tiene que restar nada ni saber dónde está su cuerpo
301    /// dentro de la surface — que es justo la trampa que `SDD-IDE.md` anota para
302    /// la rueda («shuma hospedada en el cajón de pata no sabe el origen de su
303    /// cuerpo»): acá no aparece porque el compositor ya resolvió el origen.
304    pub fn enviar_clic(&mut self, x: f32, y: f32) -> io::Result<()> {
305        self.enviar(&DelAnfitrion::Puntero { x, y, apretado: true })
306    }
307
308    /// Manda un movimiento del puntero, local al tablado.
309    pub fn enviar_movimiento(&mut self, x: f32, y: f32) -> io::Result<()> {
310        self.enviar(&DelAnfitrion::Puntero { x, y, apretado: false })
311    }
312
313    /// Avisa que el puntero se fue. Ver [`DelAnfitrion::PunteroSalio`]: sin esto
314    /// el huésped deja una fila resaltada para siempre.
315    pub fn enviar_salida(&mut self) -> io::Result<()> {
316        self.enviar(&DelAnfitrion::PunteroSalio)
317    }
318
319    pub fn enviar_tecla(&mut self, e: KeyEvent) -> io::Result<()> {
320        self.enviar(&DelAnfitrion::Tecla(e))
321    }
322
323    pub fn enviar_ime(&mut self, e: ImeEvent) -> io::Result<()> {
324        self.enviar(&DelAnfitrion::Ime(e))
325    }
326
327    pub fn enviar_rueda(&mut self, d: WheelDelta) -> io::Result<()> {
328        self.enviar(&DelAnfitrion::Rueda(d))
329    }
330
331    /// Avisa el tamaño del tablado. Se manda **sólo cuando cambia**: el huésped
332    /// re-compone al recibirlo, y mandarlo por cuadro lo haría re-componer
333    /// siempre. Quien llama guarda el último — ver [`Tamanos`].
334    /// Le pide al huésped que se pare en `ruta`. Ver [`DelAnfitrion::Ir`].
335    pub fn enviar_ir(&mut self, ruta: &str) -> io::Result<()> {
336        self.enviar(&DelAnfitrion::Ir(ruta.to_string()))
337    }
338
339    /// Le pide al huésped que monte un dispositivo. Ver
340    /// [`DelAnfitrion::AbrirDispositivo`].
341    pub fn enviar_abrir_dispositivo(&mut self, id: &str) -> io::Result<()> {
342        self.enviar(&DelAnfitrion::AbrirDispositivo(id.to_string()))
343    }
344
345    /// Le pide al huésped que abra una colección. Ver
346    /// [`DelAnfitrion::AbrirColeccion`].
347    pub fn enviar_abrir_coleccion(&mut self, id: &str) -> io::Result<()> {
348        self.enviar(&DelAnfitrion::AbrirColeccion(id.to_string()))
349    }
350
351    pub fn enviar_tamano(&mut self, ancho: f32, alto: f32) -> io::Result<()> {
352        self.enviar(&DelAnfitrion::Tamano { ancho, alto })
353    }
354
355    /// Se despide. El huésped debería salir en vez de seguir pintando contra un
356    /// caño muerto.
357    pub fn despedirse(&mut self) -> io::Result<()> {
358        self.enviar(&DelAnfitrion::Adios)
359    }
360
361}
362
363/// Qué se atendió en una vuelta de [`Anfitrion::atender_uno`].
364#[derive(Debug)]
365pub enum Atendido {
366    /// Llegó un fotograma: el huésped terminó de componer.
367    Cuadro(Box<EscenaWire>),
368    /// Se contestó una medida. El huésped sigue componiendo.
369    Medida,
370}
371
372impl Anfitrion<std::os::unix::net::UnixStream> {
373    /// Atiende hasta que llegue un cuadro **o se acabe el plazo**.
374    ///
375    /// Es la respuesta práctica a la pregunta que abrió F4.bis: medir es síncrono
376    /// para el huésped, así que si el anfitrión contestara una medida por cuadro
377    /// de UI, un árbol con cincuenta etiquetas tardaría cincuenta cuadros en
378    /// aparecer. Acá se contestan **todas las que hagan falta** dentro de una
379    /// misma vuelta, y el primer fotograma sale enseguida.
380    ///
381    /// El plazo es lo que impide que eso se convierta en un cuelgue: un huésped
382    /// trabado —o uno malicioso que pide medidas para siempre— no puede quedarse
383    /// con el hilo que dibuja. Se vuelve con `Ok(None)` y el tablado sigue
384    /// mostrando lo último que tenía; a la vuelta siguiente se retoma donde
385    /// quedó, porque el socket no pierde nada.
386    ///
387    /// **`Ok(None)` no es un error y no se cuenta como muerte.** Distinguirlos es
388    /// justamente lo que evita matar a un huésped lento.
389    pub fn atender_hasta<F>(
390        &mut self,
391        plazo: core::time::Duration,
392        mut medir: F,
393    ) -> io::Result<Option<Box<EscenaWire>>>
394    where
395        F: FnMut(&PeticionMedida) -> MedidaTexto,
396    {
397        let hasta = std::time::Instant::now() + plazo;
398        loop {
399            let queda = hasta.saturating_duration_since(std::time::Instant::now());
400            if queda.is_zero() {
401                return Ok(None);
402            }
403            // El timeout se vuelve a poner en cada vuelta con lo que QUEDA: uno
404            // fijo por lectura dejaría que N medidas lentas sumaran N plazos.
405            self.flujo.set_read_timeout(Some(queda))?;
406            match self.atender_uno(&mut medir) {
407                Ok(Atendido::Cuadro(e)) => return Ok(Some(e)),
408                Ok(Atendido::Medida) => continue,
409                Err(e) if agotado(&e) => return Ok(None),
410                Err(e) => return Err(e),
411            }
412        }
413    }
414}
415
416/// ¿Este error es «se acabó el tiempo» y no «se rompió algo»?
417///
418/// Los dos nombres existen porque el sistema usa uno u otro según la
419/// configuración del socket, y tratar el plazo como una rotura mataría al
420/// huésped lento en vez de esperarlo.
421fn agotado(e: &io::Error) -> bool {
422    matches!(e.kind(), io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut)
423}
424
425/// Recuerda el último tamaño avisado, para no repetirlo.
426///
427/// Existe porque el anfitrión conoce su rectángulo **en cada pintada** y la
428/// tentación es avisarlo ahí: el huésped re-compone al recibir un tamaño, así
429/// que avisarlo por cuadro lo haría re-componer sesenta veces por segundo sin
430/// que nada haya cambiado. Comparar es de una línea; olvidarse cuesta el
431/// fotograma entero.
432#[derive(Debug, Clone, Copy, Default)]
433pub struct Tamanos {
434    ultimo: Option<(f32, f32)>,
435}
436
437impl Tamanos {
438    pub fn new() -> Self {
439        Self::default()
440    }
441
442    /// El último tamaño avisado, o `None` si **nunca se avisó ninguno**. Es el
443    /// dato que separa «el huésped no contesta» de «al huésped nadie le dijo de
444    /// qué tamaño es», que en pantalla se ven igual.
445    pub fn ultimo(&self) -> Option<(f32, f32)> {
446        self.ultimo
447    }
448
449    /// `true` si este tamaño hay que avisarlo (es el primero, o cambió).
450    pub fn cambio(&mut self, ancho: f32, alto: f32) -> bool {
451        // Comparación exacta a propósito: un layout que devuelve el mismo número
452        // devuelve EL MISMO número. Una tolerancia acá dejaría pasar cambios
453        // reales de subpíxel en pantallas con escalado fraccionario.
454        if self.ultimo == Some((ancho, alto)) {
455            return false;
456        }
457        self.ultimo = Some((ancho, alto));
458        true
459    }
460}
461
462#[cfg(test)]
463mod tests {
464    use super::*;
465    use std::os::unix::net::UnixStream;
466
467    fn peticion() -> PeticionMedida {
468        PeticionMedida {
469            cadena: "hola".into(),
470            estilo: Default::default(),
471            ancho_max: None,
472        }
473    }
474
475    fn medida(w: f32) -> MedidaTexto {
476        MedidaTexto { width: w, full_width: w, height: 10.0 }
477    }
478
479    /// El marco sobrevive el viaje: lo que entra es lo que sale.
480    #[test]
481    fn un_mensaje_va_y_vuelve_igual() {
482        let (a, b) = UnixStream::pair().unwrap();
483        let mut hue = Huesped::new(a);
484        let mut anf = Anfitrion::new(b);
485
486        hue.enviar_cuadro(&EscenaWire::new()).unwrap();
487        match anf.recibir().unwrap() {
488            DelHuesped::Cuadro(e) => assert_eq!(e.ordenes().len(), 0),
489            otro => panic!("llegó {otro:?}"),
490        }
491    }
492
493    /// **La razón de ser del encolado.** El anfitrión manda un evento justo
494    /// mientras el huésped espera una medida. Sin la cola, el huésped leería el
495    /// evento y lo interpretaría como la medida — tres `f32` sacados de un clic.
496    #[test]
497    fn un_evento_que_llega_mientras_se_espera_la_medida_no_se_pierde() {
498        let (a, b) = UnixStream::pair().unwrap();
499        let mut hue = Huesped::new(a);
500        let mut anf = Anfitrion::new(b);
501
502        // El anfitrión se adelanta: manda un evento ANTES de la respuesta.
503        anf.enviar(&DelAnfitrion::Puntero { x: 3.0, y: 4.0, apretado: true }).unwrap();
504        anf.responder_medida(medida(42.0)).unwrap();
505
506        let m = hue.medir(&peticion()).unwrap();
507        assert_eq!(m.width, 42.0, "la medida es LA medida, no el evento de antes");
508
509        let evs: Vec<_> = hue.eventos().collect();
510        assert_eq!(
511            evs,
512            vec![DelAnfitrion::Puntero { x: 3.0, y: 4.0, apretado: true }],
513            "y el evento sigue ahí, en orden"
514        );
515    }
516
517    /// Brazo de control del anterior: sin nada en el medio, la cola queda vacía.
518    /// Sin esto, un `eventos()` que siempre devolviera algo pasaría el test de
519    /// arriba igual.
520    #[test]
521    fn sin_eventos_en_el_medio_la_cola_queda_vacia() {
522        let (a, b) = UnixStream::pair().unwrap();
523        let mut hue = Huesped::new(a);
524        let mut anf = Anfitrion::new(b);
525
526        anf.responder_medida(medida(7.0)).unwrap();
527        assert_eq!(hue.medir(&peticion()).unwrap().width, 7.0);
528        assert_eq!(hue.eventos().count(), 0);
529    }
530
531    /// Varios eventos en el medio conservan su orden, y el segundo pedido de
532    /// medida no se come los que quedaron.
533    #[test]
534    fn dos_medidas_seguidas_conservan_los_eventos_de_las_dos() {
535        let (a, b) = UnixStream::pair().unwrap();
536        let mut hue = Huesped::new(a);
537        let mut anf = Anfitrion::new(b);
538
539        anf.enviar(&DelAnfitrion::Tamano { ancho: 100.0, alto: 50.0 }).unwrap();
540        anf.responder_medida(medida(1.0)).unwrap();
541        anf.enviar(&DelAnfitrion::Puntero { x: 1.0, y: 1.0, apretado: false }).unwrap();
542        anf.responder_medida(medida(2.0)).unwrap();
543
544        assert_eq!(hue.medir(&peticion()).unwrap().width, 1.0);
545        assert_eq!(hue.medir(&peticion()).unwrap().width, 2.0);
546        let evs: Vec<_> = hue.eventos().collect();
547        assert_eq!(evs.len(), 2, "los dos eventos, ninguno perdido");
548        assert!(matches!(evs[0], DelAnfitrion::Tamano { .. }), "y en orden de llegada");
549        assert!(matches!(evs[1], DelAnfitrion::Puntero { .. }));
550    }
551
552    /// `eventos()` **no lee del caño**: si leyera, el huésped se colgaría
553    /// esperando un evento que un asiento quieto nunca manda.
554    #[test]
555    fn drenar_eventos_no_bloquea_con_el_cano_vacio() {
556        let (a, _b) = UnixStream::pair().unwrap();
557        let mut hue = Huesped::new(a);
558        assert_eq!(hue.eventos().count(), 0);
559    }
560
561    /// El contador es el instrumento que el SDD manda mirar antes de creerle
562    /// nada al diseño.
563    #[test]
564    fn las_medidas_se_cuentan() {
565        let (a, b) = UnixStream::pair().unwrap();
566        let mut hue = Huesped::new(a);
567        let mut anf = Anfitrion::new(b);
568        assert_eq!(hue.medidas_pedidas(), 0);
569        anf.responder_medida(medida(1.0)).unwrap();
570        anf.responder_medida(medida(1.0)).unwrap();
571        hue.medir(&peticion()).unwrap();
572        hue.medir(&peticion()).unwrap();
573        assert_eq!(hue.medidas_pedidas(), 2);
574    }
575
576    /// Una tecla viaja **entera**: el carácter, el estado, el texto resultante y
577    /// los modificadores. Es lo que hace que un `Ctrl+S` adentro de la jaula sea
578    /// el mismo `Ctrl+S` que afuera.
579    #[test]
580    fn una_tecla_llega_con_todo_lo_que_lleva() {
581        use llimphi_entrada::{Key, KeyState, Modifiers, NamedKey};
582        let (a, b) = UnixStream::pair().unwrap();
583        let mut hue = Huesped::new(a);
584        let mut anf = Anfitrion::new(b);
585
586        let tecla = KeyEvent {
587            key: Key::Character("s".into()),
588            state: KeyState::Pressed,
589            text: Some("s".into()),
590            modifiers: Modifiers { ctrl: true, ..Default::default() },
591            repeat: false,
592        };
593        anf.enviar_tecla(tecla.clone()).unwrap();
594        match hue.esperar_evento().unwrap() {
595            DelAnfitrion::Tecla(t) => {
596                assert_eq!(t, tecla, "ni el texto ni los modificadores se pierden");
597                assert!(t.modifiers.ctrl, "y el Ctrl sigue puesto del otro lado");
598            }
599            otro => panic!("llegó {otro:?}"),
600        }
601
602        // Y una tecla con nombre, que es el otro brazo del enum.
603        let flecha = KeyEvent {
604            key: Key::Named(NamedKey::ArrowDown),
605            state: KeyState::Pressed,
606            text: None,
607            modifiers: Modifiers::default(),
608            repeat: true,
609        };
610        anf.enviar_tecla(flecha.clone()).unwrap();
611        match hue.esperar_evento().unwrap() {
612            DelAnfitrion::Tecla(t) => assert_eq!(t, flecha),
613            otro => panic!("llegó {otro:?}"),
614        }
615    }
616
617    /// El texto compuesto va por su propio carril: aplanarlo contra
618    /// [`DelAnfitrion::Tecla`] perdería la preedición, que es lo que se pinta
619    /// mientras el usuario todavía está componiendo.
620    #[test]
621    fn la_preedicion_del_ime_sobrevive_el_viaje() {
622        let (a, b) = UnixStream::pair().unwrap();
623        let mut hue = Huesped::new(a);
624        let mut anf = Anfitrion::new(b);
625
626        let pre = ImeEvent::Preedit { text: "á".into(), cursor: Some((0, 2)) };
627        anf.enviar_ime(pre.clone()).unwrap();
628        assert_eq!(hue.esperar_evento().unwrap(), DelAnfitrion::Ime(pre));
629
630        anf.enviar_ime(ImeEvent::Commit("á".into())).unwrap();
631        assert_eq!(
632            hue.esperar_evento().unwrap(),
633            DelAnfitrion::Ime(ImeEvent::Commit("á".into())),
634            "y el commit llega distinguible de la preedición"
635        );
636    }
637
638    /// El clic viaja con las coordenadas **tal como las da el compositor**:
639    /// locales al nodo. El anfitrión no resta nada.
640    #[test]
641    fn el_clic_viaja_local_y_apretado() {
642        let (a, b) = UnixStream::pair().unwrap();
643        let mut hue = Huesped::new(a);
644        let mut anf = Anfitrion::new(b);
645
646        anf.enviar_clic(12.5, 4.0).unwrap();
647        assert_eq!(
648            hue.esperar_evento().unwrap(),
649            DelAnfitrion::Puntero { x: 12.5, y: 4.0, apretado: true }
650        );
651        // Y el movimiento es el mismo evento sin apretar: un huésped que quiera
652        // hover no necesita otro canal.
653        anf.enviar_movimiento(1.0, 2.0).unwrap();
654        assert_eq!(
655            hue.esperar_evento().unwrap(),
656            DelAnfitrion::Puntero { x: 1.0, y: 2.0, apretado: false }
657        );
658    }
659
660    /// **La salida del puntero es un evento, no la ausencia de eventos.** Sin
661    /// esto, lo último que el huésped sabe es dónde estaba el puntero cuando se
662    /// fue, y deja una fila resaltada para siempre.
663    #[test]
664    fn la_salida_del_puntero_se_avisa() {
665        let (a, b) = UnixStream::pair().unwrap();
666        let mut hue = Huesped::new(a);
667        let mut anf = Anfitrion::new(b);
668        anf.enviar_salida().unwrap();
669        assert_eq!(hue.esperar_evento().unwrap(), DelAnfitrion::PunteroSalio);
670    }
671
672    /// El tamaño se avisa cuando cambia, y **sólo** cuando cambia: el huésped
673    /// re-compone al recibirlo.
674    #[test]
675    fn el_tamano_solo_se_avisa_cuando_cambia() {
676        let mut t = Tamanos::new();
677        assert!(t.cambio(100.0, 50.0), "el primero siempre se avisa");
678        assert!(!t.cambio(100.0, 50.0), "el mismo, no");
679        assert!(!t.cambio(100.0, 50.0));
680        assert!(t.cambio(100.0, 51.0), "un píxel de alto SÍ es un cambio");
681        assert!(t.cambio(99.5, 51.0), "y medio píxel de ancho también");
682    }
683
684    /// Los eventos de entrada también se encolan si llegan mientras se espera
685    /// una medida. Es el mismo mecanismo que ya estaba, con el tipo nuevo: si
686    /// una tecla se colara como medida, el layout saldría de un scancode.
687    #[test]
688    fn una_tecla_en_el_medio_de_una_medida_tampoco_se_pierde() {
689        use llimphi_entrada::{Key, KeyState, Modifiers};
690        let (a, b) = UnixStream::pair().unwrap();
691        let mut hue = Huesped::new(a);
692        let mut anf = Anfitrion::new(b);
693
694        anf.enviar_tecla(KeyEvent {
695            key: Key::Character("x".into()),
696            state: KeyState::Pressed,
697            text: Some("x".into()),
698            modifiers: Modifiers::default(),
699            repeat: false,
700        })
701        .unwrap();
702        anf.responder_medida(medida(5.0)).unwrap();
703
704        assert_eq!(hue.medir(&peticion()).unwrap().width, 5.0);
705        let evs: Vec<_> = hue.eventos().collect();
706        assert_eq!(evs.len(), 1);
707        assert!(matches!(evs[0], DelAnfitrion::Tecla(_)));
708    }
709
710    /// **El primer fotograma sale en una vuelta, no en cincuenta.** El huésped
711    /// pide tres medidas y recién después manda el cuadro; el anfitrión contesta
712    /// las tres y devuelve el cuadro en la misma llamada.
713    #[test]
714    fn se_contestan_todas_las_medidas_que_hagan_falta_antes_del_cuadro() {
715        let (a, b) = UnixStream::pair().unwrap();
716        let mut anf = Anfitrion::new(b);
717
718        // El huésped, en su hilo: mide tres veces y después compone.
719        let h = std::thread::spawn(move || {
720            let mut hue = Huesped::new(a);
721            for _ in 0..3 {
722                hue.medir(&peticion()).unwrap();
723            }
724            hue.enviar_cuadro(&EscenaWire::new()).unwrap();
725            hue.medidas_pedidas()
726        });
727
728        let mut medidas = 0;
729        let cuadro = anf
730            .atender_hasta(std::time::Duration::from_secs(5), |_p| {
731                medidas += 1;
732                medida(10.0)
733            })
734            .unwrap();
735
736        assert!(cuadro.is_some(), "el cuadro llegó en la MISMA vuelta");
737        assert_eq!(medidas, 3, "y se contestaron las tres medidas de camino");
738        assert_eq!(h.join().unwrap(), 3);
739    }
740
741    /// **Un huésped trabado no se queda con el hilo que dibuja.** Pide medidas
742    /// para siempre y nunca manda cuadro; el anfitrión vuelve con `None` al
743    /// vencer el plazo, sin error y sin matarlo.
744    #[test]
745    fn un_huesped_que_no_manda_cuadro_devuelve_el_hilo_al_vencer_el_plazo() {
746        let (a, b) = UnixStream::pair().unwrap();
747        let mut anf = Anfitrion::new(b);
748
749        let (fin_tx, fin_rx) = std::sync::mpsc::channel::<()>();
750        let h = std::thread::spawn(move || {
751            let mut hue = Huesped::new(a);
752            // Mide hasta que el test le diga que pare. Nunca compone.
753            while fin_rx.try_recv().is_err() {
754                if hue.medir(&peticion()).is_err() {
755                    break;
756                }
757            }
758        });
759
760        let arranque = std::time::Instant::now();
761        let cuadro = anf
762            .atender_hasta(std::time::Duration::from_millis(150), |_p| medida(1.0))
763            .expect("vencer el plazo NO es un error");
764        assert!(cuadro.is_none(), "no hubo cuadro, y eso está bien");
765        assert!(
766            arranque.elapsed() < std::time::Duration::from_secs(2),
767            "y volvió cerca del plazo, no cuando al huésped se le dio la gana"
768        );
769
770        let _ = fin_tx.send(());
771        drop(anf);
772        let _ = h.join();
773    }
774
775    /// Con el caño quieto y nadie del otro lado, el plazo vence igual: no se
776    /// cuelga esperando a un huésped que no va a hablar.
777    #[test]
778    fn con_el_cano_mudo_el_plazo_vence_solo() {
779        let (a, b) = UnixStream::pair().unwrap();
780        let _vivo = a; // se sostiene: cerrarlo daría EOF, que es otra cosa
781        let mut anf = Anfitrion::new(b);
782        let r = anf.atender_hasta(std::time::Duration::from_millis(80), |_p| medida(1.0));
783        assert!(matches!(r, Ok(None)), "plazo vencido, no error: dio {r:?}");
784    }
785
786    /// **Brazo de control del anterior**: si el otro lado CIERRA, eso sí es una
787    /// muerte y tiene que distinguirse de un plazo vencido. Sin este test, un
788    /// `atender_hasta` que devolviera `Ok(None)` ante cualquier error dejaría al
789    /// anfitrión esperando para siempre a un huésped que ya no está.
790    #[test]
791    fn el_cierre_no_se_confunde_con_un_plazo_vencido() {
792        let (a, b) = UnixStream::pair().unwrap();
793        drop(a);
794        let mut anf = Anfitrion::new(b);
795        let e = anf
796            .atender_hasta(std::time::Duration::from_secs(5), |_p| medida(1.0))
797            .expect_err("el otro lado cerró: eso es una muerte");
798        assert_eq!(e.kind(), io::ErrorKind::UnexpectedEof);
799    }
800
801    /// `atender_uno` contesta la medida con lo que el llamador diga, y le pasa
802    /// la petición entera — no un resumen.
803    #[test]
804    fn atender_uno_contesta_con_lo_que_el_anfitrion_mide() {
805        let (a, b) = UnixStream::pair().unwrap();
806        let mut anf = Anfitrion::new(b);
807        let h = std::thread::spawn(move || {
808            let mut hue = Huesped::new(a);
809            hue.medir(&peticion()).unwrap()
810        });
811        let mut vista = None;
812        match anf.atender_uno(|p| {
813            vista = Some(p.cadena.clone());
814            medida(33.0)
815        }) {
816            Ok(Atendido::Medida) => {}
817            otro => panic!("esperaba una medida, dio {otro:?}"),
818        }
819        assert_eq!(vista.as_deref(), Some("hola"), "la petición llega entera");
820        assert_eq!(h.join().unwrap().width, 33.0);
821    }
822
823    /// Un largo de basura no puede hacer alocar 4 GiB.
824    #[test]
825    fn un_largo_imposible_es_error_y_no_una_alocacion() {
826        let mut basura: &[u8] = &u32::MAX.to_le_bytes();
827        let r = leer::<DelAnfitrion, _>(&mut basura);
828        let e = r.expect_err("un marco de 4 GiB tiene que rebotar");
829        assert_eq!(e.kind(), io::ErrorKind::InvalidData);
830    }
831
832    /// Un marco cortado a la mitad es un error, no medio mensaje.
833    #[test]
834    fn un_marco_incompleto_no_entrega_nada() {
835        let mut cortado: &[u8] = &[10, 0, 0, 0, 1, 2, 3];
836        let r = leer::<DelAnfitrion, _>(&mut cortado);
837        assert!(r.is_err(), "faltan bytes: no hay mensaje");
838    }
839
840    /// El otro lado cerró: se distingue de un marco roto.
841    #[test]
842    fn el_cierre_del_otro_lado_se_reconoce() {
843        let (a, b) = UnixStream::pair().unwrap();
844        drop(b);
845        let mut anf = Anfitrion::new(a);
846        let e = anf.recibir().expect_err("el otro lado cerró");
847        assert_eq!(e.kind(), io::ErrorKind::UnexpectedEof);
848    }
849}