2024-08-10 23:13:20 +02:00
|
|
|
let panicImpl;
|
|
|
|
let logImpl;
|
|
|
|
|
|
|
|
function makeLogFunction(level) {
|
|
|
|
return (length, pMessage) => {
|
|
|
|
logImpl(level, length, pMessage);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let { instance: hakuInstance, module: hakuModule } = await WebAssembly.instantiateStreaming(
|
2024-09-04 21:48:42 +02:00
|
|
|
fetch(HAKU_WASM_PATH),
|
2024-08-10 23:13:20 +02:00
|
|
|
{
|
|
|
|
env: {
|
|
|
|
panic(length, pMessage) {
|
|
|
|
panicImpl(length, pMessage);
|
|
|
|
},
|
|
|
|
trace: makeLogFunction("trace"),
|
|
|
|
debug: makeLogFunction("debug"),
|
|
|
|
info: makeLogFunction("info"),
|
|
|
|
warn: makeLogFunction("warn"),
|
|
|
|
error: makeLogFunction("error"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let memory = hakuInstance.exports.memory;
|
|
|
|
let w = hakuInstance.exports;
|
|
|
|
|
|
|
|
let textEncoder = new TextEncoder();
|
|
|
|
function allocString(string) {
|
|
|
|
let size = string.length * 3;
|
|
|
|
let align = 1;
|
|
|
|
let pString = 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(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(size, pCString);
|
|
|
|
}
|
|
|
|
|
|
|
|
class Panic extends Error {
|
|
|
|
name = "Panic";
|
|
|
|
}
|
|
|
|
|
|
|
|
panicImpl = (length, pMessage) => {
|
|
|
|
throw new Panic(readString(length, pMessage));
|
|
|
|
};
|
|
|
|
|
|
|
|
logImpl = (level, length, pMessage) => {
|
|
|
|
console[level](readString(length, pMessage));
|
|
|
|
};
|
|
|
|
|
|
|
|
w.haku_init_logging();
|
|
|
|
|
|
|
|
export class Pixmap {
|
|
|
|
#pPixmap = 0;
|
|
|
|
|
|
|
|
constructor(width, height) {
|
|
|
|
this.#pPixmap = 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;
|
|
|
|
}
|
|
|
|
|
2024-09-03 22:16:28 +02:00
|
|
|
getArrayBuffer() {
|
|
|
|
return new Uint8ClampedArray(
|
|
|
|
memory.buffer,
|
|
|
|
w.haku_pixmap_data(this.#pPixmap),
|
|
|
|
this.width * this.height * 4,
|
2024-08-10 23:13:20 +02:00
|
|
|
);
|
|
|
|
}
|
2024-09-03 22:16:28 +02:00
|
|
|
|
|
|
|
getImageData() {
|
|
|
|
return new ImageData(this.getArrayBuffer(), this.width, this.height);
|
|
|
|
}
|
2024-08-10 23:13:20 +02:00
|
|
|
}
|
|
|
|
|
2024-09-08 13:53:29 +02:00
|
|
|
// NOTE: This must be kept in sync with ContKind on the haku-wasm side.
|
|
|
|
export const ContKind = {
|
|
|
|
Scribble: 0,
|
|
|
|
Dotter: 1,
|
|
|
|
};
|
|
|
|
|
2024-08-10 23:13:20 +02:00
|
|
|
export class Haku {
|
|
|
|
#pInstance = 0;
|
|
|
|
#pBrush = 0;
|
|
|
|
#brushCode = null;
|
|
|
|
|
2024-08-15 20:01:23 +02:00
|
|
|
constructor(limits) {
|
2024-09-03 22:16:28 +02:00
|
|
|
console.groupCollapsed("construct Haku");
|
|
|
|
|
2024-08-15 20:01:23 +02:00
|
|
|
let pLimits = w.haku_limits_new();
|
|
|
|
for (let name of Object.keys(limits)) {
|
|
|
|
w[`haku_limits_set_${name}`](pLimits, limits[name]);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.#pInstance = w.haku_instance_new(pLimits);
|
2024-08-10 23:13:20 +02:00
|
|
|
this.#pBrush = w.haku_brush_new();
|
2024-08-15 20:01:23 +02:00
|
|
|
|
|
|
|
w.haku_limits_destroy(pLimits);
|
2024-09-03 22:16:28 +02:00
|
|
|
|
|
|
|
console.groupEnd();
|
2024-08-10 23:13:20 +02:00
|
|
|
}
|
|
|
|
|
2024-08-22 20:49:24 +02:00
|
|
|
destroy() {
|
|
|
|
w.haku_brush_destroy(this.#pBrush);
|
|
|
|
w.haku_instance_destroy(this.#pInstance);
|
|
|
|
}
|
|
|
|
|
2024-08-10 23:13:20 +02:00
|
|
|
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,
|
|
|
|
);
|
|
|
|
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) {
|
|
|
|
diagnostics.push({
|
|
|
|
start: w.haku_diagnostic_start(this.#pBrush, i),
|
|
|
|
end: w.haku_diagnostic_end(this.#pBrush, i),
|
|
|
|
message: readString(
|
|
|
|
w.haku_diagnostic_message_len(this.#pBrush, i),
|
|
|
|
w.haku_diagnostic_message(this.#pBrush, i),
|
|
|
|
),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
status: "error",
|
|
|
|
errorKind: "diagnostics",
|
|
|
|
diagnostics,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
status: "error",
|
|
|
|
errorKind: "plain",
|
|
|
|
message: readCString(w.haku_status_string(statusCode)),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return { status: "ok" };
|
|
|
|
}
|
|
|
|
|
2024-08-15 20:01:23 +02:00
|
|
|
#statusCodeToResultObject(statusCode) {
|
2024-08-10 23:13:20 +02:00
|
|
|
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)),
|
|
|
|
};
|
|
|
|
}
|
2024-08-15 20:01:23 +02:00
|
|
|
} else {
|
|
|
|
return { status: "ok" };
|
2024-08-10 23:13:20 +02:00
|
|
|
}
|
2024-08-15 20:01:23 +02:00
|
|
|
}
|
2024-08-10 23:13:20 +02:00
|
|
|
|
2024-09-08 13:53:29 +02:00
|
|
|
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),
|
|
|
|
);
|
2024-08-15 20:01:23 +02:00
|
|
|
}
|
|
|
|
|
2024-09-08 13:53:29 +02:00
|
|
|
contDotter({ fromX, fromY, toX, toY, num }) {
|
2024-08-15 20:01:23 +02:00
|
|
|
return this.#statusCodeToResultObject(
|
2024-09-08 13:53:29 +02:00
|
|
|
w.haku_cont_dotter(this.#pInstance, fromX, fromY, toX, toY, num),
|
2024-08-15 20:01:23 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-09-08 13:53:29 +02:00
|
|
|
async evalBrush(options) {
|
|
|
|
let { runDotter, runScribble } = options;
|
|
|
|
|
|
|
|
let result;
|
|
|
|
result = this.beginBrush();
|
|
|
|
if (result.status != "ok") return result;
|
|
|
|
|
|
|
|
while (this.expectedContKind() != ContKind.Invalid) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return { status: "ok" };
|
2024-08-10 23:13:20 +02:00
|
|
|
}
|
2024-10-25 21:38:18 +02:00
|
|
|
|
|
|
|
get astSize() {
|
|
|
|
if (this.#pBrush != 0) {
|
|
|
|
return w.haku_stat_ast_size(this.#pBrush);
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
get numRefs() {
|
|
|
|
return w.haku_stat_num_refs(this.#pInstance);
|
|
|
|
}
|
|
|
|
|
|
|
|
get remainingFuel() {
|
|
|
|
return w.haku_stat_remaining_fuel(this.#pInstance);
|
|
|
|
}
|
|
|
|
|
|
|
|
get remainingMemory() {
|
|
|
|
return w.haku_stat_remaining_memory(this.#pInstance);
|
|
|
|
}
|
2024-08-10 23:13:20 +02:00
|
|
|
}
|