Mirai's Miscellaneous Misadventures
M30 / 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),
context = canvas.getContext("2d"),
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 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,
...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} = exports
let {mimimi_wasm_texture, mimimi_wasm_invalidate, mimimi_wasm_stamp} = exports
let {[startChapter]: start} = exports
let getView = () => new DataView(memory.buffer)
let f = table.grow(3)
table.set(f + 0, mimimi_wasm_texture)
table.set(f + 1, mimimi_wasm_invalidate)
table.set(f + 2, mimimi_wasm_stamp)
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)
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 = []
}
let view = getView()
view.setUint8(keys, down|tap)
table.get(view.getUint32(behavior, true))(view.getUint32(behavior + 8, true))
let now = performance.now()
setTimeout(step, (100/3) + past - now)
past = now
}
step()
return canvas
}