121 / Advanced

Keep a value across wasm events

With the per-event arena on (runNyxWasm(bytes, { arena: true })) everything a handler allocates is thrown away when the event ends. A String or Array built in one event and stored in a global would be read by the next event as recycled memory — silently, with no error. arena_persist(x) copies the value into the persistent region first, so what you keep on purpose survives and everything else is still reclaimed.

121-arena-persist-wasm.nxSource →
// 121 — wasm: guardar entre eventos un valor que nació en un evento
// wasm: keep a value born in one event for the next ones
//
// Target wasm32-wasi (corre en el navegador, o bajo node con
// examples/browser/run-node.mjs). Con la arena por evento encendida
// (`runNyxWasm(bytes, { arena: true })`), todo lo que un handler aloca se
// descarta al terminar el evento. Un String o un Array armado en un evento y
// guardado en un global se leería en el evento siguiente como memoria
// reciclada, en silencio.
//
// arena_persist(x) lo copia a la región persistente:
//   - acepta un String, o un Array de String/int/float/bool (y arrays de
//     esos: una línea de ticket con sus modificadores se copia entera);
//   - un struct, un Map, un Fn o un Array<Struct> es NYX1039 al compilar: no
//     llevan en tiempo de ejecución la descripción de lo que contienen.
//     Persiste sus campos uno por uno y reconstruye el valor donde se usa;
//   - es un builtin: no hace falta importar nada, y en nativo es la identidad
//     (no hay arena), así que la lógica que lo usa corre con `nyx test`.
//
// arena_stats() (import "std/wasm_mem", solo wasm) da los bytes persistentes
// y los del turno, para vigilar que una página larga no crezca sin control.
//
// Construir:  nyx build --target wasm32-wasi

import "std/wasm_mem"

var g_ultimo_codigo: String = ""
var g_ticket: Array = []

// Cada escaneo arma un String y una línea de ticket DURANTE el evento.
#[export_name = "al_escanear"]
fn al_escanear(n: int) {
    let codigo: String = "SKU-" + int_to_string(n)
    g_ultimo_codigo = arena_persist(codigo)
    let modificadores: Array = ["sin hielo", "grande"]
    let linea: Array = [codigo, int_to_float(n) * 1.5, n % 2 == 0, modificadores]
    g_ticket = arena_persist(linea)
}

// Un evento posterior lee lo guardado: sigue intacto.
#[export_name = "al_cobrar"]
fn al_cobrar() -> int {
    let precio: float = g_ticket[1]
    let mods: Array = g_ticket[3]
    let primero: String = mods[0]
    print("cobrando " + g_ultimo_codigo + " a " + float_to_string(precio) + " (" + primero + ")")
    return g_ultimo_codigo.length()
}

// Diagnóstico: lo persistido solo crece con lo que se guarda a propósito.
#[export_name = "memoria"]
fn memoria() -> int {
    let st: ArenaStats = arena_stats()
    return st.persistent
}

fn main() {
    print("listo: la página llama a al_escanear y a al_cobrar")
}
Illustrative outputstdout
listo: la página llama a al_escanear y a al_cobrar
cobrando SKU-7 a 10.5 (sin hielo)

How it works

It accepts a String or an Array of String, int, float and bool — including arrays of those, so a ticket line with its modifiers is copied whole, as g_ticket shows. A struct, a Map, an Fn or an Array<Struct> is a compile error (NYX1039): at run time they do not carry a description of what they contain, so persist their fields one by one and rebuild the value where you use it.

arena_persist is a builtin, so nothing is imported for it, and on a native build it is the identity function (there is no arena). That is what lets the logic around it run under nyx test. arena_stats(), from std/wasm_mem and wasm-only, returns the persistent bytes and the current turn's, which is how a long-lived page watches for unbounded growth. This is a browser recipe — it is built with nyx build --target wasm32-wasi and the page calls its exports — so the output above is illustrative.