tawasuyu

tawasuyu/brahman

Rust 189TOML 59JSON 7HTML 12237 Sutichaykuna

⎇ 6e30dc2d7288b691aec553b8261f818c6f9dbba9 renaser/kernel/src/compositor.rs Rust · 446 siq'ikuna · 17.2 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat(renaser): Fase 8d — manipulación de ventanas dacfbad12 · sergio ·

1// =============================================================================
2//  renaser :: kernel/src/compositor.rs — Fase 8 :: el compositor teselante
3// -----------------------------------------------------------------------------
4//  El kernel no coloca las ventanas a mano: las TESELA. El motor es
5//  `mirada-layout` —el mismo nucleo `no_std` que ordena el compositor Wayland
6//  de brahman—, enlazado por `path` cruzando la frontera de workspace.
7//
8//  FASE 8b/8c :: el compositor cobra vida. Mantiene un ESCRITORIO —el registro
9//  de todas las ventanas— y, por cada una, una CACHE de respaldo con su ultimo
10//  fotograma. Gracias a esa cache, el teclado puede re-teselar el escritorio en
11//  caliente —o mover el foco— y el kernel recompone cada ventana en su marco
12//  nuevo SIN despertar a las apps: una app que solo pinto en su `init` conserva
13//  su imagen intacta a traves de cualquier reordenacion.
14//
15//  EXCLUSION DE INTERRUPCIONES. El `ESCRITORIO` lo tocan SOLO tareas
16//  cooperativas (el `tick` de una app, la tarea del compositor): el manejador
17//  de IRQ1 jamas lo bloquea. La IRQ se comunica con el mundo cooperativo por
18//  un canal estrecho y a prueba de interbloqueos: dos atomicos —el foco y el
19//  estado de Alt— y una cola lock-free de mandos. Ningun cerrojo que la IRQ
20//  pudiera disputar a una tarea cooperativa.
21// =============================================================================
22
23use core::sync::atomic::{AtomicUsize, Ordering};
24
25use alloc::vec;
26use alloc::vec::Vec;
27
28use crossbeam_queue::ArrayQueue;
29use mirada_layout::{tile, LayoutMode, LayoutParams, Rect};
30use spin::{Mutex, Once};
31
32use crate::grafico::{Color, RegionPantalla};
33
34/// Altura del strip superior reservado a la consola; las apps teselan debajo.
35/// La consola conserva ahi su registro de arranque completo —seis lineas,
36/// hasta la sonda asincrona de disco— legible sobre el teselado.
37const FRANJA_CONSOLA: usize = 296;
38
39/// El modo de teselado con que arranca el escritorio. El teclado lo cicla.
40const MODO_INICIAL: LayoutMode = LayoutMode::MasterStack;
41
42/// Margen entre ventanas teseladas, en pixeles.
43const MARGEN: i32 = 14;
44
45/// Capacidad de la cola de mandos del compositor — holgada: nadie pulsa tanto.
46const CAPACIDAD_MANDOS: usize = 32;
47
48/// Un mando del compositor — lo emite el teclado desde el contexto de IRQ, lo
49/// atiende la tarea del compositor desde el reactor cooperativo.
50#[derive(Clone, Copy)]
51pub enum Mando {
52    /// Ciclar al siguiente modo de teselado de `mirada-layout`.
53    CiclarLayout,
54    /// Mover el foco a la siguiente ventana viva.
55    FocoSiguiente,
56    /// Mover el foco a la ventana viva anterior.
57    FocoAnterior,
58    /// Promover la ventana enfocada a la posicion maestra del teselado.
59    Promover,
60    /// Mover la ventana enfocada una posicion adelante en el orden de teselado.
61    MoverAdelante,
62    /// Mover la ventana enfocada una posicion atras en el orden de teselado.
63    MoverAtras,
64}
65
66/// Una ventana del escritorio: una app, su geometria y su ultimo fotograma.
67struct Ventana {
68    /// Tamaño natural del lienzo de la app — lo que sabe pintar, fijo.
69    natural_ancho: usize,
70    natural_alto: usize,
71    /// El marco teselado actual — donde la app vive en pantalla. Cambia con
72    /// cada re-teselado.
73    marco: RegionPantalla,
74    /// CACHE de respaldo: el ultimo fotograma exitoso que la app envio. Su
75    /// tamaño esta acotado al lienzo natural —`natural_ancho × natural_alto ×
76    /// 4`— y se reserva UNA sola vez, al fundar el escritorio: jamas crece. El
77    /// re-teselado recompone la ventana desde aqui, sin molestar a la app.
78    cache: Vec<u8>,
79    /// ¿Ha enviado la app al menos un fotograma? Hasta entonces, su cache es
80    /// solo ceros y no se recompone.
81    pintada: bool,
82    /// Si el kernel desalojo la app, el color de su baliza. `None` mientras
83    /// vive; `Some(color)` la marca como muerta y la excluye del foco.
84    baliza: Option<Color>,
85}
86
87/// El escritorio: el registro de todas las ventanas y el modo de teselado.
88/// Lo tocan SOLO tareas cooperativas — nunca el manejador de IRQ1.
89struct Escritorio {
90    modo: LayoutMode,
91    ancho: usize,
92    alto: usize,
93    /// Las ventanas, indexadas por `indice_app` — su IDENTIDAD, inmutable.
94    ventanas: Vec<Ventana>,
95    /// El ORDEN de teselado: `orden[slot]` es el `indice_app` de la ventana que
96    /// ocupa esa celda del teselado. Una permutacion de `0..ventanas.len()`.
97    /// Separar el orden de la identidad permite promover y reordenar ventanas
98    /// sin tocar su `indice_app` —su canal de teclado, su ranura de estado—.
99    orden: Vec<usize>,
100}
101
102/// El escritorio global. Se funda una sola vez, en el arranque.
103static ESCRITORIO: Once<Mutex<Escritorio>> = Once::new();
104
105/// El indice de la ventana ENFOCADA. Atomico —no un campo del `Escritorio`—
106/// porque el manejador de IRQ1 lo LEE para enrutar el teclado, y un atomico no
107/// se puede disputar: jamas hay interbloqueo entre la IRQ y una tarea.
108static FOCO: AtomicUsize = AtomicUsize::new(0);
109
110/// La cola de mandos: el manejador de IRQ1 deposita aqui las ordenes del
111/// teclado (lock-free, segura en contexto de interrupcion); la tarea del
112/// compositor las drena desde el reactor cooperativo.
113static MANDOS: Once<ArrayQueue<Mando>> = Once::new();
114
115// =============================================================================
116//  Fundacion y consulta — el arranque
117// =============================================================================
118
119/// Funda el escritorio: crea una ventana por app, con su marco teselado inicial
120/// y su cache de respaldo ya reservada al tamaño natural. `naturales` da el
121/// `(ancho, alto)` del lienzo de cada app, en el orden del manifiesto.
122pub fn fundar(ancho: usize, alto: usize, naturales: &[(usize, usize)]) {
123    MANDOS.call_once(|| ArrayQueue::new(CAPACIDAD_MANDOS));
124
125    let mut ventanas = Vec::with_capacity(naturales.len());
126    for &(nat_ancho, nat_alto) in naturales {
127        ventanas.push(Ventana {
128            natural_ancho: nat_ancho,
129            natural_alto: nat_alto,
130            // Marco provisional; `aplicar_teselado` lo fija enseguida.
131            marco: RegionPantalla {
132                x: 0,
133                y: 0,
134                ancho: 0,
135                alto: 0,
136            },
137            // La cache: reservada UNA vez, acotada al lienzo natural.
138            cache: vec![0u8; nat_ancho.saturating_mul(nat_alto).saturating_mul(4)],
139            pintada: false,
140            baliza: None,
141        });
142    }
143
144    // El orden de teselado arranca como la identidad: la ventana `i` ocupa la
145    // celda `i`. Los mandos del teclado lo permutaran.
146    let orden = (0..ventanas.len()).collect();
147    let mut escritorio = Escritorio {
148        modo: MODO_INICIAL,
149        ancho,
150        alto,
151        ventanas,
152        orden,
153    };
154    aplicar_teselado(&mut escritorio);
155
156    ESCRITORIO.call_once(|| Mutex::new(escritorio));
157}
158
159/// Recalcula el teselado y asigna a cada ventana su marco. La celda `slot` del
160/// teselado va a la ventana `orden[slot]`: manda el orden, no la identidad.
161fn aplicar_teselado(escritorio: &mut Escritorio) {
162    let marcos = teselar(
163        escritorio.orden.len(),
164        escritorio.ancho,
165        escritorio.alto,
166        escritorio.modo,
167    );
168    for (slot, marco) in marcos.into_iter().enumerate() {
169        let ventana = escritorio.orden[slot];
170        escritorio.ventanas[ventana].marco = marco;
171    }
172}
173
174/// Pinta el escenario inicial del compositor: el area de apps y sus marcos
175/// teselados. Se invoca una vez, tras `fundar`, antes de encender las apps.
176pub fn componer_escenario() {
177    let Some(escritorio) = ESCRITORIO.get() else {
178        return;
179    };
180    let escritorio = escritorio.lock();
181    let area = area_apps(escritorio.ancho, escritorio.alto);
182    let marcos: Vec<RegionPantalla> = escritorio.ventanas.iter().map(|v| v.marco).collect();
183    crate::consola::pintar_escenario(area, &marcos);
184}
185
186/// El indice de la ventana enfocada. Lo LEE el manejador de IRQ1 para enrutar
187/// cada tecla — por eso es una simple lectura atomica, sin cerrojo alguno.
188pub fn foco() -> usize {
189    FOCO.load(Ordering::Relaxed)
190}
191
192/// Encola un mando del teclado. Lo invoca el manejador de IRQ1: empujar a una
193/// cola lock-free es seguro en contexto de interrupcion.
194pub fn solicitar(mando: Mando) {
195    if let Some(mandos) = MANDOS.get() {
196        // Si la cola se desborda, el mando se pierde en silencio: mas vale
197        // perder una pulsacion que arriesgar un panico dentro de una IRQ.
198        let _ = mandos.push(mando);
199    }
200}
201
202// =============================================================================
203//  El fotograma de una app — cache y composicion
204// =============================================================================
205
206/// Recibe el fotograma de la app `indice`: lo copia a su CACHE de respaldo —el
207/// kernel asume la persistencia visual— y lo compone, centrado, en su marco.
208/// Lo invoca la capacidad `sys_render_frame` desde el `tick` cooperativo.
209pub fn presentar_fotograma(indice: usize, datos: &[u8]) {
210    let Some(escritorio) = ESCRITORIO.get() else {
211        return;
212    };
213    let (marco, nat_ancho, nat_alto) = {
214        let mut escritorio = escritorio.lock();
215        let Some(ventana) = escritorio.ventanas.get_mut(indice) else {
216            return;
217        };
218        // Cachear el fotograma. El destino esta acotado al lienzo natural; se
219        // copia el minimo de ambas longitudes — jamas se desborda la cache.
220        let n = ventana.cache.len().min(datos.len());
221        ventana.cache[..n].copy_from_slice(&datos[..n]);
222        ventana.pintada = true;
223        (ventana.marco, ventana.natural_ancho, ventana.natural_alto)
224    };
225    let enfocada = FOCO.load(Ordering::Relaxed) == indice;
226    crate::consola::volcar_marco(marco, nat_ancho, nat_alto, datos, enfocada);
227}
228
229/// Marca la ventana `indice` como desalojada y tatua su marco con la baliza.
230/// Desde aqui queda excluida del foco — el teclado la salta.
231pub fn desalojar(indice: usize, color: Color) {
232    let Some(escritorio) = ESCRITORIO.get() else {
233        return;
234    };
235    let marco = {
236        let mut escritorio = escritorio.lock();
237        let Some(ventana) = escritorio.ventanas.get_mut(indice) else {
238            return;
239        };
240        ventana.baliza = Some(color);
241        ventana.marco
242    };
243    let enfocada = FOCO.load(Ordering::Relaxed) == indice;
244    crate::consola::pintar_desalojo(marco, color, enfocada);
245}
246
247// =============================================================================
248//  Los mandos del teclado — el escritorio interactivo
249// =============================================================================
250
251/// Atiende los mandos pendientes del teclado. La invoca la tarea del compositor
252/// en cada fotograma, desde el reactor cooperativo — el unico contexto donde es
253/// seguro bloquear el `ESCRITORIO` y la consola.
254pub fn atender_mandos() {
255    let Some(mandos) = MANDOS.get() else {
256        return;
257    };
258    while let Some(mando) = mandos.pop() {
259        match mando {
260            Mando::CiclarLayout => ciclar_layout(),
261            Mando::FocoSiguiente => mover_foco(true),
262            Mando::FocoAnterior => mover_foco(false),
263            Mando::Promover => promover(),
264            Mando::MoverAdelante => mover_ventana(true),
265            Mando::MoverAtras => mover_ventana(false),
266        }
267    }
268}
269
270/// Cicla al siguiente modo de teselado: recalcula los marcos de todas las
271/// ventanas y recompone el escritorio entero desde las caches de respaldo.
272fn ciclar_layout() {
273    let Some(escritorio) = ESCRITORIO.get() else {
274        return;
275    };
276    let mut escritorio = escritorio.lock();
277    escritorio.modo = escritorio.modo.next();
278    aplicar_teselado(&mut escritorio);
279    redibujar_todo(&escritorio);
280}
281
282/// Mueve el foco a la siguiente ventana VIVA, recorriendo el ORDEN de teselado
283/// —de modo que el foco salte entre ventanas visualmente contiguas— y saltando
284/// las desalojadas. Redibuja la ventana que pierde el foco y la que lo gana.
285fn mover_foco(adelante: bool) {
286    let Some(escritorio) = ESCRITORIO.get() else {
287        return;
288    };
289    let escritorio = escritorio.lock();
290    let n = escritorio.orden.len();
291    if n == 0 {
292        return;
293    }
294    let anterior = FOCO.load(Ordering::Relaxed);
295    // La posicion del foco en el orden de teselado — el punto de partida.
296    let pos = escritorio
297        .orden
298        .iter()
299        .position(|&v| v == anterior)
300        .unwrap_or(0);
301
302    // Avanzar saltando las ventanas desalojadas. Si no hay ninguna viva, tras
303    // `n` pasos se vuelve al punto de partida y el foco no cambia.
304    let mut nueva_pos = pos;
305    let mut nuevo = anterior;
306    for _ in 0..n {
307        nueva_pos = if adelante {
308            (nueva_pos + 1) % n
309        } else {
310            (nueva_pos + n - 1) % n
311        };
312        let candidata = escritorio.orden[nueva_pos];
313        if escritorio.ventanas[candidata].baliza.is_none() {
314            nuevo = candidata;
315            break;
316        }
317    }
318    FOCO.store(nuevo, Ordering::Relaxed);
319
320    if let Some(ventana) = escritorio.ventanas.get(anterior) {
321        redibujar_ventana(ventana, false);
322    }
323    redibujar_ventana(&escritorio.ventanas[nuevo], true);
324}
325
326/// Promueve la ventana enfocada a la posicion maestra —la celda 0— del
327/// teselado: la saca de su lugar en el orden y la inserta al frente. Las demas
328/// se desplazan una posicion. El escritorio entero se recompone.
329fn promover() {
330    let Some(escritorio) = ESCRITORIO.get() else {
331        return;
332    };
333    let mut escritorio = escritorio.lock();
334    let foco = FOCO.load(Ordering::Relaxed);
335    if let Some(pos) = escritorio.orden.iter().position(|&v| v == foco) {
336        let ventana = escritorio.orden.remove(pos);
337        escritorio.orden.insert(0, ventana);
338        aplicar_teselado(&mut escritorio);
339        redibujar_todo(&escritorio);
340    }
341}
342
343/// Mueve la ventana enfocada una posicion en el orden de teselado,
344/// intercambiandola con su vecina. El foco viaja con la ventana — el indice no
345/// cambia, solo su lugar en el orden.
346fn mover_ventana(adelante: bool) {
347    let Some(escritorio) = ESCRITORIO.get() else {
348        return;
349    };
350    let mut escritorio = escritorio.lock();
351    let n = escritorio.orden.len();
352    if n < 2 {
353        return;
354    }
355    let foco = FOCO.load(Ordering::Relaxed);
356    if let Some(pos) = escritorio.orden.iter().position(|&v| v == foco) {
357        let destino = if adelante {
358            (pos + 1) % n
359        } else {
360            (pos + n - 1) % n
361        };
362        escritorio.orden.swap(pos, destino);
363        aplicar_teselado(&mut escritorio);
364        redibujar_todo(&escritorio);
365    }
366}
367
368/// Recompone el escritorio entero: repinta el escenario —area y paneles— con
369/// los marcos nuevos y, sobre el, cada ventana desde su cache de respaldo.
370fn redibujar_todo(escritorio: &Escritorio) {
371    let area = area_apps(escritorio.ancho, escritorio.alto);
372    let marcos: Vec<RegionPantalla> = escritorio.ventanas.iter().map(|v| v.marco).collect();
373    crate::consola::pintar_escenario(area, &marcos);
374
375    let foco = FOCO.load(Ordering::Relaxed);
376    for (i, ventana) in escritorio.ventanas.iter().enumerate() {
377        redibujar_ventana(ventana, i == foco);
378    }
379}
380
381/// Redibuja UNA ventana en su marco actual: si fue desalojada, su baliza; si ya
382/// pinto, su ultimo fotograma desde la cache; si aun no pinto, nada —el panel
383/// del escenario ya esta puesto—.
384fn redibujar_ventana(ventana: &Ventana, enfocada: bool) {
385    match ventana.baliza {
386        Some(color) => crate::consola::pintar_desalojo(ventana.marco, color, enfocada),
387        None => {
388            if ventana.pintada {
389                crate::consola::volcar_marco(
390                    ventana.marco,
391                    ventana.natural_ancho,
392                    ventana.natural_alto,
393                    &ventana.cache,
394                    enfocada,
395                );
396            }
397        }
398    }
399}
400
401// =============================================================================
402//  Teselado — la geometria pura de `mirada-layout`
403// =============================================================================
404
405/// El area de pantalla que el compositor tesela: toda la pantalla menos la
406/// franja de la consola en la cima.
407pub fn area_apps(ancho_pantalla: usize, alto_pantalla: usize) -> RegionPantalla {
408    RegionPantalla {
409        x: 0,
410        y: FRANJA_CONSOLA.min(alto_pantalla),
411        ancho: ancho_pantalla,
412        alto: alto_pantalla.saturating_sub(FRANJA_CONSOLA),
413    }
414}
415
416/// Tesela el area de apps en `n` marcos con el modo dado. El vector resultante
417/// tiene exactamente `n` elementos, en el orden de las apps del manifiesto.
418fn teselar(n: usize, ancho: usize, alto: usize, modo: LayoutMode) -> Vec<RegionPantalla> {
419    let area = area_apps(ancho, alto);
420    let pantalla = Rect::new(
421        area.x as i32,
422        area.y as i32,
423        area.ancho as i32,
424        area.alto as i32,
425    );
426    let params = LayoutParams {
427        mode: modo,
428        gap: MARGEN,
429        ..LayoutParams::default()
430    };
431    tile(pantalla, n, &params)
432        .into_iter()
433        .map(rect_a_region)
434        .collect()
435}
436
437/// Traduce un `Rect` de `mirada-layout` (`i32`) a la `RegionPantalla` del
438/// kernel (`usize`). Un rectangulo degenerado queda en cero.
439fn rect_a_region(r: Rect) -> RegionPantalla {
440    RegionPantalla {
441        x: r.x.max(0) as usize,
442        y: r.y.max(0) as usize,
443        ancho: r.w.max(0) as usize,
444        alto: r.h.max(0) as usize,
445    }
446}