feat(renaser): Fases 8b y 8c — el escritorio interactivo
El compositor de la 8a teselaba, pero era inmovil. Las 8b/8c lo hacen vivo: el teclado reordena el escritorio y mueve el foco en caliente. - Cache de fotogramas: cada ventana guarda en RAM del kernel su ultimo fotograma —reservada una vez, acotada al lienzo natural—. Al re-teselar o mover el foco, el kernel recompone desde la cache: las apps que solo pintan en init (cronista) conservan su imagen sin enterarse del cambio. - compositor: el registro ESCRITORIO (ventanas, marcos, caches, modo); presentar_fotograma, desalojar, atender_mandos, ciclar_layout, mover_foco. Foco en un AtomicUsize, mandos en una cola lock-free. - teclado: la IRQ1 deja de difundir. Alt es el modificador del sistema — Alt+Espacio cicla el teselado, Alt+J/K mueven el foco—; una tecla ordinaria va SOLO a la app enfocada (CANALES reindexado por indice_app). - consola: borde de foco (indigo / gris) en cada marco. Guardarrail anti-interbloqueo: la IRQ1 jamas bloquea ESCRITORIO; se comunica por dos atomicos y una cola lock-free. Las caches se reservan una sola vez, al tamaño natural — sin asignacion en el bucle del reactor. Verificado en QEMU (screendump + sendkey): arranque teselado con hola enfocada; Alt+Espacio cicla a CenteredMaster y las apps estaticas conservan su contenido; Alt+J mueve el foco; las teclas llegan solo a la app enfocada. Cierra la Fase 8 — el compositor teselante e interactivo. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -672,3 +672,62 @@ COMPOSITOR: el kernel ya no coloca las ventanas a mano, las TESELA con
|
|||||||
centrado en su panel; las apps desalojadas (discola, glotona) tiñen su marco
|
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
|
entero con la baliza. El registro de arranque permanece legible en la franja
|
||||||
superior de la consola.
|
superior de la consola.
|
||||||
|
|
||||||
|
## Fases 8b y 8c — El escritorio interactivo — 2026-05-22
|
||||||
|
|
||||||
|
El compositor de la Fase 8a teselaba, pero era inmóvil. Las Fases 8b y 8c lo
|
||||||
|
hacen VIVO: el teclado reordena el escritorio en caliente y mueve el foco. Y
|
||||||
|
para que las apps estáticas no se queden atrás en una reordenación, el kernel
|
||||||
|
asume la persistencia visual con una caché de fotogramas.
|
||||||
|
|
||||||
|
### Añadido
|
||||||
|
- **Caché de fotogramas (backbuffer).** Cada ventana del compositor guarda, en
|
||||||
|
RAM del kernel, el último fotograma que su app envió. La caché se reserva UNA
|
||||||
|
vez, acotada al lienzo natural de la app —jamás crece—. Al re-teselar o mover
|
||||||
|
el foco, el kernel recompone cada ventana desde su caché: una app que sólo
|
||||||
|
pintó en su `init` —la cronista— conserva su imagen intacta a través de
|
||||||
|
cualquier reordenación, sin enterarse del cambio geométrico.
|
||||||
|
- **`compositor`: el escritorio interactivo.** Un registro `ESCRITORIO` —las
|
||||||
|
ventanas, sus marcos, sus cachés, el modo de teselado—; `presentar_fotograma`
|
||||||
|
(cachea y compone), `desalojar`, `atender_mandos`, `ciclar_layout`,
|
||||||
|
`mover_foco`. El foco vive en un `AtomicUsize`; los mandos del teclado, en
|
||||||
|
una cola lock-free.
|
||||||
|
- **`tarea_compositor`** — atiende los mandos del teclado en cada fotograma del
|
||||||
|
reactor: el contexto cooperativo donde es seguro re-teselar y recomponer.
|
||||||
|
- `Color::FOCO` (índigo brillante) y `Color::SIN_FOCO` (gris mate) — el borde
|
||||||
|
de cada ventana, que delata de un vistazo quién tiene el foco.
|
||||||
|
|
||||||
|
### Cambiado
|
||||||
|
- **Teclado (Fase 8c).** El manejador de IRQ1 deja de difundir a ciegas:
|
||||||
|
- La tecla **Alt** es el modificador del sistema. `Alt+Espacio` cicla el
|
||||||
|
modo de teselado; `Alt+J` / `Alt+K` mueven el foco. Estos mandos se
|
||||||
|
consumen en la IRQ — jamás llegan a una app.
|
||||||
|
- Una tecla ordinaria se entrega SÓLO a la app ENFOCADA. El censo de canales
|
||||||
|
se reindexó por `indice_app` (`Vec<Option<CanalTeclado>>`): el foco —un
|
||||||
|
índice— elige el canal exacto.
|
||||||
|
- `consola::volcar_marco` y `pintar_desalojo` trazan el borde de foco de la
|
||||||
|
ventana. `sys_render_frame` ya no compone directo: delega en
|
||||||
|
`compositor::presentar_fotograma`, que cachea antes de componer.
|
||||||
|
- `AplicacionWasm` / `ContextoCapacidades` pierden el `marco` —ahora vive en el
|
||||||
|
registro del compositor, mutable—; conservan sólo el `indice_app`.
|
||||||
|
|
||||||
|
### Exclusión de interrupciones (guardarraíl)
|
||||||
|
- El registro `ESCRITORIO` lo tocan SÓLO tareas cooperativas; el manejador de
|
||||||
|
IRQ1 jamás lo bloquea. La IRQ se comunica con el mundo cooperativo por un
|
||||||
|
canal estrecho y a prueba de interbloqueos: dos atómicos —el foco y el estado
|
||||||
|
de Alt— y una cola lock-free de mandos. Ningún cerrojo en disputa entre la
|
||||||
|
interrupción y una tarea.
|
||||||
|
- Las cachés se reservan en `compositor::fundar`, una sola vez, al tamaño
|
||||||
|
natural de cada app: ninguna asignación dinámica dentro del bucle del reactor.
|
||||||
|
|
||||||
|
### Verificado
|
||||||
|
- QEMU (captura headless + `sendkey` por el monitor):
|
||||||
|
- **Arranque** — cinco apps teseladas en `MasterStack`; `hola` enfocada luce
|
||||||
|
el borde índigo, las demás el gris.
|
||||||
|
- **`Alt+Espacio`** — el escritorio cicla a `CenteredMaster`; discola,
|
||||||
|
glotona y cronista conservan su contenido a través del re-teselado: la
|
||||||
|
caché de fotogramas en acción.
|
||||||
|
- **`Alt+J`** — el foco salta de `hola` a `memoriosa`; el borde índigo se
|
||||||
|
mueve con él.
|
||||||
|
- **Enrutamiento** — con `memoriosa` enfocada, cuatro pulsaciones llegan sólo
|
||||||
|
a ella —cuatro celdas violetas—; las demás apps, intactas.
|
||||||
|
|||||||
+5
-5
@@ -73,11 +73,11 @@ QEMU 11, OVMF en `/usr/share/edk2/x64/OVMF.4m.fd` (sin módulo KVM → TCG).
|
|||||||
|
|
||||||
## Estado
|
## Estado
|
||||||
|
|
||||||
Fases 1 a 5, 6.0, 6.1, 6.2 y la Fase 7 COMPLETA —el userspace nace del grafo
|
Fases 1 a 5, 6.0, 6.1, 6.2, la Fase 7 COMPLETA —el userspace nace del grafo de
|
||||||
de objetos: Manifiesto de Génesis (7a), imagen sembrada por `boot` (7b) y
|
objetos— y la Fase 8 COMPLETA —el compositor teselante e interactivo: teselado
|
||||||
persistencia inter-sesión por-app (7c)—. Fase 8 en curso: 8a hecha —el
|
con `mirada-layout` (8a), `Alt+Espacio` cicla el layout (8b), foco con
|
||||||
compositor tesela las ventanas con `mirada-layout`—. Todo verificado en QEMU.
|
`Alt+J`/`Alt+K` y enrutamiento selectivo del teclado (8c)—. Todo verificado en
|
||||||
Ver `ROADMAP.md`.
|
QEMU. Ver `ROADMAP.md`.
|
||||||
|
|
||||||
## Flujo de trabajo
|
## Flujo de trabajo
|
||||||
|
|
||||||
|
|||||||
@@ -342,6 +342,36 @@ 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
|
quien lo centra con cuidado en el espacio que le tocó. Nadie tuvo que cambiar
|
||||||
para vivir mejor repartido.
|
para vivir mejor repartido.
|
||||||
|
|
||||||
|
## La casa atiende
|
||||||
|
|
||||||
|
El arquitecto sabía repartir los cuartos, pero su plano era de piedra: una vez
|
||||||
|
puesto, no se movía. Hoy la casa aprendió a obedecer. Quien la habita puede
|
||||||
|
pedirle, con un gesto del teclado, que reordene las habitaciones —de una
|
||||||
|
disposición con una pieza grande a un lado a otra con la pieza central—; y el
|
||||||
|
arquitecto, en un parpadeo, las reparte de nuevo.
|
||||||
|
|
||||||
|
Pero reordenar tiene un peligro. Algunos inquilinos pintan su cuadro una sola
|
||||||
|
vez, al llegar, y luego se quedan quietos —la cronista es así—. Si la casa
|
||||||
|
moviera su cuarto, la pared nueva quedaría en blanco: el inquilino, dormido, no
|
||||||
|
sabría que debe volver a pintar. La casa lo resolvió con delicadeza: de cada
|
||||||
|
cuarto guarda una fotografía del último cuadro. Cuando reordena, no despierta a
|
||||||
|
nadie; descuelga las fotografías y las vuelve a colgar, ya encuadradas, en las
|
||||||
|
paredes nuevas. El que dormía sigue durmiendo, y su obra reaparece intacta.
|
||||||
|
|
||||||
|
Y la casa aprendió, además, a mirar. Ahora hay siempre un cuarto ENFOCADO —el
|
||||||
|
que tiene la atención—, y se le reconoce por un marco de luz índigo; los demás
|
||||||
|
llevan un marco gris, discreto. Con dos teclas se pasea esa luz de un inquilino
|
||||||
|
a otro. No es un adorno: es una decisión. Porque desde hoy, cuando alguien
|
||||||
|
teclea, sus palabras ya no se gritan a toda la casa —como hasta ayer—; se
|
||||||
|
entregan, en privado, sólo al inquilino que tiene el foco. Se le habla a quien
|
||||||
|
se mira.
|
||||||
|
|
||||||
|
Detrás de esa cortesía hay una disciplina estricta. La campanilla del teclado
|
||||||
|
—la interrupción— es impaciente y no puede esperar a nadie; por eso jamás toca
|
||||||
|
los libros de la casa. Sólo deja una nota breve en un casillero a prueba de
|
||||||
|
prisas, y sigue su camino. Es el arquitecto quien, más tarde y con calma, lee
|
||||||
|
la nota y mueve los tabiques. Nadie se pisa; nada se traba.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*El diario continúa. La próxima página la escribirá la próxima jornada.*
|
*El diario continúa. La próxima página la escribirá la próxima jornada.*
|
||||||
|
|||||||
+20
-12
@@ -29,21 +29,29 @@ bare-metal.
|
|||||||
- `EntradaApp.region_ancho/alto` pasa a significar el tamaño NATURAL del lienzo
|
- `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.
|
de la app; `region_x/y` quedan vestigiales — el compositor decide la posición.
|
||||||
|
|
||||||
### 8b — Teselado interactivo
|
### 8b — Teselado interactivo — ✅ HECHA
|
||||||
|
|
||||||
- El teclado cicla en caliente los siete modos de `mirada-layout`
|
- `Alt+Espacio` cicla en caliente los siete modos de teselado de
|
||||||
(`MasterStack`, `CenteredMaster`, `Spiral`, `Grid`, `Columns`, `Rows`,
|
`mirada-layout` (`MasterStack`, `CenteredMaster`, `Spiral`, …).
|
||||||
`Monocle`).
|
- Re-teselar exige re-componer. La solución: el kernel guarda una CACHÉ del
|
||||||
- Re-teselar exige re-componer: las apps que sólo pintan en `init` necesitan
|
último fotograma de cada app —acotada al lienzo natural, reservada una sola
|
||||||
una señal de redibujado, o el kernel debe conservar su último fotograma para
|
vez— y recompone desde ella. Una app que sólo pintó en su `init` conserva su
|
||||||
recomponerlo en el marco nuevo.
|
imagen a través de cualquier reordenación, sin enterarse del cambio.
|
||||||
|
|
||||||
### 8c — Foco y `Workspace`
|
### 8c — Foco y enrutamiento selectivo — ✅ HECHA
|
||||||
|
|
||||||
- Adoptar el `Workspace` de `mirada-layout`: foco, orden-Z, alta y baja de
|
- Una ventana ENFOCADA, con borde índigo brillante; las demás, borde gris
|
||||||
ventanas en vivo.
|
mate. `Alt+J` / `Alt+K` pasean el foco entre las ventanas vivas.
|
||||||
- Una ventana enfocada, resaltada; el teclado mueve el foco y promueve apps al
|
- El teclado deja de difundir a ciegas: una tecla ordinaria se entrega sólo a
|
||||||
área maestra.
|
la app enfocada; los mandos `Alt+…` se consumen en la IRQ.
|
||||||
|
- El registro `ESCRITORIO` del compositor cumple el papel del `Workspace` de
|
||||||
|
`mirada-layout` —ventanas, marcos, foco— sin adoptar su tipo: el kernel
|
||||||
|
necesita además la caché de respaldo, que el `Workspace` no contempla.
|
||||||
|
|
||||||
|
### Pendiente
|
||||||
|
|
||||||
|
- Orden-Z y solapamiento (ventanas flotantes); alta y baja de apps en vivo.
|
||||||
|
- Promover la app enfocada al área maestra (`Alt+Enter`).
|
||||||
|
|
||||||
## Estructura de archivos
|
## Estructura de archivos
|
||||||
|
|
||||||
|
|||||||
+10
-6
@@ -120,7 +120,7 @@ destierra — las aplicaciones pasan a ser objetos del grafo, gobernadas por un
|
|||||||
y la app retoma donde quedó. Capacidades `sys_estado_cargar` /
|
y la app retoma donde quedó. Capacidades `sys_estado_cargar` /
|
||||||
`sys_estado_guardar`; el kernel custodia un manifiesto VIVO y mutable.
|
`sys_estado_guardar`; el kernel custodia un manifiesto VIVO y mutable.
|
||||||
|
|
||||||
## Fase 8 — el compositor teselante
|
## Fase 8 — el compositor teselante e interactivo (completada)
|
||||||
|
|
||||||
El kernel deja de colocar las ventanas a mano: las **tesela**. El motor es
|
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
|
`mirada-layout` —el mismo núcleo `no_std` que ordena el compositor Wayland de
|
||||||
@@ -132,12 +132,16 @@ en `FASE8.md`.
|
|||||||
de cada app dentro de su marco; las apps no cambian una instrucción.
|
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
|
`region_x/y` del manifiesto quedan vestigiales — la posición la decide el
|
||||||
compositor.
|
compositor.
|
||||||
- **8b — teselado interactivo (pendiente).** El teclado cicla en caliente los
|
- **8b — teselado interactivo (completada).** `Alt+Espacio` cicla los modos de
|
||||||
siete modos de teselado de `mirada-layout`.
|
teselado en caliente. El kernel cachea el último fotograma de cada app y
|
||||||
- **8c — foco y `Workspace` (pendiente).** Foco, orden-Z y alta/baja de
|
recompone desde la caché: las apps estáticas sobreviven al re-teselado sin
|
||||||
ventanas en vivo, con el `Workspace` de `mirada-layout`.
|
enterarse del cambio.
|
||||||
|
- **8c — foco y enrutamiento selectivo (completada).** Una ventana enfocada,
|
||||||
|
con borde índigo; `Alt+J` / `Alt+K` mueven el foco entre las ventanas vivas.
|
||||||
|
El teclado deja de difundir: entrega cada tecla sólo a la app enfocada.
|
||||||
|
|
||||||
Líneas abiertas posteriores: más capacidades del host (temporización, audio).
|
Líneas abiertas posteriores: orden-Z y ventanas flotantes; más capacidades del
|
||||||
|
host (temporización, audio).
|
||||||
|
|
||||||
## Principios que persisten entre fases
|
## Principios que persisten entre fases
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,28 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
// renaser :: async_system/teclado.rs — el canal de scancodes del teclado
|
// renaser :: async_system/teclado.rs — el canal de scancodes del teclado
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// El manejador de IRQ1 es un mero PRODUCTOR: deposita cada scancode en colas
|
// El manejador de IRQ1 es el PRODUCTOR: deposita cada scancode en colas
|
||||||
// lock-free, seguras frente a interrupciones. Los consumidores —las apps WASM,
|
// lock-free, seguras frente a interrupciones. Los consumidores —las apps WASM,
|
||||||
// via la capacidad `sys_get_scancode`— las drenan sin bloquear.
|
// via la capacidad `sys_get_scancode`— las drenan sin bloquear.
|
||||||
//
|
//
|
||||||
// FASE 5 :: con varias apps concurrentes, una sola cola compartida no sirve:
|
// FASE 5 :: cada app abre su PROPIO canal; la primera en sondear no le roba la
|
||||||
// la primera en sondear le robaria la pulsacion a las demas. Por eso cada
|
// pulsacion a las demas.
|
||||||
// aplicacion abre su PROPIO canal y la IRQ1 DIFUNDE cada scancode a todos —
|
//
|
||||||
// cada app recibe su copia integra del flujo de entrada.
|
// FASE 8c :: el teclado deja de DIFUNDIR a ciegas. Ahora discrimina:
|
||||||
|
//
|
||||||
|
// * La tecla Alt es el MODIFICADOR del sistema. Con Alt pulsada, los make
|
||||||
|
// codes son MANDOS del compositor (ciclar el teselado, mover el foco): se
|
||||||
|
// consumen aqui, jamas llegan a una app.
|
||||||
|
// * Una tecla ordinaria se entrega SOLO a la app ENFOCADA — la que el
|
||||||
|
// compositor senala. El censo de canales se indexa por el `indice_app`,
|
||||||
|
// de modo que el foco —un atomico— elija el canal exacto.
|
||||||
|
//
|
||||||
|
// Todo esto corre en contexto de IRQ y NO bloquea ningun cerrojo cooperativo:
|
||||||
|
// el modificador es un atomico, los mandos van a una cola lock-free.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
@@ -18,16 +30,33 @@ use crossbeam_queue::ArrayQueue;
|
|||||||
use spin::{Mutex, Once};
|
use spin::{Mutex, Once};
|
||||||
use x86_64::instructions::interrupts;
|
use x86_64::instructions::interrupts;
|
||||||
|
|
||||||
|
use crate::compositor::{self, Mando};
|
||||||
|
|
||||||
/// Capacidad de la cola de scancodes de cada app. Holgada: nadie teclea tanto.
|
/// Capacidad de la cola de scancodes de cada app. Holgada: nadie teclea tanto.
|
||||||
const CAPACIDAD_COLA: usize = 256;
|
const CAPACIDAD_COLA: usize = 256;
|
||||||
|
|
||||||
|
// --- Scancodes del set 1 que el teclado interpreta como mandos del sistema. ---
|
||||||
|
/// Alt izquierda — make (pulsada) y break (soltada).
|
||||||
|
const ALT_MAKE: u8 = 0x38;
|
||||||
|
const ALT_BREAK: u8 = 0xB8;
|
||||||
|
/// Barra espaciadora — `Alt + Espacio` cicla el modo de teselado.
|
||||||
|
const ESPACIO: u8 = 0x39;
|
||||||
|
/// Tecla J — `Alt + J` mueve el foco a la ventana siguiente.
|
||||||
|
const TECLA_J: u8 = 0x24;
|
||||||
|
/// Tecla K — `Alt + K` mueve el foco a la ventana anterior.
|
||||||
|
const TECLA_K: u8 = 0x25;
|
||||||
|
|
||||||
/// Un canal de teclado: la cola lock-free de scancodes de UNA aplicacion.
|
/// Un canal de teclado: la cola lock-free de scancodes de UNA aplicacion.
|
||||||
pub type CanalTeclado = Arc<ArrayQueue<u8>>;
|
pub type CanalTeclado = Arc<ArrayQueue<u8>>;
|
||||||
|
|
||||||
/// Censo de canales — uno por aplicacion del userspace. El manejador de IRQ1
|
/// Censo de canales, INDEXADO por el `indice_app` de cada aplicacion. Una
|
||||||
/// difunde cada scancode a TODOS: asi cada app recibe su propia copia del
|
/// ranura `None` es una app que no abrio canal o que fue desalojada. El
|
||||||
/// evento, sin que una le arrebate la pulsacion a otra.
|
/// indexado estable permite que el foco —un simple indice— elija el canal.
|
||||||
static CANALES: Once<Mutex<Vec<CanalTeclado>>> = Once::new();
|
static CANALES: Once<Mutex<Vec<Option<CanalTeclado>>>> = Once::new();
|
||||||
|
|
||||||
|
/// ¿Esta la tecla Alt pulsada? El modificador de los mandos del sistema. Lo
|
||||||
|
/// escribe y lo lee SOLO el manejador de IRQ1 — un atomico, sin cerrojo.
|
||||||
|
static ALT_PULSADO: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
/// Funda el censo de canales del teclado. Requiere el heap ya activo; debe
|
/// Funda el censo de canales del teclado. Requiere el heap ya activo; debe
|
||||||
/// invocarse una sola vez, antes de habilitar las interrupciones.
|
/// invocarse una sola vez, antes de habilitar las interrupciones.
|
||||||
@@ -35,40 +64,79 @@ pub fn init() {
|
|||||||
CANALES.call_once(|| Mutex::new(Vec::new()));
|
CANALES.call_once(|| Mutex::new(Vec::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Crea un canal de teclado nuevo, AUN sin inscribir en la difusion. Cada
|
/// Crea un canal de teclado nuevo, AUN sin inscribir en el censo. Cada
|
||||||
/// aplicacion reclama el suyo al empezar a cargarse.
|
/// aplicacion reclama el suyo al empezar a cargarse.
|
||||||
pub fn crear_canal() -> CanalTeclado {
|
pub fn crear_canal() -> CanalTeclado {
|
||||||
Arc::new(ArrayQueue::new(CAPACIDAD_COLA))
|
Arc::new(ArrayQueue::new(CAPACIDAD_COLA))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inscribe un canal en el censo de difusion. Desde este instante, la IRQ1
|
/// Inscribe el canal de la app `indice` en el censo. Desde este instante, una
|
||||||
/// empuja cada scancode tambien a este canal. Se invoca al final de la carga
|
/// tecla ordinaria llega a esta app cuando tiene el foco. Se invoca al final de
|
||||||
/// de una app: una carga fallida no debe dejar canales huerfanos.
|
/// la carga de una app: una carga fallida no debe dejar canales huerfanos.
|
||||||
pub fn registrar_canal(canal: &CanalTeclado) {
|
pub fn registrar_canal(indice: usize, canal: &CanalTeclado) {
|
||||||
if let Some(censo) = CANALES.get() {
|
if let Some(censo) = CANALES.get() {
|
||||||
// El cerrojo lo disputa el manejador de IRQ1: tomarlo con las
|
// El cerrojo lo disputa el manejador de IRQ1: tomarlo con las
|
||||||
// interrupciones acalladas hace imposible el interbloqueo.
|
// interrupciones acalladas hace imposible el interbloqueo.
|
||||||
interrupts::without_interrupts(|| censo.lock().push(canal.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Da de baja un canal del censo de difusion. Lo invoca el `Drop` de una
|
|
||||||
/// aplicacion desalojada: la IRQ1 deja, de inmediato, de empujarle scancodes.
|
|
||||||
pub fn cerrar_canal(canal: &CanalTeclado) {
|
|
||||||
if let Some(censo) = CANALES.get() {
|
|
||||||
interrupts::without_interrupts(|| {
|
interrupts::without_interrupts(|| {
|
||||||
censo.lock().retain(|inscrito| !Arc::ptr_eq(inscrito, canal));
|
let mut censo = censo.lock();
|
||||||
|
while censo.len() <= indice {
|
||||||
|
censo.push(None);
|
||||||
|
}
|
||||||
|
censo[indice] = Some(canal.clone());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Punto de entrada DESDE el manejador de IRQ1. DIFUNDE el scancode a cuantos
|
/// Da de baja el canal de la app `indice`. Lo invoca el `Drop` de una
|
||||||
/// canales haya abiertos. Deliberadamente breve y libre de panicos: corre en
|
/// aplicacion desalojada: la ranura queda en `None` y la IRQ deja de enrutarle
|
||||||
/// contexto de interrupcion.
|
/// teclas, sin desplazar los indices de las demas.
|
||||||
pub fn recibir_scancode(scancode: u8) {
|
pub fn cerrar_canal(indice: usize) {
|
||||||
if let Some(censo) = CANALES.get() {
|
if let Some(censo) = CANALES.get() {
|
||||||
for canal in censo.lock().iter() {
|
interrupts::without_interrupts(|| {
|
||||||
// Si un canal desborda, se descarta el scancode en silencio: mas
|
let mut censo = censo.lock();
|
||||||
|
if let Some(ranura) = censo.get_mut(indice) {
|
||||||
|
*ranura = None;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Punto de entrada DESDE el manejador de IRQ1. Rastrea el modificador Alt,
|
||||||
|
/// intercepta los mandos del sistema y enruta la tecla ordinaria a la app
|
||||||
|
/// enfocada. Deliberadamente breve y libre de panicos: corre en contexto de
|
||||||
|
/// interrupcion y no bloquea ningun cerrojo cooperativo.
|
||||||
|
pub fn recibir_scancode(scancode: u8) {
|
||||||
|
// 1. Rastrear la tecla Alt — el modificador de los mandos del sistema. Se
|
||||||
|
// consume: el modificador nunca se difunde a una app.
|
||||||
|
match scancode {
|
||||||
|
ALT_MAKE => {
|
||||||
|
ALT_PULSADO.store(true, Ordering::Relaxed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ALT_BREAK => {
|
||||||
|
ALT_PULSADO.store(false, Ordering::Relaxed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Con Alt pulsada, los make codes son MANDOS del compositor. Se traducen
|
||||||
|
// a una orden en la cola lock-free y se consumen — jamas llegan a una app.
|
||||||
|
if ALT_PULSADO.load(Ordering::Relaxed) {
|
||||||
|
match scancode {
|
||||||
|
ESPACIO => compositor::solicitar(Mando::CiclarLayout),
|
||||||
|
TECLA_J => compositor::solicitar(Mando::FocoSiguiente),
|
||||||
|
TECLA_K => compositor::solicitar(Mando::FocoAnterior),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Tecla ordinaria: se entrega SOLO a la app que tiene el foco. El foco
|
||||||
|
// es un indice atomico; el censo, un vector indexado por `indice_app`.
|
||||||
|
if let Some(censo) = CANALES.get() {
|
||||||
|
if let Some(Some(canal)) = censo.lock().get(compositor::foco()) {
|
||||||
|
// Si el canal desborda, se descarta el scancode en silencio: mas
|
||||||
// vale perder una tecla que colapsar dentro de una interrupcion.
|
// vale perder una tecla que colapsar dentro de una interrupcion.
|
||||||
let _ = canal.push(scancode);
|
let _ = canal.push(scancode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,323 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
// renaser :: kernel/src/compositor.rs — Fase 8 :: el compositor teselante
|
// 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:
|
// El kernel no coloca las ventanas a mano: las TESELA. El motor es
|
||||||
// coordenadas fijas, una composicion rigida. La Fase 8 entrega esa decision a
|
// `mirada-layout` —el mismo nucleo `no_std` que ordena el compositor Wayland
|
||||||
// un COMPOSITOR: el kernel ya no coloca las ventanas a mano, las TESELA.
|
// de brahman—, enlazado por `path` cruzando la frontera de workspace.
|
||||||
//
|
//
|
||||||
// El motor de teselado es `mirada-layout` — el mismo nucleo `no_std` que
|
// FASE 8b/8c :: el compositor cobra vida. Mantiene un ESCRITORIO —el registro
|
||||||
// ordena las ventanas del compositor Wayland de brahman. Cruza la frontera de
|
// de todas las ventanas— y, por cada una, una CACHE de respaldo con su ultimo
|
||||||
// workspace y se enlaza aqui sin una linea de codigo nueva: geometria pura,
|
// fotograma. Gracias a esa cache, el teclado puede re-teselar el escritorio en
|
||||||
// determinista, la misma en Linux y en el bare-metal de renaser.
|
// caliente —o mover el foco— y el kernel recompone cada ventana en su marco
|
||||||
|
// nuevo SIN despertar a las apps: una app que solo pinto en su `init` conserva
|
||||||
|
// su imagen intacta a traves de cualquier reordenacion.
|
||||||
//
|
//
|
||||||
// Cada app conserva su tamaño NATURAL —el lienzo que sabe pintar, fijo—; el
|
// EXCLUSION DE INTERRUPCIONES. El `ESCRITORIO` lo tocan SOLO tareas
|
||||||
// compositor decide DONDE va ese lienzo. El kernel centra el fotograma natural
|
// cooperativas (el `tick` de una app, la tarea del compositor): el manejador
|
||||||
// de la app dentro del marco teselado. Asi el compositor reordena la pantalla
|
// de IRQ1 jamas lo bloquea. La IRQ se comunica con el mundo cooperativo por
|
||||||
// sin que ninguna app cambie una sola instruccion.
|
// un canal estrecho y a prueba de interbloqueos: dos atomicos —el foco y el
|
||||||
|
// estado de Alt— y una cola lock-free de mandos. Ningun cerrojo que la IRQ
|
||||||
|
// pudiera disputar a una tarea cooperativa.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crossbeam_queue::ArrayQueue;
|
||||||
use mirada_layout::{tile, LayoutMode, LayoutParams, Rect};
|
use mirada_layout::{tile, LayoutMode, LayoutParams, Rect};
|
||||||
|
use spin::{Mutex, Once};
|
||||||
|
|
||||||
use crate::grafico::RegionPantalla;
|
use crate::grafico::{Color, RegionPantalla};
|
||||||
|
|
||||||
/// Altura del strip superior reservado a la consola; las apps teselan debajo.
|
/// Altura del strip superior reservado a la consola; las apps teselan debajo.
|
||||||
/// La consola conserva ahi su registro de arranque completo —seis lineas,
|
/// La consola conserva ahi su registro de arranque completo —seis lineas,
|
||||||
/// hasta la sonda asincrona de disco— legible sobre el teselado.
|
/// hasta la sonda asincrona de disco— legible sobre el teselado.
|
||||||
const FRANJA_CONSOLA: usize = 296;
|
const FRANJA_CONSOLA: usize = 296;
|
||||||
|
|
||||||
/// El modo de teselado del compositor. Fijo por ahora — la Fase 8b lo hara
|
/// El modo de teselado con que arranca el escritorio. El teclado lo cicla.
|
||||||
/// conmutable en caliente desde el teclado, recorriendo los siete modos que
|
const MODO_INICIAL: LayoutMode = LayoutMode::MasterStack;
|
||||||
/// `mirada-layout` ofrece.
|
|
||||||
const MODO: LayoutMode = LayoutMode::MasterStack;
|
|
||||||
|
|
||||||
/// Margen entre ventanas teseladas, en pixeles — el aire que separa un marco
|
/// Margen entre ventanas teseladas, en pixeles.
|
||||||
/// de sus vecinos.
|
|
||||||
const MARGEN: i32 = 14;
|
const MARGEN: i32 = 14;
|
||||||
|
|
||||||
|
/// Capacidad de la cola de mandos del compositor — holgada: nadie pulsa tanto.
|
||||||
|
const CAPACIDAD_MANDOS: usize = 32;
|
||||||
|
|
||||||
|
/// Un mando del compositor — lo emite el teclado desde el contexto de IRQ, lo
|
||||||
|
/// atiende la tarea del compositor desde el reactor cooperativo.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Mando {
|
||||||
|
/// Ciclar al siguiente modo de teselado de `mirada-layout`.
|
||||||
|
CiclarLayout,
|
||||||
|
/// Mover el foco a la siguiente ventana viva.
|
||||||
|
FocoSiguiente,
|
||||||
|
/// Mover el foco a la ventana viva anterior.
|
||||||
|
FocoAnterior,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Una ventana del escritorio: una app, su geometria y su ultimo fotograma.
|
||||||
|
struct Ventana {
|
||||||
|
/// Tamaño natural del lienzo de la app — lo que sabe pintar, fijo.
|
||||||
|
natural_ancho: usize,
|
||||||
|
natural_alto: usize,
|
||||||
|
/// El marco teselado actual — donde la app vive en pantalla. Cambia con
|
||||||
|
/// cada re-teselado.
|
||||||
|
marco: RegionPantalla,
|
||||||
|
/// CACHE de respaldo: el ultimo fotograma exitoso que la app envio. Su
|
||||||
|
/// tamaño esta acotado al lienzo natural —`natural_ancho × natural_alto ×
|
||||||
|
/// 4`— y se reserva UNA sola vez, al fundar el escritorio: jamas crece. El
|
||||||
|
/// re-teselado recompone la ventana desde aqui, sin molestar a la app.
|
||||||
|
cache: Vec<u8>,
|
||||||
|
/// ¿Ha enviado la app al menos un fotograma? Hasta entonces, su cache es
|
||||||
|
/// solo ceros y no se recompone.
|
||||||
|
pintada: bool,
|
||||||
|
/// Si el kernel desalojo la app, el color de su baliza. `None` mientras
|
||||||
|
/// vive; `Some(color)` la marca como muerta y la excluye del foco.
|
||||||
|
baliza: Option<Color>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// El escritorio: el registro de todas las ventanas y el modo de teselado.
|
||||||
|
/// Lo tocan SOLO tareas cooperativas — nunca el manejador de IRQ1.
|
||||||
|
struct Escritorio {
|
||||||
|
modo: LayoutMode,
|
||||||
|
ancho: usize,
|
||||||
|
alto: usize,
|
||||||
|
ventanas: Vec<Ventana>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// El escritorio global. Se funda una sola vez, en el arranque.
|
||||||
|
static ESCRITORIO: Once<Mutex<Escritorio>> = Once::new();
|
||||||
|
|
||||||
|
/// El indice de la ventana ENFOCADA. Atomico —no un campo del `Escritorio`—
|
||||||
|
/// porque el manejador de IRQ1 lo LEE para enrutar el teclado, y un atomico no
|
||||||
|
/// se puede disputar: jamas hay interbloqueo entre la IRQ y una tarea.
|
||||||
|
static FOCO: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
/// La cola de mandos: el manejador de IRQ1 deposita aqui las ordenes del
|
||||||
|
/// teclado (lock-free, segura en contexto de interrupcion); la tarea del
|
||||||
|
/// compositor las drena desde el reactor cooperativo.
|
||||||
|
static MANDOS: Once<ArrayQueue<Mando>> = Once::new();
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Fundacion y consulta — el arranque
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Funda el escritorio: crea una ventana por app, con su marco teselado inicial
|
||||||
|
/// y su cache de respaldo ya reservada al tamaño natural. `naturales` da el
|
||||||
|
/// `(ancho, alto)` del lienzo de cada app, en el orden del manifiesto.
|
||||||
|
pub fn fundar(ancho: usize, alto: usize, naturales: &[(usize, usize)]) {
|
||||||
|
MANDOS.call_once(|| ArrayQueue::new(CAPACIDAD_MANDOS));
|
||||||
|
|
||||||
|
let marcos = teselar(naturales.len(), ancho, alto, MODO_INICIAL);
|
||||||
|
let mut ventanas = Vec::with_capacity(naturales.len());
|
||||||
|
for (i, &(nat_ancho, nat_alto)) in naturales.iter().enumerate() {
|
||||||
|
ventanas.push(Ventana {
|
||||||
|
natural_ancho: nat_ancho,
|
||||||
|
natural_alto: nat_alto,
|
||||||
|
marco: marcos[i],
|
||||||
|
// La cache: reservada UNA vez, acotada al lienzo natural.
|
||||||
|
cache: vec![0u8; nat_ancho.saturating_mul(nat_alto).saturating_mul(4)],
|
||||||
|
pintada: false,
|
||||||
|
baliza: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ESCRITORIO.call_once(|| {
|
||||||
|
Mutex::new(Escritorio {
|
||||||
|
modo: MODO_INICIAL,
|
||||||
|
ancho,
|
||||||
|
alto,
|
||||||
|
ventanas,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pinta el escenario inicial del compositor: el area de apps y sus marcos
|
||||||
|
/// teselados. Se invoca una vez, tras `fundar`, antes de encender las apps.
|
||||||
|
pub fn componer_escenario() {
|
||||||
|
let Some(escritorio) = ESCRITORIO.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let escritorio = escritorio.lock();
|
||||||
|
let area = area_apps(escritorio.ancho, escritorio.alto);
|
||||||
|
let marcos: Vec<RegionPantalla> = escritorio.ventanas.iter().map(|v| v.marco).collect();
|
||||||
|
crate::consola::pintar_escenario(area, &marcos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// El indice de la ventana enfocada. Lo LEE el manejador de IRQ1 para enrutar
|
||||||
|
/// cada tecla — por eso es una simple lectura atomica, sin cerrojo alguno.
|
||||||
|
pub fn foco() -> usize {
|
||||||
|
FOCO.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encola un mando del teclado. Lo invoca el manejador de IRQ1: empujar a una
|
||||||
|
/// cola lock-free es seguro en contexto de interrupcion.
|
||||||
|
pub fn solicitar(mando: Mando) {
|
||||||
|
if let Some(mandos) = MANDOS.get() {
|
||||||
|
// Si la cola se desborda, el mando se pierde en silencio: mas vale
|
||||||
|
// perder una pulsacion que arriesgar un panico dentro de una IRQ.
|
||||||
|
let _ = mandos.push(mando);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// El fotograma de una app — cache y composicion
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Recibe el fotograma de la app `indice`: lo copia a su CACHE de respaldo —el
|
||||||
|
/// kernel asume la persistencia visual— y lo compone, centrado, en su marco.
|
||||||
|
/// Lo invoca la capacidad `sys_render_frame` desde el `tick` cooperativo.
|
||||||
|
pub fn presentar_fotograma(indice: usize, datos: &[u8]) {
|
||||||
|
let Some(escritorio) = ESCRITORIO.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (marco, nat_ancho, nat_alto) = {
|
||||||
|
let mut escritorio = escritorio.lock();
|
||||||
|
let Some(ventana) = escritorio.ventanas.get_mut(indice) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// Cachear el fotograma. El destino esta acotado al lienzo natural; se
|
||||||
|
// copia el minimo de ambas longitudes — jamas se desborda la cache.
|
||||||
|
let n = ventana.cache.len().min(datos.len());
|
||||||
|
ventana.cache[..n].copy_from_slice(&datos[..n]);
|
||||||
|
ventana.pintada = true;
|
||||||
|
(ventana.marco, ventana.natural_ancho, ventana.natural_alto)
|
||||||
|
};
|
||||||
|
let enfocada = FOCO.load(Ordering::Relaxed) == indice;
|
||||||
|
crate::consola::volcar_marco(marco, nat_ancho, nat_alto, datos, enfocada);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marca la ventana `indice` como desalojada y tatua su marco con la baliza.
|
||||||
|
/// Desde aqui queda excluida del foco — el teclado la salta.
|
||||||
|
pub fn desalojar(indice: usize, color: Color) {
|
||||||
|
let Some(escritorio) = ESCRITORIO.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let marco = {
|
||||||
|
let mut escritorio = escritorio.lock();
|
||||||
|
let Some(ventana) = escritorio.ventanas.get_mut(indice) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
ventana.baliza = Some(color);
|
||||||
|
ventana.marco
|
||||||
|
};
|
||||||
|
let enfocada = FOCO.load(Ordering::Relaxed) == indice;
|
||||||
|
crate::consola::pintar_desalojo(marco, color, enfocada);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Los mandos del teclado — el escritorio interactivo
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Atiende los mandos pendientes del teclado. La invoca la tarea del compositor
|
||||||
|
/// en cada fotograma, desde el reactor cooperativo — el unico contexto donde es
|
||||||
|
/// seguro bloquear el `ESCRITORIO` y la consola.
|
||||||
|
pub fn atender_mandos() {
|
||||||
|
let Some(mandos) = MANDOS.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
while let Some(mando) = mandos.pop() {
|
||||||
|
match mando {
|
||||||
|
Mando::CiclarLayout => ciclar_layout(),
|
||||||
|
Mando::FocoSiguiente => mover_foco(true),
|
||||||
|
Mando::FocoAnterior => mover_foco(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cicla al siguiente modo de teselado: recalcula los marcos de todas las
|
||||||
|
/// ventanas y recompone el escritorio entero desde las caches de respaldo.
|
||||||
|
fn ciclar_layout() {
|
||||||
|
let Some(escritorio) = ESCRITORIO.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mut escritorio = escritorio.lock();
|
||||||
|
escritorio.modo = escritorio.modo.next();
|
||||||
|
|
||||||
|
let marcos = teselar(
|
||||||
|
escritorio.ventanas.len(),
|
||||||
|
escritorio.ancho,
|
||||||
|
escritorio.alto,
|
||||||
|
escritorio.modo,
|
||||||
|
);
|
||||||
|
for (ventana, marco) in escritorio.ventanas.iter_mut().zip(marcos) {
|
||||||
|
ventana.marco = marco;
|
||||||
|
}
|
||||||
|
redibujar_todo(&escritorio);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mueve el foco a la siguiente ventana VIVA —saltando las desalojadas—; tras
|
||||||
|
/// el salto, redibuja la ventana que pierde el foco y la que lo gana, para que
|
||||||
|
/// el borde de cada una cambie de color.
|
||||||
|
fn mover_foco(adelante: bool) {
|
||||||
|
let Some(escritorio) = ESCRITORIO.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let escritorio = escritorio.lock();
|
||||||
|
let n = escritorio.ventanas.len();
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let anterior = FOCO.load(Ordering::Relaxed).min(n - 1);
|
||||||
|
|
||||||
|
// Avanzar saltando las ventanas desalojadas. Si no hay ninguna viva, tras
|
||||||
|
// `n` pasos se vuelve al punto de partida y el foco no cambia.
|
||||||
|
let mut nuevo = anterior;
|
||||||
|
for _ in 0..n {
|
||||||
|
nuevo = if adelante {
|
||||||
|
(nuevo + 1) % n
|
||||||
|
} else {
|
||||||
|
(nuevo + n - 1) % n
|
||||||
|
};
|
||||||
|
if escritorio.ventanas[nuevo].baliza.is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FOCO.store(nuevo, Ordering::Relaxed);
|
||||||
|
|
||||||
|
redibujar_ventana(&escritorio.ventanas[anterior], false);
|
||||||
|
redibujar_ventana(&escritorio.ventanas[nuevo], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recompone el escritorio entero: repinta el escenario —area y paneles— con
|
||||||
|
/// los marcos nuevos y, sobre el, cada ventana desde su cache de respaldo.
|
||||||
|
fn redibujar_todo(escritorio: &Escritorio) {
|
||||||
|
let area = area_apps(escritorio.ancho, escritorio.alto);
|
||||||
|
let marcos: Vec<RegionPantalla> = escritorio.ventanas.iter().map(|v| v.marco).collect();
|
||||||
|
crate::consola::pintar_escenario(area, &marcos);
|
||||||
|
|
||||||
|
let foco = FOCO.load(Ordering::Relaxed);
|
||||||
|
for (i, ventana) in escritorio.ventanas.iter().enumerate() {
|
||||||
|
redibujar_ventana(ventana, i == foco);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redibuja UNA ventana en su marco actual: si fue desalojada, su baliza; si ya
|
||||||
|
/// pinto, su ultimo fotograma desde la cache; si aun no pinto, nada —el panel
|
||||||
|
/// del escenario ya esta puesto—.
|
||||||
|
fn redibujar_ventana(ventana: &Ventana, enfocada: bool) {
|
||||||
|
match ventana.baliza {
|
||||||
|
Some(color) => crate::consola::pintar_desalojo(ventana.marco, color, enfocada),
|
||||||
|
None => {
|
||||||
|
if ventana.pintada {
|
||||||
|
crate::consola::volcar_marco(
|
||||||
|
ventana.marco,
|
||||||
|
ventana.natural_ancho,
|
||||||
|
ventana.natural_alto,
|
||||||
|
&ventana.cache,
|
||||||
|
enfocada,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Teselado — la geometria pura de `mirada-layout`
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
/// El area de pantalla que el compositor tesela: toda la pantalla menos la
|
/// El area de pantalla que el compositor tesela: toda la pantalla menos la
|
||||||
/// franja de la consola en la cima.
|
/// franja de la consola en la cima.
|
||||||
pub fn area_apps(ancho_pantalla: usize, alto_pantalla: usize) -> RegionPantalla {
|
pub fn area_apps(ancho_pantalla: usize, alto_pantalla: usize) -> RegionPantalla {
|
||||||
@@ -47,11 +329,10 @@ pub fn area_apps(ancho_pantalla: usize, alto_pantalla: usize) -> RegionPantalla
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tesela el area de apps en `n` marcos —uno por ventana, en el orden de las
|
/// Tesela el area de apps en `n` marcos con el modo dado. El vector resultante
|
||||||
/// apps del manifiesto— con el algoritmo de `mirada-layout`. El vector
|
/// tiene exactamente `n` elementos, en el orden de las apps del manifiesto.
|
||||||
/// resultante tiene exactamente `n` elementos.
|
fn teselar(n: usize, ancho: usize, alto: usize, modo: LayoutMode) -> Vec<RegionPantalla> {
|
||||||
pub fn disponer(n: usize, ancho_pantalla: usize, alto_pantalla: usize) -> Vec<RegionPantalla> {
|
let area = area_apps(ancho, alto);
|
||||||
let area = area_apps(ancho_pantalla, alto_pantalla);
|
|
||||||
let pantalla = Rect::new(
|
let pantalla = Rect::new(
|
||||||
area.x as i32,
|
area.x as i32,
|
||||||
area.y as i32,
|
area.y as i32,
|
||||||
@@ -59,7 +340,7 @@ pub fn disponer(n: usize, ancho_pantalla: usize, alto_pantalla: usize) -> Vec<Re
|
|||||||
area.alto as i32,
|
area.alto as i32,
|
||||||
);
|
);
|
||||||
let params = LayoutParams {
|
let params = LayoutParams {
|
||||||
mode: MODO,
|
mode: modo,
|
||||||
gap: MARGEN,
|
gap: MARGEN,
|
||||||
..LayoutParams::default()
|
..LayoutParams::default()
|
||||||
};
|
};
|
||||||
@@ -69,9 +350,8 @@ pub fn disponer(n: usize, ancho_pantalla: usize, alto_pantalla: usize) -> Vec<Re
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Traduce un `Rect` de `mirada-layout` (`i32`, en teoria con signo) a la
|
/// Traduce un `Rect` de `mirada-layout` (`i32`) a la `RegionPantalla` del
|
||||||
/// `RegionPantalla` del kernel (`usize`). Un rectangulo degenerado queda en
|
/// kernel (`usize`). Un rectangulo degenerado queda en cero.
|
||||||
/// cero — el kernel no compondra nada en el.
|
|
||||||
fn rect_a_region(r: Rect) -> RegionPantalla {
|
fn rect_a_region(r: Rect) -> RegionPantalla {
|
||||||
RegionPantalla {
|
RegionPantalla {
|
||||||
x: r.x.max(0) as usize,
|
x: r.x.max(0) as usize,
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ impl Consola {
|
|||||||
nat_ancho: usize,
|
nat_ancho: usize,
|
||||||
nat_alto: usize,
|
nat_alto: usize,
|
||||||
datos: &[u8],
|
datos: &[u8],
|
||||||
|
enfocada: bool,
|
||||||
) {
|
) {
|
||||||
if nat_ancho == 0 || nat_alto == 0 {
|
if nat_ancho == 0 || nat_alto == 0 {
|
||||||
return;
|
return;
|
||||||
@@ -165,17 +166,47 @@ impl Consola {
|
|||||||
self.lienzo.pixeles[y * self.lienzo.ancho + x] =
|
self.lienzo.pixeles[y * self.lienzo.ancho + x] =
|
||||||
codificar(self.lienzo.formato, color);
|
codificar(self.lienzo.formato, color);
|
||||||
}
|
}
|
||||||
|
// El borde del compositor: delata, de un vistazo, quien tiene el foco.
|
||||||
|
self.dibujar_borde(marco, enfocada);
|
||||||
self.presentar();
|
self.presentar();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inunda una region entera con un color plano y la presenta. Es la baliza
|
/// Inunda una region entera con un color plano —la baliza de desalojo: una
|
||||||
/// de desalojo: cuando una aplicacion falla, su marco se tatua de purpura.
|
/// app que falla tatua su marco de purpura— y le traza su borde de foco.
|
||||||
fn pintar_region(&mut self, region: RegionPantalla, color: Color) {
|
fn pintar_region(&mut self, region: RegionPantalla, color: Color, enfocada: bool) {
|
||||||
self.lienzo
|
self.lienzo
|
||||||
.rellenar_rect(region.x, region.y, region.ancho, region.alto, color);
|
.rellenar_rect(region.x, region.y, region.ancho, region.alto, color);
|
||||||
|
self.dibujar_borde(region, enfocada);
|
||||||
self.presentar();
|
self.presentar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Traza un borde de 3 px alrededor de un marco: indigo brillante si la
|
||||||
|
/// ventana tiene el foco del compositor, gris mate si no (Fase 8c).
|
||||||
|
fn dibujar_borde(&mut self, marco: RegionPantalla, enfocada: bool) {
|
||||||
|
const GROSOR: usize = 3;
|
||||||
|
let color = if enfocada { Color::FOCO } else { Color::SIN_FOCO };
|
||||||
|
// Lados superior e inferior.
|
||||||
|
self.lienzo
|
||||||
|
.rellenar_rect(marco.x, marco.y, marco.ancho, GROSOR, color);
|
||||||
|
self.lienzo.rellenar_rect(
|
||||||
|
marco.x,
|
||||||
|
marco.y + marco.alto.saturating_sub(GROSOR),
|
||||||
|
marco.ancho,
|
||||||
|
GROSOR,
|
||||||
|
color,
|
||||||
|
);
|
||||||
|
// Lados izquierdo y derecho.
|
||||||
|
self.lienzo
|
||||||
|
.rellenar_rect(marco.x, marco.y, GROSOR, marco.alto, color);
|
||||||
|
self.lienzo.rellenar_rect(
|
||||||
|
marco.x + marco.ancho.saturating_sub(GROSOR),
|
||||||
|
marco.y,
|
||||||
|
GROSOR,
|
||||||
|
marco.alto,
|
||||||
|
color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Pinta el escenario del compositor (Fase 8): inunda el area de apps con
|
/// Pinta el escenario del compositor (Fase 8): inunda el area de apps con
|
||||||
/// el reposo del lienzo —borrando cuanto hubiera debajo— y, sobre ella,
|
/// 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
|
/// tiñe cada marco teselado con el color de panel. Asi el teselado se ve
|
||||||
@@ -205,36 +236,38 @@ impl Consola {
|
|||||||
/// asincronas y las capacidades del userspace escriben en ella tras su `Mutex`.
|
/// asincronas y las capacidades del userspace escriben en ella tras su `Mutex`.
|
||||||
pub(crate) static CONSOLA: Once<Mutex<Consola>> = Once::new();
|
pub(crate) static CONSOLA: Once<Mutex<Consola>> = Once::new();
|
||||||
|
|
||||||
/// Puerta del kernel para la capacidad `sys_render_frame` del userspace WASM:
|
/// Compone un fotograma del userspace —ya cacheado por el compositor— centrado
|
||||||
/// compone sobre la consola global un fotograma —cuyos limites el host ya
|
/// en su marco teselado, con su borde de foco. La invoca `compositor` al
|
||||||
/// verifico matematicamente contra la memoria lineal del modulo— centrado en
|
/// recibir un `sys_render_frame` y al recomponer el escritorio tras un mando.
|
||||||
/// el marco que el compositor asigno a esa aplicacion. `nat_ancho`/`nat_alto`
|
pub(crate) fn volcar_marco(
|
||||||
/// son el tamaño natural del lienzo de la app.
|
|
||||||
pub(crate) fn volcar_marco_wasm(
|
|
||||||
marco: RegionPantalla,
|
marco: RegionPantalla,
|
||||||
nat_ancho: usize,
|
nat_ancho: usize,
|
||||||
nat_alto: usize,
|
nat_alto: usize,
|
||||||
datos: &[u8],
|
datos: &[u8],
|
||||||
|
enfocada: bool,
|
||||||
) {
|
) {
|
||||||
if let Some(consola) = CONSOLA.get() {
|
if let Some(consola) = CONSOLA.get() {
|
||||||
consola.lock().volcar_marco(marco, nat_ancho, nat_alto, datos);
|
consola
|
||||||
|
.lock()
|
||||||
|
.volcar_marco(marco, nat_ancho, nat_alto, datos, enfocada);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pinta el escenario del compositor (Fase 8): el area de apps y, sobre ella,
|
/// 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.
|
/// cada marco teselado. La invoca `compositor` al arrancar y al re-teselar.
|
||||||
pub(crate) fn pintar_escenario(area: RegionPantalla, marcos: &[RegionPantalla]) {
|
pub(crate) fn pintar_escenario(area: RegionPantalla, marcos: &[RegionPantalla]) {
|
||||||
if let Some(consola) = CONSOLA.get() {
|
if let Some(consola) = CONSOLA.get() {
|
||||||
consola.lock().pintar_escenario(area, marcos);
|
consola.lock().pintar_escenario(area, marcos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tatua la baliza de desalojo sobre la region de una aplicacion que el kernel
|
/// Tatua la baliza de desalojo sobre el marco de una aplicacion que el kernel
|
||||||
/// ha dado por terminada. El color delata la causa —purpura para una falla de
|
/// ha dado por terminada, con su borde de foco. El color delata la causa
|
||||||
/// ejecucion o de combustible, amarillo palido para un desbordo de memoria—. Es
|
/// —purpura para una falla de ejecucion o de combustible, amarillo palido para
|
||||||
/// una advertencia NO fatal: la app muere, el kernel y sus vecinas siguen vivos.
|
/// un desbordo de memoria—. Es una advertencia NO fatal: la app muere, el
|
||||||
pub(crate) fn pintar_desalojo(region: RegionPantalla, color: Color) {
|
/// kernel y sus vecinas siguen vivos.
|
||||||
|
pub(crate) fn pintar_desalojo(marco: RegionPantalla, color: Color, enfocada: bool) {
|
||||||
if let Some(consola) = CONSOLA.get() {
|
if let Some(consola) = CONSOLA.get() {
|
||||||
consola.lock().pintar_region(region, color);
|
consola.lock().pintar_region(marco, color, enfocada);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,22 @@ impl Color {
|
|||||||
b: 0x30,
|
b: 0x30,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Borde de la ventana ENFOCADA (Fase 8c): un indigo brillante. Delata, de
|
||||||
|
/// un vistazo, a quien recibe el teclado.
|
||||||
|
pub(crate) const FOCO: Color = Color {
|
||||||
|
r: 0x4B,
|
||||||
|
g: 0x00,
|
||||||
|
b: 0x82,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Borde de una ventana sin foco (Fase 8c): un gris mate, discreto — marca
|
||||||
|
/// el marco sin reclamar la atencion.
|
||||||
|
pub(crate) const SIN_FOCO: Color = Color {
|
||||||
|
r: 0x3A,
|
||||||
|
g: 0x40,
|
||||||
|
b: 0x4E,
|
||||||
|
};
|
||||||
|
|
||||||
/// Alerta de colapso: un rojo saturado, imposible de ignorar.
|
/// Alerta de colapso: un rojo saturado, imposible de ignorar.
|
||||||
pub(crate) const ALERTA: Color = Color {
|
pub(crate) const ALERTA: Color = Color {
|
||||||
r: 0xD4,
|
r: 0xD4,
|
||||||
|
|||||||
+47
-35
@@ -60,16 +60,16 @@ mod sync;
|
|||||||
mod texto;
|
mod texto;
|
||||||
mod wasm;
|
mod wasm;
|
||||||
|
|
||||||
// Reexportaciones para que los submodulos conserven rutas `crate::` estables.
|
// Reexportacion para que los submodulos conserven rutas `crate::` estables.
|
||||||
pub(crate) use consola::volcar_marco_wasm;
|
|
||||||
pub(crate) use sync::CeldaSync;
|
pub(crate) use sync::CeldaSync;
|
||||||
|
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
use async_system::executor::Executor;
|
use async_system::executor::Executor;
|
||||||
use baliza::BALIZA_PANICO;
|
use baliza::BALIZA_PANICO;
|
||||||
use consola::{Consola, CONSOLA};
|
use consola::{Consola, CONSOLA};
|
||||||
use grafico::{
|
use grafico::{
|
||||||
codificar, reclamar_memoria_lienzo, Color, Lienzo, Pantalla, RegionPantalla, ALTO_MAX,
|
codificar, reclamar_memoria_lienzo, Color, Lienzo, Pantalla, ALTO_MAX, ANCHO_MAX,
|
||||||
ANCHO_MAX,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Configuracion que el cargador `bootloader` aplicara antes de cedernos la CPU.
|
/// Configuracion que el cargador `bootloader` aplicara antes de cedernos la CPU.
|
||||||
@@ -94,20 +94,32 @@ pub(crate) fn detener() -> ! {
|
|||||||
/// Tarea cooperativa de una aplicacion WASM. En cada pulso del reloj le concede
|
/// Tarea cooperativa de una aplicacion WASM. En cada pulso del reloj le concede
|
||||||
/// un `tick` —un fotograma de trabajo— y cede la CPU hasta el siguiente; entre
|
/// un `tick` —un fotograma de trabajo— y cede la CPU hasta el siguiente; entre
|
||||||
/// medias corren sus vecinas. Si la app falla o agota su combustible, se la
|
/// medias corren sus vecinas. Si la app falla o agota su combustible, se la
|
||||||
/// DESALOJA: se tatua su region con la baliza de purpura y la tarea concluye.
|
/// DESALOJA: el compositor tatua su ventana con la baliza y la tarea concluye.
|
||||||
/// El ejecutor la retira del censo, su memoria se libera, el kernel sigue vivo.
|
/// El ejecutor la retira del censo, su memoria se libera, el kernel sigue vivo.
|
||||||
async fn tarea_aplicacion(mut app: wasm::AplicacionWasm) {
|
async fn tarea_aplicacion(mut app: wasm::AplicacionWasm) {
|
||||||
loop {
|
loop {
|
||||||
async_system::reloj::EsperaFrame::nueva().await;
|
async_system::reloj::EsperaFrame::nueva().await;
|
||||||
if let Err(falla) = app.tick() {
|
if let Err(falla) = app.tick() {
|
||||||
// El color de la baliza delata la causa: purpura si agoto su tiempo
|
// El color de la baliza delata la causa: purpura si agoto su tiempo
|
||||||
// o aborto, amarillo si reviento su techo de memoria.
|
// o aborto, amarillo si reviento su techo de memoria. El compositor
|
||||||
consola::pintar_desalojo(app.marco(), falla.color_baliza());
|
// la pinta en el marco actual de la ventana y la marca como muerta.
|
||||||
|
compositor::desalojar(app.indice(), falla.color_baliza());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// FASE 8 :: la tarea del compositor. En cada fotograma drena la cola de mandos
|
||||||
|
/// que el teclado dejo —ciclar el modo de teselado, mover el foco— y los
|
||||||
|
/// aplica. Corre en el reactor cooperativo: el unico contexto donde es seguro
|
||||||
|
/// re-teselar el escritorio y recomponer el lienzo desde las caches.
|
||||||
|
async fn tarea_compositor() {
|
||||||
|
loop {
|
||||||
|
async_system::reloj::EsperaFrame::nueva().await;
|
||||||
|
compositor::atender_mandos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// FASE 6.2 — la prueba viva de la E/S asincrona. Esta tarea del reactor lee el
|
/// FASE 6.2 — la prueba viva de la E/S asincrona. Esta tarea del reactor lee el
|
||||||
/// sector 0 del disco SIN bloquear: cede la CPU mientras el disco trabaja —las
|
/// sector 0 del disco SIN bloquear: cede la CPU mientras el disco trabaja —las
|
||||||
/// apps siguen pintando entre tanto— y la IRQ del disco la reanuda cuando el
|
/// apps siguen pintando entre tanto— y la IRQ del disco la reanuda cuando el
|
||||||
@@ -132,19 +144,13 @@ async fn tarea_sonda_disco() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Da vida a una aplicacion del userspace a partir de su `EntradaApp` del
|
/// Da vida a una aplicacion del userspace a partir de su `EntradaApp` del
|
||||||
/// manifiesto: recupera su bytecode del grafo, la carga en el `marco` que el
|
/// manifiesto: recupera su bytecode del grafo, lo carga en la ventana `indice`
|
||||||
/// compositor le teselo y la despacha como tarea cooperativa del reactor. Si
|
/// del escritorio del compositor y despacha la app como tarea cooperativa del
|
||||||
/// el bytecode falta, esta corrupto, o la carga fracasa, se salda pintando el
|
/// reactor. Si el bytecode falta, esta corrupto, o la carga fracasa, el
|
||||||
/// marco con la baliza de desalojo — el kernel no se inmuta y sigue con las
|
/// compositor desaloja esa ventana — el kernel sigue con las demas.
|
||||||
/// demas.
|
fn encender_app(ejecutor: &mut Executor, indice: usize, entrada: &manifiesto::EntradaApp) {
|
||||||
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
|
// 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.
|
// dicta su `EntradaApp`; el compositor decide en que marco lo coloca.
|
||||||
let natural = manifiesto::region(entrada);
|
let natural = manifiesto::region(entrada);
|
||||||
// Recuperar el bytecode del grafo. `recuperar` recomputa el hash del
|
// Recuperar el bytecode del grafo. `recuperar` recomputa el hash del
|
||||||
// objeto y verifica su integridad: un bytecode corrupto se delata aqui
|
// objeto y verifica su integridad: un bytecode corrupto se delata aqui
|
||||||
@@ -152,22 +158,21 @@ fn encender_app(
|
|||||||
let bytecode = match almacen::recuperar(&entrada.bytecode) {
|
let bytecode = match almacen::recuperar(&entrada.bytecode) {
|
||||||
Ok(Some(objeto)) => objeto.datos,
|
Ok(Some(objeto)) => objeto.datos,
|
||||||
_ => {
|
_ => {
|
||||||
consola::pintar_desalojo(marco, Color::DESALOJO);
|
compositor::desalojar(indice, Color::DESALOJO);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// `indice` es la identidad de la app en el manifiesto: las capacidades de
|
// `indice` es la identidad de la app: su ventana en el escritorio del
|
||||||
// estado persistido (Fase 7c) la usan para hallar SU ranura `estado`.
|
// compositor y su ranura de estado persistido (Fase 7c).
|
||||||
match wasm::AplicacionWasm::cargar(
|
match wasm::AplicacionWasm::cargar(
|
||||||
&bytecode,
|
&bytecode,
|
||||||
marco,
|
|
||||||
natural.ancho,
|
natural.ancho,
|
||||||
natural.alto,
|
natural.alto,
|
||||||
entrada.techo_memoria as usize,
|
entrada.techo_memoria as usize,
|
||||||
indice,
|
indice,
|
||||||
) {
|
) {
|
||||||
Ok(app) => ejecutor.spawn(tarea_aplicacion(app)),
|
Ok(app) => ejecutor.spawn(tarea_aplicacion(app)),
|
||||||
Err(_) => consola::pintar_desalojo(marco, Color::DESALOJO),
|
Err(_) => compositor::desalojar(indice, Color::DESALOJO),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,18 +223,25 @@ fn cargar_userspace(ejecutor: &mut Executor, ancho_pantalla: usize, alto_pantall
|
|||||||
// consulta lee del manifiesto vivo.
|
// consulta lee del manifiesto vivo.
|
||||||
manifiesto::instalar(m.clone());
|
manifiesto::instalar(m.clone());
|
||||||
|
|
||||||
// FASE 8 :: el compositor tesela el area de apps — un marco por
|
// FASE 8 :: fundar el escritorio del compositor — una ventana por app,
|
||||||
// ventana, calculado por `mirada-layout`. Se pinta el escenario (el
|
// con su cache de respaldo y su marco teselado por `mirada-layout`— y
|
||||||
// area y sus marcos) antes de encender las apps: el teselado se ve
|
// pintar el escenario antes de encender las apps: el teselado se ve
|
||||||
// aunque alguna app no llegue siquiera a pintar su primer fotograma.
|
// aunque alguna app no llegue a pintar su primer fotograma.
|
||||||
let marcos = compositor::disponer(m.apps.len(), ancho_pantalla, alto_pantalla);
|
let naturales: Vec<(usize, usize)> = m
|
||||||
consola::pintar_escenario(
|
.apps
|
||||||
compositor::area_apps(ancho_pantalla, alto_pantalla),
|
.iter()
|
||||||
&marcos,
|
.map(|e| (e.region_ancho as usize, e.region_alto as usize))
|
||||||
);
|
.collect();
|
||||||
for (indice, (entrada, marco)) in m.apps.iter().zip(marcos).enumerate() {
|
compositor::fundar(ancho_pantalla, alto_pantalla, &naturales);
|
||||||
encender_app(ejecutor, indice, entrada, marco);
|
compositor::componer_escenario();
|
||||||
|
|
||||||
|
for (indice, entrada) in m.apps.iter().enumerate() {
|
||||||
|
encender_app(ejecutor, indice, entrada);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// La tarea del compositor: atiende los mandos del teclado —ciclar el
|
||||||
|
// teselado, mover el foco— en cada fotograma del reactor.
|
||||||
|
ejecutor.spawn(tarea_compositor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,18 +29,15 @@ use wasmi::{Caller, Error, Extern, Linker, Memory, StoreLimits};
|
|||||||
|
|
||||||
use crate::almacen::Hash;
|
use crate::almacen::Hash;
|
||||||
use crate::async_system::teclado::CanalTeclado;
|
use crate::async_system::teclado::CanalTeclado;
|
||||||
use crate::grafico::RegionPantalla;
|
|
||||||
|
|
||||||
/// El estado del host adscrito al `Store` de una aplicacion: cuanto necesita
|
/// El estado del host adscrito al `Store` de una aplicacion: cuanto necesita
|
||||||
/// una capacidad para servir a ESA app y a ninguna otra — su region de pantalla,
|
/// 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.
|
/// su canal de teclado y sus cuotas de recursos. Dos apps jamas comparten nada.
|
||||||
pub(crate) struct ContextoCapacidades {
|
pub(crate) struct ContextoCapacidades {
|
||||||
/// 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
|
/// El tamaño natural del lienzo de la app, en pixeles. El fotograma que
|
||||||
/// entrega `sys_render_frame` mide exactamente `natural_ancho × natural_alto`;
|
/// entrega `sys_render_frame` mide exactamente `natural_ancho × natural_alto`;
|
||||||
/// el compositor lo coloca, sin deformarlo, dentro del `marco`.
|
/// el compositor lo cachea y lo compone, sin deformarlo, en el marco que el
|
||||||
|
/// teselado le asigno.
|
||||||
pub(crate) natural_ancho: usize,
|
pub(crate) natural_ancho: usize,
|
||||||
pub(crate) natural_alto: usize,
|
pub(crate) natural_alto: usize,
|
||||||
/// El canal de teclado propio de la aplicacion.
|
/// El canal de teclado propio de la aplicacion.
|
||||||
@@ -48,9 +45,9 @@ pub(crate) struct ContextoCapacidades {
|
|||||||
/// El techo de recursos de la aplicacion — hoy, su memoria lineal maxima.
|
/// El techo de recursos de la aplicacion — hoy, su memoria lineal maxima.
|
||||||
/// `wasmi` lo consulta en cada `memory.grow` via `Store::limiter`.
|
/// `wasmi` lo consulta en cada `memory.grow` via `Store::limiter`.
|
||||||
pub(crate) limites: StoreLimits,
|
pub(crate) limites: StoreLimits,
|
||||||
/// El indice de esta app en el Manifiesto de Genesis — su identidad. Las
|
/// El indice de esta app — su identidad. La usan las capacidades de estado
|
||||||
/// capacidades de estado (Fase 7c) lo usan para hallar la `EntradaApp`
|
/// (Fase 7c) para hallar su `EntradaApp` del manifiesto, y el compositor
|
||||||
/// correcta: cada app persiste en SU ranura, jamas en la de otra.
|
/// (Fase 8) para hallar su ventana en el escritorio: jamas la de otra.
|
||||||
pub(crate) indice_app: usize,
|
pub(crate) indice_app: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +95,7 @@ pub(crate) fn enlazar_capacidades(
|
|||||||
"renaser",
|
"renaser",
|
||||||
"sys_render_frame",
|
"sys_render_frame",
|
||||||
|caller: Caller<'_, ContextoCapacidades>, ptr: u32, len: u32| -> Result<(), Error> {
|
|caller: Caller<'_, ContextoCapacidades>, ptr: u32, len: u32| -> Result<(), Error> {
|
||||||
let marco = caller.data().marco;
|
let indice = caller.data().indice_app;
|
||||||
let nat_ancho = caller.data().natural_ancho;
|
let nat_ancho = caller.data().natural_ancho;
|
||||||
let nat_alto = caller.data().natural_alto;
|
let nat_alto = caller.data().natural_alto;
|
||||||
|
|
||||||
@@ -124,9 +121,10 @@ pub(crate) fn enlazar_capacidades(
|
|||||||
"WASM :: sys_render_frame desbordo la memoria lineal del modulo",
|
"WASM :: sys_render_frame desbordo la memoria lineal del modulo",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Limites verificados: el compositor centra el fotograma natural
|
// Limites verificados: el compositor cachea el fotograma —para
|
||||||
// de la app dentro del marco que el teselado le asigno.
|
// poder recomponerlo si el escritorio se re-tesela— y lo compone,
|
||||||
crate::volcar_marco_wasm(marco, nat_ancho, nat_alto, fotograma);
|
// centrado, en el marco que el teselado asigno a esta app.
|
||||||
|
crate::compositor::presentar_fotograma(indice, fotograma);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use wasmi::{
|
|||||||
CompilationMode, Config, Engine, Linker, Module, Store, StoreLimitsBuilder, TrapCode, TypedFunc,
|
CompilationMode, Config, Engine, Linker, Module, Store, StoreLimitsBuilder, TrapCode, TypedFunc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::grafico::{Color, RegionPantalla};
|
use crate::grafico::Color;
|
||||||
use env::ContextoCapacidades;
|
use env::ContextoCapacidades;
|
||||||
|
|
||||||
/// Combustible concedido a `init`. Cubre con holgura el pintado inicial del
|
/// Combustible concedido a `init`. Cubre con holgura el pintado inicial del
|
||||||
@@ -64,34 +64,28 @@ impl FallaApp {
|
|||||||
/// aqui la instancia se conserva y el kernel la hace avanzar `tick` a `tick`.
|
/// aqui la instancia se conserva y el kernel la hace avanzar `tick` a `tick`.
|
||||||
pub struct AplicacionWasm {
|
pub struct AplicacionWasm {
|
||||||
/// El almacen: todo el estado de ESTA instancia — su memoria lineal, sus
|
/// El almacen: todo el estado de ESTA instancia — su memoria lineal, sus
|
||||||
/// globales y el contexto de capacidades con su region de pantalla.
|
/// globales y el contexto de capacidades con su identidad e indice.
|
||||||
almacen: Store<ContextoCapacidades>,
|
almacen: Store<ContextoCapacidades>,
|
||||||
/// El punto de entrada de fotograma, ya resuelto y con seguridad de tipos.
|
/// El punto de entrada de fotograma, ya resuelto y con seguridad de tipos.
|
||||||
/// `TypedFunc` es un asa autosuficiente dentro del `Store`: conservada esta,
|
/// `TypedFunc` es un asa autosuficiente dentro del `Store`: conservada esta,
|
||||||
/// el handle de la `Instance` no aporta nada y no se retiene.
|
/// el handle de la `Instance` no aporta nada y no se retiene.
|
||||||
func_tick: TypedFunc<(), ()>,
|
func_tick: TypedFunc<(), ()>,
|
||||||
/// 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 {
|
impl AplicacionWasm {
|
||||||
/// Carga, valida, instancia y arranca una aplicacion WASM aislada, ligada a
|
/// Carga, valida, instancia y arranca una aplicacion WASM aislada. Si algo
|
||||||
/// una region de pantalla. Si algo falla en el camino, se devuelve la falla
|
/// falla en el camino, se devuelve la falla en lugar de incendiar el kernel.
|
||||||
/// en lugar de incendiar el kernel.
|
|
||||||
///
|
///
|
||||||
/// El nuevo ABI del userspace exige dos exportaciones: `init` —invocada una
|
/// El ABI del userspace exige dos exportaciones: `init` —invocada una sola
|
||||||
/// sola vez, aqui— y `tick` —un fotograma de trabajo, invocada despues por
|
/// vez, aqui— y `tick` —un fotograma de trabajo, invocada despues por el
|
||||||
/// el reactor en cada pulso del reloj.
|
/// reactor en cada pulso del reloj.
|
||||||
///
|
///
|
||||||
/// `marco` es el rectangulo que el compositor (Fase 8) asigno a la app;
|
/// `natural_ancho`/`natural_alto` son el tamaño del lienzo de la app;
|
||||||
/// `natural_ancho`/`natural_alto`, el tamaño de su lienzo. `techo_memoria`
|
/// `techo_memoria`, su cuota de memoria lineal —la dicta su `EntradaApp` del
|
||||||
/// es su cuota de memoria lineal —la dicta su `EntradaApp` del manifiesto—,
|
/// manifiesto—; e `indice_app`, su identidad: la posicion con que el
|
||||||
/// e `indice_app` su posicion en el: su identidad para las capacidades de
|
/// compositor halla su ventana y las capacidades de estado su ranura.
|
||||||
/// estado persistido (Fase 7c).
|
|
||||||
pub fn cargar(
|
pub fn cargar(
|
||||||
bytecode: &[u8],
|
bytecode: &[u8],
|
||||||
marco: RegionPantalla,
|
|
||||||
natural_ancho: usize,
|
natural_ancho: usize,
|
||||||
natural_alto: usize,
|
natural_alto: usize,
|
||||||
techo_memoria: usize,
|
techo_memoria: usize,
|
||||||
@@ -108,10 +102,10 @@ impl AplicacionWasm {
|
|||||||
// 2. Validar y traducir el modulo — ya instrumentado con fuel.
|
// 2. Validar y traducir el modulo — ya instrumentado con fuel.
|
||||||
let modulo = Module::new(&motor, bytecode).map_err(|_| FallaApp::Carga)?;
|
let modulo = Module::new(&motor, bytecode).map_err(|_| FallaApp::Carga)?;
|
||||||
|
|
||||||
// 3. El almacen, con el contexto de capacidades de ESTA app: su region
|
// 3. El almacen, con el contexto de capacidades de ESTA app: su lienzo
|
||||||
// de pantalla, su canal de teclado y su techo de memoria. El canal
|
// natural, su canal de teclado y su techo de memoria. El canal se
|
||||||
// se crea ahora pero se inscribe en la difusion de la IRQ1 al final,
|
// crea ahora pero se inscribe en el censo de la IRQ1 al final, ya con
|
||||||
// ya con la app cargada: una carga fallida no deja canales huerfanos.
|
// la app cargada: una carga fallida no deja canales huerfanos.
|
||||||
let canal = crate::async_system::teclado::crear_canal();
|
let canal = crate::async_system::teclado::crear_canal();
|
||||||
let limites = StoreLimitsBuilder::new()
|
let limites = StoreLimitsBuilder::new()
|
||||||
.memory_size(techo_memoria)
|
.memory_size(techo_memoria)
|
||||||
@@ -122,7 +116,6 @@ impl AplicacionWasm {
|
|||||||
let mut almacen = Store::new(
|
let mut almacen = Store::new(
|
||||||
&motor,
|
&motor,
|
||||||
ContextoCapacidades {
|
ContextoCapacidades {
|
||||||
marco,
|
|
||||||
natural_ancho,
|
natural_ancho,
|
||||||
natural_alto,
|
natural_alto,
|
||||||
canal,
|
canal,
|
||||||
@@ -161,14 +154,11 @@ impl AplicacionWasm {
|
|||||||
.map_err(|_| FallaApp::Carga)?;
|
.map_err(|_| FallaApp::Carga)?;
|
||||||
|
|
||||||
// 8. Con la app ya cargada e instanciada, inscribir su canal de teclado
|
// 8. Con la app ya cargada e instanciada, inscribir su canal de teclado
|
||||||
// en la difusion de la IRQ1: desde aqui recibe cada pulsacion.
|
// en el censo de la IRQ1, en la ranura de su `indice_app`: desde
|
||||||
crate::async_system::teclado::registrar_canal(&almacen.data().canal);
|
// aqui recibe las teclas cuando el compositor le da el foco.
|
||||||
|
crate::async_system::teclado::registrar_canal(indice_app, &almacen.data().canal);
|
||||||
|
|
||||||
Ok(AplicacionWasm {
|
Ok(AplicacionWasm { almacen, func_tick })
|
||||||
almacen,
|
|
||||||
func_tick,
|
|
||||||
marco,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hace avanzar la aplicacion un fotograma. Recarga su presupuesto de
|
/// Hace avanzar la aplicacion un fotograma. Recarga su presupuesto de
|
||||||
@@ -194,10 +184,11 @@ impl AplicacionWasm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// El marco de pantalla que el compositor asigno a la aplicacion — donde
|
/// El indice de la aplicacion — su identidad en el escritorio del
|
||||||
/// se tatua su baliza si el kernel llega a desalojarla.
|
/// compositor. Lo usa la tarea de la app para decirle al compositor que
|
||||||
pub fn marco(&self) -> RegionPantalla {
|
/// ventana desalojar si la app llega a fallar.
|
||||||
self.marco
|
pub fn indice(&self) -> usize {
|
||||||
|
self.almacen.data().indice_app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +198,6 @@ impl AplicacionWasm {
|
|||||||
/// empujando scancodes a una cola muerta: una fuga lenta pero segura.
|
/// empujando scancodes a una cola muerta: una fuga lenta pero segura.
|
||||||
impl Drop for AplicacionWasm {
|
impl Drop for AplicacionWasm {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
crate::async_system::teclado::cerrar_canal(&self.almacen.data().canal);
|
crate::async_system::teclado::cerrar_canal(self.almacen.data().indice_app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user