feat(renaser): Fase 10 — alta y baja de aplicaciones en vivo
El censo de aplicaciones deja de fijarse en el arranque: una app puede nacer o cerrarse con el reactor ya en marcha. - El reactor admite NACIMIENTOS en vivo: cola `NACIMIENTOS` + `engendrar()`, drenada al inicio de cada vuelta de `run()`; `Task::adoptar` acoge un futuro ya empaquetado. - `Alt+Q` (`Mando::Cerrar`): baja limpia. El compositor saca la ventana enfocada del teselado y del orden-Z; la app advierte la baja (`ventana_cerrada`) y concluye su tarea — su memoria, su combustible y su canal de teclado se liberan. Sin baliza. - `Alt+N` (`Mando::Lanzar`): alta en vivo. `nacer_ventana` añade la ventana y entrega su índice; el orquestador instancia el WASM y engendra su tarea. Las apps de génesis dejan su bytecode cacheado como `Plantilla`; cada `Alt+N` instancia una en rotación. Verificado en QEMU (sendkey): tres Alt+N hacen crecer el escritorio de 5 a 8 ventanas; tres Alt+Q lo reducen de 8 a 5. Kernel estable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -5,19 +5,46 @@
|
||||
// vivas y una cola de las que estan listas para avanzar. Cuando no queda nada
|
||||
// por hacer, no malgasta la CPU en un bucle ocupado: la duerme con `hlt`
|
||||
// hasta que el proximo impulso de hardware la despierte.
|
||||
//
|
||||
// FASE 10 :: el censo deja de ser inmutable tras el arranque. Una cola de
|
||||
// NACIMIENTOS permite engendrar tareas EN VIVO —con el reactor ya en marcha—:
|
||||
// el orquestador deposita un futuro y el ejecutor lo adopta en su proxima
|
||||
// vuelta. Asi el userspace puede crecer despues del arranque.
|
||||
// =============================================================================
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::{BTreeMap, VecDeque};
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll, Waker};
|
||||
|
||||
use spin::Mutex;
|
||||
use spin::{Mutex, Once};
|
||||
use x86_64::instructions::interrupts;
|
||||
|
||||
use super::task::{Task, TaskId};
|
||||
use super::waker::{self, ColaListas};
|
||||
|
||||
/// Un futuro de tarea ya anclado en el heap: la moneda de los nacimientos.
|
||||
pub type FuturoTarea = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
|
||||
|
||||
/// Cola de NACIMIENTOS: tareas engendradas EN VIVO, mientras el reactor ya
|
||||
/// corre. Una tarea cooperativa deposita aqui un futuro; el ejecutor lo recoge
|
||||
/// al inicio de su proxima vuelta y lo da de alta. No la toca ningun manejador
|
||||
/// de IRQ —solo tareas cooperativas y el propio ejecutor—, asi que un `Mutex`
|
||||
/// llano basta: en un solo nucleo cooperativo nadie disputa el cerrojo.
|
||||
static NACIMIENTOS: Once<Mutex<Vec<FuturoTarea>>> = Once::new();
|
||||
|
||||
/// Engendra una tarea nueva mientras el reactor ya corre (Fase 10). El ejecutor
|
||||
/// la adoptara en su proxima vuelta. La invocan los orquestadores del kernel
|
||||
/// —jamas una IRQ—.
|
||||
pub fn engendrar(futuro: FuturoTarea) {
|
||||
if let Some(nacimientos) = NACIMIENTOS.get() {
|
||||
nacimientos.lock().push(futuro);
|
||||
}
|
||||
}
|
||||
|
||||
/// El ejecutor cooperativo de renaser.
|
||||
pub struct Executor {
|
||||
/// Censo de todas las tareas vivas, indexadas por su identidad.
|
||||
@@ -31,6 +58,7 @@ pub struct Executor {
|
||||
impl Executor {
|
||||
/// Crea un ejecutor vacio. Requiere que el heap ya este fundado.
|
||||
pub fn nuevo() -> Executor {
|
||||
NACIMIENTOS.call_once(|| Mutex::new(Vec::new()));
|
||||
Executor {
|
||||
tareas: BTreeMap::new(),
|
||||
cola_listas: Arc::new(Mutex::new(VecDeque::new())),
|
||||
@@ -40,7 +68,12 @@ impl Executor {
|
||||
|
||||
/// Da de alta una tarea nueva y la marca como lista para su primer avance.
|
||||
pub fn spawn(&mut self, futuro: impl Future<Output = ()> + Send + 'static) {
|
||||
let tarea = Task::nueva(futuro);
|
||||
self.alistar(Task::nueva(futuro));
|
||||
}
|
||||
|
||||
/// Inscribe una tarea ya construida en el censo y la encola para su primer
|
||||
/// avance. Es la via comun de `spawn` y de la recoleccion de nacimientos.
|
||||
fn alistar(&mut self, tarea: Task) {
|
||||
let id = tarea.id;
|
||||
if self.tareas.insert(id, tarea).is_some() {
|
||||
panic!("renaser :: TaskId duplicado — lo imposible ha ocurrido");
|
||||
@@ -48,6 +81,24 @@ impl Executor {
|
||||
interrupts::without_interrupts(|| self.cola_listas.lock().push_back(id));
|
||||
}
|
||||
|
||||
/// Recoge las tareas engendradas EN VIVO desde la ultima vuelta y las da de
|
||||
/// alta. Se invoca al inicio de cada ciclo del reactor (Fase 10).
|
||||
fn recoger_nacimientos(&mut self) {
|
||||
let Some(nacimientos) = NACIMIENTOS.get() else {
|
||||
return;
|
||||
};
|
||||
let recien: Vec<FuturoTarea> = {
|
||||
let mut cola = nacimientos.lock();
|
||||
if cola.is_empty() {
|
||||
return;
|
||||
}
|
||||
core::mem::take(&mut *cola)
|
||||
};
|
||||
for futuro in recien {
|
||||
self.alistar(Task::adoptar(futuro));
|
||||
}
|
||||
}
|
||||
|
||||
/// Avanza, hasta agotarla, la cola de tareas listas.
|
||||
fn avanzar_listas(&mut self) {
|
||||
loop {
|
||||
@@ -79,11 +130,17 @@ impl Executor {
|
||||
|
||||
/// Si no queda trabajo, duerme la CPU hasta la proxima interrupcion. El
|
||||
/// chequeo y el `hlt` se hacen con las interrupciones acalladas para que
|
||||
/// ningun despertar se pierda en la rendija entre uno y otro.
|
||||
/// ningun despertar se pierda en la rendija entre uno y otro. Una tarea
|
||||
/// recien engendrada cuenta como trabajo: no se duerme con nacimientos
|
||||
/// pendientes de adoptar.
|
||||
fn dormir_si_inactivo(&self) {
|
||||
interrupts::disable();
|
||||
let hay_trabajo = !self.cola_listas.lock().is_empty();
|
||||
if hay_trabajo {
|
||||
let hay_listas = !self.cola_listas.lock().is_empty();
|
||||
let hay_nacimientos = NACIMIENTOS
|
||||
.get()
|
||||
.map(|nacimientos| !nacimientos.lock().is_empty())
|
||||
.unwrap_or(false);
|
||||
if hay_listas || hay_nacimientos {
|
||||
// Llego trabajo justo ahora: reactivar y seguir sin dormir.
|
||||
interrupts::enable();
|
||||
} else {
|
||||
@@ -96,6 +153,7 @@ impl Executor {
|
||||
/// vive del latir de sus interrupciones.
|
||||
pub fn run(&mut self) -> ! {
|
||||
loop {
|
||||
self.recoger_nacimientos();
|
||||
self.avanzar_listas();
|
||||
self.dormir_si_inactivo();
|
||||
}
|
||||
|
||||
@@ -38,6 +38,16 @@ impl Task {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adopta un `Future` ya anclado en el heap como una tarea con identidad
|
||||
/// propia. Es la via de los nacimientos en vivo (Fase 10): el orquestador
|
||||
/// entrega el futuro ya empaquetado y el ejecutor lo acoge sin tocarlo.
|
||||
pub fn adoptar(futuro: Pin<Box<dyn Future<Output = ()> + Send + 'static>>) -> Task {
|
||||
Task {
|
||||
id: TaskId::nuevo(),
|
||||
futuro,
|
||||
}
|
||||
}
|
||||
|
||||
/// Hace avanzar la tarea un paso. `Poll::Ready` significa que concluyo.
|
||||
pub fn poll(&mut self, contexto: &mut Context) -> Poll<()> {
|
||||
self.futuro.as_mut().poll(contexto)
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
//
|
||||
// * La tecla Alt es el MODIFICADOR del sistema. Con Alt pulsada, los make
|
||||
// codes son MANDOS del compositor (ciclar el teselado, mover el foco,
|
||||
// promover, reordenar y hacer flotar ventanas): se consumen aqui, jamas
|
||||
// llegan a una app.
|
||||
// promover, reordenar y hacer flotar ventanas, cerrar y lanzar apps): 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.
|
||||
@@ -54,6 +54,10 @@ const TECLA_L: u8 = 0x26;
|
||||
const ENTER: u8 = 0x1C;
|
||||
/// Tecla F — `Alt + F` alterna la ventana enfocada entre teselada y flotante.
|
||||
const TECLA_F: u8 = 0x21;
|
||||
/// Tecla Q — `Alt + Q` cierra la aplicacion enfocada (baja en vivo).
|
||||
const TECLA_Q: u8 = 0x10;
|
||||
/// Tecla N — `Alt + N` lanza una aplicacion nueva (alta en vivo).
|
||||
const TECLA_N: u8 = 0x31;
|
||||
|
||||
/// Un canal de teclado: la cola lock-free de scancodes de UNA aplicacion.
|
||||
pub type CanalTeclado = Arc<ArrayQueue<u8>>;
|
||||
@@ -140,6 +144,8 @@ pub fn recibir_scancode(scancode: u8) {
|
||||
TECLA_L => compositor::solicitar(Mando::MoverAdelante),
|
||||
TECLA_H => compositor::solicitar(Mando::MoverAtras),
|
||||
TECLA_F => compositor::solicitar(Mando::Flotar),
|
||||
TECLA_Q => compositor::solicitar(Mando::Cerrar),
|
||||
TECLA_N => compositor::solicitar(Mando::Lanzar),
|
||||
_ => {}
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -21,6 +21,14 @@
|
||||
// RECOMPONE el escritorio entero, capa a capa, de modo que el solapamiento se
|
||||
// resuelva por el orden del pintado, sin recortes ni mascaras.
|
||||
//
|
||||
// FASE 10 :: el escritorio deja de ser un censo fijo. Una ventana puede
|
||||
// CERRARSE en vivo (`Alt+Q`): se la marca, su app concluye su tarea por su
|
||||
// voluntad y el teselado reclama su espacio. Y puede NACER una ventana nueva
|
||||
// (`Alt+N`): `nacer_ventana` la añade al censo y devuelve su indice al
|
||||
// orquestador, que instancia su WASM y engendra su tarea. El censo de
|
||||
// ventanas solo crece —los indices son la IDENTIDAD, jamas se reciclan—; una
|
||||
// ventana cerrada queda como una ranura inerte, fuera del orden y del foco.
|
||||
//
|
||||
// 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
|
||||
@@ -82,6 +90,10 @@ pub enum Mando {
|
||||
MoverAtras,
|
||||
/// Alternar la ventana enfocada entre teselada y flotante (Fase 9).
|
||||
Flotar,
|
||||
/// Cerrar la aplicacion enfocada — una baja limpia, en vivo (Fase 10).
|
||||
Cerrar,
|
||||
/// Lanzar una aplicacion nueva — un alta en vivo (Fase 10).
|
||||
Lanzar,
|
||||
}
|
||||
|
||||
/// Una ventana del escritorio: una app, su geometria y su ultimo fotograma.
|
||||
@@ -103,6 +115,10 @@ struct Ventana {
|
||||
/// 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>,
|
||||
/// ¿Se ha pedido cerrar esta ventana en vivo (Fase 10)? Una vez `true`, su
|
||||
/// app concluye su tarea, la ranura queda inerte —fuera del orden, del
|
||||
/// orden-Z y del foco— y el teselado reclama su espacio.
|
||||
cerrada: bool,
|
||||
}
|
||||
|
||||
/// El escritorio: el registro de todas las ventanas y el modo de teselado.
|
||||
@@ -138,6 +154,12 @@ static FOCO: AtomicUsize = AtomicUsize::new(0);
|
||||
/// compositor las drena desde el reactor cooperativo.
|
||||
static MANDOS: Once<ArrayQueue<Mando>> = Once::new();
|
||||
|
||||
/// Cuantos lanzamientos de aplicacion (Fase 10) aguardan. Lo incrementa
|
||||
/// `atender_mandos` al recibir un `Mando::Lanzar`; lo drena `partos_pendientes`,
|
||||
/// que lo lee el orquestador del kernel —el unico que sabe instanciar un WASM—.
|
||||
/// Atomico: el compositor lo escribe, el orquestador lo lee y lo pone a cero.
|
||||
static PARTOS: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
// =============================================================================
|
||||
// Fundacion y consulta — el arranque
|
||||
// =============================================================================
|
||||
@@ -164,6 +186,7 @@ pub fn fundar(ancho: usize, alto: usize, naturales: &[(usize, usize)]) {
|
||||
cache: vec![0u8; nat_ancho.saturating_mul(nat_alto).saturating_mul(4)],
|
||||
pintada: false,
|
||||
baliza: None,
|
||||
cerrada: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -246,6 +269,11 @@ pub fn presentar_fotograma(indice: usize, datos: &[u8]) {
|
||||
let Some(ventana) = escritorio.ventanas.get_mut(indice) else {
|
||||
return;
|
||||
};
|
||||
// Una ventana cerrada (Fase 10) ya no se pinta: su app pudo emitir un
|
||||
// ultimo fotograma antes de que su tarea advirtiera la baja.
|
||||
if ventana.cerrada {
|
||||
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());
|
||||
@@ -280,6 +308,11 @@ pub fn desalojar(indice: usize, color: Color) {
|
||||
let Some(ventana) = escritorio.ventanas.get_mut(indice) else {
|
||||
return;
|
||||
};
|
||||
// Una ventana ya cerrada (Fase 10) no recibe baliza: la baja limpia
|
||||
// gana a un desalojo que llegue tarde, en la misma vuelta.
|
||||
if ventana.cerrada {
|
||||
return;
|
||||
}
|
||||
ventana.baliza = Some(color);
|
||||
}
|
||||
|
||||
@@ -313,6 +346,13 @@ pub fn atender_mandos() {
|
||||
Mando::MoverAdelante => mover_ventana(true),
|
||||
Mando::MoverAtras => mover_ventana(false),
|
||||
Mando::Flotar => flotar(),
|
||||
Mando::Cerrar => cerrar(),
|
||||
// El alta de una app necesita instanciar un WASM — algo que el
|
||||
// compositor no sabe hacer—. Solo se cuenta la peticion; el
|
||||
// orquestador del kernel la atendera (ver `partos_pendientes`).
|
||||
Mando::Lanzar => {
|
||||
PARTOS.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -516,6 +556,106 @@ fn recomponer(escritorio: &Escritorio) {
|
||||
consola::recomponer(area, &capas);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FASE 10 — alta y baja de aplicaciones en vivo
|
||||
// =============================================================================
|
||||
|
||||
/// Cierra la aplicacion enfocada (`Alt+Q`): una baja LIMPIA, distinta del
|
||||
/// desalojo por falla. Marca la ventana como cerrada, libera su cache de
|
||||
/// respaldo, la saca del teselado y del orden-Z, y traslada el foco a una
|
||||
/// ventana viva contigua. La app, en su tarea, advertira la baja y concluira.
|
||||
fn cerrar() {
|
||||
let Some(escritorio) = ESCRITORIO.get() else {
|
||||
return;
|
||||
};
|
||||
let mut escritorio = escritorio.lock();
|
||||
let foco = FOCO.load(Ordering::Relaxed);
|
||||
// Solo se cierra una ventana viva. El foco jamas se posa en una muerta o
|
||||
// cerrada, pero la guarda lo deja explicito.
|
||||
match escritorio.ventanas.get(foco) {
|
||||
Some(v) if v.baliza.is_none() && !v.cerrada => {}
|
||||
_ => return,
|
||||
}
|
||||
// Marcar la baja y liberar el respaldo: la cache de un fotograma puede
|
||||
// pesar un megabyte — no tiene sentido retenerla en una ranura inerte.
|
||||
let ventana = &mut escritorio.ventanas[foco];
|
||||
ventana.cerrada = true;
|
||||
ventana.pintada = false;
|
||||
ventana.cache = Vec::new();
|
||||
// Sacarla del teselado y del orden-Z. El censo conserva la ranura —los
|
||||
// indices son la identidad, jamas se reciclan—, pero ya nadie la dibuja.
|
||||
escritorio.orden.retain(|&v| v != foco);
|
||||
escritorio.flotantes.retain(|&v| v != foco);
|
||||
// El foco salta a la primera ventana viva que quede; si no queda ninguna,
|
||||
// se queda donde estaba —inofensivo: no hay a quien enrutar el teclado—.
|
||||
let nuevo = escritorio
|
||||
.orden
|
||||
.iter()
|
||||
.chain(escritorio.flotantes.iter())
|
||||
.copied()
|
||||
.find(|&v| {
|
||||
let w = &escritorio.ventanas[v];
|
||||
w.baliza.is_none() && !w.cerrada
|
||||
})
|
||||
.unwrap_or(foco);
|
||||
FOCO.store(nuevo, Ordering::Relaxed);
|
||||
alzar_si_flota(&mut escritorio, nuevo);
|
||||
aplicar_teselado(&mut escritorio);
|
||||
recomponer(&escritorio);
|
||||
}
|
||||
|
||||
/// Da de alta una ventana NUEVA y devuelve su indice —su identidad—. La crea
|
||||
/// con su cache de respaldo al tamaño natural, la añade al final del orden de
|
||||
/// teselado, recalcula el teselado y recompone. La invoca el orquestador del
|
||||
/// kernel justo antes de instanciar el WASM de la app, que necesita ese indice.
|
||||
pub fn nacer_ventana(nat_ancho: usize, nat_alto: usize) -> usize {
|
||||
let Some(escritorio) = ESCRITORIO.get() else {
|
||||
return 0;
|
||||
};
|
||||
let mut escritorio = escritorio.lock();
|
||||
let indice = escritorio.ventanas.len();
|
||||
escritorio.ventanas.push(Ventana {
|
||||
natural_ancho: nat_ancho,
|
||||
natural_alto: nat_alto,
|
||||
marco: RegionPantalla {
|
||||
x: 0,
|
||||
y: 0,
|
||||
ancho: 0,
|
||||
alto: 0,
|
||||
},
|
||||
cache: vec![0u8; nat_ancho.saturating_mul(nat_alto).saturating_mul(4)],
|
||||
pintada: false,
|
||||
baliza: None,
|
||||
cerrada: false,
|
||||
});
|
||||
escritorio.orden.push(indice);
|
||||
aplicar_teselado(&mut escritorio);
|
||||
recomponer(&escritorio);
|
||||
indice
|
||||
}
|
||||
|
||||
/// ¿Se ha pedido cerrar la ventana `indice`? Cada app la consulta en su tarea,
|
||||
/// fotograma a fotograma: cuando es `true`, concluye su tarea y se libera. Una
|
||||
/// ventana inexistente cuenta como cerrada.
|
||||
pub fn ventana_cerrada(indice: usize) -> bool {
|
||||
let Some(escritorio) = ESCRITORIO.get() else {
|
||||
return false;
|
||||
};
|
||||
escritorio
|
||||
.lock()
|
||||
.ventanas
|
||||
.get(indice)
|
||||
.map(|ventana| ventana.cerrada)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
/// Cuantas aplicaciones nuevas se han pedido lanzar desde la ultima consulta —y
|
||||
/// pone el contador a cero—. La invoca el orquestador del kernel —el unico que
|
||||
/// sabe instanciar un WASM— en cada fotograma de la tarea del compositor.
|
||||
pub fn partos_pendientes() -> usize {
|
||||
PARTOS.swap(0, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Teselado — la geometria pura de `mirada-layout`
|
||||
// =============================================================================
|
||||
|
||||
@@ -36,12 +36,13 @@
|
||||
|
||||
extern crate alloc; // El heap esta vivo: `alloc::*` queda disponible (Fase 3).
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::format;
|
||||
|
||||
use bootloader_api::config::{BootloaderConfig, Mapping};
|
||||
use bootloader_api::info::{FrameBufferInfo, MemoryRegionKind, MemoryRegions, PixelFormat};
|
||||
use bootloader_api::{entry_point, BootInfo};
|
||||
use spin::Mutex;
|
||||
use spin::{Mutex, Once};
|
||||
|
||||
// --- Subsistemas del kernel ---
|
||||
mod almacen;
|
||||
@@ -64,6 +65,7 @@ mod wasm;
|
||||
pub(crate) use sync::CeldaSync;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use async_system::executor::Executor;
|
||||
use baliza::BALIZA_PANICO;
|
||||
@@ -91,6 +93,23 @@ pub(crate) fn detener() -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
/// FASE 10 :: el molde de una aplicacion para los lanzamientos EN VIVO. Guarda
|
||||
/// su bytecode —cacheado en RAM al arrancar, para no volver al disco despues—
|
||||
/// y la geometria y la cuota de memoria con que instanciarla.
|
||||
struct Plantilla {
|
||||
bytecode: Vec<u8>,
|
||||
nat_ancho: usize,
|
||||
nat_alto: usize,
|
||||
techo: usize,
|
||||
}
|
||||
|
||||
/// Las plantillas de las apps de genesis. Se fijan una vez, en el arranque;
|
||||
/// cada `Alt+N` instancia la siguiente en rotacion.
|
||||
static PLANTILLAS: Once<Vec<Plantilla>> = Once::new();
|
||||
|
||||
/// El cursor rotatorio sobre `PLANTILLAS`: que app nace en el proximo `Alt+N`.
|
||||
static CURSOR_PLANTILLA: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
/// 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
|
||||
@@ -99,6 +118,13 @@ pub(crate) fn detener() -> ! {
|
||||
async fn tarea_aplicacion(mut app: wasm::AplicacionWasm) {
|
||||
loop {
|
||||
async_system::reloj::EsperaFrame::nueva().await;
|
||||
// ¿El compositor pidio cerrar esta ventana (`Alt+Q`)? La tarea concluye
|
||||
// por su propia voluntad: al retornar, `AplicacionWasm` se libera —su
|
||||
// memoria lineal, su combustible, su canal de teclado— y el ejecutor la
|
||||
// retira del censo. Una baja LIMPIA, sin baliza (Fase 10).
|
||||
if compositor::ventana_cerrada(app.indice()) {
|
||||
return;
|
||||
}
|
||||
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. El compositor
|
||||
@@ -117,6 +143,12 @@ async fn tarea_compositor() {
|
||||
loop {
|
||||
async_system::reloj::EsperaFrame::nueva().await;
|
||||
compositor::atender_mandos();
|
||||
// FASE 10 :: atender las altas en vivo. Por cada `Alt+N` pendiente,
|
||||
// dar a luz una aplicacion nueva — el compositor solo conto la
|
||||
// peticion; instanciar el WASM es trabajo del orquestador.
|
||||
for _ in 0..compositor::partos_pendientes() {
|
||||
lanzar_app();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +180,11 @@ async fn tarea_sonda_disco() {
|
||||
/// 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) {
|
||||
fn encender_app(
|
||||
ejecutor: &mut Executor,
|
||||
indice: usize,
|
||||
entrada: &manifiesto::EntradaApp,
|
||||
) -> Option<Plantilla> {
|
||||
// El tamaño NATURAL del lienzo de la app —lo que sabe pintar, fijo— lo
|
||||
// dicta su `EntradaApp`; el compositor decide en que marco lo coloca.
|
||||
let natural = manifiesto::region(entrada);
|
||||
@@ -159,7 +195,7 @@ fn encender_app(ejecutor: &mut Executor, indice: usize, entrada: &manifiesto::En
|
||||
Ok(Some(objeto)) => objeto.datos,
|
||||
_ => {
|
||||
compositor::desalojar(indice, Color::DESALOJO);
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
// `indice` es la identidad de la app: su ventana en el escritorio del
|
||||
@@ -174,6 +210,48 @@ fn encender_app(ejecutor: &mut Executor, indice: usize, entrada: &manifiesto::En
|
||||
Ok(app) => ejecutor.spawn(tarea_aplicacion(app)),
|
||||
Err(_) => compositor::desalojar(indice, Color::DESALOJO),
|
||||
}
|
||||
// FASE 10 :: el bytecode, ya recuperado y verificado, queda como PLANTILLA:
|
||||
// un molde en RAM con que `Alt+N` instanciara copias en vivo, sin volver al
|
||||
// disco —que la E/S por sondeo en mitad del reactor seria un mal vecino—.
|
||||
Some(Plantilla {
|
||||
bytecode,
|
||||
nat_ancho: natural.ancho,
|
||||
nat_alto: natural.alto,
|
||||
techo: entrada.techo_memoria as usize,
|
||||
})
|
||||
}
|
||||
|
||||
/// FASE 10 :: da a luz una aplicacion EN VIVO. Elige la siguiente plantilla en
|
||||
/// rotacion, abre su ventana en el compositor —que le asigna su indice—,
|
||||
/// instancia su WASM con ese indice y engendra su tarea en el reactor ya en
|
||||
/// marcha. Si la carga falla, la ventana recien nacida se desaloja; el kernel
|
||||
/// sigue. La invoca la tarea del compositor al atender un `Alt+N`.
|
||||
fn lanzar_app() {
|
||||
let Some(plantillas) = PLANTILLAS.get() else {
|
||||
return;
|
||||
};
|
||||
if plantillas.is_empty() {
|
||||
return;
|
||||
}
|
||||
// El cursor rota sobre las plantillas: cada `Alt+N` engendra la siguiente.
|
||||
let cursor = CURSOR_PLANTILLA.fetch_add(1, Ordering::Relaxed);
|
||||
let plantilla = &plantillas[cursor % plantillas.len()];
|
||||
|
||||
// La ventana nace primero: el compositor le entrega su indice —su
|
||||
// identidad—, que el WASM necesita para hallar su ventana y su canal.
|
||||
let indice = compositor::nacer_ventana(plantilla.nat_ancho, plantilla.nat_alto);
|
||||
match wasm::AplicacionWasm::cargar(
|
||||
&plantilla.bytecode,
|
||||
plantilla.nat_ancho,
|
||||
plantilla.nat_alto,
|
||||
plantilla.techo,
|
||||
indice,
|
||||
) {
|
||||
// La tarea se ENGENDRA, no se hace `spawn`: el reactor ya corre y el
|
||||
// ejecutor la adoptara en su proxima vuelta (Fase 10).
|
||||
Ok(app) => async_system::executor::engendrar(Box::pin(tarea_aplicacion(app))),
|
||||
Err(_) => compositor::desalojar(indice, Color::DESALOJO),
|
||||
}
|
||||
}
|
||||
|
||||
/// Escribe una linea en la consola global y la presenta. Atajo para los
|
||||
@@ -235,12 +313,18 @@ fn cargar_userspace(ejecutor: &mut Executor, ancho_pantalla: usize, alto_pantall
|
||||
compositor::fundar(ancho_pantalla, alto_pantalla, &naturales);
|
||||
compositor::componer_escenario();
|
||||
|
||||
let mut plantillas: Vec<Plantilla> = Vec::new();
|
||||
for (indice, entrada) in m.apps.iter().enumerate() {
|
||||
encender_app(ejecutor, indice, entrada);
|
||||
if let Some(plantilla) = encender_app(ejecutor, indice, entrada) {
|
||||
plantillas.push(plantilla);
|
||||
}
|
||||
}
|
||||
// FASE 10 :: fijar las plantillas de las apps. A partir de aqui, cada
|
||||
// `Alt+N` instancia una copia viva, en rotacion.
|
||||
PLANTILLAS.call_once(|| plantillas);
|
||||
|
||||
// La tarea del compositor: atiende los mandos del teclado —ciclar el
|
||||
// teselado, mover el foco— en cada fotograma del reactor.
|
||||
// teselado, mover el foco, cerrar y lanzar apps— en cada fotograma.
|
||||
ejecutor.spawn(tarea_compositor());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user