Mirai's Miscellaneous Misadventures
M37 / main.js
// copyright 2022 zamfofex
// license: AGPLv3 or later
let global = v =>
{
if (typeof v === "number") return v
return v.value
}
let createCanvas = (document, width, height) =>
{
let canvas = document.createElement("canvas")
canvas.width = width
canvas.height = height
canvas.tabIndex = 0
return canvas
}
export let run = async (
{
imports: extraImports = {},
window = globalThis.window,
document = window.document,
canvas = createCanvas(document, 512, 256),
url = "mimimi.wasm",
body,
module,
chapter: startChapter = "mimimi_test",
} = {}) =>
{
let memcpy = (a, b, n) =>
{
new Uint8Array(memory.buffer, a, n).set(new Uint8Array(memory.buffer, b, n))
return a
}
let memmove = (a, b, n) =>
{
new Uint8Array(memory.buffer, a, n).set(new Uint8Array(memory.buffer, b, n).slice())
return a
}
let memset = (a, b, n) =>
{
new Uint8Array(memory.buffer, a, n).set(Array(n).fill(b))
return a
}
let memcmp = (a, b, n) =>
{
a = new Uint8Array(memory.buffer, a, n)
b = new Uint8Array(memory.buffer, b, n)
for (let i = 0 ; i < n ; i++)
if (a[i] !== b[i]) return a[i] - b[i]
return 0
}
let coroutineYield = coroutineBuffer =>
{
let struct = getView().getUint32(coroutineBuffer + 16, true)
if (asyncify_get_state() === 2)
asyncify_stop_rewind()
else
asyncify_start_unwind(struct)
}
let coroutineContinue = coroutineBuffer =>
{
let start = getView().getUint32(coroutineBuffer + 0, true)
let data = getView().getUint32(coroutineBuffer + 12, true)
let struct = getView().getUint32(coroutineBuffer + 16, true)
let behavior = getView().getUint32(coroutineBuffer + 20, true)
let state = getView().getUint32(coroutineBuffer + 24, true)
if (state !== 0)
asyncify_start_rewind(struct),
table.get(start)()
else
getView().setUint32(coroutineBuffer + 24, 1, true),
table.get(start)(behavior, data)
if (asyncify_get_state() === 1)
asyncify_stop_unwind()
else
finishCoroutine(coroutineBuffer)
}
let finishCoroutine = coroutineBuffer =>
{
let finish = getView().getUint32(coroutineBuffer + 4, true)
let buffer = getView().getUint32(coroutineBuffer + 8, true)
let data = getView().getUint32(coroutineBuffer + 12, true)
let struct = getView().getUint32(coroutineBuffer + 16, true)
let behavior = getView().getUint32(coroutineBuffer + 20, true)
getView().setUint32(coroutineBuffer + 24, 2, true)
table.get(finish)(data)
mimimi_wasm_deallocate(0, buffer)
mimimi_wasm_deallocate(0, struct)
mimimi_wasm_deallocate(0, behavior)
}
let finishCoroutine2 = coroutineBuffer =>
{
let state = getView().getUint32(coroutineBuffer + 24, true)
if (state !== 2) finishCoroutine(coroutineBuffer)
let coroutine = getView().getUint32(coroutineBuffer + 28, true)
mimimi_wasm_deallocate(0, coroutine)
mimimi_wasm_deallocate(0, coroutineBuffer)
}
let createCoroutine = (start, finish, data) =>
{
let buffer = mimimi_wasm_allocate(0, 8192)
let struct = mimimi_wasm_allocate(0, 8)
getView().setUint32(struct + 0, buffer, true)
getView().setUint32(struct + 4, buffer + 8192, true)
let coroutineBuffer = mimimi_wasm_allocate(0, 32)
getView().setUint32(coroutineBuffer + 0, start, true)
getView().setUint32(coroutineBuffer + 4, finish, true)
getView().setUint32(coroutineBuffer + 8, buffer, true)
getView().setUint32(coroutineBuffer + 12, data, true)
getView().setUint32(coroutineBuffer + 16, struct, true)
getView().setUint32(coroutineBuffer + 24, 0, true)
let behavior = mimimi_wasm_allocate(0, 12)
getView().setUint32(behavior + 0, yieldIndex, true)
getView().setUint32(behavior + 4, finishIndex, true)
getView().setUint32(behavior + 8, coroutineBuffer, true)
getView().setUint32(coroutineBuffer + 20, behavior, true)
let coroutine = mimimi_wasm_allocate(0, 12)
getView().setUint32(coroutine + 0, continueIndex, true)
getView().setUint32(coroutine + 4, finishIndex2, true)
getView().setUint32(coroutine + 8, coroutineBuffer, true)
getView().setUint32(coroutineBuffer + 28, coroutine, true)
return coroutine
}
let cache = new Map()
let pending = []
let schedule = f =>
{
if (pending.length === 0)
f()
else
pending.push(f)
}
let rch = [0x11, 0x44, 0x77, 0x99, 0xCC, 0xFF]
let gch = [0x11, 0x33, 0x55, 0x77, 0x99, 0xBB, 0xDD, 0xFF]
let bch = [0x22, 0x55, 0x88, 0xBB, 0xEE]
let texture = (data0, asset) =>
{
let view = getView()
let width = view.getUint32(asset, true)
let height = view.getUint32(asset + 4, true)
let colors = view.getUint32(asset + 8, true)
if (width === 0 || height === 0)
{
cache.set(asset, null)
return null
}
let image = new ImageData(width, height)
let {data} = image
for (let x = 0 ; x < width ; x++)
for (let y = 0 ; y < height ; y++)
{
let o = x + y * width
let color = view.getUint8(colors + o)
if (color === 0) continue
if (color < 0x10)
{
let ch = color * 0x11
data[o * 4 + 0] = ch
data[o * 4 + 1] = ch
data[o * 4 + 2] = ch
data[o * 4 + 3] = 0xFF
}
else
{
color -= 0x10
let r = rch[Math.floor(color / 40) % 6]
let g = gch[Math.floor(color / 5) % 8]
let b = bch[Math.floor(color / 1) % 5]
data[o * 4 + 0] = r
data[o * 4 + 1] = g
data[o * 4 + 2] = b
data[o * 4 + 3] = 0xFF
}
}
pending.push(async () => cache.set(asset, await createImageBitmap(image)))
return asset
}
let stamp = (data, x, y, asset) => schedule(() => context.drawImage(cache.get(asset), x, y))
let invalidate = (data, asset) => schedule(() => cache.delete(asset))
let imports =
{
env:
{
memcpy, memmove, memset, memcmp,
mimimi_wasm_javascript_texture: texture,
mimimi_wasm_javascript_invalidate: invalidate,
mimimi_wasm_javascript_stamp: stamp,
mimimi_wasm_javascript_create_coroutine: createCoroutine,
mimimi_wasm_javascript_yield: coroutineYield,
mimimi_wasm_javascript_continue: coroutineContinue,
mimimi_wasm_javascript_finish_coroutine: finishCoroutine,
mimimi_wasm_javascript_finish_coroutine2: finishCoroutine2,
...extraImports,
}
}
if (!module && !body) body = await fetch(url)
let exports
if (module)
{
let {instance} = await WebAssembly.instantiate(module, imports)
exports = instance.exports
}
else if (body)
{
try { exports = (await WebAssembly.instantiateStreaming(body, imports)).instance.exports }
catch (e) { exports = (await WebAssembly.instantiate(await body.arrayBuffer(), imports)).instance.exports }
}
let {memory, __indirect_function_table: table} = exports
let {mimimi_wasm_allocator, mimimi_wasm_allocate, mimimi_wasm_deallocate} = exports
let {mimimi_wasm_texture, mimimi_wasm_invalidate, mimimi_wasm_stamp} = exports
let {mimimi_wasm_yield, mimimi_wasm_continue} = exports
let {mimimi_wasm_finish_coroutine, mimimi_wasm_finish_coroutine2} = exports
let {mimimi_wasm_coroutines, asyncify_get_state} = exports
let {asyncify_start_rewind, asyncify_stop_rewind} = exports
let {asyncify_start_unwind, asyncify_stop_unwind} = exports
let {[startChapter]: start} = exports
let getView = () => new DataView(memory.buffer)
let f = table.grow(7)
table.set(f + 0, mimimi_wasm_texture)
table.set(f + 1, mimimi_wasm_invalidate)
table.set(f + 2, mimimi_wasm_stamp)
if (mimimi_wasm_yield)
{
table.set(f + 3, mimimi_wasm_yield)
table.set(f + 4, mimimi_wasm_continue)
table.set(f + 5, mimimi_wasm_finish_coroutine)
table.set(f + 6, mimimi_wasm_finish_coroutine2)
}
let yieldIndex = f + 3
let continueIndex = f + 4
let finishIndex = f + 5
let finishIndex2 = f + 6
let context = canvas.getContext("2d")
let size = mimimi_wasm_allocate(0, 8)
getView().setUint32(size + 0, canvas.width, true)
getView().setUint32(size + 4, canvas.height, true)
let allocator = getView().getUint32(global(mimimi_wasm_allocator), true)
let engine = mimimi_wasm_allocate(0, 16)
getView().setUint32(engine + 4, f + 0, true)
getView().setUint32(engine + 8, f + 1, true)
getView().setUint32(engine + 12, f + 2, true)
getView().setUint32(engine + 16, size, true)
getView().setUint32(engine + 20, allocator, true)
if (mimimi_wasm_coroutines)
{
let coroutines = getView().getUint32(global(mimimi_wasm_coroutines), true)
getView().setUint32(engine + 24, coroutines, true)
}
let chapter = start(engine)
let behavior = getView().getUint32(chapter, true)
let keys = chapter + 4
let inputs = {ArrowLeft: 1, ArrowRight: 2, KeyA: 1, KeyD: 2}
let down = 0
canvas.addEventListener("keydown", ({code}) =>
{
let v = inputs[code]
if (v) down = down | v
})
canvas.addEventListener("keyup", ({code}) =>
{
let v = inputs[code]
if (v) down = down & ~v
down %= 0x100
})
let tap = 0
canvas.addEventListener("pointerdown", ({target, offsetX, button}) =>
{
if (button !== 0) return
if (offsetX < target.offsetWidth / 2)
tap = tap | 1
else
tap = tap | 2
})
canvas.addEventListener("pointerup", () => tap = 0)
let past = performance.now()
let step = async () =>
{
if (pending.length !== 0)
{
for (let f of pending) await f()
pending = []
}
getView().setUint8(keys, down|tap)
let f = table.get(getView().getUint32(behavior, true))(getView().getUint32(behavior + 8, true))
let now = performance.now()
setTimeout(step, (100/3) + past - now)
past = now
}
step()
return canvas
}