├── .github └── workflows │ └── main.yml ├── .gitignore ├── .zigversion ├── LICENSE ├── README.md ├── build.zig ├── build.zig.zon └── src └── zemscripten.zig /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | push: 7 | branches: 8 | - main 9 | concurrency: 10 | # Cancels pending runs when a PR gets updated. 11 | group: ${{ github.head_ref || github.run_id }}-${{ github.actor }} 12 | cancel-in-progress: true 13 | jobs: 14 | lint-and-build-and-test: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ubuntu-latest, windows-latest, macos-latest] 19 | runs-on: ${{matrix.os}} 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v3 23 | - name: Read .zig-version 24 | id: zigversion 25 | uses: juliangruber/read-file-action@v1 26 | with: 27 | path: ./.zigversion 28 | - name: Install Zig 29 | uses: mlugg/setup-zig@v1 30 | with: 31 | version: ${{ steps.zigversion.outputs.content }} 32 | - name: Check format 33 | continue-on-error: true 34 | run: zig fmt --check . 35 | - name: Build 36 | run: zig build 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore some special directories 2 | *.zig-cache 3 | *zig-out 4 | 5 | # Ignore some special OS files 6 | *.DS_Store 7 | -------------------------------------------------------------------------------- /.zigversion: -------------------------------------------------------------------------------- 1 | 0.14.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 zig-gamedev contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [zemscripten](https://github.com/zig-gamedev/zemscripten) 2 | 3 | Zig build package and shims for [Emscripten](https://emscripten.org) emsdk 4 | 5 | ## How to use it 6 | 7 | Add `zemscripten` and (optionally) `emsdk` to your build.zig.zon dependencies 8 | ```sh 9 | zig fetch --save https://github.com/emscripten-core/emsdk/archive/refs/tags/4.0.3.tar.gz 10 | ``` 11 | 12 | Emsdk must be activated before it can be used. You can use `activateEmsdkStep` to create a build step for that: 13 | ```zig 14 | const activate_emsdk_step = @import("zemscripten").activateEmsdkStep(b); 15 | ``` 16 | 17 | Add zemscripten's "root" module to your wasm compile target., then create an `emcc` build step. We use zemscripten's default flags and settings which can be overridden for your project specific requirements. Refer to the [emcc documentation](https://emscripten.org/docs/tools_reference/emcc.html). Example build.zig code: 18 | ```zig 19 | const wasm = b.addStaticLibrary(.{ 20 | .name = "MyGame", 21 | .root_source_file = b.path("src/main.zig"), 22 | .target = target, 23 | .optimize = optimize, 24 | }); 25 | 26 | const zemscripten = b.dependency("zemscripten", .{}); 27 | wasm.root_module.addImport("zemscripten", zemscripten.module("root")); 28 | 29 | const emcc_flags = @import("zemscripten").emccDefaultFlags(b.allocator, optimize); 30 | 31 | var emcc_settings = @import("zemscripten").emccDefaultSettings(b.allocator, .{ 32 | .optimize = optimize, 33 | }); 34 | 35 | try emcc_settings.put("ALLOW_MEMORY_GROWTH", "1"); 36 | 37 | const emcc_step = @import("zemscripten").emccStep( 38 | b, 39 | wasm, 40 | .{ 41 | .optimize = optimize, 42 | .flags = emcc_flags, 43 | .settings = emcc_settings, 44 | .use_preload_plugins = true, 45 | .embed_paths = &.{}, 46 | .preload_paths = &.{}, 47 | .install_dir = .{ .custom = "web" }, 48 | }, 49 | ); 50 | emcc_step.dependOn(activate_emsdk_step); 51 | 52 | b.getInstallStep().dependOn(emcc_step); 53 | ``` 54 | 55 | To use a custom html file emccStep() accepts a shell_file_path option: 56 | ```zig 57 | const emcc_step = @import("zemscripten").emccStep( 58 | b, 59 | wasm, 60 | .{ 61 | .optimize = optimize, 62 | .flags = emcc_flags, 63 | .settings = emcc_settings, 64 | .use_preload_plugins = true, 65 | .embed_paths = &.{}, 66 | .preload_paths = &.{}, 67 | .install_dir = .{ .custom = "web" }, 68 | .shell_file_path = "path/to/file" 69 | }, 70 | ); 71 | ``` 72 | 73 | Now you can use the provided Zig panic and log overrides in your wasm's root module and define the entry point that invoked by the js output of `emcc` (by default it looks for a symbol named `main`). For example: 74 | ```zig 75 | const std = @import("std"); 76 | 77 | const zemscripten = @import("zemscripten"); 78 | pub const panic = zemscripten.panic; 79 | 80 | pub const std_options = std.Options{ 81 | .logFn = zemscripten.log, 82 | }; 83 | 84 | export fn main() c_int { 85 | std.log.info("hello, world.", .{}); 86 | return 0; 87 | } 88 | ``` 89 | 90 | You can also define a run step that invokes `emrun`. This will serve the html locally over HTTP and try to open it using your default browser. Example build.zig code: 91 | ```zig 92 | const html_filename = try std.fmt.allocPrint(b.allocator, "{s}.html", .{wasm.name}); 93 | 94 | const emrun_args = .{}; 95 | const emrun_step = @import("zemscripten").emrunStep( 96 | b, 97 | b.getInstallPath(.{ .custom = "web" }, html_filename), 98 | &emrun_args, 99 | ); 100 | 101 | emrun_step.dependOn(emcc_step); 102 | 103 | b.step("emrun", "Build and open the web app locally using emrun").dependOn(emrun_step); 104 | ``` 105 | See the [emrun documentation](https://emscripten.org/docs/compiling/Running-html-files-with-emrun.html) for the difference args that can be used. 106 | 107 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const builtin = @import("builtin"); 2 | const std = @import("std"); 3 | 4 | pub const emsdk_ver_major = "4"; 5 | pub const emsdk_ver_minor = "0"; 6 | pub const emsdk_ver_tiny = "3"; 7 | pub const emsdk_version = emsdk_ver_major ++ "." ++ emsdk_ver_minor ++ "." ++ emsdk_ver_tiny; 8 | 9 | pub fn build(b: *std.Build) void { 10 | _ = b.addModule("root", .{ .root_source_file = b.path("src/zemscripten.zig") }); 11 | } 12 | 13 | pub fn emccPath(b: *std.Build) []const u8 { 14 | return std.fs.path.join(b.allocator, &.{ 15 | b.dependency("emsdk", .{}).path("").getPath(b), 16 | "upstream/emscripten/", 17 | switch (builtin.target.os.tag) { 18 | .windows => "emcc.bat", 19 | else => "emcc", 20 | }, 21 | }) catch unreachable; 22 | } 23 | 24 | pub fn emrunPath(b: *std.Build) []const u8 { 25 | return std.fs.path.join(b.allocator, &.{ 26 | b.dependency("emsdk", .{}).path("").getPath(b), 27 | "upstream/emscripten/", 28 | switch (builtin.target.os.tag) { 29 | .windows => "emrun.bat", 30 | else => "emrun", 31 | }, 32 | }) catch unreachable; 33 | } 34 | 35 | pub fn htmlPath(b: *std.Build) []const u8 { 36 | return std.fs.path.join(b.allocator, &.{ 37 | b.dependency("emsdk", .{}).path("").getPath(b), 38 | "upstream/emscripten/src/shell.html", 39 | }) catch unreachable; 40 | } 41 | 42 | pub fn activateEmsdkStep(b: *std.Build) *std.Build.Step { 43 | const emsdk_script_path = std.fs.path.join(b.allocator, &.{ 44 | b.dependency("emsdk", .{}).path("").getPath(b), 45 | switch (builtin.target.os.tag) { 46 | .windows => "emsdk.bat", 47 | else => "emsdk", 48 | }, 49 | }) catch unreachable; 50 | 51 | var emsdk_install = b.addSystemCommand(&.{ emsdk_script_path, "install", emsdk_version }); 52 | 53 | switch (builtin.target.os.tag) { 54 | .linux, .macos => { 55 | emsdk_install.step.dependOn(&b.addSystemCommand(&.{ "chmod", "+x", emsdk_script_path }).step); 56 | }, 57 | else => {}, 58 | } 59 | 60 | var emsdk_activate = b.addSystemCommand(&.{ emsdk_script_path, "activate", emsdk_version }); 61 | emsdk_activate.step.dependOn(&emsdk_install.step); 62 | 63 | const step = b.allocator.create(std.Build.Step) catch unreachable; 64 | step.* = std.Build.Step.init(.{ 65 | .id = .custom, 66 | .name = "Activate EMSDK", 67 | .owner = b, 68 | .makeFn = &struct { 69 | fn make(_: *std.Build.Step, _: std.Build.Step.MakeOptions) anyerror!void {} 70 | }.make, 71 | }); 72 | 73 | switch (builtin.target.os.tag) { 74 | .linux, .macos => { 75 | const chmod_emcc = b.addSystemCommand(&.{ "chmod", "+x", emccPath(b) }); 76 | chmod_emcc.step.dependOn(&emsdk_activate.step); 77 | 78 | const chmod_emrun = b.addSystemCommand(&.{ "chmod", "+x", emrunPath(b) }); 79 | chmod_emrun.step.dependOn(&emsdk_activate.step); 80 | 81 | step.dependOn(&chmod_emcc.step); 82 | step.dependOn(&chmod_emrun.step); 83 | }, 84 | else => {}, 85 | } 86 | 87 | return step; 88 | } 89 | 90 | pub const EmccFlags = std.StringHashMap(void); 91 | 92 | pub fn emccDefaultFlags(allocator: std.mem.Allocator, options: struct { 93 | optimize: std.builtin.OptimizeMode, 94 | fsanitize: bool, 95 | }) EmccFlags { 96 | var args = EmccFlags.init(allocator); 97 | switch (options.optimize) { 98 | .Debug => { 99 | args.put("-O0", {}) catch unreachable; 100 | args.put("-gsource-map", {}) catch unreachable; 101 | if (options.fsanitize) 102 | args.put("-fsanitize=undefined", {}) catch unreachable; 103 | }, 104 | .ReleaseSafe => { 105 | args.put("-O3", {}) catch unreachable; 106 | if (options.fsanitize) { 107 | args.put("-fsanitize=undefined", {}) catch unreachable; 108 | args.put("-fsanitize-minimal-runtime", {}) catch unreachable; 109 | } 110 | }, 111 | .ReleaseFast => { 112 | args.put("-O3", {}) catch unreachable; 113 | }, 114 | .ReleaseSmall => { 115 | args.put("-Oz", {}) catch unreachable; 116 | }, 117 | } 118 | return args; 119 | } 120 | 121 | pub const EmccSettings = std.StringHashMap([]const u8); 122 | 123 | pub fn emccDefaultSettings( 124 | allocator: std.mem.Allocator, 125 | options: struct { 126 | optimize: std.builtin.OptimizeMode, 127 | emsdk_allocator: enum { 128 | none, 129 | dlmalloc, 130 | emmalloc, 131 | @"emmalloc-debug", 132 | @"emmalloc-memvalidate", 133 | @"emmalloc-verbose", 134 | mimalloc, 135 | } = .emmalloc, 136 | shell_file: ?[]const u8 = null, 137 | }, 138 | ) EmccSettings { 139 | var settings = EmccSettings.init(allocator); 140 | switch (options.optimize) { 141 | .Debug, .ReleaseSafe => { 142 | settings.put("SAFE_HEAP", "1") catch unreachable; 143 | settings.put("STACK_OVERFLOW_CHECK", "1") catch unreachable; 144 | settings.put("ASSERTIONS", "1") catch unreachable; 145 | }, 146 | else => {}, 147 | } 148 | settings.put("USE_OFFSET_CONVERTER", "1") catch unreachable; 149 | settings.put("MALLOC", @tagName(options.emsdk_allocator)) catch unreachable; 150 | return settings; 151 | } 152 | 153 | pub const EmccFilePath = struct { 154 | src_path: []const u8, 155 | virtual_path: ?[]const u8 = null, 156 | }; 157 | 158 | pub fn emccStep( 159 | b: *std.Build, 160 | wasm: *std.Build.Step.Compile, 161 | options: struct { 162 | optimize: std.builtin.OptimizeMode, 163 | flags: EmccFlags, 164 | settings: EmccSettings, 165 | use_preload_plugins: bool = false, 166 | embed_paths: ?[]const EmccFilePath = null, 167 | preload_paths: ?[]const EmccFilePath = null, 168 | shell_file_path: ?[]const u8 = null, 169 | install_dir: std.Build.InstallDir, 170 | }, 171 | ) *std.Build.Step { 172 | var emcc = b.addSystemCommand(&.{emccPath(b)}); 173 | 174 | var iterFlags = options.flags.iterator(); 175 | while (iterFlags.next()) |kvp| { 176 | emcc.addArg(kvp.key_ptr.*); 177 | } 178 | 179 | var iterSettings = options.settings.iterator(); 180 | while (iterSettings.next()) |kvp| { 181 | emcc.addArg(std.fmt.allocPrint( 182 | b.allocator, 183 | "-s{s}={s}", 184 | .{ kvp.key_ptr.*, kvp.value_ptr.* }, 185 | ) catch unreachable); 186 | } 187 | 188 | emcc.addArtifactArg(wasm); 189 | { 190 | for (wasm.root_module.getGraph().modules) |module| { 191 | for (module.link_objects.items) |link_object| { 192 | switch (link_object) { 193 | .other_step => |compile_step| { 194 | switch (compile_step.kind) { 195 | .lib => { 196 | emcc.addArtifactArg(compile_step); 197 | }, 198 | else => {}, 199 | } 200 | }, 201 | else => {}, 202 | } 203 | } 204 | } 205 | } 206 | 207 | emcc.addArg("-o"); 208 | const out_file = emcc.addOutputFileArg(b.fmt("{s}.html", .{wasm.name})); 209 | 210 | if (options.use_preload_plugins) { 211 | emcc.addArg("--use-preload-plugins"); 212 | } 213 | 214 | if (options.embed_paths) |embed_paths| { 215 | for (embed_paths) |path| { 216 | const path_arg = if (path.virtual_path) |virtual_path| 217 | std.fmt.allocPrint( 218 | b.allocator, 219 | "{s}@{s}", 220 | .{ path.src_path, virtual_path }, 221 | ) catch unreachable 222 | else 223 | path.src_path; 224 | emcc.addArgs(&.{ "--embed-file", path_arg }); 225 | } 226 | } 227 | 228 | if (options.preload_paths) |preload_paths| { 229 | for (preload_paths) |path| { 230 | const path_arg = if (path.virtual_path) |virtual_path| 231 | std.fmt.allocPrint( 232 | b.allocator, 233 | "{s}@{s}", 234 | .{ path.src_path, virtual_path }, 235 | ) catch unreachable 236 | else 237 | path.src_path; 238 | emcc.addArgs(&.{ "--preload-file", path_arg }); 239 | } 240 | } 241 | 242 | if (options.shell_file_path) |shell_file_path| { 243 | emcc.addArgs(&.{ "--shell-file", shell_file_path }); 244 | } 245 | 246 | const install_step = b.addInstallDirectory(.{ 247 | .source_dir = out_file.dirname(), 248 | .install_dir = options.install_dir, 249 | .install_subdir = "", 250 | }); 251 | install_step.step.dependOn(&emcc.step); 252 | 253 | return &install_step.step; 254 | } 255 | 256 | pub fn emrunStep( 257 | b: *std.Build, 258 | html_path: []const u8, 259 | extra_args: []const []const u8, 260 | ) *std.Build.Step { 261 | var emrun = b.addSystemCommand(&.{emrunPath(b)}); 262 | emrun.addArgs(extra_args); 263 | emrun.addArg(html_path); 264 | // emrun.addArg("--"); 265 | 266 | return &emrun.step; 267 | } 268 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = .zemscripten, 3 | .fingerprint = 0x2de6d80ca84319b1, // Changing this has security and trust implications. 4 | .version = "0.2.0-dev", 5 | .paths = .{ 6 | "build.zig", 7 | "build.zig.zon", 8 | "src", 9 | "LICENSE", 10 | "README.md", 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /src/zemscripten.zig: -------------------------------------------------------------------------------- 1 | //! Zig bindings and glue for Emscripten 2 | 3 | const std = @import("std"); 4 | 5 | comptime { 6 | _ = std.testing.refAllDeclsRecursive(@This()); 7 | } 8 | 9 | pub extern fn emscripten_sleep(ms: u32) void; 10 | 11 | pub const MainLoopCallback = *const fn () callconv(.C) void; 12 | extern fn emscripten_set_main_loop(MainLoopCallback, c_int, c_int) void; 13 | pub fn setMainLoop(cb: MainLoopCallback, maybe_fps: ?i16, simulate_infinite_loop: bool) void { 14 | emscripten_set_main_loop(cb, if (maybe_fps) |fps| fps else -1, @intFromBool(simulate_infinite_loop)); 15 | } 16 | 17 | extern fn emscripten_cancel_main_loop() void; 18 | pub fn cancelMainLoop() void { 19 | emscripten_cancel_main_loop(); 20 | } 21 | 22 | pub const MainLoopArgCallback = *const fn (arg: *anyopaque) callconv(.C) void; 23 | extern fn emscripten_set_main_loop_arg(MainLoopArgCallback, arg: *anyopaque, c_int, c_int) void; 24 | pub fn setMainLoopArg(cb: MainLoopArgCallback, arg: *anyopaque, maybe_fps: ?i16, simulate_infinite_loop: bool) void { 25 | emscripten_set_main_loop_arg(cb, arg, if (maybe_fps) |fps| fps else -1, @intFromBool(simulate_infinite_loop)); 26 | } 27 | 28 | pub const AnimationFrameCallback = *const fn (f64, ?*anyopaque) callconv(.C) c_int; 29 | extern fn emscripten_request_animation_frame_loop(AnimationFrameCallback, ?*anyopaque) void; 30 | pub const requestAnimationFrameLoop = emscripten_request_animation_frame_loop; 31 | 32 | pub const EmscriptenResult = enum(i16) { 33 | success = 0, 34 | deferred = 1, 35 | not_supported = -1, 36 | failed_not_deferred = -2, 37 | invalid_target = -3, 38 | unknown_target = -4, 39 | invalid_param = -5, 40 | failed = -6, 41 | no_data = -7, 42 | timed_out = -8, 43 | }; 44 | pub const CanvasSizeChangedCallback = *const fn ( 45 | i16, 46 | *anyopaque, 47 | ?*anyopaque, 48 | ) callconv(.C) c_int; 49 | pub fn setResizeCallback( 50 | cb: CanvasSizeChangedCallback, 51 | use_capture: bool, 52 | user_data: ?*anyopaque, 53 | ) EmscriptenResult { 54 | const result = emscripten_set_resize_callback_on_thread( 55 | "2", 56 | user_data, 57 | @intFromBool(use_capture), 58 | cb, 59 | 2, 60 | ); 61 | return @enumFromInt(result); 62 | } 63 | extern fn emscripten_set_resize_callback_on_thread( 64 | [*:0]const u8, 65 | ?*anyopaque, 66 | c_int, 67 | CanvasSizeChangedCallback, 68 | c_int, 69 | ) c_int; 70 | 71 | pub fn getElementCssSize( 72 | target_id: [:0]const u8, 73 | width: *f64, 74 | height: *f64, 75 | ) EmscriptenResult { 76 | return @enumFromInt(emscripten_get_element_css_size( 77 | target_id, 78 | width, 79 | height, 80 | )); 81 | } 82 | extern fn emscripten_get_element_css_size([*:0]const u8, *f64, *f64) c_int; 83 | 84 | // EmmalocAllocator allocator 85 | // use with linker flag -sMALLOC=emmalloc 86 | // for details see docs: https://github.com/emscripten-core/emscripten/blob/main/system/lib/emmalloc.c 87 | extern fn emmalloc_memalign(u32, u32) ?*anyopaque; 88 | extern fn emmalloc_realloc_try(?*anyopaque, u32) ?*anyopaque; 89 | extern fn emmalloc_free(?*anyopaque) void; 90 | pub const EmmalocAllocator = struct { 91 | const Self = @This(); 92 | dummy: u32 = undefined, 93 | 94 | pub fn allocator(self: *Self) std.mem.Allocator { 95 | return .{ 96 | .ptr = self, 97 | .vtable = &.{ 98 | .alloc = &alloc, 99 | .resize = &resize, 100 | .remap = &remap, 101 | .free = &free, 102 | }, 103 | }; 104 | } 105 | 106 | fn alloc( 107 | ctx: *anyopaque, 108 | len: usize, 109 | ptr_align_log2: std.mem.Alignment, 110 | return_address: usize, 111 | ) ?[*]u8 { 112 | _ = ctx; 113 | _ = return_address; 114 | const ptr_align: u32 = @as(u32, 1) << @as(u5, @intFromEnum(ptr_align_log2)); 115 | if (!std.math.isPowerOfTwo(ptr_align)) unreachable; 116 | const ptr = emmalloc_memalign(ptr_align, len) orelse return null; 117 | return @ptrCast(ptr); 118 | } 119 | 120 | fn resize( 121 | ctx: *anyopaque, 122 | buf: []u8, 123 | buf_align_log2: std.mem.Alignment, 124 | new_len: usize, 125 | return_address: usize, 126 | ) bool { 127 | _ = ctx; 128 | _ = return_address; 129 | _ = buf_align_log2; 130 | return emmalloc_realloc_try(buf.ptr, new_len) != null; 131 | } 132 | 133 | fn remap( 134 | ctx: *anyopaque, 135 | buf: []u8, 136 | buf_align_log2: std.mem.Alignment, 137 | new_len: usize, 138 | return_address: usize, 139 | ) ?[*]u8 { 140 | return if (resize(ctx, buf, buf_align_log2, new_len, return_address)) buf.ptr else null; 141 | } 142 | 143 | fn free( 144 | ctx: *anyopaque, 145 | buf: []u8, 146 | buf_align_log2: std.mem.Alignment, 147 | return_address: usize, 148 | ) void { 149 | _ = ctx; 150 | _ = buf_align_log2; 151 | _ = return_address; 152 | return emmalloc_free(buf.ptr); 153 | } 154 | }; 155 | 156 | extern fn emscripten_err([*c]const u8) void; 157 | extern fn emscripten_console_error([*c]const u8) void; 158 | extern fn emscripten_console_warn([*c]const u8) void; 159 | extern fn emscripten_console_log([*c]const u8) void; 160 | /// std.panic impl 161 | pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn { 162 | _ = error_return_trace; 163 | _ = ret_addr; 164 | 165 | var buf: [1024]u8 = undefined; 166 | const error_msg: [:0]u8 = std.fmt.bufPrintZ(&buf, "PANIC! {s}", .{msg}) catch unreachable; 167 | emscripten_err(error_msg.ptr); 168 | 169 | while (true) { 170 | @breakpoint(); 171 | } 172 | } 173 | 174 | /// std.log impl 175 | pub fn log( 176 | comptime level: std.log.Level, 177 | comptime scope: @TypeOf(.EnumLiteral), 178 | comptime format: []const u8, 179 | args: anytype, 180 | ) void { 181 | const level_txt = comptime level.asText(); 182 | const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; 183 | const prefix = level_txt ++ prefix2; 184 | 185 | var buf: [1024]u8 = undefined; 186 | const msg = std.fmt.bufPrintZ(buf[0 .. buf.len - 1], prefix ++ format, args) catch |err| { 187 | switch (err) { 188 | error.NoSpaceLeft => { 189 | emscripten_console_error("log message too long, skipped."); 190 | return; 191 | }, 192 | } 193 | }; 194 | switch (level) { 195 | .err => emscripten_console_error(@ptrCast(msg.ptr)), 196 | .warn => emscripten_console_warn(@ptrCast(msg.ptr)), 197 | else => emscripten_console_log(@ptrCast(msg.ptr)), 198 | } 199 | } 200 | --------------------------------------------------------------------------------