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:
@@ -805,3 +805,45 @@ propio y libre, por encima de las demás.
|
|||||||
ventana la flota en cascada, solapando a la primera. `Alt+K` devuelve el foco
|
ventana la flota en cascada, solapando a la primera. `Alt+K` devuelve el foco
|
||||||
a la ventana grande y ésta sube al frente, tapando por completo a la pequeña.
|
a la ventana grande y ésta sube al frente, tapando por completo a la pequeña.
|
||||||
Un `Alt+F` final la reintegra al teselado.
|
Un `Alt+F` final la reintegra al teselado.
|
||||||
|
|
||||||
|
## Fase 10 — Alta y baja de aplicaciones en vivo — 2026-05-22
|
||||||
|
|
||||||
|
Hasta la Fase 9 el censo de aplicaciones se fijaba en el arranque: las apps
|
||||||
|
nacían del manifiesto y sólo morían al fallar. La Fase 10 lo vuelve DINÁMICO —
|
||||||
|
una app puede nacer o cerrarse con el reactor ya en marcha.
|
||||||
|
|
||||||
|
### Añadido
|
||||||
|
- **El reactor admite nacimientos en vivo.** `executor`: una cola de
|
||||||
|
NACIMIENTOS (`Vec<FuturoTarea>` tras un `Mutex`) y la función `engendrar`.
|
||||||
|
El ejecutor la drena al inicio de cada vuelta (`recoger_nacimientos`) y
|
||||||
|
adopta cada futuro como tarea. `Task::adoptar` acoge un futuro ya
|
||||||
|
empaquetado. `dormir_si_inactivo` cuenta los nacimientos como trabajo.
|
||||||
|
- **Baja en vivo — `Alt+Q`.** Mando `Cerrar`: el compositor marca la ventana
|
||||||
|
enfocada como `cerrada`, libera su caché de respaldo, la saca del teselado y
|
||||||
|
del orden-Z, y traslada el foco a una ventana viva contigua. La app, en su
|
||||||
|
tarea, consulta `compositor::ventana_cerrada` cada fotograma; al verla
|
||||||
|
cerrada concluye su tarea — y `AplicacionWasm::drop` libera su memoria
|
||||||
|
lineal, su combustible y su canal de teclado. Una baja LIMPIA, sin baliza.
|
||||||
|
- **Alta en vivo — `Alt+N`.** Mando `Lanzar`: el compositor cuenta la petición
|
||||||
|
(`PARTOS`); la tarea del compositor la atiende con `partos_pendientes` y
|
||||||
|
`lanzar_app`. `compositor::nacer_ventana` añade la ventana y devuelve su
|
||||||
|
índice; el orquestador instancia el WASM con ese índice y `engendra` su
|
||||||
|
tarea. Las apps de génesis dejan su bytecode cacheado en RAM como
|
||||||
|
`Plantilla`; cada `Alt+N` instancia la siguiente en rotación, sin volver al
|
||||||
|
disco —una E/S por sondeo en mitad del reactor sería un mal vecino—.
|
||||||
|
- `compositor`: campo `Ventana.cerrada`, mandos `Cerrar` / `Lanzar`, y las
|
||||||
|
funciones `cerrar`, `nacer_ventana`, `ventana_cerrada`, `partos_pendientes`.
|
||||||
|
- `teclado`: `Alt+Q` → `Cerrar`, `Alt+N` → `Lanzar`.
|
||||||
|
|
||||||
|
### Cambiado
|
||||||
|
- `encender_app` devuelve la `Plantilla` de la app —su bytecode y geometría—
|
||||||
|
para los lanzamientos en vivo.
|
||||||
|
- `tarea_aplicacion` consulta `ventana_cerrada` antes de cada `tick`.
|
||||||
|
- `presentar_fotograma` y `desalojar` ignoran una ventana ya cerrada: una baja
|
||||||
|
limpia gana a un fotograma o a un desalojo que lleguen tarde.
|
||||||
|
|
||||||
|
### Verificado
|
||||||
|
- QEMU (`sendkey`): tres `Alt+N` dan a luz tres apps nuevas y el escritorio se
|
||||||
|
re-tesela de 5 a 8 ventanas. Tres `Alt+Q` cierran la app enfocada una a una
|
||||||
|
y el teselado reclama su espacio, de 8 de vuelta a 5. El kernel sigue estable
|
||||||
|
a través de todas las altas y bajas.
|
||||||
|
|||||||
+3
-2
@@ -76,8 +76,9 @@ QEMU 11, OVMF en `/usr/share/edk2/x64/OVMF.4m.fd` (sin módulo KVM → TCG).
|
|||||||
Fases 1 a 5, 6.0, 6.1, 6.2, la Fase 7 COMPLETA —el userspace nace del grafo de
|
Fases 1 a 5, 6.0, 6.1, 6.2, la Fase 7 COMPLETA —el userspace nace del grafo de
|
||||||
objetos—, la Fase 8 COMPLETA —el compositor teselante e interactivo: teselado
|
objetos—, la Fase 8 COMPLETA —el compositor teselante e interactivo: teselado
|
||||||
con `mirada-layout` (8a), ciclado de layout (8b), foco y enrutamiento selectivo
|
con `mirada-layout` (8a), ciclado de layout (8b), foco y enrutamiento selectivo
|
||||||
del teclado (8c), promoción y reordenación de ventanas (8d)— y la Fase 9
|
del teclado (8c), promoción y reordenación de ventanas (8d)—, la Fase 9
|
||||||
COMPLETA —orden-Z y ventanas flotantes: composición con solapamiento (`Alt+F`)—.
|
COMPLETA —orden-Z y ventanas flotantes: composición con solapamiento (`Alt+F`)—
|
||||||
|
y la Fase 10 COMPLETA —alta y baja de aplicaciones en vivo (`Alt+N` / `Alt+Q`)—.
|
||||||
Todo verificado en QEMU. Ver `ROADMAP.md`.
|
Todo verificado en QEMU. Ver `ROADMAP.md`.
|
||||||
|
|
||||||
## Flujo de trabajo
|
## Flujo de trabajo
|
||||||
|
|||||||
@@ -414,6 +414,26 @@ Y hubo una regla pequeña y elegante: el cuarto flotante en que se posa la
|
|||||||
mirada sube siempre al frente. Mirar algo, en esta casa, es traerlo a primer
|
mirada sube siempre al frente. Mirar algo, en esta casa, es traerlo a primer
|
||||||
plano.
|
plano.
|
||||||
|
|
||||||
|
## La casa que respira — inquilinos que llegan y se van
|
||||||
|
|
||||||
|
Una casa de verdad no tiene un número fijo de habitantes para siempre. Llegan
|
||||||
|
nuevos, se marchan otros; la casa se acomoda. Hasta hoy, la de renaser era de
|
||||||
|
censo cerrado: sus inquilinos entraban todos a la vez, al amanecer, y sólo se
|
||||||
|
iban si tropezaban. No se podía invitar a nadie más, ni despedir a nadie en paz.
|
||||||
|
|
||||||
|
Hoy la casa aprendió a respirar. Con una tecla se invita a un inquilino nuevo:
|
||||||
|
aparece su cuarto, los demás se corren para hacerle sitio y se instala con sus
|
||||||
|
cosas. Con otra tecla se despide al inquilino del cuarto en que está puesta la
|
||||||
|
mirada: recoge en silencio, su cuarto se desvanece y el espacio que deja lo
|
||||||
|
reparten los que quedan. Una despedida serena —no un tropiezo, no una alarma—:
|
||||||
|
simplemente, ya no está.
|
||||||
|
|
||||||
|
Para que un inquilino pudiera llegar tarde, el ama de llaves —el reactor— tuvo
|
||||||
|
que cambiar una costumbre. Antes apuntaba a todos en su libro al abrir la casa
|
||||||
|
y no volvía a tocar la lista. Ahora tiene una bandeja donde van dejándose los
|
||||||
|
recién llegados, y en cada ronda la mira y les da su sitio. La casa ya no se
|
||||||
|
escribe entera de una vez: se va escribiendo, día a día, mientras se vive.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*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
-2
@@ -163,8 +163,26 @@ el teselado y FLOTAR sobre las demás. Verificada en QEMU (`sendkey`).
|
|||||||
- El foco recorre todas las ventanas; al posarse en una flotante, la alza al
|
- El foco recorre todas las ventanas; al posarse en una flotante, la alza al
|
||||||
frente: la flotante enfocada está siempre delante.
|
frente: la flotante enfocada está siempre delante.
|
||||||
|
|
||||||
Líneas abiertas posteriores: alta y baja de aplicaciones en vivo; más
|
## Fase 10 — alta y baja de aplicaciones en vivo (completada)
|
||||||
capacidades del host (temporización, audio).
|
|
||||||
|
Hasta la Fase 9 el censo de aplicaciones se fijaba en el arranque. La Fase 10
|
||||||
|
lo vuelve DINÁMICO: una app puede nacer o cerrarse con el reactor ya en marcha.
|
||||||
|
Verificada en QEMU (`sendkey`).
|
||||||
|
|
||||||
|
- El reactor admite NACIMIENTOS en vivo: una cola que `engendrar` alimenta y
|
||||||
|
que el ejecutor drena al inicio de cada vuelta, adoptando cada futuro como
|
||||||
|
tarea. El censo de tareas deja de ser inmutable tras el arranque.
|
||||||
|
- `Alt+Q` cierra la app enfocada: una baja LIMPIA. El compositor saca la
|
||||||
|
ventana del teselado y del orden-Z; la app, al advertir la baja, concluye su
|
||||||
|
tarea y `AplicacionWasm::drop` libera su memoria, su combustible y su canal.
|
||||||
|
- `Alt+N` lanza una app nueva: `nacer_ventana` la añade 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.
|
||||||
|
- El censo de ventanas sólo crece —los índices son la identidad, jamás se
|
||||||
|
reciclan—; una ventana cerrada queda como ranura inerte, fuera del teselado.
|
||||||
|
|
||||||
|
Líneas abiertas posteriores: más capacidades del host (temporización, audio);
|
||||||
|
reciclado de las ranuras de ventana cerradas.
|
||||||
|
|
||||||
## Principios que persisten entre fases
|
## Principios que persisten entre fases
|
||||||
|
|
||||||
|
|||||||
@@ -5,19 +5,46 @@
|
|||||||
// vivas y una cola de las que estan listas para avanzar. Cuando no queda nada
|
// 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`
|
// por hacer, no malgasta la CPU en un bucle ocupado: la duerme con `hlt`
|
||||||
// hasta que el proximo impulso de hardware la despierte.
|
// 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::collections::{BTreeMap, VecDeque};
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
|
use alloc::vec::Vec;
|
||||||
use core::future::Future;
|
use core::future::Future;
|
||||||
|
use core::pin::Pin;
|
||||||
use core::task::{Context, Poll, Waker};
|
use core::task::{Context, Poll, Waker};
|
||||||
|
|
||||||
use spin::Mutex;
|
use spin::{Mutex, Once};
|
||||||
use x86_64::instructions::interrupts;
|
use x86_64::instructions::interrupts;
|
||||||
|
|
||||||
use super::task::{Task, TaskId};
|
use super::task::{Task, TaskId};
|
||||||
use super::waker::{self, ColaListas};
|
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.
|
/// El ejecutor cooperativo de renaser.
|
||||||
pub struct Executor {
|
pub struct Executor {
|
||||||
/// Censo de todas las tareas vivas, indexadas por su identidad.
|
/// Censo de todas las tareas vivas, indexadas por su identidad.
|
||||||
@@ -31,6 +58,7 @@ pub struct Executor {
|
|||||||
impl Executor {
|
impl Executor {
|
||||||
/// Crea un ejecutor vacio. Requiere que el heap ya este fundado.
|
/// Crea un ejecutor vacio. Requiere que el heap ya este fundado.
|
||||||
pub fn nuevo() -> Executor {
|
pub fn nuevo() -> Executor {
|
||||||
|
NACIMIENTOS.call_once(|| Mutex::new(Vec::new()));
|
||||||
Executor {
|
Executor {
|
||||||
tareas: BTreeMap::new(),
|
tareas: BTreeMap::new(),
|
||||||
cola_listas: Arc::new(Mutex::new(VecDeque::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.
|
/// 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) {
|
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;
|
let id = tarea.id;
|
||||||
if self.tareas.insert(id, tarea).is_some() {
|
if self.tareas.insert(id, tarea).is_some() {
|
||||||
panic!("renaser :: TaskId duplicado — lo imposible ha ocurrido");
|
panic!("renaser :: TaskId duplicado — lo imposible ha ocurrido");
|
||||||
@@ -48,6 +81,24 @@ impl Executor {
|
|||||||
interrupts::without_interrupts(|| self.cola_listas.lock().push_back(id));
|
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.
|
/// Avanza, hasta agotarla, la cola de tareas listas.
|
||||||
fn avanzar_listas(&mut self) {
|
fn avanzar_listas(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
@@ -79,11 +130,17 @@ impl Executor {
|
|||||||
|
|
||||||
/// Si no queda trabajo, duerme la CPU hasta la proxima interrupcion. El
|
/// Si no queda trabajo, duerme la CPU hasta la proxima interrupcion. El
|
||||||
/// chequeo y el `hlt` se hacen con las interrupciones acalladas para que
|
/// 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) {
|
fn dormir_si_inactivo(&self) {
|
||||||
interrupts::disable();
|
interrupts::disable();
|
||||||
let hay_trabajo = !self.cola_listas.lock().is_empty();
|
let hay_listas = !self.cola_listas.lock().is_empty();
|
||||||
if hay_trabajo {
|
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.
|
// Llego trabajo justo ahora: reactivar y seguir sin dormir.
|
||||||
interrupts::enable();
|
interrupts::enable();
|
||||||
} else {
|
} else {
|
||||||
@@ -96,6 +153,7 @@ impl Executor {
|
|||||||
/// vive del latir de sus interrupciones.
|
/// vive del latir de sus interrupciones.
|
||||||
pub fn run(&mut self) -> ! {
|
pub fn run(&mut self) -> ! {
|
||||||
loop {
|
loop {
|
||||||
|
self.recoger_nacimientos();
|
||||||
self.avanzar_listas();
|
self.avanzar_listas();
|
||||||
self.dormir_si_inactivo();
|
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.
|
/// Hace avanzar la tarea un paso. `Poll::Ready` significa que concluyo.
|
||||||
pub fn poll(&mut self, contexto: &mut Context) -> Poll<()> {
|
pub fn poll(&mut self, contexto: &mut Context) -> Poll<()> {
|
||||||
self.futuro.as_mut().poll(contexto)
|
self.futuro.as_mut().poll(contexto)
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
//
|
//
|
||||||
// * La tecla Alt es el MODIFICADOR del sistema. Con Alt pulsada, los make
|
// * La tecla Alt es el MODIFICADOR del sistema. Con Alt pulsada, los make
|
||||||
// codes son MANDOS del compositor (ciclar el teselado, mover el foco,
|
// codes son MANDOS del compositor (ciclar el teselado, mover el foco,
|
||||||
// promover, reordenar y hacer flotar ventanas): se consumen aqui, jamas
|
// promover, reordenar y hacer flotar ventanas, cerrar y lanzar apps): se
|
||||||
// llegan a una app.
|
// consumen aqui, jamas llegan a una app.
|
||||||
// * Una tecla ordinaria se entrega SOLO a la app ENFOCADA — la que el
|
// * 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`,
|
// compositor senala. El censo de canales se indexa por el `indice_app`,
|
||||||
// de modo que el foco —un atomico— elija el canal exacto.
|
// de modo que el foco —un atomico— elija el canal exacto.
|
||||||
@@ -54,6 +54,10 @@ const TECLA_L: u8 = 0x26;
|
|||||||
const ENTER: u8 = 0x1C;
|
const ENTER: u8 = 0x1C;
|
||||||
/// Tecla F — `Alt + F` alterna la ventana enfocada entre teselada y flotante.
|
/// Tecla F — `Alt + F` alterna la ventana enfocada entre teselada y flotante.
|
||||||
const TECLA_F: u8 = 0x21;
|
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.
|
/// 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>>;
|
||||||
@@ -140,6 +144,8 @@ pub fn recibir_scancode(scancode: u8) {
|
|||||||
TECLA_L => compositor::solicitar(Mando::MoverAdelante),
|
TECLA_L => compositor::solicitar(Mando::MoverAdelante),
|
||||||
TECLA_H => compositor::solicitar(Mando::MoverAtras),
|
TECLA_H => compositor::solicitar(Mando::MoverAtras),
|
||||||
TECLA_F => compositor::solicitar(Mando::Flotar),
|
TECLA_F => compositor::solicitar(Mando::Flotar),
|
||||||
|
TECLA_Q => compositor::solicitar(Mando::Cerrar),
|
||||||
|
TECLA_N => compositor::solicitar(Mando::Lanzar),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -21,6 +21,14 @@
|
|||||||
// RECOMPONE el escritorio entero, capa a capa, de modo que el solapamiento se
|
// RECOMPONE el escritorio entero, capa a capa, de modo que el solapamiento se
|
||||||
// resuelva por el orden del pintado, sin recortes ni mascaras.
|
// 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
|
// EXCLUSION DE INTERRUPCIONES. El `ESCRITORIO` lo tocan SOLO tareas
|
||||||
// cooperativas (el `tick` de una app, la tarea del compositor): el manejador
|
// 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
|
// de IRQ1 jamas lo bloquea. La IRQ se comunica con el mundo cooperativo por
|
||||||
@@ -82,6 +90,10 @@ pub enum Mando {
|
|||||||
MoverAtras,
|
MoverAtras,
|
||||||
/// Alternar la ventana enfocada entre teselada y flotante (Fase 9).
|
/// Alternar la ventana enfocada entre teselada y flotante (Fase 9).
|
||||||
Flotar,
|
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.
|
/// 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
|
/// 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.
|
/// vive; `Some(color)` la marca como muerta y la excluye del foco.
|
||||||
baliza: Option<Color>,
|
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.
|
/// 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.
|
/// compositor las drena desde el reactor cooperativo.
|
||||||
static MANDOS: Once<ArrayQueue<Mando>> = Once::new();
|
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
|
// 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)],
|
cache: vec![0u8; nat_ancho.saturating_mul(nat_alto).saturating_mul(4)],
|
||||||
pintada: false,
|
pintada: false,
|
||||||
baliza: None,
|
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 {
|
let Some(ventana) = escritorio.ventanas.get_mut(indice) else {
|
||||||
return;
|
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
|
// Cachear el fotograma. El destino esta acotado al lienzo natural; se
|
||||||
// copia el minimo de ambas longitudes — jamas se desborda la cache.
|
// copia el minimo de ambas longitudes — jamas se desborda la cache.
|
||||||
let n = ventana.cache.len().min(datos.len());
|
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 {
|
let Some(ventana) = escritorio.ventanas.get_mut(indice) else {
|
||||||
return;
|
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);
|
ventana.baliza = Some(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,6 +346,13 @@ pub fn atender_mandos() {
|
|||||||
Mando::MoverAdelante => mover_ventana(true),
|
Mando::MoverAdelante => mover_ventana(true),
|
||||||
Mando::MoverAtras => mover_ventana(false),
|
Mando::MoverAtras => mover_ventana(false),
|
||||||
Mando::Flotar => flotar(),
|
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);
|
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`
|
// Teselado — la geometria pura de `mirada-layout`
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
@@ -36,12 +36,13 @@
|
|||||||
|
|
||||||
extern crate alloc; // El heap esta vivo: `alloc::*` queda disponible (Fase 3).
|
extern crate alloc; // El heap esta vivo: `alloc::*` queda disponible (Fase 3).
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
|
|
||||||
use bootloader_api::config::{BootloaderConfig, Mapping};
|
use bootloader_api::config::{BootloaderConfig, Mapping};
|
||||||
use bootloader_api::info::{FrameBufferInfo, MemoryRegionKind, MemoryRegions, PixelFormat};
|
use bootloader_api::info::{FrameBufferInfo, MemoryRegionKind, MemoryRegions, PixelFormat};
|
||||||
use bootloader_api::{entry_point, BootInfo};
|
use bootloader_api::{entry_point, BootInfo};
|
||||||
use spin::Mutex;
|
use spin::{Mutex, Once};
|
||||||
|
|
||||||
// --- Subsistemas del kernel ---
|
// --- Subsistemas del kernel ---
|
||||||
mod almacen;
|
mod almacen;
|
||||||
@@ -64,6 +65,7 @@ mod wasm;
|
|||||||
pub(crate) use sync::CeldaSync;
|
pub(crate) use sync::CeldaSync;
|
||||||
|
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
use async_system::executor::Executor;
|
use async_system::executor::Executor;
|
||||||
use baliza::BALIZA_PANICO;
|
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
|
/// 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
|
||||||
@@ -99,6 +118,13 @@ pub(crate) fn detener() -> ! {
|
|||||||
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;
|
||||||
|
// ¿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() {
|
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. El compositor
|
// o aborto, amarillo si reviento su techo de memoria. El compositor
|
||||||
@@ -117,6 +143,12 @@ async fn tarea_compositor() {
|
|||||||
loop {
|
loop {
|
||||||
async_system::reloj::EsperaFrame::nueva().await;
|
async_system::reloj::EsperaFrame::nueva().await;
|
||||||
compositor::atender_mandos();
|
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
|
/// del escritorio del compositor y despacha la app como tarea cooperativa del
|
||||||
/// reactor. Si el bytecode falta, esta corrupto, o la carga fracasa, el
|
/// reactor. Si el bytecode falta, esta corrupto, o la carga fracasa, el
|
||||||
/// compositor desaloja esa ventana — el kernel sigue con las demas.
|
/// 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
|
// 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.
|
// dicta su `EntradaApp`; el compositor decide en que marco lo coloca.
|
||||||
let natural = manifiesto::region(entrada);
|
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,
|
Ok(Some(objeto)) => objeto.datos,
|
||||||
_ => {
|
_ => {
|
||||||
compositor::desalojar(indice, Color::DESALOJO);
|
compositor::desalojar(indice, Color::DESALOJO);
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// `indice` es la identidad de la app: su ventana en el escritorio del
|
// `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)),
|
Ok(app) => ejecutor.spawn(tarea_aplicacion(app)),
|
||||||
Err(_) => compositor::desalojar(indice, Color::DESALOJO),
|
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
|
/// 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::fundar(ancho_pantalla, alto_pantalla, &naturales);
|
||||||
compositor::componer_escenario();
|
compositor::componer_escenario();
|
||||||
|
|
||||||
|
let mut plantillas: Vec<Plantilla> = Vec::new();
|
||||||
for (indice, entrada) in m.apps.iter().enumerate() {
|
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
|
// 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());
|
ejecutor.spawn(tarea_compositor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user