haku2: on the client
resource indicators are currently unimplemented
This commit is contained in:
parent
45c954cb03
commit
e667c6336a
10 changed files with 437 additions and 364 deletions
324
static/haku.js
324
static/haku.js
|
@ -1,5 +1,16 @@
|
|||
let panicImpl;
|
||||
let logImpl;
|
||||
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) => {
|
||||
|
@ -7,9 +18,17 @@ function makeLogFunction(level) {
|
|||
};
|
||||
}
|
||||
|
||||
let { instance: hakuInstance, module: hakuModule } = await WebAssembly.instantiateStreaming(
|
||||
fetch(HAKU_WASM_PATH),
|
||||
{
|
||||
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);
|
||||
|
@ -20,17 +39,37 @@ let { instance: hakuInstance, module: hakuModule } = await WebAssembly.instantia
|
|||
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"),
|
||||
|
||||
let memory = hakuInstance.exports.memory;
|
||||
let w = hakuInstance.exports;
|
||||
// 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 = w.haku_alloc(size, align);
|
||||
let pString = allocCheck(w.haku_alloc(size, align));
|
||||
|
||||
let buffer = new Uint8Array(memory.buffer, pString, size);
|
||||
let result = textEncoder.encodeInto(string, buffer);
|
||||
|
@ -48,7 +87,7 @@ function freeString(alloc) {
|
|||
}
|
||||
|
||||
let textDecoder = new TextDecoder();
|
||||
function readString(size, pString) {
|
||||
function readString(memory, size, pString) {
|
||||
let buffer = new Uint8Array(memory.buffer, pString, size);
|
||||
return textDecoder.decode(buffer);
|
||||
}
|
||||
|
@ -62,7 +101,36 @@ function readCString(pCString) {
|
|||
}
|
||||
|
||||
let size = pCursor - pCString;
|
||||
return readString(size, 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 {
|
||||
|
@ -70,20 +138,34 @@ class Panic extends Error {
|
|||
}
|
||||
|
||||
panicImpl = (length, pMessage) => {
|
||||
throw new Panic(readString(length, pMessage));
|
||||
throw new Panic(readString(memory, length, pMessage));
|
||||
};
|
||||
|
||||
logImpl = (level, length, pMessage) => {
|
||||
console[level](readString(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 = w.haku_pixmap_new(width, height);
|
||||
this.#pPixmap = allocCheck(w.haku_pixmap_new(width, height));
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
@ -113,7 +195,6 @@ export class Pixmap {
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: This must be kept in sync with ContKind on the haku-wasm side.
|
||||
export const ContKind = {
|
||||
Scribble: 0,
|
||||
Dotter: 1,
|
||||
|
@ -121,53 +202,65 @@ export const ContKind = {
|
|||
|
||||
export class Haku {
|
||||
#pInstance = 0;
|
||||
#pBrush = 0;
|
||||
#brushCode = null;
|
||||
|
||||
#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]);
|
||||
}
|
||||
|
||||
let pLimits = 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);
|
||||
}
|
||||
|
||||
this.#pInstance = w.haku_instance_new(pLimits);
|
||||
this.#pBrush = w.haku_brush_new();
|
||||
|
||||
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_brush_destroy(this.#pBrush);
|
||||
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) {
|
||||
w.haku_reset(this.#pInstance);
|
||||
// NOTE: Brush is invalid at this point, because we reset removes all defs and registered chunks.
|
||||
|
||||
if (this.#brushCode != null) freeString(this.#brushCode);
|
||||
this.#brushCode = allocString(code);
|
||||
|
||||
let statusCode = w.haku_compile_brush(
|
||||
this.#pInstance,
|
||||
this.#pBrush,
|
||||
this.#brushCode.length,
|
||||
this.#brushCode.ptr,
|
||||
);
|
||||
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_diagnostics(this.#pBrush); ++i) {
|
||||
for (let i = 0; i < w.haku_num_diagnostics2(this.#pInstance); ++i) {
|
||||
diagnostics.push({
|
||||
start: w.haku_diagnostic_start(this.#pBrush, i),
|
||||
end: w.haku_diagnostic_end(this.#pBrush, i),
|
||||
start: w.haku_diagnostic_start2(this.#pInstance, i),
|
||||
end: w.haku_diagnostic_end2(this.#pInstance, i),
|
||||
message: readString(
|
||||
w.haku_diagnostic_message_len(this.#pBrush, i),
|
||||
w.haku_diagnostic_message(this.#pBrush, i),
|
||||
memory,
|
||||
w.haku_diagnostic_message_len2(this.#pInstance, i),
|
||||
w.haku_diagnostic_message2(this.#pInstance, i),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
@ -185,51 +278,106 @@ export class Haku {
|
|||
}
|
||||
}
|
||||
|
||||
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" };
|
||||
}
|
||||
|
||||
#statusCodeToResultObject(statusCode) {
|
||||
if (!w.haku_is_ok(statusCode)) {
|
||||
if (w.haku_is_exception(statusCode)) {
|
||||
return {
|
||||
status: "error",
|
||||
errorKind: "exception",
|
||||
description: readCString(w.haku_status_string(statusCode)),
|
||||
message: readString(
|
||||
w.haku_exception_message_len(this.#pInstance),
|
||||
w.haku_exception_message(this.#pInstance),
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
status: "error",
|
||||
errorKind: "plain",
|
||||
message: readCString(w.haku_status_string(statusCode)),
|
||||
};
|
||||
}
|
||||
#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" };
|
||||
}
|
||||
}
|
||||
|
||||
beginBrush() {
|
||||
return this.#statusCodeToResultObject(w.haku_begin_brush(this.#pInstance, this.#pBrush));
|
||||
}
|
||||
|
||||
expectedContKind() {
|
||||
return w.haku_cont_kind(this.#pInstance);
|
||||
}
|
||||
|
||||
contScribble(pixmap, translationX, translationY) {
|
||||
return this.#statusCodeToResultObject(
|
||||
w.haku_cont_scribble(this.#pInstance, pixmap.ptr, translationX, translationY),
|
||||
);
|
||||
}
|
||||
|
||||
contDotter({ fromX, fromY, toX, toY, num }) {
|
||||
return this.#statusCodeToResultObject(
|
||||
w.haku_cont_dotter(this.#pInstance, 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) {
|
||||
|
@ -239,7 +387,7 @@ export class Haku {
|
|||
result = this.beginBrush();
|
||||
if (result.status != "ok") return result;
|
||||
|
||||
while (this.expectedContKind() != ContKind.Invalid) {
|
||||
while (true) {
|
||||
switch (this.expectedContKind()) {
|
||||
case ContKind.Scribble:
|
||||
result = await runScribble((pixmap, translationX, translationY) => {
|
||||
|
@ -254,27 +402,21 @@ export class Haku {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { status: "ok" };
|
||||
}
|
||||
|
||||
get astSize() {
|
||||
if (this.#pBrush != 0) {
|
||||
return w.haku_stat_ast_size(this.#pBrush);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 0; // TODO
|
||||
}
|
||||
|
||||
get numRefs() {
|
||||
return w.haku_stat_num_refs(this.#pInstance);
|
||||
return 0; // TODO
|
||||
}
|
||||
|
||||
get remainingFuel() {
|
||||
return w.haku_stat_remaining_fuel(this.#pInstance);
|
||||
return 0; // TODO
|
||||
}
|
||||
|
||||
get remainingMemory() {
|
||||
return w.haku_stat_remaining_memory(this.#pInstance);
|
||||
return 0; // TODO
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue