Mirai's Miscellaneous Misadventures

M41 / main.js

// copyright 2023 zamfofex
// license: AGPLv3 or later

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 (
{
	window = globalThis.window,
	document = window.document,
	canvas = createCanvas(document, 512, 256),
	url = "mimimi.wasm",
	body,
	module,
} = {}) =>
{
	let left = 0
	let right = 0
	
	canvas.addEventListener("keydown", ({code}) =>
	{
		if (code === "ArrowLeft") left = 1
		if (code === "ArrowRight") right = 1
		if (code === "KeyA") left = 1
		if (code === "KeyD") right = 1
	})
	
	canvas.addEventListener("keyup", ({code}) =>
	{
		if (code === "ArrowLeft") left = 0
		if (code === "ArrowRight") right = 0
		if (code === "KeyA") left = 0
		if (code === "KeyD") right = 0
	})
	
	let leftT = 0
	let rightT = 0
	
	canvas.addEventListener("pointerdown", ({target, offsetX, button}) =>
	{
		if (button !== 0) return
		
		if (offsetX < target.offsetWidth / 2)
			leftT = 1
		else
			rightT = 1
	})
	
	canvas.addEventListener("pointerup", () => { leftT = 0 ; rightT = 0 })
	
	let _memset = (a, b, n) => new Uint8Array(memory.buffer, a, n).set(Array(n).fill(b))
	let _memcpy = (a, b, n) => new Uint8Array(memory.buffer, a, n).set(new Uint8Array(memory.buffer, b, n))
	
	let c =
	{
		_memset, _memcpy,
		mimimi_wasm_yield: data =>
		{
			if (exports.asyncify_get_state() === 2)
			{
				exports.asyncify_stop_rewind()
			}
			else
			{
				let view = new DataView(memory.buffer)
				let size = stackStart - stackPointer.value
				view.setUint32(data + 4, size, true)
				_memcpy(view.getUint32(data + 8, true), stackPointer.value, size)
				exports.asyncify_start_unwind(data + 12)
			}
		},
		mimimi_wasm_continue: data =>
		{
			let view = new DataView(memory.buffer)
			
			if (view.getUint32(data, true) === 0)
			{
				stackPointer.value = stackStart
				view.setUint32(data, 1, true)
				exports.mimimi_wasm_start_coroutine(data)
			}
			else
			{
				let size = view.getUint32(data + 4, true)
				_memcpy(stackStart - size, view.getUint32(data + 8, true), size)
				exports.asyncify_start_rewind(data + 12)
				exports.mimimi_wasm_start_coroutine()
			}
			
			if (exports.asyncify_get_state() === 1)
				exports.asyncify_stop_unwind()
			else
				exports.mimimi_wasm_finish_coroutine(data)
		},
	}
	
	let noop = () => { }
	let keys = ["write", "args_sizes_get", "lseek", "_tmpfile", "close", "unlink", "args_get", "_getcwd", "open", "read", "proc_exit"]
	for (let key of keys) c[key] = noop
	
	if (!module && !body) body = await fetch(url)
	
	let exports
	if (module)
	{
		let {instance} = await WebAssembly.instantiate(module, {c})
		exports = instance.exports
	}
	else if (body)
	{
		let result
		
		try { result = await WebAssembly.instantiateStreaming(body, {c}) }
		catch (e) { result = await WebAssembly.instantiate(await body.arrayBuffer(), {c}) }
		
		exports = result.instance.exports
	}
	
	let {mimimi_wasm_behave, main} = exports
	let {memory, __stack_pointer: stackPointer} = exports
	main()
	
	let stackStart = stackPointer && stackPointer.value
	
	let context = canvas.getContext("2d")
	
	let image = new ImageData(canvas.width, canvas.height)
	
	let past = performance.now()
	let step = () =>
	{
		let data = mimimi_wasm_behave(left|leftT, right|rightT)
		image.data.set(new Uint8Array(memory.buffer, data, canvas.width * canvas.height * 4))
		context.putImageData(image, 0, 0)
		
		let now = performance.now()
		setTimeout(step, (100 / 3) + past - now)
		past = now
	}
	step()
	
	return canvas
}