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:
@@ -1,16 +1,28 @@
|
||||
// =============================================================================
|
||||
// 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,
|
||||
// via la capacidad `sys_get_scancode`— las drenan sin bloquear.
|
||||
//
|
||||
// FASE 5 :: con varias apps concurrentes, una sola cola compartida no sirve:
|
||||
// la primera en sondear le robaria la pulsacion a las demas. Por eso cada
|
||||
// aplicacion abre su PROPIO canal y la IRQ1 DIFUNDE cada scancode a todos —
|
||||
// cada app recibe su copia integra del flujo de entrada.
|
||||
// FASE 5 :: cada app abre su PROPIO canal; la primera en sondear no le roba la
|
||||
// pulsacion a las demas.
|
||||
//
|
||||
// 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::vec::Vec;
|
||||
|
||||
@@ -18,16 +30,33 @@ use crossbeam_queue::ArrayQueue;
|
||||
use spin::{Mutex, Once};
|
||||
use x86_64::instructions::interrupts;
|
||||
|
||||
use crate::compositor::{self, Mando};
|
||||
|
||||
/// Capacidad de la cola de scancodes de cada app. Holgada: nadie teclea tanto.
|
||||
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.
|
||||
pub type CanalTeclado = Arc<ArrayQueue<u8>>;
|
||||
|
||||
/// Censo de canales — uno por aplicacion del userspace. El manejador de IRQ1
|
||||
/// difunde cada scancode a TODOS: asi cada app recibe su propia copia del
|
||||
/// evento, sin que una le arrebate la pulsacion a otra.
|
||||
static CANALES: Once<Mutex<Vec<CanalTeclado>>> = Once::new();
|
||||
/// Censo de canales, INDEXADO por el `indice_app` de cada aplicacion. Una
|
||||
/// ranura `None` es una app que no abrio canal o que fue desalojada. El
|
||||
/// indexado estable permite que el foco —un simple indice— elija el canal.
|
||||
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
|
||||
/// invocarse una sola vez, antes de habilitar las interrupciones.
|
||||
@@ -35,40 +64,79 @@ pub fn init() {
|
||||
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.
|
||||
pub fn crear_canal() -> CanalTeclado {
|
||||
Arc::new(ArrayQueue::new(CAPACIDAD_COLA))
|
||||
}
|
||||
|
||||
/// Inscribe un canal en el censo de difusion. Desde este instante, la IRQ1
|
||||
/// empuja cada scancode tambien a este canal. Se invoca al final de la carga
|
||||
/// de una app: una carga fallida no debe dejar canales huerfanos.
|
||||
pub fn registrar_canal(canal: &CanalTeclado) {
|
||||
/// Inscribe el canal de la app `indice` en el censo. Desde este instante, una
|
||||
/// tecla ordinaria llega a esta app cuando tiene el foco. Se invoca al final de
|
||||
/// la carga de una app: una carga fallida no debe dejar canales huerfanos.
|
||||
pub fn registrar_canal(indice: usize, canal: &CanalTeclado) {
|
||||
if let Some(censo) = CANALES.get() {
|
||||
// El cerrojo lo disputa el manejador de IRQ1: tomarlo con las
|
||||
// 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(|| {
|
||||
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
|
||||
/// canales haya abiertos. Deliberadamente breve y libre de panicos: corre en
|
||||
/// contexto de interrupcion.
|
||||
pub fn recibir_scancode(scancode: u8) {
|
||||
/// Da de baja el canal de la app `indice`. Lo invoca el `Drop` de una
|
||||
/// aplicacion desalojada: la ranura queda en `None` y la IRQ deja de enrutarle
|
||||
/// teclas, sin desplazar los indices de las demas.
|
||||
pub fn cerrar_canal(indice: usize) {
|
||||
if let Some(censo) = CANALES.get() {
|
||||
for canal in censo.lock().iter() {
|
||||
// Si un canal desborda, se descarta el scancode en silencio: mas
|
||||
interrupts::without_interrupts(|| {
|
||||
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.
|
||||
let _ = canal.push(scancode);
|
||||
}
|
||||
|
||||
@@ -1,41 +1,323 @@
|
||||
// =============================================================================
|
||||
// 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 kernel no coloca las ventanas a mano: las TESELA. El motor es
|
||||
// `mirada-layout` —el mismo nucleo `no_std` que ordena el compositor Wayland
|
||||
// de brahman—, enlazado por `path` cruzando la frontera de workspace.
|
||||
//
|
||||
// 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.
|
||||
// FASE 8b/8c :: el compositor cobra vida. Mantiene un ESCRITORIO —el registro
|
||||
// de todas las ventanas— y, por cada una, una CACHE de respaldo con su ultimo
|
||||
// fotograma. Gracias a esa cache, el teclado puede re-teselar el escritorio en
|
||||
// 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
|
||||
// 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.
|
||||
// EXCLUSION DE INTERRUPCIONES. El `ESCRITORIO` lo tocan SOLO tareas
|
||||
// cooperativas (el `tick` de una app, la tarea del compositor): el manejador
|
||||
// de IRQ1 jamas lo bloquea. La IRQ se comunica con el mundo cooperativo por
|
||||
// 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 crossbeam_queue::ArrayQueue;
|
||||
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.
|
||||
/// 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;
|
||||
/// El modo de teselado con que arranca el escritorio. El teclado lo cicla.
|
||||
const MODO_INICIAL: LayoutMode = LayoutMode::MasterStack;
|
||||
|
||||
/// Margen entre ventanas teseladas, en pixeles — el aire que separa un marco
|
||||
/// de sus vecinos.
|
||||
/// Margen entre ventanas teseladas, en pixeles.
|
||||
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
|
||||
/// franja de la consola en la cima.
|
||||
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
|
||||
/// 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<RegionPantalla> {
|
||||
let area = area_apps(ancho_pantalla, alto_pantalla);
|
||||
/// Tesela el area de apps en `n` marcos con el modo dado. El vector resultante
|
||||
/// tiene exactamente `n` elementos, en el orden de las apps del manifiesto.
|
||||
fn teselar(n: usize, ancho: usize, alto: usize, modo: LayoutMode) -> Vec<RegionPantalla> {
|
||||
let area = area_apps(ancho, alto);
|
||||
let pantalla = Rect::new(
|
||||
area.x 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,
|
||||
);
|
||||
let params = LayoutParams {
|
||||
mode: MODO,
|
||||
mode: modo,
|
||||
gap: MARGEN,
|
||||
..LayoutParams::default()
|
||||
};
|
||||
@@ -69,9 +350,8 @@ pub fn disponer(n: usize, ancho_pantalla: usize, alto_pantalla: usize) -> Vec<Re
|
||||
.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.
|
||||
/// Traduce un `Rect` de `mirada-layout` (`i32`) a la `RegionPantalla` del
|
||||
/// kernel (`usize`). Un rectangulo degenerado queda en cero.
|
||||
fn rect_a_region(r: Rect) -> RegionPantalla {
|
||||
RegionPantalla {
|
||||
x: r.x.max(0) as usize,
|
||||
|
||||
@@ -130,6 +130,7 @@ impl Consola {
|
||||
nat_ancho: usize,
|
||||
nat_alto: usize,
|
||||
datos: &[u8],
|
||||
enfocada: bool,
|
||||
) {
|
||||
if nat_ancho == 0 || nat_alto == 0 {
|
||||
return;
|
||||
@@ -165,17 +166,47 @@ impl Consola {
|
||||
self.lienzo.pixeles[y * self.lienzo.ancho + x] =
|
||||
codificar(self.lienzo.formato, color);
|
||||
}
|
||||
// El borde del compositor: delata, de un vistazo, quien tiene el foco.
|
||||
self.dibujar_borde(marco, enfocada);
|
||||
self.presentar();
|
||||
}
|
||||
|
||||
/// Inunda una region entera con un color plano y la presenta. Es la baliza
|
||||
/// de desalojo: cuando una aplicacion falla, su marco se tatua de purpura.
|
||||
fn pintar_region(&mut self, region: RegionPantalla, color: Color) {
|
||||
/// Inunda una region entera con un color plano —la baliza de desalojo: una
|
||||
/// app que falla tatua su marco de purpura— y le traza su borde de foco.
|
||||
fn pintar_region(&mut self, region: RegionPantalla, color: Color, enfocada: bool) {
|
||||
self.lienzo
|
||||
.rellenar_rect(region.x, region.y, region.ancho, region.alto, color);
|
||||
self.dibujar_borde(region, enfocada);
|
||||
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
|
||||
/// 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
|
||||
@@ -205,36 +236,38 @@ impl Consola {
|
||||
/// asincronas y las capacidades del userspace escriben en ella tras su `Mutex`.
|
||||
pub(crate) static CONSOLA: Once<Mutex<Consola>> = 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— 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(
|
||||
/// Compone un fotograma del userspace —ya cacheado por el compositor— centrado
|
||||
/// en su marco teselado, con su borde de foco. La invoca `compositor` al
|
||||
/// recibir un `sys_render_frame` y al recomponer el escritorio tras un mando.
|
||||
pub(crate) fn volcar_marco(
|
||||
marco: RegionPantalla,
|
||||
nat_ancho: usize,
|
||||
nat_alto: usize,
|
||||
datos: &[u8],
|
||||
enfocada: bool,
|
||||
) {
|
||||
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,
|
||||
/// 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]) {
|
||||
if let Some(consola) = CONSOLA.get() {
|
||||
consola.lock().pintar_escenario(area, marcos);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tatua la baliza de desalojo sobre la region de una aplicacion que el kernel
|
||||
/// ha dado por terminada. El color delata la causa —purpura para una falla de
|
||||
/// ejecucion o de combustible, amarillo palido para un desbordo de memoria—. Es
|
||||
/// una advertencia NO fatal: la app muere, el kernel y sus vecinas siguen vivos.
|
||||
pub(crate) fn pintar_desalojo(region: RegionPantalla, color: Color) {
|
||||
/// Tatua la baliza de desalojo sobre el marco de una aplicacion que el kernel
|
||||
/// ha dado por terminada, con su borde de foco. El color delata la causa
|
||||
/// —purpura para una falla de ejecucion o de combustible, amarillo palido para
|
||||
/// un desbordo de memoria—. Es una advertencia NO fatal: la app muere, el
|
||||
/// kernel y sus vecinas siguen vivos.
|
||||
pub(crate) fn pintar_desalojo(marco: RegionPantalla, color: Color, enfocada: bool) {
|
||||
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,
|
||||
};
|
||||
|
||||
/// 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.
|
||||
pub(crate) const ALERTA: Color = Color {
|
||||
r: 0xD4,
|
||||
|
||||
+47
-35
@@ -60,16 +60,16 @@ mod sync;
|
||||
mod texto;
|
||||
mod wasm;
|
||||
|
||||
// Reexportaciones para que los submodulos conserven rutas `crate::` estables.
|
||||
pub(crate) use consola::volcar_marco_wasm;
|
||||
// Reexportacion para que los submodulos conserven rutas `crate::` estables.
|
||||
pub(crate) use sync::CeldaSync;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use async_system::executor::Executor;
|
||||
use baliza::BALIZA_PANICO;
|
||||
use consola::{Consola, CONSOLA};
|
||||
use grafico::{
|
||||
codificar, reclamar_memoria_lienzo, Color, Lienzo, Pantalla, RegionPantalla, ALTO_MAX,
|
||||
ANCHO_MAX,
|
||||
codificar, reclamar_memoria_lienzo, Color, Lienzo, Pantalla, ALTO_MAX, ANCHO_MAX,
|
||||
};
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
/// 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.
|
||||
async fn tarea_aplicacion(mut app: wasm::AplicacionWasm) {
|
||||
loop {
|
||||
async_system::reloj::EsperaFrame::nueva().await;
|
||||
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.marco(), falla.color_baliza());
|
||||
// o aborto, amarillo si reviento su techo de memoria. El compositor
|
||||
// la pinta en el marco actual de la ventana y la marca como muerta.
|
||||
compositor::desalojar(app.indice(), falla.color_baliza());
|
||||
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
|
||||
/// 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
|
||||
@@ -132,19 +144,13 @@ 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 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,
|
||||
) {
|
||||
/// manifiesto: recupera su bytecode del grafo, lo carga en la ventana `indice`
|
||||
/// del escritorio del compositor y despacha la app como tarea cooperativa del
|
||||
/// reactor. Si el bytecode falta, esta corrupto, o la carga fracasa, el
|
||||
/// compositor desaloja esa ventana — el kernel sigue con las demas.
|
||||
fn encender_app(ejecutor: &mut Executor, indice: usize, entrada: &manifiesto::EntradaApp) {
|
||||
// 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);
|
||||
// Recuperar el bytecode del grafo. `recuperar` recomputa el hash del
|
||||
// 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) {
|
||||
Ok(Some(objeto)) => objeto.datos,
|
||||
_ => {
|
||||
consola::pintar_desalojo(marco, Color::DESALOJO);
|
||||
compositor::desalojar(indice, 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`.
|
||||
// `indice` es la identidad de la app: su ventana en el escritorio del
|
||||
// compositor y su ranura de estado persistido (Fase 7c).
|
||||
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(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.
|
||||
manifiesto::instalar(m.clone());
|
||||
|
||||
// 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);
|
||||
// FASE 8 :: fundar el escritorio del compositor — una ventana por app,
|
||||
// con su cache de respaldo y su marco teselado por `mirada-layout`— y
|
||||
// pintar el escenario antes de encender las apps: el teselado se ve
|
||||
// aunque alguna app no llegue a pintar su primer fotograma.
|
||||
let naturales: Vec<(usize, usize)> = m
|
||||
.apps
|
||||
.iter()
|
||||
.map(|e| (e.region_ancho as usize, e.region_alto as usize))
|
||||
.collect();
|
||||
compositor::fundar(ancho_pantalla, alto_pantalla, &naturales);
|
||||
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::async_system::teclado::CanalTeclado;
|
||||
use crate::grafico::RegionPantalla;
|
||||
|
||||
/// 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,
|
||||
/// su canal de teclado y sus cuotas de recursos. Dos apps jamas comparten nada.
|
||||
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
|
||||
/// 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_alto: usize,
|
||||
/// 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.
|
||||
/// `wasmi` lo consulta en cada `memory.grow` via `Store::limiter`.
|
||||
pub(crate) limites: StoreLimits,
|
||||
/// El indice de esta app en el Manifiesto de Genesis — su identidad. Las
|
||||
/// capacidades de estado (Fase 7c) lo usan para hallar la `EntradaApp`
|
||||
/// correcta: cada app persiste en SU ranura, jamas en la de otra.
|
||||
/// El indice de esta app — su identidad. La usan las capacidades de estado
|
||||
/// (Fase 7c) para hallar su `EntradaApp` del manifiesto, y el compositor
|
||||
/// (Fase 8) para hallar su ventana en el escritorio: jamas la de otra.
|
||||
pub(crate) indice_app: usize,
|
||||
}
|
||||
|
||||
@@ -98,7 +95,7 @@ pub(crate) fn enlazar_capacidades(
|
||||
"renaser",
|
||||
"sys_render_frame",
|
||||
|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_alto = caller.data().natural_alto;
|
||||
|
||||
@@ -124,9 +121,10 @@ pub(crate) fn enlazar_capacidades(
|
||||
"WASM :: sys_render_frame desbordo la memoria lineal del modulo",
|
||||
)?;
|
||||
|
||||
// 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);
|
||||
// Limites verificados: el compositor cachea el fotograma —para
|
||||
// poder recomponerlo si el escritorio se re-tesela— y lo compone,
|
||||
// centrado, en el marco que el teselado asigno a esta app.
|
||||
crate::compositor::presentar_fotograma(indice, fotograma);
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -20,7 +20,7 @@ use wasmi::{
|
||||
CompilationMode, Config, Engine, Linker, Module, Store, StoreLimitsBuilder, TrapCode, TypedFunc,
|
||||
};
|
||||
|
||||
use crate::grafico::{Color, RegionPantalla};
|
||||
use crate::grafico::Color;
|
||||
use env::ContextoCapacidades;
|
||||
|
||||
/// 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`.
|
||||
pub struct AplicacionWasm {
|
||||
/// 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>,
|
||||
/// El punto de entrada de fotograma, ya resuelto y con seguridad de tipos.
|
||||
/// `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<(), ()>,
|
||||
/// 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 {
|
||||
/// Carga, valida, instancia y arranca una aplicacion WASM aislada, ligada a
|
||||
/// una region de pantalla. Si algo falla en el camino, se devuelve la falla
|
||||
/// en lugar de incendiar el kernel.
|
||||
/// Carga, valida, instancia y arranca una aplicacion WASM aislada. Si algo
|
||||
/// falla en el camino, se devuelve la falla en lugar de incendiar el kernel.
|
||||
///
|
||||
/// El nuevo ABI del userspace exige dos exportaciones: `init` —invocada una
|
||||
/// sola vez, aqui— y `tick` —un fotograma de trabajo, invocada despues por
|
||||
/// el reactor en cada pulso del reloj.
|
||||
/// El ABI del userspace exige dos exportaciones: `init` —invocada una sola
|
||||
/// vez, aqui— y `tick` —un fotograma de trabajo, invocada despues por el
|
||||
/// reactor en cada pulso del reloj.
|
||||
///
|
||||
/// `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).
|
||||
/// `natural_ancho`/`natural_alto` son el tamaño del lienzo de la app;
|
||||
/// `techo_memoria`, su cuota de memoria lineal —la dicta su `EntradaApp` del
|
||||
/// manifiesto—; e `indice_app`, su identidad: la posicion con que el
|
||||
/// compositor halla su ventana y las capacidades de estado su ranura.
|
||||
pub fn cargar(
|
||||
bytecode: &[u8],
|
||||
marco: RegionPantalla,
|
||||
natural_ancho: usize,
|
||||
natural_alto: usize,
|
||||
techo_memoria: usize,
|
||||
@@ -108,10 +102,10 @@ impl AplicacionWasm {
|
||||
// 2. Validar y traducir el modulo — ya instrumentado con fuel.
|
||||
let modulo = Module::new(&motor, bytecode).map_err(|_| FallaApp::Carga)?;
|
||||
|
||||
// 3. El almacen, con el contexto de capacidades de ESTA app: su region
|
||||
// de pantalla, su canal de teclado y su techo de memoria. El canal
|
||||
// se crea ahora pero se inscribe en la difusion de la IRQ1 al final,
|
||||
// ya con la app cargada: una carga fallida no deja canales huerfanos.
|
||||
// 3. El almacen, con el contexto de capacidades de ESTA app: su lienzo
|
||||
// natural, su canal de teclado y su techo de memoria. El canal se
|
||||
// crea ahora pero se inscribe en el censo de la IRQ1 al final, ya con
|
||||
// la app cargada: una carga fallida no deja canales huerfanos.
|
||||
let canal = crate::async_system::teclado::crear_canal();
|
||||
let limites = StoreLimitsBuilder::new()
|
||||
.memory_size(techo_memoria)
|
||||
@@ -122,7 +116,6 @@ impl AplicacionWasm {
|
||||
let mut almacen = Store::new(
|
||||
&motor,
|
||||
ContextoCapacidades {
|
||||
marco,
|
||||
natural_ancho,
|
||||
natural_alto,
|
||||
canal,
|
||||
@@ -161,14 +154,11 @@ impl AplicacionWasm {
|
||||
.map_err(|_| FallaApp::Carga)?;
|
||||
|
||||
// 8. Con la app ya cargada e instanciada, inscribir su canal de teclado
|
||||
// en la difusion de la IRQ1: desde aqui recibe cada pulsacion.
|
||||
crate::async_system::teclado::registrar_canal(&almacen.data().canal);
|
||||
// en el censo de la IRQ1, en la ranura de su `indice_app`: desde
|
||||
// aqui recibe las teclas cuando el compositor le da el foco.
|
||||
crate::async_system::teclado::registrar_canal(indice_app, &almacen.data().canal);
|
||||
|
||||
Ok(AplicacionWasm {
|
||||
almacen,
|
||||
func_tick,
|
||||
marco,
|
||||
})
|
||||
Ok(AplicacionWasm { almacen, func_tick })
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// se tatua su baliza si el kernel llega a desalojarla.
|
||||
pub fn marco(&self) -> RegionPantalla {
|
||||
self.marco
|
||||
/// El indice de la aplicacion — su identidad en el escritorio del
|
||||
/// compositor. Lo usa la tarea de la app para decirle al compositor que
|
||||
/// ventana desalojar si la app llega a fallar.
|
||||
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.
|
||||
impl Drop for AplicacionWasm {
|
||||
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