1
0
Fork 0

Not finished WFC

This commit is contained in:
Pabloader 2024-08-18 08:09:26 +00:00
parent 665892a798
commit 8a9b437ae7
19 changed files with 429 additions and 37 deletions

3
.clang-format Normal file
View File

@ -0,0 +1,3 @@
IndentWidth: 4
ColumnLimit: 240
PointerAlignment: Left

View File

@ -103,8 +103,7 @@ bun run build
sudo apt install clang lld wabt sudo apt install clang lld wabt
``` ```
- Supports only function exports & `memory` - Supports only function exports & `memory`
- exported all non-static functions - No stdlib
- no stdlib
## Publishing ## Publishing

69
build/assets/stdlib.c Normal file
View File

@ -0,0 +1,69 @@
#include <stdlib.h>
#define BLOCK_SIZE (64 * 1024)
#define BLOCK_1MB 16
static uintptr_t bump_pointer = (uintptr_t)&__heap_base;
static uintptr_t heap_end = BLOCK_1MB * BLOCK_SIZE;
IMPORT(grow) void grow(uint32_t blocks);
static void* bump_alloc(uintptr_t n) {
uintptr_t r = bump_pointer;
bump_pointer += n;
while (bump_pointer >= heap_end) {
grow(heap_end / BLOCK_SIZE);
heap_end *= 2;
}
return (void*)r;
}
void* malloc(uintptr_t n) {
return bump_alloc(n);
}
void free(void* p) { (void)p; }
void* memset(void* s, uint8_t c, uint32_t n) {
uint8_t* p = (uint8_t*)s;
while (n--) {
*p++ = c;
}
return s;
}
void* memcpy(void* dest, const void* src, uint32_t n) {
uint8_t* d = (uint8_t*)dest;
const uint8_t* s = (const uint8_t*)src;
while (n--) {
*d++ = *s++;
}
return dest;
}
int memcmp(const void* s1, const void* s2, uint32_t n) {
const uint8_t* p1 = (const uint8_t*)s1;
const uint8_t* p2 = (const uint8_t*)s2;
while (n--) {
if (*p1 != *p2) {
return (*p1 - *p2);
}
p1++;
p2++;
}
return 0; // Memory blocks are equal
}
static uint64_t rand_state;
void srand(uint64_t seed) { rand_state = seed; }
uint64_t rand(void) {
uint64_t z = (rand_state += 0x9e3779b97f4a7c15);
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
return z ^ (z >> 31);
}

19
build/assets/stdlib.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
extern unsigned char __heap_base;
#define IMPORT(name) __attribute__((import_module("env"), import_name(#name)))
#define EXPORT(name) __attribute__((export_name(#name)))
EXPORT(malloc) void* malloc(uintptr_t);
void free(void*);
void* memset(void* s, uint8_t c, uint32_t n);
void* memcpy(void* dest, const void* src, uint32_t n);
int memcmp(const void* s1, const void* s2, uint32_t n);
IMPORT(log) void print_int(int64_t);
EXPORT(__srand) void srand(uint64_t seed);
uint64_t rand(void);

View File

@ -3,7 +3,7 @@ import { $ } from 'bun';
import path from 'path'; import path from 'path';
import fs from 'fs/promises'; import fs from 'fs/promises';
import { buildHTML } from "./html"; import { buildHTML } from "./html";
import inquirer from 'inquirer'; import select from '@inquirer/select';
import { isGame, getGames } from './isGame'; import { isGame, getGames } from './isGame';
const outDir = path.resolve(import.meta.dir, '..', '..', 'dist'); const outDir = path.resolve(import.meta.dir, '..', '..', 'dist');
@ -13,12 +13,10 @@ let game = process.argv[2];
const publish = process.env.PUBLISH_LOCATION; const publish = process.env.PUBLISH_LOCATION;
while (!await isGame(game)) { while (!await isGame(game)) {
const answer = await inquirer.prompt([{ const game = await select({
type: 'list', message: 'Game to build:',
name: 'game', choices: (await getGames()).map(value => ({ value })),
choices: await getGames(), });
}]);
game = answer.game;
} }
const html = await buildHTML(game, true); const html = await buildHTML(game, true);

View File

@ -15,13 +15,34 @@ const wasmPlugin = ({ production, portable }: WasmLoaderConfig = {}): BunPlugin
let wasmPath = path.resolve(import.meta.dir, '..', '..', 'dist', 'tmp.wasm'); let wasmPath = path.resolve(import.meta.dir, '..', '..', 'dist', 'tmp.wasm');
let jsContent: string = ` let jsContent: string = `
async function instantiate(url) { async function instantiate(url) {
const { instance } = await WebAssembly.instantiateStreaming(fetch(url)); const memory = new WebAssembly.Memory({
initial: 16,
});
let data = new DataView(memory.buffer);
const { instance } = await WebAssembly.instantiateStreaming(fetch(url), {
env: {
memory,
log(...args) {
console.log('[wasm]', ...args);
},
grow(blocks) {
if (blocks > 0) {
memory.grow(blocks);
data = new DataView(memory.buffer);
}
}
}
});
return { return {
...instance.exports, ...instance.exports,
data: new DataView(instance.exports.memory.buffer), memory,
get data() { return data; },
}; };
} }
const module = await instantiate(new URL("tmp.wasm", import.meta.url)); const module = await instantiate(new URL($WASM$));
if (typeof module.__srand === 'function') module.__srand(BigInt(Date.now()));
export default module; export default module;
`; `;
if (args.path.endsWith('.ts')) { if (args.path.endsWith('.ts')) {
@ -49,7 +70,11 @@ const wasmPlugin = ({ production, portable }: WasmLoaderConfig = {}): BunPlugin
} else if (args.path.endsWith('.wasm')) { } else if (args.path.endsWith('.wasm')) {
wasmPath = args.path; wasmPath = args.path;
} else { } else {
const result = await $`clang --target=wasm32 -O3 --no-standard-libraries -Wl,--export-all -Wl,--no-entry -fno-builtin -o ${wasmPath} ${args.path}`; const buildAssets = path.resolve(import.meta.dir, 'assets');
const stdlib = `${buildAssets}/stdlib.c`;
const opt = production ? '-O3': '-O0';
const result = await $`clang --target=wasm32 ${opt} -flto -fno-builtin --no-standard-libraries -I ${buildAssets} -Wall -Wextra -Wpedantic -Werror -Wl,--lto-O3 -Wl,--no-entry -Wl,--import-memory -o ${wasmPath} ${args.path} ${stdlib}`;
if (result.exitCode !== 0) { if (result.exitCode !== 0) {
throw new Error('Compile failed, check output'); throw new Error('Compile failed, check output');
} }

BIN
bun.lockb

Binary file not shown.

9
compile_flags.txt Normal file
View File

@ -0,0 +1,9 @@
--target=wasm32
-fno-builtin
--no-standard-libraries
-I
build/assets/
-Wall
-Wextra
-Wpedantic
-Werror

View File

@ -8,6 +8,7 @@
"build": "bun build/build.ts" "build": "bun build/build.ts"
}, },
"dependencies": { "dependencies": {
"@inquirer/select": "2.3.10",
"classnames": "2.5.1", "classnames": "2.5.1",
"preact": "10.22.0" "preact": "10.22.0"
}, },
@ -18,7 +19,6 @@
"assemblyscript": "0.27.29", "assemblyscript": "0.27.29",
"bun-lightningcss": "0.2.0", "bun-lightningcss": "0.2.0",
"html-minifier": "4.0.0", "html-minifier": "4.0.0",
"inquirer": "9.3.4",
"typescript": "5.5.2", "typescript": "5.5.2",
"uglify-js": "3" "uglify-js": "3"
} }

View File

@ -1,5 +1,4 @@
import { render } from "preact"; import { render } from "preact";
import type { Display } from ".";
import { clamp, range } from "@common/utils"; import { clamp, range } from "@common/utils";
import classNames from "classnames"; import classNames from "classnames";
@ -18,7 +17,7 @@ export interface BrickDisplayImage {
height: number; height: number;
} }
export class BrickDisplay implements Display { export class BrickDisplay {
#field: boolean[] = new Array(FIELD_HEIGHT * FIELD_WIDTH); #field: boolean[] = new Array(FIELD_HEIGHT * FIELD_WIDTH);
#miniField: boolean[] = new Array(MINI_FIELD_HEIGHT * MINI_FIELD_WIDTH); #miniField: boolean[] = new Array(MINI_FIELD_HEIGHT * MINI_FIELD_WIDTH);
#score: number = 0; #score: number = 0;

View File

@ -0,0 +1,25 @@
export function createCanvas(width: number, height: number) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.style.height = '100%';
canvas.style.imageRendering = 'pixelated';
document.body.style.display = 'flex';
document.body.style.justifyContent = 'center';
document.body.append(canvas);
return canvas;
}
export function getImageData(image: HTMLImageElement): ImageData {
const canvas = document.createElement('canvas');
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const ctx = canvas.getContext('2d')!;
ctx.drawImage(image, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
return imageData;
}

View File

@ -1,6 +0,0 @@
export interface Display {
init(): void;
update(): void;
}
export { BrickDisplay, type BrickDisplayImage } from './brick';

View File

@ -1,4 +1,4 @@
import { BrickDisplay } from "@common/display"; import { BrickDisplay } from "@common/display/brick";
import { isPressed, updateKeys } from "@common/input"; import { isPressed, updateKeys } from "@common/input";
import spritesheetImage from './assets/spritesheet.png'; import spritesheetImage from './assets/spritesheet.png';

View File

@ -1,9 +1,10 @@
import { createCanvas } from "@common/display/canvas";
import life from "./life.c"; import life from "./life.c";
const width = life.getWidth(); const width = life.getWidth();
const height = life.getHeight(); const height = life.getHeight();
const canvas = document.createElement('canvas'); const canvas = createCanvas(width, height);
const context = canvas.getContext('2d')!; const context = canvas.getContext('2d')!;
const imageData = context.createImageData(width, height); const imageData = context.createImageData(width, height);
@ -12,15 +13,7 @@ const step = life.step as CallableFunction;
const pixels = new Uint8Array(life.memory.buffer, life.getPixels(), width * height * 4); const pixels = new Uint8Array(life.memory.buffer, life.getPixels(), width * height * 4);
export default function main() { export default function main() {
document.body.append(canvas);
initField(Date.now()); 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); console.log(life, pixels.length);

View File

@ -11,11 +11,11 @@ void* malloc(uint32_t n);
static uint8_t rand8(void); static uint8_t rand8(void);
static int countNeighbours(int x, int y); static int countNeighbours(int x, int y);
int getWidth() { return width; } int getWidth(void) { return width; }
int getHeight() { return height; } int getHeight(void) { return height; }
uint8_t* getPixels() { return pixels; } uint8_t* getPixels(void) { return pixels; }
void step() void step(void)
{ {
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
@ -76,7 +76,7 @@ static uint8_t rand_state[STATE_BYTES] = { 0x87, 0xdd, 0xdc, 0x10, 0x35, 0xbc, 0
static uint8_t rand8(void) static uint8_t rand8(void)
{ {
static uint16_t c = 0x42; static uint16_t c = 0x42;
static int i = 0; static uint32_t i = 0;
uint16_t t; uint16_t t;
uint8_t x; uint8_t x;

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

43
src/games/wfc/index.ts Normal file
View File

@ -0,0 +1,43 @@
import wfc from './wfc.c';
import wfcImage from './assets/wfc.png';
import { createCanvas, getImageData } from "@common/display/canvas"
const width = 256;
const height = 256;
const canvas = createCanvas(width, height);
const context = canvas.getContext('2d')!;
const imageData = context.createImageData(width, height);
let pixels: Uint8ClampedArray;
export default function main() {
console.log(wfc);
const imageData = getImageData(wfcImage);
const dataPtr = wfc.malloc(imageData.data.length);
let buffer = new Uint8ClampedArray(wfc.memory.buffer, dataPtr, imageData.data.length);
buffer.set(imageData.data);
wfc.init(dataPtr, imageData.width, imageData.height, canvas.width, canvas.height);
pixels = new Uint8ClampedArray(wfc.memory.buffer, wfc.getPixels(), width * height * 4);
requestAnimationFrame(loop);
}
let sum = 0;
let count = 0;
async function loop() {
const start = performance.now();
wfc.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);
}

212
src/games/wfc/wfc.c Normal file
View File

@ -0,0 +1,212 @@
#include <stdlib.h>
#define PATTERN_SIZE 3
#define PATTERN_BATCH 8
typedef struct {
uint32_t neighbours[9];
} pattern_t;
typedef struct {
/** pattern indices */
uint32_t* possibilities;
uint32_t num_possibilities;
} superposition_t;
typedef enum { NORTH, EAST, SOUTH, WEST, DIRECTION_MAX } direction_t;
pattern_t* patterns;
uint32_t num_patterns;
uint32_t* pixels;
superposition_t* superpositions;
uint32_t width;
uint32_t height;
void patterns_init(uint32_t* pixel_data, uint16_t image_width, uint16_t image_height);
void superposition_init(void);
EXPORT(init) void init(uint32_t* pixel_data, uint16_t image_width, uint16_t image_height, uint16_t canvas_width, uint16_t canvas_height) {
width = canvas_width;
height = canvas_height;
patterns_init(pixel_data, image_width, image_height);
superposition_init();
}
pattern_t pattern_extract(uint32_t* pixel_data, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t batch) {
pattern_t pattern = {0};
for (uint8_t i = 0; i < PATTERN_SIZE * PATTERN_SIZE; i++) {
int8_t dx = (i % PATTERN_SIZE) - (PATTERN_SIZE / 2);
int8_t dy = (i / PATTERN_SIZE) - (PATTERN_SIZE / 2);
int8_t tmp;
if (batch == 1) { // horizontal flip
dx = PATTERN_SIZE - dx;
} else if (batch == 2) { // vertical flip
dy = PATTERN_SIZE - dy;
} else if (batch == 3) { // both flips
dx = PATTERN_SIZE - dx;
dy = PATTERN_SIZE - dy;
} else if (batch == 4) { // rotate 90
tmp = dx;
dx = dy;
dy = PATTERN_SIZE - tmp;
} else if (batch == 5) { // rotate 270
tmp = dy;
dy = dx;
dx = PATTERN_SIZE - tmp;
} else if (batch == 6) { // flip main axis
tmp = dy;
dy = dx;
dx = tmp;
} else if (batch == 7) { // flip secondary axis
tmp = dy;
dy = PATTERN_SIZE - dx;
dx = PATTERN_SIZE - tmp;
}
uint16_t xx = (x + dx + width) % width;
uint16_t yy = (y + dy + height) % height;
uint32_t idx = xx + yy * width;
pattern.neighbours[i] = pixel_data[idx];
}
return pattern;
}
bool pattern_check(pattern_t pattern) {
for (uint32_t i = 0; i < num_patterns; i++) {
if (memcmp(&pattern, &patterns[i], sizeof(pattern)) == 0) {
return false; // found duplicate
}
}
return true;
}
void patterns_init(uint32_t* pixel_data, uint16_t image_width, uint16_t image_height) {
uint32_t max_patterns = image_width * image_height * PATTERN_BATCH;
patterns = malloc(max_patterns * sizeof(*patterns));
num_patterns = 0;
for (uint16_t y = 0; y < image_height; y++) {
for (uint16_t x = 0; x < image_width; x++) {
for (uint8_t batch = 0; batch < PATTERN_BATCH; batch++) {
pattern_t pattern = pattern_extract(pixel_data, x, y, image_width, image_height, batch);
if (pattern_check(pattern)) {
patterns[num_patterns++] = pattern;
}
}
}
}
print_int(num_patterns);
print_int(max_patterns);
}
void superposition_init(void) {
pixels = malloc(width * height * sizeof(*pixels));
superpositions = malloc(width * height * sizeof(*superpositions));
for (uint32_t i = 0; i < width * height; i++) {
superposition_t superposition = {
.num_possibilities = num_patterns,
.possibilities = malloc(num_patterns * sizeof(uint32_t)),
};
for (uint32_t p = 0; p < num_patterns; p++) {
superposition.possibilities[p] = p;
}
superpositions[i] = superposition;
}
}
bool pattern_match(pattern_t a, pattern_t b, direction_t direction) {
switch (direction) {
case NORTH:
for (uint8_t x = 0; x < PATTERN_SIZE; x++) {
if (a.neighbours[x] != b.neighbours[PATTERN_SIZE * (PATTERN_SIZE - 1) + x]) {
return false;
}
}
return true;
case EAST:
for (uint8_t y = 0; y < PATTERN_SIZE; y++) {
if (a.neighbours[y * PATTERN_SIZE + PATTERN_SIZE - 1] != b.neighbours[y * PATTERN_SIZE]) {
return false;
}
}
return true;
case SOUTH:
for (uint8_t x = 0; x < PATTERN_SIZE; x++) {
if (a.neighbours[PATTERN_SIZE * (PATTERN_SIZE - 1) + x] != b.neighbours[x]) {
return false;
}
}
return true;
case WEST:
for (uint8_t y = 0; y < PATTERN_SIZE; y++) {
if (a.neighbours[y * PATTERN_SIZE] != b.neighbours[y * PATTERN_SIZE + PATTERN_SIZE - 1]) {
return false;
}
}
return true;
default:
return false;
}
}
void superposition_remove(superposition_t* superposition, uint32_t pattern) {
uint32_t i;
for (i = 0; i < superposition->num_possibilities; i++) {
if (superposition->possibilities[i] == pattern)
break;
}
if (i < superposition->num_possibilities) {
for (; i < superposition->num_possibilities - 1; i++) {
superposition->possibilities[i] = superposition->possibilities[i + 1];
}
superposition->num_possibilities--;
}
}
void superpositions_draw(void) {
for (uint32_t i = 0; i < width * height; i++) {
uint64_t sum = 0;
superposition_t superposition = superpositions[i];
for (uint32_t p = 0; p < superposition.num_possibilities; p++) {
sum += patterns[superposition.possibilities[p]].neighbours[(PATTERN_SIZE * PATTERN_SIZE) / 2];
}
if (superposition.num_possibilities > 0) {
pixels[i] = sum / superposition.num_possibilities;
} else {
pixels[i] = 0;
}
}
}
void superpositions_propagate(void) {
// TODO
}
void superposition_collapse(superposition_t* superposition) {
if (superposition->num_possibilities > 1) {
uint32_t choice = rand() % superposition->num_possibilities;
superposition->possibilities[0] = superposition->possibilities[choice];
superposition->num_possibilities = 1;
superpositions_propagate();
}
}
EXPORT(step) void step(void) {
superposition_collapse(&superpositions[420]);
superpositions_draw();
}
EXPORT(getPixels) uint32_t* get_pixels(void) { return pixels; }

4
src/types.d.ts vendored
View File

@ -48,6 +48,8 @@ declare module "*.c" {
const instance: { const instance: {
memory: WebAssembly.Memory; memory: WebAssembly.Memory;
data: DataView; data: DataView;
malloc: (size: number) => number;
free: (ptr: number) => void;
[x: string]: (...args: any) => any; [x: string]: (...args: any) => any;
}; };
@ -57,6 +59,8 @@ declare module "*.cpp" {
const instance: { const instance: {
memory: WebAssembly.Memory; memory: WebAssembly.Memory;
data: DataView; data: DataView;
malloc: (size: number) => void;
free: (ptr: number) => void;
[x: string]: (...args: any) => any; [x: string]: (...args: any) => any;
}; };