let panicImpl; let logImpl, log2Impl; let canvasBeginImpl, canvasLineImpl, canvasRectangleImpl, canvasCircleImpl, canvasFillImpl, canvasStrokeImpl; function allocCheck(p) { if (p == 0) throw new Error("out of memory"); return p; } function makeLogFunction(level) { return (length, pMessage) => { logImpl(level, length, pMessage); }; } function makeLogFunction2(level) { return (pScope, scopeLen, pMsg, len) => { log2Impl(level, pScope, scopeLen, pMsg, len); }; } // NOTE: It may seem tempting to share memory between the two modules, but that's *impossible*. // This is because Wasm binaries are not position-independent; addresses of the stack and the heap // are hardcoded. let [hakuWasm, haku2Wasm] = await Promise.all([ WebAssembly.instantiateStreaming(fetch(HAKU_WASM_PATH), { env: { panic(length, pMessage) { panicImpl(length, pMessage); }, trace: makeLogFunction("trace"), debug: makeLogFunction("debug"), info: makeLogFunction("info"), warn: makeLogFunction("warn"), error: makeLogFunction("error"), }, }), WebAssembly.instantiateStreaming(fetch(HAKU2_WASM_PATH), { env: { __haku2_log_err: makeLogFunction2("error"), __haku2_log_warn: makeLogFunction2("warn"), __haku2_log_info: makeLogFunction2("info"), __haku2_log_debug: makeLogFunction2("debug"), // TODO: Renderer __haku2_canvas_begin: (c) => canvasBeginImpl(c), __haku2_canvas_line: (c, x1, y1, x2, y2) => canvasLineImpl(c, x1, y1, x2, y2), __haku2_canvas_rectangle: (c, x, y, width, height) => canvasRectangleImpl(c, x, y, width, height), __haku2_canvas_circle: (c, x, y, r) => canvasCircleImpl(c, x, y, r), __haku2_canvas_fill: (c, r, g, b, a) => canvasFillImpl(c, r, g, b, a), __haku2_canvas_stroke: (c, r, g, b, a, thickness) => canvasStrokeImpl(c, r, g, b, a, thickness), }, }), ]); let memory = hakuWasm.instance.exports.memory; let w = hakuWasm.instance.exports; let memory2 = haku2Wasm.instance.exports.memory; let w2 = haku2Wasm.instance.exports; let textEncoder = new TextEncoder(); function allocString(string) { let size = string.length * 3; let align = 1; let pString = allocCheck(w.haku_alloc(size, align)); let buffer = new Uint8Array(memory.buffer, pString, size); let result = textEncoder.encodeInto(string, buffer); return { ptr: pString, length: result.written, size, align, }; } function freeString(alloc) { w.haku_free(alloc.ptr, alloc.size, alloc.align); } let textDecoder = new TextDecoder(); function readString(memory, size, pString) { let buffer = new Uint8Array(memory.buffer, pString, size); return textDecoder.decode(buffer); } function readCString(pCString) { let memoryBuffer = new Uint8Array(memory.buffer); let pCursor = pCString; while (memoryBuffer[pCursor] != 0 && memoryBuffer[pCursor] != null) { pCursor++; } let size = pCursor - pCString; return readString(memory, size, pCString); } function dup1to2(pHaku1, size, align) { if (size <= 0) { return { ptr: 0, length: 0, size: 0, align: 1, }; } let pHaku2 = allocCheck(w2.haku2_alloc(size, align)); let src = new Uint8Array(memory.buffer, pHaku1, size); let dst = new Uint8Array(memory2.buffer, pHaku2, size); dst.set(src); return { ptr: pHaku2, length: size, size, align, }; } function freeString2(alloc) { if (alloc.ptr == 0) return; w2.haku2_free(alloc.ptr, alloc.size, alloc.align); } class Panic extends Error { name = "Panic"; } panicImpl = (length, pMessage) => { throw new Panic(readString(memory, length, pMessage)); }; logImpl = (level, length, pMessage) => { console[level]("h1:", readString(memory, length, pMessage)); }; log2Impl = (level, pScope, scopeLen, pMsg, len) => { console[level]( "h2:", `${readString(memory2, scopeLen, pScope)}: ${readString(memory2, len, pMsg)}`, ); }; canvasBeginImpl = w.haku_pixmap_begin; canvasLineImpl = w.haku_pixmap_line; canvasRectangleImpl = w.haku_pixmap_rectangle; canvasCircleImpl = w.haku_pixmap_circle; canvasFillImpl = w.haku_pixmap_fill; canvasStrokeImpl = w.haku_pixmap_stroke; w.haku_init_logging(); export class Pixmap { #pPixmap = 0; constructor(width, height) { this.#pPixmap = allocCheck(w.haku_pixmap_new(width, height)); this.width = width; this.height = height; } destroy() { w.haku_pixmap_destroy(this.#pPixmap); } clear(r, g, b, a) { w.haku_pixmap_clear(this.#pPixmap, r, g, b, a); } get ptr() { return this.#pPixmap; } getArrayBuffer() { return new Uint8ClampedArray( memory.buffer, w.haku_pixmap_data(this.#pPixmap), this.width * this.height * 4, ); } getImageData() { return new ImageData(this.getArrayBuffer(), this.width, this.height); } } export const ContKind = { Scribble: 0, Dotter: 1, }; export class Haku { #pInstance = 0; #pLimits2 = 0; #pScratch2 = 0; #pVm2 = 0; #bytecode2 = null; #localCount = 0; #fuel = 0; #renderMaxDepth = 0; constructor(limits) { console.groupCollapsed("construct Haku"); { let pLimits = allocCheck(w.haku_limits_new()); for (let name of Object.keys(limits)) { w[`haku_limits_set_${name}`](pLimits, limits[name]); } this.#pInstance = allocCheck(w.haku_instance_new(pLimits)); w.haku_limits_destroy(pLimits); } console.groupEnd(); // NOTE(v2): VM is not constructed until there is a brush ready this.#pLimits2 = allocCheck(w2.haku2_limits_new()); w2.haku2_limits_set_stack_capacity(this.#pLimits2, limits.stack_capacity); w2.haku2_limits_set_call_stack_capacity(this.#pLimits2, limits.call_stack_capacity); this.#fuel = limits.fuel; this.#renderMaxDepth = limits.render_max_depth; this.#pScratch2 = allocCheck(w2.haku2_scratch_new(limits.memory)); } destroy() { w.haku_instance_destroy(this.#pInstance); w2.haku2_scratch_destroy(this.#pScratch2); w2.haku2_vm_destroy(this.#pVm2); w2.haku2_limits_destroy(this.#pLimits2); w2.haku_dealloc(this.#bytecode2.ptr); } setBrush(code) { let brushCode = allocString(code); let statusCode = w.haku_compile_brush2(this.#pInstance, brushCode.length, brushCode.ptr); freeString(brushCode); if (!w.haku_is_ok(statusCode)) { if (w.haku_is_diagnostics_emitted(statusCode)) { let diagnostics = []; for (let i = 0; i < w.haku_num_diagnostics2(this.#pInstance); ++i) { diagnostics.push({ start: w.haku_diagnostic_start2(this.#pInstance, i), end: w.haku_diagnostic_end2(this.#pInstance, i), message: readString( memory, w.haku_diagnostic_message_len2(this.#pInstance, i), w.haku_diagnostic_message2(this.#pInstance, i), ), }); } return { status: "error", errorKind: "diagnostics", diagnostics, }; } else { return { status: "error", errorKind: "plain", message: readCString(w.haku_status_string(statusCode)), }; } } if (this.#pVm2 != 0) w2.haku2_vm_destroy(this.#pVm2); if (this.#bytecode2 != null) freeString2(this.#bytecode2); let pDefsString = dup1to2( w.haku_defs2(this.#pInstance), w.haku_defs_len2(this.#pInstance), 1, ); let pTagsString = dup1to2( w.haku_tags2(this.#pInstance), w.haku_tags_len2(this.#pInstance), 1, ); let defs = allocCheck( w2.haku2_defs_parse( pDefsString, w.haku_defs_len2(this.#pInstance), pTagsString, w.haku_tags_len2(this.#pInstance), ), ); w2.haku2_scratch_reset(this.#pScratch2); this.#pVm2 = allocCheck(w2.haku2_vm_new(this.#pScratch2, defs, this.#pLimits2)); this.#bytecode2 = dup1to2( w.haku_bytecode2(this.#pInstance), w.haku_bytecode_len2(this.#pInstance), 1, ); this.#localCount = w.haku_local_count2(this.#pInstance); w2.haku2_defs_destroy(defs); freeString2(pDefsString); freeString2(pTagsString); return { status: "ok" }; } #exceptionMessage() { let len = w2.haku2_vm_exception_len(this.#pVm2); let pExn = allocCheck(w2.haku2_alloc(len, 1)); w2.haku2_vm_exception_render(this.#pVm2, pExn); let exn = readString(memory2, len, pExn); w2.haku2_free(pExn, len, 1); return exn; } #exceptionResult() { return { status: "error", errorKind: "exception", description: "Runtime error", message: this.#exceptionMessage(), }; } beginBrush() { if (this.#pVm2 == 0) { console.warn("VM instance is not available for drawing"); return; } w2.haku2_vm_reset(this.#pVm2, this.#fuel); let ok = w2.haku2_vm_run_main( this.#pVm2, this.#pScratch2, this.#bytecode2.ptr, this.#bytecode2.size, this.#localCount, ); if (!ok) { return this.#exceptionResult(); } return { status: "ok" }; } expectedContKind() { if (w2.haku2_vm_is_dotter(this.#pVm2)) return ContKind.Dotter; else return ContKind.Scribble; } contScribble(pixmap, translationX, translationY) { w.haku_pixmap_set_translation(pixmap.ptr, translationX, translationY); let ok = w2.haku2_render(this.#pVm2, pixmap.ptr, this.#renderMaxDepth); if (!ok) { return this.#exceptionResult(); } else { return { status: "ok" }; } } contDotter({ fromX, fromY, toX, toY, num }) { let ok = w2.haku2_vm_run_dotter(this.#pVm2, this.#pScratch2, fromX, fromY, toX, toY, num); if (!ok) { return this.#exceptionResult(); } else { return { status: "ok" }; } } async evalBrush(options) { let { runDotter, runScribble } = options; let result; result = this.beginBrush(); if (result.status != "ok") return result; while (true) { switch (this.expectedContKind()) { case ContKind.Scribble: result = await runScribble((pixmap, translationX, translationY) => { return this.contScribble(pixmap, translationX, translationY); }); return result; case ContKind.Dotter: let dotter = await runDotter(); result = this.contDotter(dotter); if (result.status != "ok") return result; break; } } } get astSize() { return 0; // TODO } get numRefs() { return 0; // TODO } get remainingFuel() { return 0; // TODO } get remainingMemory() { return 0; // TODO } }