diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md index ed24566..245bbc2 100644 --- a/renaser/CHANGELOG.md +++ b/renaser/CHANGELOG.md @@ -630,3 +630,45 @@ separado, a un reinicio. - **Reinicio** — `boot` respeta el disco; memoriosa despierta con las 5 celdas intactas y el testigo en ámbar: su `init` releyó el estado del grafo. El estado por-app sobrevivió al apagón. + +## Fase 8a — El compositor teselante — 2026-05-22 + +Hasta la Fase 7, cada app llevaba su región escrita a mano en el manifiesto — +coordenadas fijas, una composición rígida. La Fase 8 entrega esa decisión a un +COMPOSITOR: el kernel ya no coloca las ventanas a mano, las TESELA con +`mirada-layout`. + +### Añadido +- **`kernel/compositor.rs`** — el compositor teselante. Enlaza `mirada-layout` + —el mismo núcleo `no_std` que ordena las ventanas del compositor Wayland de + brahman, ya enlazado por `path` cruzando la frontera de workspace— y, con su + algoritmo `MasterStack`, calcula un marco para cada app. `disponer(n, …)` + devuelve un marco por ventana; `area_apps`, la zona teselable (la pantalla + menos la franja superior de la consola). +- `consola`: `pintar_escenario` —inunda el área de apps y tiñe cada marco con + el color de panel, de modo que el teselado se vea como una rejilla— y el + color `Color::PANEL`. + +### Cambiado +- `consola::volcar_marco` ya no deposita el fotograma en `(region.x, region.y)`: + CENTRA el fotograma natural de la app dentro de su marco teselado y lo + recorta a sus bordes. +- `ContextoCapacidades` lleva ahora `marco` (el rectángulo teselado) y + `natural_ancho`/`natural_alto` (el tamaño del lienzo de la app); + `sys_render_frame` valida el fotograma contra el tamaño natural. +- `AplicacionWasm` / `encender_app` / `cargar_userspace` cablean el marco + teselado. `cargar_userspace` recibe las dimensiones de pantalla, tesela con + el compositor y pinta el escenario antes de encender las apps. +- `RegionPantalla::pixeles` se retira — sin uso desde que `sys_render_frame` + mide contra el tamaño natural. +- **Las apps NO cambian.** Renderizan su lienzo natural, fijo; el kernel lo + compone centrado en el marco. El compositor reordena la pantalla sin que + ninguna app toque una sola instrucción. + +### Verificado +- QEMU (captura headless): las cinco apps de génesis aparecen teseladas en + `MasterStack` — `hola` como ventana maestra (izquierda, 60% del ancho) y + memoriosa, discola, glotona y cronista apiladas a la derecha. Cada lienzo, + centrado en su panel; las apps desalojadas (discola, glotona) tiñen su marco + entero con la baliza. El registro de arranque permanece legible en la franja + superior de la consola. diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md index b84c152..1f80ce4 100644 --- a/renaser/CLAUDE.md +++ b/renaser/CLAUDE.md @@ -45,10 +45,10 @@ cp target/wasm32-unknown-unknown/release/.wasm ../../kernel/assets/.wa - `apps/` — aplicaciones del userspace, módulos `.wasm` (target `wasm32-unknown-unknown`). Workspaces propios, excluidos. -Módulos del kernel (`kernel/src/`): `main`, `grafico`, `consola`, `baliza`, -`sync`, `gdt`, `interrupts`, `pic`, `drivers/`, `almacen`, `memory/`, -`async_system/`, `texto`, `wasm/`. El detalle de cada uno está en -`ARCHITECTURE.md`. +Módulos del kernel (`kernel/src/`): `main`, `grafico`, `consola`, `compositor`, +`baliza`, `sync`, `gdt`, `interrupts`, `pic`, `drivers/`, `almacen`, +`manifiesto`, `memory/`, `async_system/`, `texto`, `wasm/`. El detalle de cada +uno está en `ARCHITECTURE.md`. ## Toolchain @@ -75,8 +75,9 @@ QEMU 11, OVMF en `/usr/share/edk2/x64/OVMF.4m.fd` (sin módulo KVM → TCG). Fases 1 a 5, 6.0, 6.1, 6.2 y la Fase 7 COMPLETA —el userspace nace del grafo de objetos: Manifiesto de Génesis (7a), imagen sembrada por `boot` (7b) y -persistencia inter-sesión por-app (7c)—. Todo verificado en QEMU. Ver -`ROADMAP.md`. +persistencia inter-sesión por-app (7c)—. Fase 8 en curso: 8a hecha —el +compositor tesela las ventanas con `mirada-layout`—. Todo verificado en QEMU. +Ver `ROADMAP.md`. ## Flujo de trabajo diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md index 0365bf9..8168f83 100644 --- a/renaser/DIARIO.md +++ b/renaser/DIARIO.md @@ -318,6 +318,30 @@ Sus inquilinos cierran los ojos en el apagón y los abren, al volver, justo donde los cerraron. La casa ya no sólo perdura: recuerda, uno por uno, a los suyos. +## El plano prestado + +Hasta hoy, cada inquilino tenía su habitación marcada con coordenadas exactas +en el cuaderno: aquí empieza tu cuarto, aquí acaba. Funcionaba, pero era +rígido — mover un tabique exigía reescribir el cuaderno a mano. + +Hoy la casa contrató a un arquitecto. Ya nadie dibuja las habitaciones a mano: +se le dice cuántos inquilinos hay y él reparte el suelo entero —una pieza +amplia para el principal, las demás en una columna ordenada al lado—, sin +huecos ni solapes, ajustando cada cuarto al número de quienes viven. A eso se +le llama teselar, y es un oficio delicado. + +Lo más hermoso es de dónde salió ese arquitecto. renaser tiene una casa +hermana —mirada, el escritorio que vive sobre Linux— y esa casa ya tenía uno, +con buen ojo para repartir cuartos. En vez de contratar a otro, renaser le +pidió prestado el suyo: la misma cabeza, el mismo plano, sirviendo a dos +mundos tan distintos como un escritorio gráfico y un núcleo desnudo sobre el +metal. Una sola sabiduría, dos casas. + +Y los inquilinos ni se enteraron. Cada uno sigue pintando su cuadro del tamaño +de siempre; es el arquitecto quien decide en qué pared colgarlo, y la casa +quien lo centra con cuidado en el espacio que le tocó. Nadie tuvo que cambiar +para vivir mejor repartido. + --- *El diario continúa. La próxima página la escribirá la próxima jornada.* diff --git a/renaser/FASE8.md b/renaser/FASE8.md new file mode 100644 index 0000000..0e4ed24 --- /dev/null +++ b/renaser/FASE8.md @@ -0,0 +1,56 @@ +# renaser — Fase 8 :: El compositor teselante + +Plan de ataque. Para el estado general ver `ROADMAP.md`; para la arquitectura, +`ARCHITECTURE.md`. + +## El objetivo + +Hasta la Fase 7, cada aplicación llevaba su región escrita a mano en el +Manifiesto de Génesis: cuatro coordenadas fijas (`region_x`, `region_y`, +`region_ancho`, `region_alto`). El kernel las leía y componía cada app en su +rectángulo, inmóvil — una disposición rígida, decidida en la siembra. + +La Fase 8 entrega esa decisión a un COMPOSITOR. El kernel deja de colocar las +ventanas a mano: las **tesela**. El motor del teselado es `mirada-layout` — el +mismo núcleo `no_std` que ordena las ventanas del compositor Wayland de +brahman, enlazado por `path` cruzando la frontera de workspace. Una sola lógica +de teselado sirve a dos mundos: el escritorio sobre Linux y el kernel +bare-metal. + +## Sub-fases + +### 8a — El compositor tesela — ✅ HECHA + +- **`compositor.rs`** — enlaza `mirada-layout` y calcula un marco por app con + el algoritmo `MasterStack`, dentro del área de pantalla (todo menos la franja + superior reservada a la consola). +- El kernel **centra** el fotograma natural de cada app dentro de su marco + teselado; las apps conservan su tamaño natural y no cambian una instrucción. +- `EntradaApp.region_ancho/alto` pasa a significar el tamaño NATURAL del lienzo + de la app; `region_x/y` quedan vestigiales — el compositor decide la posición. + +### 8b — Teselado interactivo + +- El teclado cicla en caliente los siete modos de `mirada-layout` + (`MasterStack`, `CenteredMaster`, `Spiral`, `Grid`, `Columns`, `Rows`, + `Monocle`). +- Re-teselar exige re-componer: las apps que sólo pintan en `init` necesitan + una señal de redibujado, o el kernel debe conservar su último fotograma para + recomponerlo en el marco nuevo. + +### 8c — Foco y `Workspace` + +- Adoptar el `Workspace` de `mirada-layout`: foco, orden-Z, alta y baja de + ventanas en vivo. +- Una ventana enfocada, resaltada; el teclado mueve el foco y promueve apps al + área maestra. + +## Estructura de archivos + +| archivo | estado | rol en la Fase 8 | +| --- | --- | --- | +| `kernel/src/compositor.rs` | **nuevo (8a)** | teselado con `mirada-layout` | +| `kernel/src/consola.rs` | a modificar (8a) | composición centrada en el marco | +| `kernel/src/wasm/mod.rs` | a modificar (8a) | `marco` + tamaño natural | +| `kernel/src/wasm/env.rs` | a modificar (8a) | validación contra el natural | +| `kernel/src/main.rs` | a modificar (8a) | el compositor en el arranque | diff --git a/renaser/ROADMAP.md b/renaser/ROADMAP.md index 07f3248..bb95b60 100644 --- a/renaser/ROADMAP.md +++ b/renaser/ROADMAP.md @@ -120,8 +120,24 @@ destierra — las aplicaciones pasan a ser objetos del grafo, gobernadas por un y la app retoma donde quedó. Capacidades `sys_estado_cargar` / `sys_estado_guardar`; el kernel custodia un manifiesto VIVO y mutable. -Líneas abiertas posteriores: más capacidades del host (temporización, audio); -la Fase 8 — el compositor sobre `mirada-layout`. +## Fase 8 — el compositor teselante + +El kernel deja de colocar las ventanas a mano: las **tesela**. El motor es +`mirada-layout` —el mismo núcleo `no_std` que ordena el compositor Wayland de +brahman—, enlazado por `path` cruzando la frontera de workspace. Plan completo +en `FASE8.md`. + +- **8a — el compositor tesela (completada).** `compositor.rs` calcula un marco + por app con el algoritmo `MasterStack`. El kernel centra el fotograma natural + de cada app dentro de su marco; las apps no cambian una instrucción. + `region_x/y` del manifiesto quedan vestigiales — la posición la decide el + compositor. +- **8b — teselado interactivo (pendiente).** El teclado cicla en caliente los + siete modos de teselado de `mirada-layout`. +- **8c — foco y `Workspace` (pendiente).** Foco, orden-Z y alta/baja de + ventanas en vivo, con el `Workspace` de `mirada-layout`. + +Líneas abiertas posteriores: más capacidades del host (temporización, audio). ## Principios que persisten entre fases diff --git a/renaser/kernel/src/compositor.rs b/renaser/kernel/src/compositor.rs new file mode 100644 index 0000000..ac51508 --- /dev/null +++ b/renaser/kernel/src/compositor.rs @@ -0,0 +1,82 @@ +// ============================================================================= +// renaser :: kernel/src/compositor.rs — Fase 8 :: el compositor teselante +// ----------------------------------------------------------------------------- +// Hasta la Fase 7, cada app llevaba su region escrita a mano en el manifiesto: +// coordenadas fijas, una composicion rigida. La Fase 8 entrega esa decision a +// un COMPOSITOR: el kernel ya no coloca las ventanas a mano, las TESELA. +// +// El motor de teselado es `mirada-layout` — el mismo nucleo `no_std` que +// ordena las ventanas del compositor Wayland de brahman. Cruza la frontera de +// workspace y se enlaza aqui sin una linea de codigo nueva: geometria pura, +// determinista, la misma en Linux y en el bare-metal de renaser. +// +// Cada app conserva su tamaño NATURAL —el lienzo que sabe pintar, fijo—; el +// compositor decide DONDE va ese lienzo. El kernel centra el fotograma natural +// de la app dentro del marco teselado. Asi el compositor reordena la pantalla +// sin que ninguna app cambie una sola instruccion. +// ============================================================================= + +use alloc::vec::Vec; + +use mirada_layout::{tile, LayoutMode, LayoutParams, Rect}; + +use crate::grafico::RegionPantalla; + +/// Altura del strip superior reservado a la consola; las apps teselan debajo. +/// La consola conserva ahi su registro de arranque completo —seis lineas, +/// hasta la sonda asincrona de disco— legible sobre el teselado. +const FRANJA_CONSOLA: usize = 296; + +/// El modo de teselado del compositor. Fijo por ahora — la Fase 8b lo hara +/// conmutable en caliente desde el teclado, recorriendo los siete modos que +/// `mirada-layout` ofrece. +const MODO: LayoutMode = LayoutMode::MasterStack; + +/// Margen entre ventanas teseladas, en pixeles — el aire que separa un marco +/// de sus vecinos. +const MARGEN: i32 = 14; + +/// El area de pantalla que el compositor tesela: toda la pantalla menos la +/// franja de la consola en la cima. +pub fn area_apps(ancho_pantalla: usize, alto_pantalla: usize) -> RegionPantalla { + RegionPantalla { + x: 0, + y: FRANJA_CONSOLA.min(alto_pantalla), + ancho: ancho_pantalla, + alto: alto_pantalla.saturating_sub(FRANJA_CONSOLA), + } +} + +/// Tesela el area de apps en `n` marcos —uno por ventana, en el orden de las +/// apps del manifiesto— con el algoritmo de `mirada-layout`. El vector +/// resultante tiene exactamente `n` elementos. +pub fn disponer(n: usize, ancho_pantalla: usize, alto_pantalla: usize) -> Vec { + let area = area_apps(ancho_pantalla, alto_pantalla); + let pantalla = Rect::new( + area.x as i32, + area.y as i32, + area.ancho as i32, + area.alto as i32, + ); + let params = LayoutParams { + mode: MODO, + gap: MARGEN, + ..LayoutParams::default() + }; + tile(pantalla, n, ¶ms) + .into_iter() + .map(rect_a_region) + .collect() +} + +/// Traduce un `Rect` de `mirada-layout` (`i32`, en teoria con signo) a la +/// `RegionPantalla` del kernel (`usize`). Un rectangulo degenerado queda en +/// cero — el kernel no compondra nada en el. +fn rect_a_region(r: Rect) -> RegionPantalla { + RegionPantalla { + x: r.x.max(0) as usize, + y: r.y.max(0) as usize, + ancho: r.w.max(0) as usize, + alto: r.h.max(0) as usize, + } +} diff --git a/renaser/kernel/src/consola.rs b/renaser/kernel/src/consola.rs index 68796a6..8ed5886 100644 --- a/renaser/kernel/src/consola.rs +++ b/renaser/kernel/src/consola.rs @@ -119,21 +119,42 @@ impl Consola { } /// Compone un fotograma crudo del userspace WASM —pixeles `0x00RRGGBB`, con - /// sus limites ya verificados por el host— sobre la SUB-REGION asignada a su - /// aplicacion. Cada pixel se recodifica al formato nativo del framebuffer y - /// se deposita desplazado por `(region.x, region.y)`: una app jamas escribe - /// fuera de su ventana, y varias cohabitan el lienzo sin pisarse. - fn volcar_marco(&mut self, region: RegionPantalla, datos: &[u8]) { + /// sus limites ya verificados por el host— dentro del MARCO que el + /// compositor (Fase 8) asigno a su aplicacion. El fotograma mide el tamaño + /// NATURAL de la app (`nat_ancho × nat_alto`); se CENTRA en el marco —que el + /// teselado pudo hacer mayor o menor que ese natural— y se recorta con + /// firmeza a sus bordes. Una app jamas pinta un pixel fuera de su marco. + fn volcar_marco( + &mut self, + marco: RegionPantalla, + nat_ancho: usize, + nat_alto: usize, + datos: &[u8], + ) { + if nat_ancho == 0 || nat_alto == 0 { + return; + } + // Centrar el fotograma natural dentro del marco. Si el natural excede + // al marco, el desplazamiento queda en cero y el sobrante se recorta. + let off_x = marco.x + marco.ancho.saturating_sub(nat_ancho) / 2; + let off_y = marco.y + marco.alto.saturating_sub(nat_alto) / 2; + let marco_x_fin = marco.x + marco.ancho; + let marco_y_fin = marco.y + marco.alto; + for (indice, trozo) in datos.chunks_exact(4).enumerate() { - let columna = indice % region.ancho; - let fila = indice / region.ancho; - if fila >= region.alto { - break; // el fotograma excede el alto de la region: se ignora el resto + let columna = indice % nat_ancho; + let fila = indice / nat_ancho; + if fila >= nat_alto { + break; // el fotograma excede su alto natural: se ignora el resto + } + let x = off_x + columna; + let y = off_y + fila; + // Recorte firme: al marco —el confinamiento de la app— y al lienzo. + if x >= marco_x_fin || y >= marco_y_fin { + continue; } - let x = region.x + columna; - let y = region.y + fila; if x >= self.lienzo.ancho || y >= self.lienzo.alto { - continue; // recorte firme: nada se pinta fuera del lienzo + continue; } let p = u32::from_le_bytes([trozo[0], trozo[1], trozo[2], trozo[3]]); let color = Color { @@ -148,13 +169,32 @@ impl Consola { } /// Inunda una region entera con un color plano y la presenta. Es la baliza - /// de desalojo: cuando una aplicacion falla, su ventana se tatua de purpura. + /// de desalojo: cuando una aplicacion falla, su marco se tatua de purpura. fn pintar_region(&mut self, region: RegionPantalla, color: Color) { self.lienzo .rellenar_rect(region.x, region.y, region.ancho, region.alto, color); self.presentar(); } + /// Pinta el escenario del compositor (Fase 8): inunda el area de apps con + /// el reposo del lienzo —borrando cuanto hubiera debajo— y, sobre ella, + /// tiñe cada marco teselado con el color de panel. Asi el teselado se ve + /// como una rejilla de paneles aun antes de que sus apps pinten nada. + fn pintar_escenario(&mut self, area: RegionPantalla, marcos: &[RegionPantalla]) { + self.lienzo.rellenar_rect( + area.x, + area.y, + area.ancho, + area.alto, + Color::LIENZO_EN_REPOSO, + ); + for marco in marcos { + self.lienzo + .rellenar_rect(marco.x, marco.y, marco.ancho, marco.alto, Color::PANEL); + } + self.presentar(); + } + /// Vuelca el lienzo sobre la pantalla fisica. pub(crate) fn presentar(&mut self) { self.pantalla.presentar(&self.lienzo); @@ -167,11 +207,25 @@ pub(crate) static CONSOLA: Once> = Once::new(); /// Puerta del kernel para la capacidad `sys_render_frame` del userspace WASM: /// compone sobre la consola global un fotograma —cuyos limites el host ya -/// verifico matematicamente contra la memoria lineal del modulo— dentro de la -/// region de pantalla que el kernel asigno a esa aplicacion. -pub(crate) fn volcar_marco_wasm(region: RegionPantalla, datos: &[u8]) { +/// verifico matematicamente contra la memoria lineal del modulo— centrado en +/// el marco que el compositor asigno a esa aplicacion. `nat_ancho`/`nat_alto` +/// son el tamaño natural del lienzo de la app. +pub(crate) fn volcar_marco_wasm( + marco: RegionPantalla, + nat_ancho: usize, + nat_alto: usize, + datos: &[u8], +) { if let Some(consola) = CONSOLA.get() { - consola.lock().volcar_marco(region, datos); + consola.lock().volcar_marco(marco, nat_ancho, nat_alto, datos); + } +} + +/// Pinta el escenario del compositor (Fase 8): el area de apps y, sobre ella, +/// cada marco teselado. Se invoca una vez, en el arranque, tras teselar. +pub(crate) fn pintar_escenario(area: RegionPantalla, marcos: &[RegionPantalla]) { + if let Some(consola) = CONSOLA.get() { + consola.lock().pintar_escenario(area, marcos); } } diff --git a/renaser/kernel/src/grafico.rs b/renaser/kernel/src/grafico.rs index 721d12b..2edecb5 100644 --- a/renaser/kernel/src/grafico.rs +++ b/renaser/kernel/src/grafico.rs @@ -43,6 +43,15 @@ impl Color { b: 0x20, }; + /// Panel del compositor (Fase 8): un slate apenas mas claro que el reposo. + /// Tiñe cada marco teselado, de modo que el teselado se vea —como una + /// rejilla de paneles— aunque sus apps aun no hayan pintado nada. + pub(crate) const PANEL: Color = Color { + r: 0x1B, + g: 0x21, + b: 0x30, + }; + /// Alerta de colapso: un rojo saturado, imposible de ignorar. pub(crate) const ALERTA: Color = Color { r: 0xD4, @@ -101,13 +110,6 @@ pub(crate) struct RegionPantalla { pub(crate) alto: usize, } -impl RegionPantalla { - /// Numero total de pixeles que abarca la region. - pub(crate) const fn pixeles(&self) -> usize { - self.ancho * self.alto - } -} - /// Traduce un [`Color`] logico al valor nativo de 32 bits que el framebuffer /// espera, respetando el orden de canales que reporta el firmware UEFI. pub(crate) fn codificar(formato: PixelFormat, color: Color) -> u32 { diff --git a/renaser/kernel/src/main.rs b/renaser/kernel/src/main.rs index 4a0ecaa..4f63a8b 100644 --- a/renaser/kernel/src/main.rs +++ b/renaser/kernel/src/main.rs @@ -19,6 +19,7 @@ // interrupcion: el bus PCI y el virtio-blk (Fases 6.1, 6.2). // almacen — el grafo de objetos direccionado por contenido (Fase 6.1c). // manifiesto — el Manifiesto de Genesis: que apps nacen del grafo (Fase 7). +// compositor — el teselado de las ventanas con `mirada-layout` (Fase 8). // memory — el heap dinamico del kernel (`#[global_allocator]`). // async_system — el reactor cooperativo: ejecutor, tareas, wakers, teclado // y el reloj que marca el compas de los fotogramas (Fase 5). @@ -46,6 +47,7 @@ use spin::Mutex; mod almacen; mod async_system; mod baliza; +mod compositor; mod consola; mod drivers; mod gdt; @@ -66,7 +68,8 @@ use async_system::executor::Executor; use baliza::BALIZA_PANICO; use consola::{Consola, CONSOLA}; use grafico::{ - codificar, reclamar_memoria_lienzo, Color, Lienzo, Pantalla, ALTO_MAX, ANCHO_MAX, + codificar, reclamar_memoria_lienzo, Color, Lienzo, Pantalla, RegionPantalla, ALTO_MAX, + ANCHO_MAX, }; /// Configuracion que el cargador `bootloader` aplicara antes de cedernos la CPU. @@ -99,7 +102,7 @@ async fn tarea_aplicacion(mut app: wasm::AplicacionWasm) { if let Err(falla) = app.tick() { // El color de la baliza delata la causa: purpura si agoto su tiempo // o aborto, amarillo si reviento su techo de memoria. - consola::pintar_desalojo(app.region(), falla.color_baliza()); + consola::pintar_desalojo(app.marco(), falla.color_baliza()); return; } } @@ -129,27 +132,42 @@ async fn tarea_sonda_disco() { } /// Da vida a una aplicacion del userspace a partir de su `EntradaApp` del -/// manifiesto: recupera su bytecode del grafo, la carga en su region y la -/// despacha como tarea cooperativa del reactor. Si el bytecode falta, esta -/// corrupto, o la carga fracasa, se salda pintando la region de la app con -/// la baliza de desalojo — el kernel no se inmuta y sigue con las demas. -fn encender_app(ejecutor: &mut Executor, indice: usize, entrada: &manifiesto::EntradaApp) { - let region = manifiesto::region(entrada); +/// manifiesto: recupera su bytecode del grafo, la carga en el `marco` que el +/// compositor le teselo y la despacha como tarea cooperativa del reactor. Si +/// el bytecode falta, esta corrupto, o la carga fracasa, se salda pintando el +/// marco con la baliza de desalojo — el kernel no se inmuta y sigue con las +/// demas. +fn encender_app( + ejecutor: &mut Executor, + indice: usize, + entrada: &manifiesto::EntradaApp, + marco: RegionPantalla, +) { + // El tamaño NATURAL del lienzo de la app —lo que sabe pintar, fijo— lo + // dicta su `EntradaApp`; el compositor le asigno `marco` como ventana. + let natural = manifiesto::region(entrada); // Recuperar el bytecode del grafo. `recuperar` recomputa el hash del // objeto y verifica su integridad: un bytecode corrupto se delata aqui // —y la app se niega, no se instancia un modulo en el que no se confia. let bytecode = match almacen::recuperar(&entrada.bytecode) { Ok(Some(objeto)) => objeto.datos, _ => { - consola::pintar_desalojo(region, Color::DESALOJO); + consola::pintar_desalojo(marco, Color::DESALOJO); return; } }; // `indice` es la identidad de la app en el manifiesto: las capacidades de // estado persistido (Fase 7c) la usan para hallar SU ranura `estado`. - match wasm::AplicacionWasm::cargar(&bytecode, region, entrada.techo_memoria as usize, indice) { + match wasm::AplicacionWasm::cargar( + &bytecode, + marco, + natural.ancho, + natural.alto, + entrada.techo_memoria as usize, + indice, + ) { Ok(app) => ejecutor.spawn(tarea_aplicacion(app)), - Err(_) => consola::pintar_desalojo(region, Color::DESALOJO), + Err(_) => consola::pintar_desalojo(marco, Color::DESALOJO), } } @@ -170,7 +188,7 @@ fn reportar(linea: &str) { /// aplicacion. Toda falla se reporta a la consola y NO detiene el arranque: el /// kernel se levanta con las apps que pueda — o con ninguna, si el grafo no /// tiene userspace. -fn cargar_userspace(ejecutor: &mut Executor) { +fn cargar_userspace(ejecutor: &mut Executor, ancho_pantalla: usize, alto_pantalla: usize) { let manifiesto = match manifiesto::cargar() { Ok(Some(m)) => Some(m), // Disco sin manifiesto anclado: `boot` no lo sembro. El kernel se @@ -197,11 +215,20 @@ fn cargar_userspace(ejecutor: &mut Executor) { if let Some(m) = manifiesto { // Instalar el manifiesto VIVO ANTES de instanciar las apps: el `init` // de cada app puede consultar su estado persistido (Fase 7c), y esa - // consulta lee del manifiesto vivo. Se instala una copia; la otra se - // itera para encender cada app con su indice — su identidad. + // consulta lee del manifiesto vivo. manifiesto::instalar(m.clone()); - for (indice, entrada) in m.apps.iter().enumerate() { - encender_app(ejecutor, indice, entrada); + + // FASE 8 :: el compositor tesela el area de apps — un marco por + // ventana, calculado por `mirada-layout`. Se pinta el escenario (el + // area y sus marcos) antes de encender las apps: el teselado se ve + // aunque alguna app no llegue siquiera a pintar su primer fotograma. + let marcos = compositor::disponer(m.apps.len(), ancho_pantalla, alto_pantalla); + consola::pintar_escenario( + compositor::area_apps(ancho_pantalla, alto_pantalla), + &marcos, + ); + for (indice, (entrada, marco)) in m.apps.iter().zip(marcos).enumerate() { + encender_app(ejecutor, indice, entrada, marco); } } } @@ -304,12 +331,9 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { Some(m) => m, None => detener(), }; - let mut lienzo = Lienzo::nuevo( - memoria, - info.width.min(ANCHO_MAX), - info.height.min(ALTO_MAX), - formato, - ); + let ancho_lienzo = info.width.min(ANCHO_MAX); + let alto_lienzo = info.height.min(ALTO_MAX); + let mut lienzo = Lienzo::nuevo(memoria, ancho_lienzo, alto_lienzo, formato); lienzo.limpiar(Color::LIENZO_EN_REPOSO); let mut consola = Consola::nueva(lienzo, pantalla); @@ -347,7 +371,7 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // compas de los fotogramas y la IRQ del teclado difundira cada // scancode a los canales que las apps consultan. --- let mut ejecutor = Executor::nuevo(); - cargar_userspace(&mut ejecutor); + cargar_userspace(&mut ejecutor, ancho_lienzo, alto_lienzo); // FASE 6.2 :: una tarea mas del reactor — no una app WASM— que sondea el // disco de forma ASINCRONA: la demostracion de que la IRQ del disco // conduce la E/S sin detener a las aplicaciones visuales. diff --git a/renaser/kernel/src/wasm/env.rs b/renaser/kernel/src/wasm/env.rs index 1b7a3d7..b560cec 100644 --- a/renaser/kernel/src/wasm/env.rs +++ b/renaser/kernel/src/wasm/env.rs @@ -35,8 +35,14 @@ use crate::grafico::RegionPantalla; /// una capacidad para servir a ESA app y a ninguna otra — su region de pantalla, /// su canal de teclado y sus cuotas de recursos. Dos apps jamas comparten nada. pub(crate) struct ContextoCapacidades { - /// La sub-region de pantalla asignada a la aplicacion. - pub(crate) region: RegionPantalla, + /// El marco que el compositor (Fase 8) asigno a la app — el rectangulo de + /// pantalla donde vive. El kernel centra en el el fotograma de la app. + pub(crate) marco: RegionPantalla, + /// El tamaño natural del lienzo de la app, en pixeles. El fotograma que + /// entrega `sys_render_frame` mide exactamente `natural_ancho × natural_alto`; + /// el compositor lo coloca, sin deformarlo, dentro del `marco`. + pub(crate) natural_ancho: usize, + pub(crate) natural_alto: usize, /// El canal de teclado propio de la aplicacion. pub(crate) canal: CanalTeclado, /// El techo de recursos de la aplicacion — hoy, su memoria lineal maxima. @@ -92,15 +98,17 @@ pub(crate) fn enlazar_capacidades( "renaser", "sys_render_frame", |caller: Caller<'_, ContextoCapacidades>, ptr: u32, len: u32| -> Result<(), Error> { - let region = caller.data().region; + let marco = caller.data().marco; + let nat_ancho = caller.data().natural_ancho; + let nat_alto = caller.data().natural_alto; - // El fotograma debe medir EXACTAMENTE los pixeles de la region. Un - // tamaño distinto delata a una app que pinta fuera de su ventana: + // El fotograma debe medir EXACTAMENTE el lienzo natural de la app. + // Un tamaño distinto delata a una app que pinta fuera de su lienzo: // se aborta antes de tocar un byte. - let esperado = region.pixeles() * 4; + let esperado = nat_ancho * nat_alto * 4; if len as usize != esperado { return Err(Error::new( - "WASM :: sys_render_frame con un fotograma ajeno a la region asignada", + "WASM :: sys_render_frame con un fotograma ajeno al lienzo natural", )); } @@ -109,15 +117,16 @@ pub(crate) fn enlazar_capacidades( // VALIDACION INFRANQUEABLE: si (ptr, len) se sale de la memoria // lineal del modulo, se aborta la app —no el kernel—. - let marco = rango( + let fotograma = rango( datos, ptr, len as usize, "WASM :: sys_render_frame desbordo la memoria lineal del modulo", )?; - // Limites verificados: la region es segura de leer y componer. - crate::volcar_marco_wasm(region, marco); + // Limites verificados: el compositor centra el fotograma natural + // de la app dentro del marco que el teselado le asigno. + crate::volcar_marco_wasm(marco, nat_ancho, nat_alto, fotograma); Ok(()) }, )?; diff --git a/renaser/kernel/src/wasm/mod.rs b/renaser/kernel/src/wasm/mod.rs index d908cef..b6c3bf6 100644 --- a/renaser/kernel/src/wasm/mod.rs +++ b/renaser/kernel/src/wasm/mod.rs @@ -70,9 +70,9 @@ pub struct AplicacionWasm { /// `TypedFunc` es un asa autosuficiente dentro del `Store`: conservada esta, /// el handle de la `Instance` no aporta nada y no se retiene. func_tick: TypedFunc<(), ()>, - /// La region de pantalla de la app — su ventana, y donde se tatua su baliza - /// de desalojo si llega a fallar. - region: RegionPantalla, + /// El marco que el compositor asigno a la app — su ventana en pantalla, y + /// donde se tatua su baliza de desalojo si llega a fallar. + marco: RegionPantalla, } impl AplicacionWasm { @@ -84,13 +84,16 @@ impl AplicacionWasm { /// sola vez, aqui— y `tick` —un fotograma de trabajo, invocada despues por /// el reactor en cada pulso del reloj. /// - /// `techo_memoria` es la cuota de memoria lineal de ESTA app, en bytes — - /// desde la Fase 7 la dicta su `EntradaApp` del manifiesto. `indice_app` es - /// su posicion en el manifiesto: su identidad para las capacidades de + /// `marco` es el rectangulo que el compositor (Fase 8) asigno a la app; + /// `natural_ancho`/`natural_alto`, el tamaño de su lienzo. `techo_memoria` + /// es su cuota de memoria lineal —la dicta su `EntradaApp` del manifiesto—, + /// e `indice_app` su posicion en el: su identidad para las capacidades de /// estado persistido (Fase 7c). pub fn cargar( bytecode: &[u8], - region: RegionPantalla, + marco: RegionPantalla, + natural_ancho: usize, + natural_alto: usize, techo_memoria: usize, indice_app: usize, ) -> Result { @@ -119,7 +122,9 @@ impl AplicacionWasm { let mut almacen = Store::new( &motor, ContextoCapacidades { - region, + marco, + natural_ancho, + natural_alto, canal, limites, indice_app, @@ -162,7 +167,7 @@ impl AplicacionWasm { Ok(AplicacionWasm { almacen, func_tick, - region, + marco, }) } @@ -189,9 +194,10 @@ impl AplicacionWasm { } } - /// La region de pantalla asignada a la aplicacion. - pub fn region(&self) -> RegionPantalla { - self.region + /// El marco de pantalla que el compositor asigno a la aplicacion — donde + /// se tatua su baliza si el kernel llega a desalojarla. + pub fn marco(&self) -> RegionPantalla { + self.marco } }