diff --git a/apps/micropolis/cli/wasm/sim.ts b/apps/micropolis/cli/wasm/sim.ts index c4d8b09c..f080b0a7 100644 --- a/apps/micropolis/cli/wasm/sim.ts +++ b/apps/micropolis/cli/wasm/sim.ts @@ -4,13 +4,14 @@ * `extendSimCommands(argv)` adds the `sim` branch using the shared WASM loader. */ +import { writeFileSync } from 'node:fs'; import type { Argv } from 'yargs'; import type { MainModule, Micropolis } from '../../src/types/micropolisengine.d.js'; import { createNoopJsCallback } from '../../src/lib/wasm/callbacks'; import { loadMicropolisMainModule } from '../../src/lib/wasm/node'; import { normalizeStructuredFormat, stringifyStructured } from '../lib/format.js'; -type Flags = { city?: string; ticks?: number; format?: string }; +type Flags = { city?: string; ticks?: number; format?: string; output?: string }; function stringFlag(v: unknown, fallback: string): string { return typeof v === 'string' && v.length > 0 ? v : fallback; @@ -53,6 +54,7 @@ function summarize( async function runSmoke(flags: Flags) { const ticks = numberFlag(flags.ticks, 10); const city = stringFlag(flags.city, '/cities/haight.cty'); + const output = stringFlag(flags.output, ''); const engine = await loadMicropolisMainModule(); const micropolis = new engine.Micropolis(); const cb = createNoopJsCallback(engine); @@ -61,7 +63,18 @@ async function runSmoke(flags: Flags) { try { const loaded = micropolis.loadCity(city); for (let i = 0; i < ticks; i += 1) micropolis.simTick(); - return summarize(engine, micropolis, { loaded, city, ticks }); + const summary = summarize(engine, micropolis, { loaded, city, ticks }); + let saved: string | null = null; + let bytes = 0; + if (output.length > 0) { + const savedPath = '/micropolis-save.cty'; + micropolis.saveCityAs(savedPath); + const buf = engine.FS_readFile(savedPath) as Uint8Array; + writeFileSync(output, buf); + saved = output; + bytes = buf.length; + } + return { ...summary, saved, bytes }; } finally { micropolis.delete(); } @@ -104,6 +117,11 @@ export function extendSimCommands(argv: Argv): Argv { describe: 'Virtual path passed to loadCity (packaged .cty)' }) .option('ticks', { type: 'number', default: 10, describe: 'Number of simTick() calls' }) + .option('output', { + alias: 'o', + type: 'string', + describe: 'Write the city to this .cty file after ticking' + }) .option('format', { alias: 'f', type: 'string', diff --git a/apps/micropolis/src/types/micropolisengine.d.ts b/apps/micropolis/src/types/micropolisengine.d.ts index cba61954..fb969107 100644 --- a/apps/micropolis/src/types/micropolisengine.d.ts +++ b/apps/micropolis/src/types/micropolisengine.d.ts @@ -1,5 +1,6 @@ // TypeScript bindings for emscripten-generated code. Automatically generated at compile time. declare namespace RuntimeExports { + function FS_readFile(...args: any[]): any; let HEAPU16: Uint16Array; let wasmMemory: any; function FS_createPath(...args: any[]): any; @@ -369,6 +370,7 @@ export interface Micropolis extends ClassHandle { setCallback(_0: Callback | null, _1: any): void; init(): void; loadCity(_0: EmbindString): boolean; + saveCityAs(_0: EmbindString): void; simTick(): void; simUpdate(): void; generateSomeRandomCity(): void; diff --git a/packages/micropolis-engine/makefile b/packages/micropolis-engine/makefile index 7655a936..241f957b 100644 --- a/packages/micropolis-engine/makefile +++ b/packages/micropolis-engine/makefile @@ -78,7 +78,7 @@ EMLDFLAGS = \ -s EXPORT_ES6=1 \ -s 'ENVIRONMENT=web,worker,node' \ -s ALLOW_MEMORY_GROWTH=1 \ - -s 'EXPORTED_RUNTIME_METHODS=["HEAPU16","wasmMemory"]' \ + -s 'EXPORTED_RUNTIME_METHODS=["FS_readFile","HEAPU16","wasmMemory"]' \ -lembind \ --emit-tsd=micropolisengine.d.ts \ --shell-file src/micropolisengine_template.html \ diff --git a/packages/micropolis-engine/src/emscripten.cpp b/packages/micropolis-engine/src/emscripten.cpp index 303229cd..afe9383c 100644 --- a/packages/micropolis-engine/src/emscripten.cpp +++ b/packages/micropolis-engine/src/emscripten.cpp @@ -1076,6 +1076,7 @@ function createTypedArrayFromMap(mapInstance) { .function("setCallback", &Micropolis::setCallback, allow_raw_pointers()) .function("init", &Micropolis::init) .function("loadCity", &Micropolis::loadCity) + .function("saveCityAs", &Micropolis::saveCityAs) .function("simTick", &Micropolis::simTick) .function("simUpdate", &Micropolis::simUpdate) .function("generateSomeRandomCity", &Micropolis::generateSomeRandomCity)