Import C/CPP files
This commit is contained in:
parent
adb8b4549e
commit
665892a798
10
README.md
10
README.md
|
|
@ -96,6 +96,16 @@ bun run build
|
||||||
- Example: `src/games/playground/awoo.wasm.ts`
|
- Example: `src/games/playground/awoo.wasm.ts`
|
||||||
- Triggered by file name `*.wasm.ts`
|
- Triggered by file name `*.wasm.ts`
|
||||||
|
|
||||||
|
- Import `*.c`/`*.cpp` files (compile to wasm on the fly)
|
||||||
|
- Example: `src/games/life/life.c`
|
||||||
|
- To use, `clang` and wasm toochain should be present in the system
|
||||||
|
```bash
|
||||||
|
sudo apt install clang lld wabt
|
||||||
|
```
|
||||||
|
- Supports only function exports & `memory`
|
||||||
|
- exported all non-static functions
|
||||||
|
- no stdlib
|
||||||
|
|
||||||
|
|
||||||
## Publishing
|
## Publishing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { plugin, type BunPlugin } from "bun";
|
import { plugin, $, type BunPlugin } from "bun";
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import asc from 'assemblyscript/asc';
|
import asc from 'assemblyscript/asc';
|
||||||
|
|
||||||
|
|
@ -11,40 +11,58 @@ const wasmPlugin = ({ production, portable }: WasmLoaderConfig = {}): BunPlugin
|
||||||
const p: BunPlugin = {
|
const p: BunPlugin = {
|
||||||
name: "WASM loader",
|
name: "WASM loader",
|
||||||
async setup(build) {
|
async setup(build) {
|
||||||
build.onLoad({ filter: /\.wasm\.ts$/ }, async (args) => {
|
build.onLoad({ filter: /\.(c(pp)?|wasm(\.ts)?)$/ }, async (args) => {
|
||||||
if (portable) {
|
let wasmPath = path.resolve(import.meta.dir, '..', '..', 'dist', 'tmp.wasm');
|
||||||
const contents = await Bun.file(args.path).text();
|
let jsContent: string = `
|
||||||
return {
|
async function instantiate(url) {
|
||||||
contents: `import "assemblyscript/std/portable/index.js";\n${contents}`,
|
const { instance } = await WebAssembly.instantiateStreaming(fetch(url));
|
||||||
loader: 'tsx',
|
return {
|
||||||
|
...instance.exports,
|
||||||
|
data: new DataView(instance.exports.memory.buffer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const module = await instantiate(new URL("tmp.wasm", import.meta.url));
|
||||||
|
export default module;
|
||||||
|
`;
|
||||||
|
if (args.path.endsWith('.ts')) {
|
||||||
|
if (portable) {
|
||||||
|
const contents = await Bun.file(args.path).text();
|
||||||
|
return {
|
||||||
|
contents: `import "assemblyscript/std/portable/index.js";\n${contents}`,
|
||||||
|
loader: 'tsx',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const jsPath = wasmPath.replace(/\.wasm$/, '.js');
|
||||||
|
const ascArgs = [
|
||||||
|
args.path,
|
||||||
|
'--outFile', wasmPath,
|
||||||
|
'--bindings', 'esm',
|
||||||
|
'-Ospeed'
|
||||||
|
];
|
||||||
|
|
||||||
|
const { error, stderr } = await asc.main(ascArgs);
|
||||||
|
if (error) {
|
||||||
|
console.error(stderr.toString(), error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
jsContent = await Bun.file(jsPath).text();
|
||||||
|
} else if (args.path.endsWith('.wasm')) {
|
||||||
|
wasmPath = args.path;
|
||||||
|
} else {
|
||||||
|
const result = await $`clang --target=wasm32 -O3 --no-standard-libraries -Wl,--export-all -Wl,--no-entry -fno-builtin -o ${wasmPath} ${args.path}`;
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
throw new Error('Compile failed, check output');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const wasmPath = path.resolve(import.meta.dir, '..', '..', 'dist', 'tmp.wasm');
|
|
||||||
const jsPath = wasmPath.replace(/\.wasm$/, '.js');
|
|
||||||
const ascArgs = [
|
|
||||||
args.path,
|
|
||||||
'--outFile', wasmPath,
|
|
||||||
'--bindings', 'esm',
|
|
||||||
];
|
|
||||||
ascArgs.push(production ? '--optimize' : '--debug');
|
|
||||||
|
|
||||||
const { error, stderr } = await asc.main(ascArgs);
|
const wasmContent = await Bun.file(wasmPath).arrayBuffer();
|
||||||
|
const wasmBuffer = Buffer.from(wasmContent).toString('base64');
|
||||||
|
const wasmURL = `data:application/wasm;base64,${wasmBuffer}`;
|
||||||
|
|
||||||
if (error) {
|
return {
|
||||||
console.error(stderr.toString(), error.message);
|
loader: 'js',
|
||||||
throw error;
|
contents: jsContent.replace(/new URL\([^)]*\)/, `new URL(${JSON.stringify(wasmURL)})`),
|
||||||
} else {
|
};
|
||||||
const jsContent = await Bun.file(jsPath).text();
|
|
||||||
const wasmContent = await Bun.file(wasmPath).arrayBuffer();
|
|
||||||
const wasmBuffer = Buffer.from(wasmContent).toString('base64');
|
|
||||||
const wasmURL = `data:application/wasm;base64,${wasmBuffer}`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
loader: 'js',
|
|
||||||
contents: jsContent
|
|
||||||
.replace(/new URL\([^)]*\)/, `new URL(${JSON.stringify(wasmURL)})`),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import life from "./life.c";
|
||||||
|
|
||||||
|
const width = life.getWidth();
|
||||||
|
const height = life.getHeight();
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const context = canvas.getContext('2d')!;
|
||||||
|
const imageData = context.createImageData(width, height);
|
||||||
|
|
||||||
|
const initField = life.initField as CallableFunction;
|
||||||
|
const step = life.step as CallableFunction;
|
||||||
|
const pixels = new Uint8Array(life.memory.buffer, life.getPixels(), width * height * 4);
|
||||||
|
|
||||||
|
export default function main() {
|
||||||
|
document.body.append(canvas);
|
||||||
|
initField(Date.now());
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
canvas.style.height = '100%';
|
||||||
|
canvas.style.imageRendering = 'pixelated';
|
||||||
|
|
||||||
|
document.body.style.display = 'flex';
|
||||||
|
document.body.style.justifyContent = 'center';
|
||||||
|
|
||||||
|
console.log(life, pixels.length);
|
||||||
|
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sum = 0;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function loop() {
|
||||||
|
const start = performance.now();
|
||||||
|
step();
|
||||||
|
const end = performance.now();
|
||||||
|
|
||||||
|
sum += end - start;
|
||||||
|
count++;
|
||||||
|
|
||||||
|
imageData.data.set(pixels);
|
||||||
|
|
||||||
|
context.putImageData(imageData, 0, 0);
|
||||||
|
context.clearRect(0, 0, 35, 15);
|
||||||
|
context.fillText(`${(sum / count).toFixed(1)} ms`, 2, 10);
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define width 512
|
||||||
|
#define height 512
|
||||||
|
|
||||||
|
static uint8_t field[width * height];
|
||||||
|
static uint8_t nextField[width * height];
|
||||||
|
static uint8_t pixels[width * height * 4];
|
||||||
|
|
||||||
|
void* malloc(uint32_t n);
|
||||||
|
static uint8_t rand8(void);
|
||||||
|
static int countNeighbours(int x, int y);
|
||||||
|
|
||||||
|
int getWidth() { return width; }
|
||||||
|
int getHeight() { return height; }
|
||||||
|
uint8_t* getPixels() { return pixels; }
|
||||||
|
|
||||||
|
void step()
|
||||||
|
{
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
int count = countNeighbours(x, y);
|
||||||
|
uint8_t currentCell = field[x + y * width];
|
||||||
|
int nextCell = currentCell;
|
||||||
|
if (currentCell && count < 2) nextCell = 0;
|
||||||
|
else if (currentCell && count > 3) nextCell = 0;
|
||||||
|
else if (!currentCell && count == 3) nextCell = 1;
|
||||||
|
|
||||||
|
nextField[x + y * width] = nextCell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < width * height; i++) {
|
||||||
|
field[i] = nextField[i];
|
||||||
|
uint8_t px = field[i];
|
||||||
|
|
||||||
|
if (px) {
|
||||||
|
pixels[(i << 2) + 0] = 0;
|
||||||
|
pixels[(i << 2) + 1] = 0;
|
||||||
|
pixels[(i << 2) + 2] = 0;
|
||||||
|
} else {
|
||||||
|
pixels[(i << 2) + 0] = 255;
|
||||||
|
pixels[(i << 2) + 1] = 255;
|
||||||
|
pixels[(i << 2) + 2] = 255;
|
||||||
|
}
|
||||||
|
pixels[(i << 2) + 3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int countNeighbours(int x, int y)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (int i = -1; i <= 1; i++) {
|
||||||
|
for (int j = -1; j <= 1; j++) {
|
||||||
|
if (i == 0 && j == 0) continue;
|
||||||
|
|
||||||
|
int xx = (x + i + width) % width;
|
||||||
|
int yy = (y + j + height) % height;
|
||||||
|
|
||||||
|
if (field[xx + yy * width]) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Random generator implementation from https://stackoverflow.com/a/16761955 */
|
||||||
|
#define STATE_BYTES 7
|
||||||
|
#define MULT 0x13B /* for STATE_BYTES==6 only */
|
||||||
|
#define MULT_LO (MULT & 255)
|
||||||
|
#define MULT_HI (MULT & 256)
|
||||||
|
|
||||||
|
static uint8_t rand_state[STATE_BYTES] = { 0x87, 0xdd, 0xdc, 0x10, 0x35, 0xbc, 0x5c };
|
||||||
|
|
||||||
|
static uint8_t rand8(void)
|
||||||
|
{
|
||||||
|
static uint16_t c = 0x42;
|
||||||
|
static int i = 0;
|
||||||
|
uint16_t t;
|
||||||
|
uint8_t x;
|
||||||
|
|
||||||
|
x = rand_state[i];
|
||||||
|
t = (uint16_t)x * MULT_LO + c;
|
||||||
|
c = t >> 8;
|
||||||
|
#if MULT_HI
|
||||||
|
c += x;
|
||||||
|
#endif
|
||||||
|
x = t & 255;
|
||||||
|
rand_state[i] = x;
|
||||||
|
if (++i >= sizeof(rand_state))
|
||||||
|
i = 0;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initField(uint32_t randomSeed)
|
||||||
|
{
|
||||||
|
*((uint32_t*)&rand_state[STATE_BYTES - sizeof(uint32_t)]) = randomSeed; // Voodoo
|
||||||
|
|
||||||
|
for (int i = 0; i < width * height; i++) {
|
||||||
|
field[i] = rand8() & 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -34,4 +34,31 @@ declare module "*.mp3" {
|
||||||
declare module "*.ogg" {
|
declare module "*.ogg" {
|
||||||
const audio: HTMLAudioElement;
|
const audio: HTMLAudioElement;
|
||||||
export default audio;
|
export default audio;
|
||||||
|
}
|
||||||
|
declare module "*.wasm" {
|
||||||
|
const instance: {
|
||||||
|
memory: WebAssembly.Memory;
|
||||||
|
data: DataView;
|
||||||
|
|
||||||
|
[x: string]: (...args: any) => any;
|
||||||
|
};
|
||||||
|
export default instance;
|
||||||
|
}
|
||||||
|
declare module "*.c" {
|
||||||
|
const instance: {
|
||||||
|
memory: WebAssembly.Memory;
|
||||||
|
data: DataView;
|
||||||
|
|
||||||
|
[x: string]: (...args: any) => any;
|
||||||
|
};
|
||||||
|
export default instance;
|
||||||
|
}
|
||||||
|
declare module "*.cpp" {
|
||||||
|
const instance: {
|
||||||
|
memory: WebAssembly.Memory;
|
||||||
|
data: DataView;
|
||||||
|
|
||||||
|
[x: string]: (...args: any) => any;
|
||||||
|
};
|
||||||
|
export default instance;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue