Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Add zemscripten's "root" module to your wasm compile target., then create an `em
.embed_paths = &.{},
.preload_paths = &.{},
.install_dir = .{ .custom = "web" },
.shell_file_path = @import("zemscripten").htmlPath(b),
},
);
emcc_step.dependOn(activate_emsdk_step);
Expand Down
10 changes: 9 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ const std = @import("std");

pub const emsdk_ver_major = "3";
pub const emsdk_ver_minor = "1";
pub const emsdk_ver_tiny = "52";
pub const emsdk_ver_tiny = "73";
pub const emsdk_version = emsdk_ver_major ++ "." ++ emsdk_ver_minor ++ "." ++ emsdk_ver_tiny;

pub fn build(b: *std.Build) void {
_ = b.addModule("root", .{ .root_source_file = b.path("src/zemscripten.zig") });
_ = b.addModule("dummy", .{ .root_source_file = b.path("src/dummy.zig") });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is dummy.zig for?

Copy link
Contributor Author

@ckrowland ckrowland Feb 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you run natively, @import("zemscripten") imports the dummy.zig instead of zemscripten.zig, so you can run both natively and emscripten in the same program. There might be a better way to do this. We could make it so that importing zemscripten means only a emscripten build could run. I forgot to add to the README, when you build like this it would look like

const zemscripten = b.dependency("zemscripten", .{});
if (target.result.os.tag != .emscripten) {
    exe.root_module.addImport("zemscripten", zemscripten.module("dummy"));
} else {
    exe.root_module.addImport("zemscripten", zemscripten.module("root"));
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple main files is the strategy I use in personal projects. I also intend to refactor the zig-gamedev samples to be like sdl2_demo (https://github.com/zig-gamedev/zig-gamedev/tree/main/samples/sdl2_demo/src).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, yeah I do the same thing. So safe to remove dummy.zig?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think so. We could put it back if it is useful to someone later.

}

pub fn emccPath(b: *std.Build) []const u8 {
Expand All @@ -32,6 +33,13 @@ pub fn emrunPath(b: *std.Build) []const u8 {
}) catch unreachable;
}

pub fn htmlPath(b: *std.Build) []const u8 {
return std.fs.path.join(b.allocator, &.{
b.dependency("emsdk", .{}).path("").getPath(b),
"upstream/emscripten/src/shell.html",
}) catch unreachable;
}

pub fn activateEmsdkStep(b: *std.Build) *std.Build.Step {
const emsdk_script_path = std.fs.path.join(b.allocator, &.{
b.dependency("emsdk", .{}).path("").getPath(b),
Expand Down
3 changes: 2 additions & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.{
.name = "zemscripten",
.version = "0.2.0-dev",
.version = "0.2.1-dev",
.paths = .{
"build.zig",
"build.zig.zon",
"src",
"content",
"LICENSE",
"README.md",
},
Expand Down
1 change: 1 addition & 0 deletions src/dummy.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub const is_emscripten = false;
122 changes: 118 additions & 4 deletions src/zemscripten.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ comptime {
_ = std.testing.refAllDeclsRecursive(@This());
}

extern fn emscripten_err([*c]const u8) void;
extern fn emscripten_console_error([*c]const u8) void;
extern fn emscripten_console_warn([*c]const u8) void;
extern fn emscripten_console_log([*c]const u8) void;
pub extern fn emscripten_sleep(ms: u32) void;

pub const MainLoopCallback = *const fn () callconv(.C) void;
extern fn emscripten_set_main_loop(MainLoopCallback, c_int, c_int) void;
Expand All @@ -21,6 +18,123 @@ pub const AnimationFrameCallback = *const fn (f64, ?*anyopaque) callconv(.C) c_i
extern fn emscripten_request_animation_frame_loop(AnimationFrameCallback, ?*anyopaque) void;
pub const requestAnimationFrameLoop = emscripten_request_animation_frame_loop;

pub const EmscriptenResult = enum(i16) {
success = 0,
deferred = 1,
not_supported = -1,
failed_not_deferred = -2,
invalid_target = -3,
unknown_target = -4,
invalid_param = -5,
failed = -6,
no_data = -7,
timed_out = -8,
};
pub const CanvasSizeChangedCallback = *const fn (
i16,
*anyopaque,
?*anyopaque,
) callconv(.C) c_int;
pub fn setResizeCallback(
cb: CanvasSizeChangedCallback,
use_capture: bool,
user_data: ?*anyopaque,
) EmscriptenResult {
const result = emscripten_set_resize_callback_on_thread(
"2",
user_data,
@intFromBool(use_capture),
cb,
2,
);
return @enumFromInt(result);
}
extern fn emscripten_set_resize_callback_on_thread(
[*:0]const u8,
?*anyopaque,
c_int,
CanvasSizeChangedCallback,
c_int,
) c_int;

pub fn getElementCssSize(
target_id: [:0]const u8,
width: *f64,
height: *f64,
) EmscriptenResult {
return @enumFromInt(emscripten_get_element_css_size(
target_id,
width,
height,
));
}
extern fn emscripten_get_element_css_size([*:0]const u8, *f64, *f64) c_int;

/// EmmalocAllocator allocator
/// use with linker flag -sMALLOC=emmalloc
/// for details see docs: https://github.com/emscripten-core/emscripten/blob/main/system/lib/emmalloc.c
extern fn emmalloc_memalign(u32, u32) ?*anyopaque;
extern fn emmalloc_realloc_try(?*anyopaque, u32) ?*anyopaque;
extern fn emmalloc_free(?*anyopaque) void;
pub const EmmalocAllocator = struct {
const Self = @This();
dummy: u32 = undefined,

pub fn allocator(self: *Self) std.mem.Allocator {
return .{
.ptr = self,
.vtable = &.{
.alloc = &alloc,
.resize = &resize,
.free = &free,
},
};
}

fn alloc(
ctx: *anyopaque,
len: usize,
ptr_align_log2: u8,
return_address: usize,
) ?[*]u8 {
_ = ctx;
_ = return_address;
const ptr_align: u32 = @as(u32, 1) << @as(u5, @intCast(ptr_align_log2));
if (!std.math.isPowerOfTwo(ptr_align)) unreachable;
const ptr = emmalloc_memalign(ptr_align, len) orelse return null;
return @ptrCast(ptr);
}

fn resize(
ctx: *anyopaque,
buf: []u8,
buf_align_log2: u8,
new_len: usize,
return_address: usize,
) bool {
_ = ctx;
_ = return_address;
_ = buf_align_log2;
return emmalloc_realloc_try(buf.ptr, new_len) != null;
}

fn free(
ctx: *anyopaque,
buf: []u8,
buf_align_log2: u8,
return_address: usize,
) void {
_ = ctx;
_ = buf_align_log2;
_ = return_address;
return emmalloc_free(buf.ptr);
}
};

extern fn emscripten_err([*c]const u8) void;
extern fn emscripten_console_error([*c]const u8) void;
extern fn emscripten_console_warn([*c]const u8) void;
extern fn emscripten_console_log([*c]const u8) void;
/// std.panic impl
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
_ = error_return_trace;
Expand Down
Loading