Mirai's Miscellaneous Misadventures

M44 / index.html

1<!-- license: AGPLv3 or later -->
2<!-- copyright 2023 zamfofex -->
3
4<!doctype html>
5<html lang="en">
6<meta charset="utf-8">
7
8<title> Random Character Adventures </title>
9
10<style>
11
12*, ::before, ::after
13{
14	touch-action: none;
15}
16
17html, body
18{
19	width: 100%;
20	height: 100%;
21	display: flex;
22}
23
24body
25{
26	margin: 0;
27	background: #000;
28}
29
30canvas
31{
32	width: 100%;
33	height: 100%;
34	image-rendering: crisp-edges;
35	image-rendering: pixelated;
36	object-fit: contain;
37	outline: none;
38}
39
40</style>
41
42<script type="module">
43
44let canvas = document.querySelector("canvas")
45let context = canvas.getContext("2d")
46
47let global = v =>
48{
49	if (typeof v === "number") return v
50	return v.value
51}
52
53let cache = new Map()
54let pending = []
55
56let schedule = f =>
57{
58	if (pending.length === 0)
59		f()
60	else
61		pending.push(f)
62}
63
64let pointer = a =>
65{
66	if (a < 0) a = 0x10000 + a
67	return a
68}
69
70let rch = [0x11, 0x44, 0x77, 0x99, 0xCC, 0xFF]
71let gch = [0x11, 0x33, 0x55, 0x77, 0x99, 0xBB, 0xDD, 0xFF]
72let bch = [0x22, 0x55, 0x88, 0xBB, 0xEE]
73
74let createTexture = getView => (data0, asset) =>
75{
76	let view = getView()
77	asset = pointer(asset)
78	
79	let width = view.getUint32(asset, true)
80	let height = view.getUint32(asset + 4, true)
81	let colors = view.getUint32(asset + 8, true)
82	
83	if (width === 0 || height === 0)
84	{
85		cache.delete(asset)
86		return
87	}
88	
89	let image = new ImageData(width, height)
90	let {data} = image
91	
92	for (let x = 0 ; x < width ; x++)
93	for (let y = 0 ; y < height ; y++)
94	{
95		let o = x + y * width
96		let color = view.getUint8(colors + o)
97		
98		if (color === 0) continue
99		
100		if (color < 0x10)
101		{
102			let ch = color * 0x11
103			data[o * 4 + 0] = ch
104			data[o * 4 + 1] = ch
105			data[o * 4 + 2] = ch
106			data[o * 4 + 3] = 0xFF
107		}
108		else
109		{
110			color -= 0x10
111			let r = rch[Math.floor(color / 40) % 6]
112			let g = gch[Math.floor(color / 5) % 8]
113			let b = bch[Math.floor(color / 1) % 5]
114			
115			data[o * 4 + 0] = r
116			data[o * 4 + 1] = g
117			data[o * 4 + 2] = b
118			data[o * 4 + 3] = 0xFF
119		}
120	}
121	
122	pending.push(async () => cache.set(asset, await createImageBitmap(image)))
123	return asset
124}
125
126let stamp = (data, x, y, asset) => schedule(() => context.drawImage(cache.get(asset), x, y))
127
128let invalidate = (data, asset) => schedule(() => cache.delete(asset))
129
130let main = async () =>
131{
132	let run = (start, tick, size) =>
133	{
134		let chapter = allocate(size)
135		let past = performance.now()
136		table.get(start)(chapter, engine)
137		
138		let step = async () =>
139		{
140			if (pending.length !== 0)
141			{
142				for (let f of pending) await f()
143				pending = []
144			}
145			
146			table.get(tick)(chapter, left, right)
147			
148			let now = performance.now()
149			setTimeout(step, (100 / 3) + past - now)
150			past = now
151		}
152		step()
153	}
154	
155	let memcpy = (a, b, n) =>
156	{
157		new Uint8Array(memory.buffer, pointer(a), n).set(new Uint8Array(memory.buffer, pointer(b), n))
158		return a
159	}
160	
161	let memset = (a, b, n) =>
162	{
163		new Uint8Array(memory.buffer, pointer(a), n).set(Array(n).fill(pointer(b)))
164		return a
165	}
166	
167	let memcmp = (a, b, n) =>
168	{
169		a = new Uint8Array(memory.buffer, pointer(a), n)
170		b = new Uint8Array(memory.buffer, pointer(b), n)
171		for (let i = 0 ; i < n ; i++)
172		{
173			if (a[i] !== b[i])
174				return a[i] - b[i]
175		}
176		return 0
177	}
178	
179	let getView = () => new DataView(memory.buffer)
180	
181	let imports =
182	{
183		env:
184		{
185			memcpy, memmove: memcpy, memset, memcmp,
186			mimimi_wasm_javascript_texture: createTexture(getView),
187			mimimi_wasm_javascript_invalidate: invalidate,
188			mimimi_wasm_javascript_stamp: stamp,
189			mimimi_wasm: run,
190		}
191	}
192	
193	let body = await fetch("mimimi-loader.wasm")
194	
195	let exports
196	try { exports = (await WebAssembly.instantiateStreaming(body, imports)).instance.exports }
197	catch (e) { exports = (await WebAssembly.instantiate(await body.arrayBuffer(), imports)).instance.exports }
198	
199	let {memory, __indirect_function_table: table, __heap_base: base} = exports
200	let {mimimi_wasm_texture, mimimi_wasm_invalidate, mimimi_wasm_stamp} = exports
201	let {main} = exports
202	
203	let f = table.grow(3)
204	table.set(f + 0, mimimi_wasm_texture)
205	table.set(f + 1, mimimi_wasm_invalidate)
206	table.set(f + 2, mimimi_wasm_stamp)
207	
208	let offset = global(base)
209	let allocate = size =>
210	{
211		let result = offset
212		let available = memory.buffer.byteLength - offset
213		if (available < size) memory.grow(Math.ceil((size - available) / 0x10000))
214		offset += size
215		return result
216	}
217	
218	let engine = allocate(24)
219	let view = getView()
220	view.setUint32(engine + 4, f + 0, true)
221	view.setUint32(engine + 8, f + 1, true)
222	view.setUint32(engine + 12, f + 2, true)
223	view.setUint32(engine + 16, 512, true)
224	view.setUint32(engine + 20, 256, true)
225	
226	let left = 0
227	let right = 0
228	
229	addEventListener("keydown", ({code}) =>
230	{
231		if (code === "ArrowLeft") left = 1
232		if (code === "ArrowRight") right = 1
233		if (code === "KeyA") left = 1
234		if (code === "KeyD") right = 1
235	})
236	
237	addEventListener("keyup", ({code}) =>
238	{
239		if (code === "ArrowLeft") left = 0
240		if (code === "ArrowRight") right = 0
241		if (code === "KeyA") left = 0
242		if (code === "KeyD") right = 0
243	})
244	
245	addEventListener("pointerdown", ({offsetX, button}) =>
246	{
247		if (button !== 0) return
248		
249		if (offsetX < innerWidth / 2)
250			left = 1
251		else
252			right = 1
253	})
254	
255	addEventListener("pointerup", () => { left = 0 ; right = 0 })
256	
257	main()
258}
259
260main()
261
262</script>
263
264<canvas width="512" height="256"></canvas>