diff --git a/build/wasmPlugin.ts b/build/wasmPlugin.ts index 1a057e1..5e38fbc 100644 --- a/build/wasmPlugin.ts +++ b/build/wasmPlugin.ts @@ -60,15 +60,59 @@ const getCompiler = async (): Promise => { } async function instantiate(url: string) { + if (typeof WebAssembly.Function !== "function") { + const typeCodes = { + i32: 0x7f, + i64: 0x7e, + f32: 0x7d, + f64: 0x7c + }; + + type tc = keyof typeof typeCodes; + + // @ts-ignore + WebAssembly.Function = function ({ + parameters = [], + results = [], + }: { parameters: tc[], results: tc[] }, func: Function) { + const + resultAmount = results.length, + parameterAmount = parameters.length; + + const bytes = new Uint8Array([ + // Headers (Magic and Version) + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, + + // Type section + 0x01, (4 + parameterAmount + resultAmount), 0x01, 0x60, // Section ID, Type section length, Count, Function + parameterAmount, ...parameters.map(p => typeCodes[p]), // Parameter types + resultAmount, ...results.map(r => typeCodes[r]), // Result types + + // Import and Exports + 0x02, 0x07, 0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00, + 0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00 + ]); + + // Compile and instantiate + const module = new WebAssembly.Module(bytes); + const instance = new WebAssembly.Instance(module, { + e: { + f: func + } + }); + return instance.exports.f; + }; + } const memory = new WebAssembly.Memory({ initial: 32, }); + const table = new WebAssembly.Table({ initial: 1, element: 'anyfunc' }); let data = new DataView(memory.buffer); const decoder = new TextDecoder(); let buf = ''; let errBuf = ''; const { instance } = await WebAssembly.instantiateStreaming(fetch(url), { - env: { memory }, + env: { memory, __indirect_function_table: table }, wasi_snapshot_preview1: { random_get: (ptr: number, length: number) => { for (let i = 0; i < length; i++) { @@ -115,6 +159,7 @@ async function instantiate(url: string) { return { ...instance.exports, memory, + table, get data() { return data; }, }; } @@ -178,6 +223,7 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => { '--lto-O3', '--no-entry', '--import-memory', + '--import-table', ].map(f => `-Wl,${f}`); const linkResult = await $`${cc.cc} ${flags} -std=gnu23 ${linkFlags} -lstdc++ -nostartfiles -o ${wasmPath} ${objPath} ${stdlib}`; diff --git a/src/common/physics/engine.c b/src/common/physics/engine.c index f5ac99c..486bc9f 100644 --- a/src/common/physics/engine.c +++ b/src/common/physics/engine.c @@ -10,7 +10,7 @@ #define MAX_BODIES 1000 -////////// Structs +////////// Types typedef struct rigid_body_t { // Public @@ -33,6 +33,8 @@ typedef struct rigid_body_t { struct rigid_body_t* next; } rigid_body; +typedef void (*rigid_body_collision_callback_t)(rigid_body* a, rigid_body* b); + ////////// Prototypes void rigid_body_resolve_collision(rigid_body* rb); @@ -43,6 +45,7 @@ float point_to_line_dist(vec2 point, vec2 line_point, vec2 line_norm); static rigid_body* rigid_body_head = NULL; static rigid_body* rigid_body_tail = NULL; +static rigid_body_collision_callback_t rigid_body_collision_callback = NULL; ////////// Functions @@ -139,6 +142,10 @@ EXPORT(rigid_body_add_force) void rigid_body_add_force(rigid_body* rb, float fx, rb->force.y += fy; } +EXPORT(rigid_body_set_collision_callback) void rigid_body_set_collision_callback(rigid_body_collision_callback_t callback) { + rigid_body_collision_callback = callback; +} + void rigid_body_resolve_collision(rigid_body* rb) { rigid_body* current = rb->next; int is_static = isinf(rb->mass); @@ -161,6 +168,10 @@ void rigid_body_handle_collision(rigid_body* rb1, rigid_body* rb2) { return; } + if (rigid_body_collision_callback) { + rigid_body_collision_callback(rb1, rb2); + } + vec2 n = vec2_normalize(d); float factor = (isinf(rb1->mass) || isinf(rb2->mass)) ? 1 : 0.5; vec2 n_overlap = vec2_mul(n, overlap * factor); @@ -206,6 +217,10 @@ void rigid_body_handle_collision(rigid_body* rb1, rigid_body* rb2) { return; } + if (rigid_body_collision_callback) { + rigid_body_collision_callback(rb1, rb2); + } + vec2 n = rb1->plane.normal; vec2 n_overlap = vec2_mul(n, overlap); diff --git a/src/common/physics/index.ts b/src/common/physics/index.ts index 8f32d2b..df153e8 100644 --- a/src/common/physics/index.ts +++ b/src/common/physics/index.ts @@ -10,6 +10,7 @@ namespace Physics { bodies.add(body); return body; } + export function newPlane(x: number, y: number, nx: number, ny: number): number { const body = E.rigid_body_new_plane(x, y, nx, ny); bodies.add(body); @@ -60,6 +61,11 @@ namespace Physics { export function getBodies() { return Array.from(bodies).map(getBody); } + + export function setCollisionCallback(cb: (a: number, b: number) => void) { + const cbPtr = E.table.grow(1, new WebAssembly.Function({ parameters: ['i32', 'i32'], results: [] }, cb)); + E.rigid_body_set_collision_callback(cbPtr); + } } export default Physics; diff --git a/src/games/dropballs/index.ts b/src/games/dropballs/index.ts index 25aa604..1d2e7fe 100644 --- a/src/games/dropballs/index.ts +++ b/src/games/dropballs/index.ts @@ -1,8 +1,19 @@ import { createCanvas } from "@common/display/canvas"; -import { getRealPoint } from "@common/dom"; import { gameLoop } from "@common/game"; import Physics from "@common/physics"; + +let lastTick = performance.now(); +function update() { + const dt = (performance.now() - lastTick) / 1000; + if (dt < 1) { + Physics.update(dt); + } + lastTick = performance.now(); + setTimeout(update, 5); +} + + const setup = () => { const canvas = createCanvas(640, 480); const ctx = canvas.getContext('2d'); @@ -10,23 +21,13 @@ const setup = () => { throw new Error('Failed to get canvas context'); } + ctx.imageSmoothingEnabled = false; canvas.addEventListener('contextmenu', e => { e.stopPropagation(); e.preventDefault(); - }) - canvas.addEventListener('mousedown', (event) => { - event.stopPropagation(); - event.preventDefault(); - return false; }); - canvas.addEventListener('mouseup', (event) => { - event.stopPropagation(); - event.preventDefault(); - const { x, y } = getRealPoint(canvas, event); - console.log(event.button); - const mass = event.button === 0 ? Math.random() : Number.POSITIVE_INFINITY; - const r = event.button === 0 ? 16 : 4; - Physics.newCircle(x, y, r, mass); + canvas.addEventListener('mouseup', () => { + Physics.newCircle(canvas.width / 2 + Math.random() - 0.5, 50, 10, 1); return false; }); @@ -35,6 +36,8 @@ const setup = () => { Physics.newPlane(0, canvas.height, 0, -1); Physics.newPlane(canvas.width, 0, -1, 0); + update(); + return { canvas, ctx }; } @@ -43,15 +46,35 @@ const frame = (dt: number, state: ReturnType) => { Physics.update(dt); const { ctx, canvas } = state; - ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = `black`; + ctx.fillRect(0, 0, canvas.width, canvas.height); for (const { id, x, y, radius } of Physics.getBodies()) { ctx.fillStyle = `hsl(${id % 360}, 100%, 50%)`; if (radius) { - ctx.beginPath(); - ctx.arc(x, y, radius, 0, Math.PI * 2); - ctx.fill(); + fillCircle(ctx, x, y, radius); } } } +function fillCircle(ctx: CanvasRenderingContext2D, xc: number, yc: number, r: number) { + // because default circle fill is antialiased + // https://stackoverflow.com/a/45745753 + xc = xc | 0; + yc = yc | 0; + r = r | 0; + var x = r, y = 0, cd = 0; + + // middle line + ctx.fillRect(xc - x, yc, r << 1, 1); + + while (x > y) { + cd -= (--x) - (++y); + if (cd < 0) cd += x++; + ctx.fillRect(xc - y, yc - x, y << 1, 1); // upper 1/4 + ctx.fillRect(xc - x, yc - y, x << 1, 1); // upper 2/4 + ctx.fillRect(xc - x, yc + y, x << 1, 1); // lower 3/4 + ctx.fillRect(xc - y, yc + x, y << 1, 1); // lower 4/4 + } +} + export default gameLoop(setup, frame); \ No newline at end of file diff --git a/src/types.d.ts b/src/types.d.ts index a361b4b..9449f4d 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -3,6 +3,13 @@ type Rect = [number, number, number, number]; type RunGame = () => Promise; +declare namespace WebAssembly { + type tc = 'i32' | 'i64' | 'f32' | 'f64'; + export class Function { + constructor(args: { parameters: tc[], results: tc[] }, func: Function); + } +} + declare module '*.module.css' { const classes: { [key: string]: string }; export default classes; @@ -43,6 +50,7 @@ declare module "*.wasm" { declare module "*.c" { const instance: { memory: WebAssembly.Memory; + table: WebAssembly.Table; data: DataView; [x: string]: (...args: any) => any; @@ -52,6 +60,7 @@ declare module "*.c" { declare module "*.cpp" { const instance: { memory: WebAssembly.Memory; + table: WebAssembly.Table; data: DataView; [x: string]: (...args: any) => any;