tawasuyu

tawasuyu/brahman

Rust 572TOML 217Markdown 68JSON 198326 Definiciones

⎇ 60553bec441be75ad5542a9da93c871cd0314f5f renaser/apps/bitacora/src/lib.rs Rust · 289 líneas · 9.8 KB Historia Quién tocó qué crudo

feat(renaser): Fase 17 — bitácora, el editor que recuerda 60553bec4 · sergio ·

1// =============================================================================
2//  renaser :: apps/bitacora — Fase 17 :: un editor que recuerda
3// -----------------------------------------------------------------------------
4//  La fase 7c le dio a las apps memoria mas alla del arranque: `sys_estado_*`
5//  ancla la huella de un app en el grafo, y al reiniciar el kernel se la
6//  devuelve. `memoriosa` lo demostro contando teclas. `bitacora` lo lleva al
7//  siguiente paso natural: ofrecer un editor de texto.
8//
9//  La pantalla muestra un titulo en indigo y debajo el texto que el usuario va
10//  tecleando, con salto de linea automatico al llegar al margen y con `Enter`.
11//  Backspace borra el ultimo. Cada cambio se persiste de inmediato, asi que la
12//  apagada brusca no pierde nada — la proxima vida del kernel retoma exacto.
13//
14//  Tipografia: la 8x8 clasica (font8x8), escalada x2 a 16x16. Cabe en su propia
15//  memoria lineal y se renderiza pixel a pixel — el app no toca el lienzo del
16//  kernel, solo entrega su propio fotograma.
17// =============================================================================
18
19#![no_std]
20
21use font8x8::legacy::BASIC_LEGACY;
22
23#[link(wasm_import_module = "renaser")]
24extern "C" {
25    fn sys_render_frame(ptr: u32, len: u32);
26    fn sys_get_scancode() -> u32;
27    fn sys_estado_cargar(salida: u32, capacidad: u32) -> i32;
28    fn sys_estado_guardar(datos: u32, datos_len: u32) -> i32;
29}
30
31#[panic_handler]
32fn al_fallar(_: &core::panic::PanicInfo) -> ! {
33    loop {}
34}
35
36// --- Geometria ----------------------------------------------------------------
37
38/// Tamaño del lienzo natural — debe coincidir con `region` del manifiesto.
39const ANCHO: usize = 480;
40const ALTO: usize = 280;
41/// Pixeles por celda de glifo (8x8 escalado x2).
42const PASO: usize = 16;
43/// Margen horizontal para el cuerpo del texto.
44const MARGEN_X: usize = 16;
45/// Y de la linea base del titulo.
46const Y_LABEL: usize = 6;
47/// Y de la linea base de la primera fila de texto.
48const Y_TEXTO: usize = 38;
49/// Cuantas columnas caben.
50const COLUMNAS: usize = (ANCHO - 2 * MARGEN_X) / PASO;
51/// Cuantas filas caben bajo el titulo.
52const FILAS: usize = (ALTO - Y_TEXTO) / PASO;
53
54// --- Estado -------------------------------------------------------------------
55
56/// Capacidad del buffer de texto. Al desbordarse se descarta una porcion del
57/// principio (el texto mas viejo) para dejar sitio al nuevo — un cuaderno con
58/// memoria finita, no un agujero negro.
59const CAPACIDAD: usize = 512;
60
61const FONDO: u32 = 0x0A_18_30;
62const TINTA: u32 = 0xE8_EC_F4;
63const ETIQUETA: u32 = 0x8B_5C_F6;
64
65static mut BUFFER: [u8; CAPACIDAD] = [0; CAPACIDAD];
66static mut LEN: usize = 0;
67static mut LIENZO: [u32; ANCHO * ALTO] = [0; ANCHO * ALTO];
68
69// --- ABI del userspace --------------------------------------------------------
70
71#[no_mangle]
72pub extern "C" fn init() {
73    // Cargar el texto persistido — si no hay nada, `n` es 0 y empezamos vacios.
74    let buffer = unsafe { &mut *core::ptr::addr_of_mut!(BUFFER) };
75    // SEGURIDAD: `sys_estado_cargar` es una capacidad del host; (ptr, len) cae
76    // dentro de nuestra propia memoria lineal y el host lo valida sin piedad.
77    let n = unsafe { sys_estado_cargar(buffer.as_mut_ptr() as u32, CAPACIDAD as u32) };
78    if n > 0 {
79        // SEGURIDAD: lectura/escritura escalar; LEN es nuestro propio cursor.
80        unsafe {
81            LEN = (n as usize).min(CAPACIDAD);
82        }
83    }
84    pintar();
85}
86
87#[no_mangle]
88pub extern "C" fn tick() {
89    let mut cambio = false;
90    // Drenar TODOS los scancodes acumulados desde el ultimo fotograma. La cola
91    // es propia de este app — la inscribio la fase 5 en la IRQ1 — asi que
92    // mirarla aqui no le quita nada a nadie.
93    loop {
94        let sc = unsafe { sys_get_scancode() } as u8;
95        if sc == 0 {
96            break;
97        }
98        if sc & 0x80 != 0 {
99            // Codigo de KEY-UP (release). Lo ignoramos: tecleamos al pulsar.
100            continue;
101        }
102        match sc {
103            0x0E => {
104                // Backspace — borrar el ultimo caracter, si lo hay.
105                unsafe {
106                    if LEN > 0 {
107                        LEN -= 1;
108                        cambio = true;
109                    }
110                }
111            }
112            0x1C => {
113                // Enter — salto de linea explicito.
114                anexar(b'\n');
115                cambio = true;
116            }
117            otro => {
118                let c = scancode_a_caracter(otro);
119                if c != 0 {
120                    anexar(c);
121                    cambio = true;
122                }
123            }
124        }
125    }
126    if cambio {
127        guardar();
128    }
129    pintar();
130}
131
132// --- Estado: buffer -----------------------------------------------------------
133
134/// Anexa un caracter al final del buffer. Si el buffer esta lleno descarta los
135/// 64 primeros bytes para hacer hueco (amortiza el coste; no es una mudanza por
136/// cada pulsacion).
137fn anexar(c: u8) {
138    unsafe {
139        if LEN >= CAPACIDAD {
140            let buffer = &mut *core::ptr::addr_of_mut!(BUFFER);
141            buffer.copy_within(64.., 0);
142            LEN = CAPACIDAD - 64;
143        }
144        let buffer = &mut *core::ptr::addr_of_mut!(BUFFER);
145        buffer[LEN] = c;
146        LEN += 1;
147    }
148}
149
150/// Persiste el buffer en el grafo. La huella sobrevive a la siguiente arrancada.
151fn guardar() {
152    unsafe {
153        let buffer = &*core::ptr::addr_of!(BUFFER);
154        // SEGURIDAD: (ptr, len) describe nuestra propia memoria; el host lo
155        // verifica y nunca lee fuera del rango entregado.
156        let _ = sys_estado_guardar(buffer.as_ptr() as u32, LEN as u32);
157    }
158}
159
160// --- Renderizado --------------------------------------------------------------
161
162fn pintar() {
163    let lienzo = unsafe { &mut *core::ptr::addr_of_mut!(LIENZO) };
164    // Fondo limpio.
165    for pixel in lienzo.iter_mut() {
166        *pixel = FONDO;
167    }
168    // Titulo.
169    pintar_texto(lienzo, b"bitacora :: el texto persiste", MARGEN_X, Y_LABEL, ETIQUETA);
170    // Linea sutil bajo el titulo.
171    let y_linea = Y_LABEL + PASO + 4;
172    for x in MARGEN_X..(ANCHO - MARGEN_X) {
173        lienzo[y_linea * ANCHO + x] = ETIQUETA;
174        lienzo[(y_linea + 1) * ANCHO + x] = ETIQUETA;
175    }
176
177    // Cuerpo: mostrar las ultimas `FILAS` lineas del buffer, con wrap en
178    // `COLUMNAS`. Dos pasadas para saltarse las filas viejas con elegancia.
179    let buffer = unsafe { &*core::ptr::addr_of!(BUFFER) };
180    let len = unsafe { LEN };
181
182    // Pasada 1: contar filas totales (con wrap).
183    let mut filas_total = 1usize;
184    let mut col = 0usize;
185    for i in 0..len {
186        let c = buffer[i];
187        if c == b'\n' {
188            filas_total += 1;
189            col = 0;
190        } else {
191            if col >= COLUMNAS {
192                filas_total += 1;
193                col = 0;
194            }
195            col += 1;
196        }
197    }
198    let skip = filas_total.saturating_sub(FILAS);
199
200    // Pasada 2: renderizar solo a partir de la fila `skip`.
201    let mut fila_actual = 0usize;
202    let mut col2 = 0usize;
203    for i in 0..len {
204        let c = buffer[i];
205        if c == b'\n' {
206            fila_actual += 1;
207            col2 = 0;
208            continue;
209        }
210        if col2 >= COLUMNAS {
211            fila_actual += 1;
212            col2 = 0;
213        }
214        if fila_actual >= skip {
215            let rfila = fila_actual - skip;
216            if rfila < FILAS {
217                let x = MARGEN_X + col2 * PASO;
218                let y = Y_TEXTO + rfila * PASO;
219                pintar_glifo(lienzo, c, x, y, TINTA);
220            }
221        }
222        col2 += 1;
223    }
224
225    // SEGURIDAD: `sys_render_frame` valida (ptr, len) contra nuestra memoria.
226    unsafe {
227        sys_render_frame(lienzo.as_ptr() as u32, (ANCHO * ALTO * 4) as u32);
228    }
229}
230
231/// Pinta una cadena ASCII en (x, y_base), avanzando un PASO por glifo. Se
232/// detiene si el siguiente glifo no cabria dentro del lienzo.
233fn pintar_texto(lienzo: &mut [u32], texto: &[u8], x: usize, y: usize, color: u32) {
234    let mut cx = x;
235    for &c in texto {
236        if cx + PASO > ANCHO {
237            break;
238        }
239        pintar_glifo(lienzo, c, cx, y, color);
240        cx += PASO;
241    }
242}
243
244/// Pinta un solo glifo 8x8 escalado a 16x16 en (x, y). Los caracteres no ASCII
245/// se renderizan como `?`.
246fn pintar_glifo(lienzo: &mut [u32], c: u8, x: usize, y: usize, color: u32) {
247    let glifo = if (c as usize) < 128 {
248        BASIC_LEGACY[c as usize]
249    } else {
250        BASIC_LEGACY[b'?' as usize]
251    };
252    for row in 0..8 {
253        for col in 0..8 {
254            if glifo[row] & (1 << col) != 0 {
255                let px = x + col * 2;
256                let py = y + row * 2;
257                if px + 1 >= ANCHO || py + 1 >= ALTO {
258                    continue;
259                }
260                lienzo[py * ANCHO + px] = color;
261                lienzo[py * ANCHO + px + 1] = color;
262                lienzo[(py + 1) * ANCHO + px] = color;
263                lienzo[(py + 1) * ANCHO + px + 1] = color;
264            }
265        }
266    }
267}
268
269// --- Teclado: scancode -> caracter --------------------------------------------
270
271/// Traduce un MAKE-code del set 1 (US layout) a su caracter ASCII en minuscula.
272/// Devuelve 0 para los scancodes que no producen texto — modificadores,
273/// extendidos, etc.: el llamante los descarta sin gritar.
274fn scancode_a_caracter(sc: u8) -> u8 {
275    match sc {
276        0x02 => b'1', 0x03 => b'2', 0x04 => b'3', 0x05 => b'4', 0x06 => b'5',
277        0x07 => b'6', 0x08 => b'7', 0x09 => b'8', 0x0A => b'9', 0x0B => b'0',
278        0x10 => b'q', 0x11 => b'w', 0x12 => b'e', 0x13 => b'r', 0x14 => b't',
279        0x15 => b'y', 0x16 => b'u', 0x17 => b'i', 0x18 => b'o', 0x19 => b'p',
280        0x1E => b'a', 0x1F => b's', 0x20 => b'd', 0x21 => b'f', 0x22 => b'g',
281        0x23 => b'h', 0x24 => b'j', 0x25 => b'k', 0x26 => b'l',
282        0x2C => b'z', 0x2D => b'x', 0x2E => b'c', 0x2F => b'v', 0x30 => b'b',
283        0x31 => b'n', 0x32 => b'm',
284        0x33 => b',', 0x34 => b'.', 0x35 => b'/',
285        0x27 => b';', 0x28 => b'\'',
286        0x39 => b' ',
287        _ => 0,
288    }
289}