⎇ 60553bec441be75ad5542a9da93c871cd0314f5f
renaser/apps/bitacora/src/lib.rs
Historia
Quién tocó qué
crudo
feat(renaser): Fase 17 — bitácora, el editor que recuerda
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}