Skip to content

Commit

Permalink
Pack all the images into client.wasm
Browse files Browse the repository at this point in the history
  • Loading branch information
rexim committed Oct 8, 2024
1 parent cefe115 commit 77c3031
Show file tree
Hide file tree
Showing 10 changed files with 8,133 additions and 143 deletions.
53 changes: 31 additions & 22 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,35 @@ function cmd(program, args = []) {

if (!0) cmd("tsc", []);
if (!0) {
cmd("c3c", [
"compile",
"-D", "PLATFORM_WEB",
"--reloc=none",
"--target", "wasm32",
"-O5", "-g0", "--link-libc=no", "--no-entry",
"-o", "client",
"-z", "--export-table",
"-z", "--allow-undefined",
"client.c3", "common.c3",
])
cmd("c3c", [
"compile",
"-D", "PLATFORM_WEB",
"--reloc=none",
"--target", "wasm32",
"-O5", "-g0", "--link-libc=no", "--no-entry",
"-o", "server",
"-z", "--export-table",
"-z", "--allow-undefined",
"server.c3", "common.c3",
])
cmd("clang", [
"-DSTB_IMAGE_IMPLEMENTATION",
"-x", "c",
"-c",
"stb_image.h"
]).on('close', (data) => {
if (data !== 0) return;
cmd("c3c", [
"compile",
"-D", "PLATFORM_WEB",
"--reloc=none",
"--target", "wasm32",
"-O5", "-g0", "--link-libc=no", "--no-entry",
"--trust=full",
"-o", "client",
"-z", "--export-table",
"-z", "--allow-undefined",
"client.c3", "common.c3",
])
cmd("c3c", [
"compile",
"-D", "PLATFORM_WEB",
"--reloc=none",
"--target", "wasm32",
"-O5", "-g0", "--link-libc=no", "--no-entry",
"-o", "server",
"-z", "--export-table",
"-z", "--allow-undefined",
"server.c3", "common.c3",
])
})
}
70 changes: 47 additions & 23 deletions client.c3
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const float MINIMAP_SCALE = 0.07;

const uint SPRITE_ANGLES_COUNT = 8;

$exec("packer.c3;common.c3;stb_image.o");

def Color = char[<4>];

// WARNING! Must be synchronized with AssetSound in client.mts
Expand All @@ -47,18 +49,7 @@ extern fn uint platform_now_msecs();
struct Image {
usz width;
usz height;
Color[*] pixels;
}

fn Color *image_pixels(Image *image) @extern("image_pixels") @wasm {
return &image.pixels[0];
}

fn Image* allocate_image(usz width, usz height) @extern("allocate_image") @wasm {
Image* image = mem::calloc(Image.sizeof + Color.sizeof*width*height);
image.width = width;
image.height = height;
return image;
Color *pixels;
}

struct Sprite {
Expand Down Expand Up @@ -120,17 +111,19 @@ fn void Camera.update(Camera *camera) {
}

struct Display {
Image *image;
Image image;
float *zbuffer;
}

Display display;

fn void resize_display(usz width, usz height) @extern("resize_display") @wasm
{
if (display.image) mem::free(display.image);
if (display.image.pixels) mem::free(display.image.pixels);
if (display.zbuffer) mem::free(display.zbuffer);
display.image = allocate_image(width, height);
display.image.width = width;
display.image.height = height;
display.image.pixels = mem::calloc(Color.sizeof*width*height);
display.zbuffer = mem::calloc(float.sizeof*width);
}

Expand Down Expand Up @@ -833,18 +826,49 @@ fn void key_up(uint key_code) @extern("key_up") @wasm {
}
}

fn void render_game(Image *key_image, Image *bomb_image, Image *particle_image, Image *wall_image, Image *player_image, float delta_time, float time) @extern("render_game") @wasm {
fn Asset *asset_by_filename(String filename) {
foreach (&asset: assets) {
if (asset.filename == filename) {
return asset;
}
}
return null;
}

fn void render_game(float delta_time, float time) @extern("render_game") @wasm {
Asset *asset = null;

asset = asset_by_filename("assets/images/custom/key.png");
assert(asset);
Image key_image = {asset.width, asset.height, (Color*)&pack[asset.offset]};

asset = asset_by_filename("assets/images/custom/bomb.png");
assert(asset);
Image bomb_image = {asset.width, asset.height, (Color*)&pack[asset.offset]};

asset = asset_by_filename("assets/images/custom/particle.png");
assert(asset);
Image particle_image = {asset.width, asset.height, (Color*)&pack[asset.offset]};

asset = asset_by_filename("assets/images/custom/wall.png");
assert(asset);
Image wall_image = {asset.width, asset.height, (Color*)&pack[asset.offset]};

asset = asset_by_filename("assets/images/custom/player.png");
assert(asset);
Image player_image = {asset.width, asset.height, (Color*)&pack[asset.offset]};

update_all_players(common::scene, delta_time);
update_items(&sprite_pool, time, &common::items, key_image, bomb_image);
update_bombs_on_client_side(&sprite_pool, &particle_pool, bomb_image, common::scene, delta_time, &common::bombs);
update_particles(particle_image, &sprite_pool, delta_time, common::scene, &particle_pool);
update_items(&sprite_pool, time, &common::items, &key_image, &bomb_image);
update_bombs_on_client_side(&sprite_pool, &particle_pool, &bomb_image, common::scene, delta_time, &common::bombs);
update_particles(&particle_image, &sprite_pool, delta_time, common::scene, &particle_pool);

render_other_players(&sprite_pool, player_image);
render_other_players(&sprite_pool, &player_image);

render_floor_and_ceiling(display.image);
render_walls(display.image, display.zbuffer, wall_image, common::scene);
render_floor_and_ceiling(&display.image);
render_walls(&display.image, display.zbuffer, &wall_image, common::scene);
cull_and_sort_sprites(&sprite_pool);
render_sprites(display.image, display.zbuffer, &sprite_pool);
render_sprites(&display.image, display.zbuffer, &sprite_pool);

ping_server_if_needed();
reset_sprite_pool();
Expand Down
38 changes: 1 addition & 37 deletions client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -74,26 +74,6 @@ function displaySwapBackImageData(display, wasmClient) {
display.backCtx.putImageData(new ImageData(backImageData, backImageWidth), 0, 0);
display.ctx.drawImage(display.backCtx.canvas, 0, 0, display.ctx.canvas.width, display.ctx.canvas.height);
}
async function loadImage(url) {
const image = new Image();
image.src = url;
return new Promise((resolve, reject) => {
image.onload = () => resolve(image);
image.onerror = reject;
});
}
async function loadWasmImage(wasmClient, url) {
const image = await loadImage(url);
const canvas = new OffscreenCanvas(image.width, image.height);
const ctx = canvas.getContext("2d");
if (ctx === null)
throw new Error("2d canvas is not supported");
ctx.drawImage(image, 0, 0);
const imageData = ctx.getImageData(0, 0, image.width, image.height);
const ptr = wasmClient.allocate_image(image.width, image.height);
new Uint8ClampedArray(wasmClient.memory.buffer, wasmClient.image_pixels(ptr), image.width * image.height * 4).set(imageData.data);
return ptr;
}
var AssetSound;
(function (AssetSound) {
AssetSound[AssetSound["BOMB_BLAST"] = 0] = "BOMB_BLAST";
Expand Down Expand Up @@ -152,8 +132,6 @@ async function instantiateWasmClient(url) {
wasmCommon._initialize();
return {
...wasmCommon,
allocate_image: wasm.instance.exports.allocate_image,
image_pixels: wasm.instance.exports.image_pixels,
players_count: wasm.instance.exports.players_count,
unregister_all_other_players: wasm.instance.exports.unregister_all_other_players,
key_down: wasm.instance.exports.key_down,
Expand All @@ -167,24 +145,10 @@ async function instantiateWasmClient(url) {
}
async function createGame() {
const wasmClient = await instantiateWasmClient("client.wasm");
const [wallImagePtr, keyImagePtr, bombImagePtr, playerImagePtr, particleImagePtr, nullImagePtr,] = await Promise.all([
loadWasmImage(wasmClient, "assets/images/custom/wall.png"),
loadWasmImage(wasmClient, "assets/images/custom/key.png"),
loadWasmImage(wasmClient, "assets/images/custom/bomb.png"),
loadWasmImage(wasmClient, "assets/images/custom/player.png"),
loadWasmImage(wasmClient, "assets/images/custom/particle.png"),
loadWasmImage(wasmClient, "assets/images/custom/null.png"),
]);
const itemPickupSound = new Audio("assets/sounds/bomb-pickup.ogg");
const bombRicochetSound = new Audio("assets/sounds/ricochet.wav");
const bombBlastSound = new Audio("assets/sounds/blast.ogg");
const assets = {
wallImagePtr,
keyImagePtr,
bombImagePtr,
playerImagePtr,
particleImagePtr,
nullImagePtr,
bombRicochetSound,
itemPickupSound,
bombBlastSound,
Expand Down Expand Up @@ -235,7 +199,7 @@ async function createGame() {
const deltaTime = (timestamp - prevTimestamp) / 1000;
const time = timestamp / 1000;
prevTimestamp = timestamp;
game.wasmClient.render_game(game.assets.keyImagePtr, game.assets.bombImagePtr, game.assets.particleImagePtr, game.assets.wallImagePtr, game.assets.playerImagePtr, deltaTime, time);
game.wasmClient.render_game(deltaTime, time);
displaySwapBackImageData(game.display, game.wasmClient);
renderDebugInfo(game.display.ctx, deltaTime, game);
window.requestAnimationFrame(frame);
Expand Down
59 changes: 3 additions & 56 deletions client.mts
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,12 @@ interface Display {
}

interface WasmClient extends common.WasmCommon {
allocate_image: (width: number, height: number) => number,
image_pixels: (image: number) => number,
players_count: () => number,
unregister_all_other_players: () => void,
key_down: (key_code: number) => void,
key_up: (key_code: number) => void,
// TODO: render_game() should be actually called something like tick() cause that's what it is
render_game: (key_image: number, bomb_image: number, particle_image: number, wall_image: number, player_image: number, delta_time: number, time: number) => void,
render_game: (delta_time: number, time: number) => void,
ping_msecs: () => number,
process_message: (message: number) => boolean,
resize_display: (width: number, height: number) => void,
Expand Down Expand Up @@ -99,12 +97,6 @@ function displaySwapBackImageData(display: Display, wasmClient: WasmClient) {
}

interface Assets {
wallImagePtr: number,
keyImagePtr: number,
bombImagePtr: number,
playerImagePtr: number,
particleImagePtr: number,
nullImagePtr: number,
bombRicochetSound: HTMLAudioElement,
itemPickupSound: HTMLAudioElement,
bombBlastSound: HTMLAudioElement
Expand All @@ -118,27 +110,6 @@ interface Game {
display: Display,
}

async function loadImage(url: string): Promise<HTMLImageElement> {
const image = new Image();
image.src = url;
return new Promise((resolve, reject) => {
image.onload = () => resolve(image);
image.onerror = reject;
});
}

async function loadWasmImage(wasmClient: WasmClient, url: string): Promise<number> {
const image = await loadImage(url);
const canvas = new OffscreenCanvas(image.width, image.height);
const ctx = canvas.getContext("2d");
if (ctx === null) throw new Error("2d canvas is not supported");
ctx.drawImage(image, 0, 0);
const imageData = ctx.getImageData(0, 0, image.width, image.height);
const ptr = wasmClient.allocate_image(image.width, image.height);
new Uint8ClampedArray(wasmClient.memory.buffer, wasmClient.image_pixels(ptr), image.width*image.height*4).set(imageData.data);
return ptr;
}

// WARNING! Must be synchronized with AssetSound in client.c3
enum AssetSound {
BOMB_BLAST,
Expand Down Expand Up @@ -200,13 +171,11 @@ async function instantiateWasmClient(url: string): Promise<WasmClient> {

return {
...wasmCommon,
allocate_image: wasm.instance.exports.allocate_image as (width: number, height: number) => number,
image_pixels: wasm.instance.exports.image_pixels as (image: number) => number,
players_count: wasm.instance.exports.players_count as () => number,
unregister_all_other_players: wasm.instance.exports.unregister_all_other_players as () => void,
key_down: wasm.instance.exports.key_down as (key_code: number) => void,
key_up: wasm.instance.exports.key_up as (key_code: number) => void,
render_game: wasm.instance.exports.render_game as (key_image: number, bomb_image: number, particle_image: number, wall_image: number, player_image: number, delta_time: number, time: number) => void,
render_game: wasm.instance.exports.render_game as (delta_time: number, time: number) => void,
ping_msecs: wasm.instance.exports.ping_msecs as () => number,
process_message: wasm.instance.exports.process_message as (message: number) => boolean,
resize_display: wasm.instance.exports.resize_display as (width: number, height: number) => void,
Expand All @@ -217,32 +186,10 @@ async function instantiateWasmClient(url: string): Promise<WasmClient> {
async function createGame(): Promise<Game> {
const wasmClient = await instantiateWasmClient("client.wasm");

const [
wallImagePtr,
keyImagePtr,
bombImagePtr,
playerImagePtr,
particleImagePtr,
nullImagePtr,
] = await Promise.all([
loadWasmImage(wasmClient, "assets/images/custom/wall.png"),
loadWasmImage(wasmClient, "assets/images/custom/key.png"),
loadWasmImage(wasmClient, "assets/images/custom/bomb.png"),
loadWasmImage(wasmClient, "assets/images/custom/player.png"),
loadWasmImage(wasmClient, "assets/images/custom/particle.png"),
loadWasmImage(wasmClient, "assets/images/custom/null.png"),
]);

const itemPickupSound = new Audio("assets/sounds/bomb-pickup.ogg");
const bombRicochetSound = new Audio("assets/sounds/ricochet.wav");
const bombBlastSound = new Audio("assets/sounds/blast.ogg");
const assets = {
wallImagePtr,
keyImagePtr,
bombImagePtr,
playerImagePtr,
particleImagePtr,
nullImagePtr,
bombRicochetSound,
itemPickupSound,
bombBlastSound,
Expand Down Expand Up @@ -308,7 +255,7 @@ async function createGame(): Promise<Game> {
const time = timestamp/1000;
prevTimestamp = timestamp;

game.wasmClient.render_game(game.assets.keyImagePtr, game.assets.bombImagePtr, game.assets.particleImagePtr, game.assets.wallImagePtr, game.assets.playerImagePtr, deltaTime, time);
game.wasmClient.render_game(deltaTime, time);

displaySwapBackImageData(game.display, game.wasmClient);
renderDebugInfo(game.display.ctx, deltaTime, game);
Expand Down
Binary file modified client.wasm
Binary file not shown.
18 changes: 14 additions & 4 deletions common.c3
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
module common;
import std::math;
import std::io;
import std::collections::list;

struct Asset {
String filename;
usz offset;
usz width;
usz height;
}

def Assets = List(<Asset>);

const float BOMB_GRAVITY = 10;
const float BOMB_LIFETIME = 2;
Expand Down Expand Up @@ -188,16 +198,16 @@ fn ItemsSpawnedBatchMessage* reconstruct_state_of_items(Item[] *items) {
if (item.alive) itemsCount += 1;
}
if (itemsCount == 0) return null;
int size = ItemsSpawnedBatchMessage.sizeof + ItemSpawned.sizeof*itemsCount;
usz size = ItemsSpawnedBatchMessage.sizeof + ItemSpawned.sizeof*itemsCount;
ItemsSpawnedBatchMessage *message = mem::tcalloc(size);
message.size = size;
message.size = (int)size;
message.kind = MessageKind.ITEM_SPAWNED;
usz index = 0;
foreach (itemIndex, item: *items) {
if (item.alive) {
message.items[index] = {
.itemKind = item.kind,
.itemIndex = itemIndex,
.itemIndex = (int)itemIndex,
.x = item.position.x,
.y = item.position.y,
};
Expand Down Expand Up @@ -240,7 +250,7 @@ fn int throw_bomb(Vector2 position, float direction, Bombs *bombs) {
bomb.velocity_z = 0.5;
bomb.velocity *= BOMB_THROW_VELOCITY;
bomb.velocity_z *= BOMB_THROW_VELOCITY;
return index;
return (int)index;
}
}
return -1;
Expand Down
Loading

0 comments on commit 77c3031

Please sign in to comment.