implement basic wasm support
This commit is contained in:
parent
f8b61d6edd
commit
e9225639a8
11
README.md
Normal file
11
README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Flail survivor
|
||||
|
||||
## Compile for desktop
|
||||
```
|
||||
zig build
|
||||
```
|
||||
|
||||
## Compile for web
|
||||
```
|
||||
zig build -Doptimize=ReleaseSmall -Dtarget=wasm32-wasi --sysroot "$EMSDK/upstream/emscripten"
|
||||
```
|
194
build.zig
194
build.zig
@ -1,13 +1,27 @@
|
||||
const std = @import("std");
|
||||
const raylib = @import("libs/raylib/build.zig");
|
||||
const fs = std.fs;
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const app_name = "step-kill";
|
||||
|
||||
const raylibSrc = "libs/raylib/raylib/src/";
|
||||
const raylibBindingSrc = "libs/raylib/";
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
try switch (target.getOsTag()) {
|
||||
.wasi, .emscripten => buildWeb(b, target, optimize),
|
||||
else => buildDesktop(b, target, optimize)
|
||||
};
|
||||
}
|
||||
|
||||
fn buildDesktop(b: *std.Build, target: std.zig.CrossTarget, optimize: std.builtin.Mode) !void {
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "step-kill",
|
||||
.root_source_file = .{ .path = "src/main.zig" },
|
||||
.name = app_name,
|
||||
.root_source_file = .{ .path = "src/platforms/desktop.zig" },
|
||||
.main_pkg_path = .{ .path = "src" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
@ -22,11 +36,175 @@ pub fn build(b: *std.Build) void {
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
}
|
||||
|
||||
fn buildWeb(b: *std.Build, target: std.zig.CrossTarget, optimize: std.builtin.Mode) !void {
|
||||
const emscriptenSrc = "libs/raylib/emscripten/";
|
||||
const webCachedir = "zig-cache/web/";
|
||||
const webOutdir = "zig-out/web/";
|
||||
|
||||
// TODO: Add depend step on 'git submodule update --recursive'
|
||||
// TODO: Add depend step on downloading emsdk
|
||||
|
||||
std.log.info("building for emscripten\n", .{});
|
||||
if (b.sysroot == null) {
|
||||
std.log.err("\n\nUSAGE: Please build with 'zig build -Doptimize=ReleaseSmall -Dtarget=wasm32-wasi --sysroot \"$EMSDK/upstream/emscripten\"'\n\n", .{});
|
||||
return error.SysRootExpected;
|
||||
}
|
||||
const lib = b.addStaticLibrary(.{
|
||||
.name = app_name,
|
||||
.root_source_file = std.Build.LazyPath.relative("src/platforms/web.zig"),
|
||||
.main_pkg_path = .{ .path = "src" },
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
});
|
||||
lib.addIncludePath(.{ .path = raylibSrc });
|
||||
|
||||
const emcc_file = switch (b.host.target.os.tag) {
|
||||
.windows => "emcc.bat",
|
||||
else => "emcc",
|
||||
};
|
||||
const emar_file = switch (b.host.target.os.tag) {
|
||||
.windows => "emar.bat",
|
||||
else => "emar",
|
||||
};
|
||||
const emranlib_file = switch (b.host.target.os.tag) {
|
||||
.windows => "emranlib.bat",
|
||||
else => "emranlib",
|
||||
};
|
||||
|
||||
const emcc_path = try fs.path.join(b.allocator, &.{ b.sysroot.?, emcc_file });
|
||||
defer b.allocator.free(emcc_path);
|
||||
const emranlib_path = try fs.path.join(b.allocator, &.{ b.sysroot.?, emranlib_file });
|
||||
defer b.allocator.free(emranlib_path);
|
||||
const emar_path = try fs.path.join(b.allocator, &.{ b.sysroot.?, emar_file });
|
||||
defer b.allocator.free(emar_path);
|
||||
const include_path = try fs.path.join(b.allocator, &.{ b.sysroot.?, "cache", "sysroot", "include" });
|
||||
defer b.allocator.free(include_path);
|
||||
|
||||
fs.cwd().makePath(webCachedir) catch {};
|
||||
fs.cwd().makePath(webOutdir) catch {};
|
||||
|
||||
const warnings = ""; //-Wall
|
||||
|
||||
const rcoreO = b.addSystemCommand(&.{ emcc_path, "-Os", warnings, "-c", raylibSrc ++ "rcore.c", "-o", webCachedir ++ "rcore.o", "-Os", warnings, "-DPLATFORM_WEB", "-DGRAPHICS_API_OPENGL_ES2" });
|
||||
const rshapesO = b.addSystemCommand(&.{ emcc_path, "-Os", warnings, "-c", raylibSrc ++ "rshapes.c", "-o", webCachedir ++ "rshapes.o", "-Os", warnings, "-DPLATFORM_WEB", "-DGRAPHICS_API_OPENGL_ES2" });
|
||||
const rtexturesO = b.addSystemCommand(&.{ emcc_path, "-Os", warnings, "-c", raylibSrc ++ "rtextures.c", "-o", webCachedir ++ "rtextures.o", "-Os", warnings, "-DPLATFORM_WEB", "-DGRAPHICS_API_OPENGL_ES2" });
|
||||
const rtextO = b.addSystemCommand(&.{ emcc_path, "-Os", warnings, "-c", raylibSrc ++ "rtext.c", "-o", webCachedir ++ "rtext.o", "-Os", warnings, "-DPLATFORM_WEB", "-DGRAPHICS_API_OPENGL_ES2" });
|
||||
const rmodelsO = b.addSystemCommand(&.{ emcc_path, "-Os", warnings, "-c", raylibSrc ++ "rmodels.c", "-o", webCachedir ++ "rmodels.o", "-Os", warnings, "-DPLATFORM_WEB", "-DGRAPHICS_API_OPENGL_ES2" });
|
||||
const utilsO = b.addSystemCommand(&.{ emcc_path, "-Os", warnings, "-c", raylibSrc ++ "utils.c", "-o", webCachedir ++ "utils.o", "-Os", warnings, "-DPLATFORM_WEB" });
|
||||
const raudioO = b.addSystemCommand(&.{ emcc_path, "-Os", warnings, "-c", raylibSrc ++ "raudio.c", "-o", webCachedir ++ "raudio.o", "-Os", warnings, "-DPLATFORM_WEB" });
|
||||
|
||||
const libraylibA = b.addSystemCommand(&.{
|
||||
emar_path,
|
||||
"rcs",
|
||||
webCachedir ++ "libraylib.a",
|
||||
webCachedir ++ "rcore.o",
|
||||
webCachedir ++ "rshapes.o",
|
||||
webCachedir ++ "rtextures.o",
|
||||
webCachedir ++ "rtext.o",
|
||||
webCachedir ++ "rmodels.o",
|
||||
webCachedir ++ "utils.o",
|
||||
webCachedir ++ "raudio.o",
|
||||
});
|
||||
const emranlib = b.addSystemCommand(&.{
|
||||
emranlib_path,
|
||||
webCachedir ++ "libraylib.a",
|
||||
});
|
||||
|
||||
libraylibA.step.dependOn(&rcoreO.step);
|
||||
libraylibA.step.dependOn(&rshapesO.step);
|
||||
libraylibA.step.dependOn(&rtexturesO.step);
|
||||
libraylibA.step.dependOn(&rtextO.step);
|
||||
libraylibA.step.dependOn(&rmodelsO.step);
|
||||
libraylibA.step.dependOn(&utilsO.step);
|
||||
libraylibA.step.dependOn(&raudioO.step);
|
||||
emranlib.step.dependOn(&libraylibA.step);
|
||||
|
||||
//only build raylib if not already there
|
||||
_ = fs.cwd().statFile(webCachedir ++ "libraylib.a") catch {
|
||||
lib.step.dependOn(&emranlib.step);
|
||||
};
|
||||
|
||||
lib.defineCMacro("__EMSCRIPTEN__", null);
|
||||
lib.defineCMacro("PLATFORM_WEB", null);
|
||||
std.log.info("emscripten include path: {s}", .{include_path});
|
||||
lib.addIncludePath(.{ .path = include_path });
|
||||
lib.addIncludePath(.{ .path = emscriptenSrc });
|
||||
lib.addIncludePath(.{ .path = raylibBindingSrc });
|
||||
lib.addIncludePath(.{ .path = raylibSrc });
|
||||
lib.addIncludePath(.{ .path = raylibSrc ++ "extras/" });
|
||||
lib.addAnonymousModule("raylib", .{ .source_file = .{ .path = raylibBindingSrc ++ "raylib.zig" } });
|
||||
// lib.root_module.addAnonymousImport("raylib", .{ .root_source_file = .{ .path = raylibBindingSrc ++ "raylib.zig" } });
|
||||
|
||||
const libraryOutputFolder = "zig-out/lib/";
|
||||
// this installs the lib (libraylib-zig-examples.a) to the `libraryOutputFolder` folder
|
||||
b.installArtifact(lib);
|
||||
|
||||
const shell = switch (optimize) {
|
||||
.Debug => emscriptenSrc ++ "shell.html",
|
||||
else => emscriptenSrc ++ "minshell.html",
|
||||
};
|
||||
|
||||
const emcc = b.addSystemCommand(&.{
|
||||
emcc_path,
|
||||
"-o",
|
||||
webOutdir ++ "game.html",
|
||||
|
||||
emscriptenSrc ++ "entry.c",
|
||||
raylibBindingSrc ++ "marshal.c",
|
||||
|
||||
libraryOutputFolder ++ "lib" ++ app_name ++ ".a",
|
||||
"-I.",
|
||||
"-I" ++ raylibSrc,
|
||||
"-I" ++ emscriptenSrc,
|
||||
"-I" ++ raylibBindingSrc,
|
||||
"-L.",
|
||||
"-L" ++ webCachedir,
|
||||
"-L" ++ libraryOutputFolder,
|
||||
"-lraylib",
|
||||
"-l" ++ app_name,
|
||||
"--shell-file",
|
||||
shell,
|
||||
"-DPLATFORM_WEB",
|
||||
"-sUSE_GLFW=3",
|
||||
"-sWASM=1",
|
||||
"-sALLOW_MEMORY_GROWTH=1",
|
||||
"-sWASM_MEM_MAX=512MB", //going higher than that seems not to work on iOS browsers ¯\_(ツ)_/¯
|
||||
"-sTOTAL_MEMORY=512MB",
|
||||
"-sABORTING_MALLOC=0",
|
||||
"-sASYNCIFY",
|
||||
"-sFORCE_FILESYSTEM=1",
|
||||
"-sASSERTIONS=1",
|
||||
// "--memory-init-file",
|
||||
// "0",
|
||||
// "--preload-file",
|
||||
// "assets",
|
||||
// "--source-map-base",
|
||||
"-O1",
|
||||
"-Os",
|
||||
// "-sLLD_REPORT_UNDEFINED",
|
||||
"-sERROR_ON_UNDEFINED_SYMBOLS=0",
|
||||
|
||||
// optimizations
|
||||
"-O1",
|
||||
"-Os",
|
||||
|
||||
// "-sUSE_PTHREADS=1",
|
||||
// "--profiling",
|
||||
// "-sTOTAL_STACK=128MB",
|
||||
// "-sMALLOC='emmalloc'",
|
||||
// "--no-entry",
|
||||
"-sEXPORTED_FUNCTIONS=['_malloc','_free','_main', '_emsc_main','_emsc_set_window_size']",
|
||||
"-sEXPORTED_RUNTIME_METHODS=ccall,cwrap",
|
||||
});
|
||||
|
||||
emcc.step.dependOn(&lib.step);
|
||||
|
||||
b.getInstallStep().dependOn(&emcc.step);
|
||||
//-------------------------------------------------------------------------------------
|
||||
|
||||
std.log.info("\n\nOutput files will be in {s}\n---\ncd {s}\npython -m http.server\n---\n\nbuilding...", .{ webOutdir, webOutdir });
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 97f501240b3b23fa338ae589d484e914270f13eb
|
||||
Subproject commit 26a0f453914b84462bd8b07b6545810780319051
|
@ -212,7 +212,6 @@ enemies: std.ArrayList(Enemy),
|
||||
player: Player,
|
||||
rope: Rope,
|
||||
ui: UI,
|
||||
should_close: bool = false,
|
||||
paused: bool = false,
|
||||
pixel_effect: PixelPerfect,
|
||||
|
||||
@ -477,7 +476,7 @@ fn tickUI(self: *Self) !void {
|
||||
}
|
||||
|
||||
if (self.ui.button("Exit?", content.left(), content.bottom() - 30, content.width, 30)) {
|
||||
self.should_close = true;
|
||||
return error.Exit;
|
||||
}
|
||||
} else if (self.paused) {
|
||||
const modal_size = rl.Vector2{ .x = 200, .y = 200 };
|
||||
@ -503,7 +502,7 @@ fn tickUI(self: *Self) !void {
|
||||
}
|
||||
|
||||
if (self.ui.button("Exit?", content.left(), content.top() + 120, content.width, 30)) {
|
||||
self.should_close = true;
|
||||
return error.Exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
35
src/main.zig
35
src/main.zig
@ -3,26 +3,27 @@ const std = @import("std");
|
||||
|
||||
const MainScene = @import("main-scene.zig");
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
var allocator = gpa.allocator();
|
||||
defer _ = gpa.deinit();
|
||||
scene: MainScene,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) @This() {
|
||||
rl.SetTargetFPS(60);
|
||||
rl.SetConfigFlags(rl.ConfigFlags{
|
||||
.FLAG_WINDOW_RESIZABLE = true,
|
||||
});
|
||||
rl.SetConfigFlags(rl.ConfigFlags{ .FLAG_WINDOW_RESIZABLE = true });
|
||||
rl.InitWindow(1200, 1200, "Step kill");
|
||||
rl.SetExitKey(.KEY_NULL);
|
||||
defer rl.CloseWindow();
|
||||
|
||||
var scene = MainScene.init(allocator);
|
||||
defer scene.deinit();
|
||||
|
||||
while (!rl.WindowShouldClose() and !scene.should_close) {
|
||||
rl.BeginDrawing();
|
||||
defer rl.EndDrawing();
|
||||
|
||||
try scene.tick();
|
||||
}
|
||||
return @This(){
|
||||
.scene = MainScene.init(allocator)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *@This()) void {
|
||||
self.scene.deinit();
|
||||
rl.CloseWindow();
|
||||
}
|
||||
|
||||
pub fn tick(self: *@This()) !void {
|
||||
rl.BeginDrawing();
|
||||
defer rl.EndDrawing();
|
||||
|
||||
try self.scene.tick();
|
||||
}
|
||||
|
21
src/platforms/desktop.zig
Normal file
21
src/platforms/desktop.zig
Normal file
@ -0,0 +1,21 @@
|
||||
const rl = @import("raylib");
|
||||
const std = @import("std");
|
||||
|
||||
const MainEntry = @import("../main.zig");
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
var allocator = gpa.allocator();
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
var main_entry = MainEntry.init(allocator);
|
||||
defer main_entry.deinit();
|
||||
|
||||
while (!rl.WindowShouldClose()) {
|
||||
main_entry.tick() catch |err| {
|
||||
if (err == error.Exit) { break; }
|
||||
return err;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
174
src/platforms/web.zig
Normal file
174
src/platforms/web.zig
Normal file
@ -0,0 +1,174 @@
|
||||
const rl = @import("raylib");
|
||||
const std = @import("std");
|
||||
const emsdk = @cImport({
|
||||
@cDefine("__EMSCRIPTEN__", "1");
|
||||
@cDefine("PLATFORM_WEB", "1");
|
||||
@cInclude("emscripten/emscripten.h");
|
||||
});
|
||||
const Allocator = std.mem.Allocator;
|
||||
const mem = std.mem;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const MainEntry = @import("../main.zig");
|
||||
|
||||
var main_entry: MainEntry = undefined;
|
||||
|
||||
//// special entry point for Emscripten build, called from src/marshall/emscripten_entry.c
|
||||
export fn emsc_main() callconv(.C) c_int {
|
||||
return safeMain() catch |err| {
|
||||
std.log.err("ERROR: {?}", .{err});
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
|
||||
export fn emsc_set_window_size(width: c_int, height: c_int) callconv(.C) void {
|
||||
rl.SetWindowSize(@intCast(width), @intCast(height));
|
||||
}
|
||||
export fn gameLoop() callconv(.C) void {
|
||||
main_entry.tick() catch |err| std.log.err("ERROR: {?}", .{err});
|
||||
}
|
||||
|
||||
fn safeMain() !c_int {
|
||||
var allocator = Allocator{
|
||||
.ptr = undefined,
|
||||
.vtable = &e_allocator_vtable,
|
||||
};
|
||||
// var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
// var allocator = gpa.allocator();
|
||||
// defer _ = gpa.deinit();
|
||||
|
||||
main_entry = MainEntry.init(allocator);
|
||||
defer main_entry.deinit();
|
||||
|
||||
emsdk.emscripten_set_main_loop(gameLoop, 0, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const e_allocator_vtable = Allocator.VTable{
|
||||
.alloc = EmscriptenAllocator.alloc,
|
||||
.resize = EmscriptenAllocator.resize,
|
||||
.free = EmscriptenAllocator.free,
|
||||
};
|
||||
|
||||
/// basically copied the std.heap.c_allocator and replaced with emscripten malloc & free
|
||||
const EmscriptenAllocator = struct {
|
||||
const c = @cImport({
|
||||
@cDefine("__EMSCRIPTEN__", "1");
|
||||
@cInclude("emscripten/emscripten.h");
|
||||
@cInclude("stdlib.h");
|
||||
});
|
||||
|
||||
usingnamespace if (@hasDecl(c, "malloc_size"))
|
||||
struct {
|
||||
pub const supports_malloc_size = true;
|
||||
pub const malloc_size = c.malloc_size;
|
||||
}
|
||||
else if (@hasDecl(c, "malloc_usable_size"))
|
||||
struct {
|
||||
pub const supports_malloc_size = true;
|
||||
pub const malloc_size = c.malloc_usable_size;
|
||||
}
|
||||
else if (@hasDecl(c, "_msize"))
|
||||
struct {
|
||||
pub const supports_malloc_size = true;
|
||||
pub const malloc_size = c._msize;
|
||||
}
|
||||
else
|
||||
struct {
|
||||
pub const supports_malloc_size = false;
|
||||
};
|
||||
|
||||
pub const supports_posix_memalign = @hasDecl(c, "posix_memalign");
|
||||
|
||||
fn getHeader(ptr: [*]u8) *[*]u8 {
|
||||
return @as(*[*]u8, @ptrFromInt(@intFromPtr(ptr) - @sizeOf(usize)));
|
||||
}
|
||||
|
||||
fn alignedAlloc(len: usize, log2_align: u8) ?[*]u8 {
|
||||
const alignment = @as(usize, 1) << @as(Allocator.Log2Align, @intCast(log2_align));
|
||||
if (supports_posix_memalign) {
|
||||
// The posix_memalign only accepts alignment values that are a
|
||||
// multiple of the pointer size
|
||||
const eff_alignment = @max(alignment, @sizeOf(usize));
|
||||
|
||||
var aligned_ptr: ?*anyopaque = undefined;
|
||||
if (c.posix_memalign(&aligned_ptr, eff_alignment, len) != 0)
|
||||
return null;
|
||||
|
||||
return @as([*]u8, @ptrCast(aligned_ptr));
|
||||
}
|
||||
|
||||
// Thin wrapper around regular malloc, overallocate to account for
|
||||
// alignment padding and store the orignal malloc()'ed pointer before
|
||||
// the aligned address.
|
||||
const unaligned_ptr = @as([*]u8, @ptrCast(c.malloc(len + alignment - 1 + @sizeOf(usize)) orelse return null));
|
||||
const unaligned_addr = @intFromPtr(unaligned_ptr);
|
||||
const aligned_addr = mem.alignForward(unaligned_addr + @sizeOf(usize), alignment);
|
||||
const aligned_ptr = unaligned_ptr + (aligned_addr - unaligned_addr);
|
||||
getHeader(aligned_ptr).* = unaligned_ptr;
|
||||
|
||||
return aligned_ptr;
|
||||
}
|
||||
|
||||
fn alignedFree(ptr: [*]u8) void {
|
||||
if (supports_posix_memalign) {
|
||||
return c.free(ptr);
|
||||
}
|
||||
|
||||
const unaligned_ptr = getHeader(ptr).*;
|
||||
c.free(unaligned_ptr);
|
||||
}
|
||||
|
||||
fn alignedAllocSize(ptr: [*]u8) usize {
|
||||
if (supports_posix_memalign) {
|
||||
return EmscriptenAllocator.malloc_size(ptr);
|
||||
}
|
||||
|
||||
const unaligned_ptr = getHeader(ptr).*;
|
||||
const delta = @intFromPtr(ptr) - @intFromPtr(unaligned_ptr);
|
||||
return EmscriptenAllocator.malloc_size(unaligned_ptr) - delta;
|
||||
}
|
||||
|
||||
fn alloc(
|
||||
_: *anyopaque,
|
||||
len: usize,
|
||||
log2_align: u8,
|
||||
return_address: usize,
|
||||
) ?[*]u8 {
|
||||
_ = return_address;
|
||||
assert(len > 0);
|
||||
return alignedAlloc(len, log2_align);
|
||||
}
|
||||
|
||||
fn resize(
|
||||
_: *anyopaque,
|
||||
buf: []u8,
|
||||
log2_buf_align: u8,
|
||||
new_len: usize,
|
||||
return_address: usize,
|
||||
) bool {
|
||||
_ = log2_buf_align;
|
||||
_ = return_address;
|
||||
if (new_len <= buf.len) {
|
||||
return true;
|
||||
}
|
||||
if (@hasDecl(c, "malloc_size")) {
|
||||
const full_len = alignedAllocSize(buf.ptr);
|
||||
if (new_len <= full_len) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn free(
|
||||
_: *anyopaque,
|
||||
buf: []u8,
|
||||
log2_buf_align: u8,
|
||||
return_address: usize,
|
||||
) void {
|
||||
_ = log2_buf_align;
|
||||
_ = return_address;
|
||||
alignedFree(buf.ptr);
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user