-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use case: Web support using Emscripten #10836
Comments
I'm happy to look into this as I've got a running example of a cross between C++ that requires both threading and exceptions, and pure Zig code. Ideal for testing the emscripten target triple. |
Hey @kripken thanks for taking the time to help us! Interesting non-essential observations: I will try again later on with my own code, just in case. Let me know if there is anything you'd like me to test. I have latest emcc and zig. |
@KioriSun Oh, sorry, looks like I forgot to write that you need to manually add |
Thanks it works. I was wondering about the A hint for everyone else that might give this a shot: I couldn't get the _run_zig() automated, tried adding it to all sorts of onload functions, didn't work. only running from the console so far. Now that it's at least running form the console, I wanna try other apis. But I assume they'll run fine. About automating the run function, for the time being, couldn't a properly placed EM_JS call solve that? |
Yes, exactly - that's one of the things that I think could be worked on, as part of this topic.
Hmm, no, because EM_JS happens after startup. But a generic solution to this could be to add this to the HTML: Module.onRuntimeInitialized = function() {
_run_zig();
}; That uses one of the emscripten JS startup hooks. But this is kind of a hack that we should fix with proper integration in the Zig and emcc startup code, as mentioned before. |
This is exactly what i was looking for in terms of lifetimes. But it didn't work for me. Maybe I'm missing something. I tried adding it into a script tag, body onload, and adding it directly into the zig.js that emcc builds. Nothing seems to work only running from the console. If EM_JS happens after "startup", it also happens before or after "onRuntimeInitialized"? Because this is what I thought might work as a in code hack because if the objs are already there, maybe the function can be added, maybe it can't. |
I can 'contribute' a little real-world example here for testing: https://github.com/floooh/pacman.zig WASM version here: https://floooh.github.io/pacman.zig/pacman.html Build instructions: https://github.com/floooh/pacman.zig#experimental-web-support The project is a mix of cross-platform C headers which makes use of Emscripten APIs and EM_JS() feature, and platform-agnostic Zig code for the gameplay stuff. To make the build work I had to compile the Zig parts with https://github.com/floooh/pacman.zig/blob/549a73ecd6f5c9bfe4f8150b08e4b43f02eae331/build.zig#L88-L91 To reproduce the problem just comment out this line: https://github.com/floooh/pacman.zig/blob/549a73ecd6f5c9bfe4f8150b08e4b43f02eae331/build.zig#L89 ...trying to build then produces this error:
Cheers! |
PS: as I learned from this thread (https://groups.google.com/g/emscripten-discuss/c/fWPEAulgslM), building the C code with I think the biggest motivation to better support wasm32-emscripten in Zig is that this could pave the road for an EM_JS()-like feature directly in Zig :) ...ideally at some later point it would be great if such special Emscripten linker features could be somehow integrated into the Zig toolchain, or if this is 'out of scope" maybe a better way to integrate a slim EMSDK (just the linker, sysroot and JS shims) with a Zig build. |
It's possible that adding that hook in the wrong place will not work. Here is what works for me locally, this is near the end of the HTML file: };
};
Module.onRuntimeInitialized = function() { _run_zig() }; // this is the new line
</script>
<script async type="text/javascript" src="a.js"></script> |
Thanks a lot! I found the issue. All of the above works if you do |
That's odd, it really shouldn't matter. |
The next steps for this (if this were to be accepted), would be to provide proper std support for the wasm32-emscripten target triple, similar to how we provide support for WASI. This also means that the startup code of Zig must be made aware of Emscripten, so functions such as |
I am using RayLib with Zig and I can compile my program with Here is the code for const std = @import("std");
const c = @cImport({
@cInclude("raylib.h");
});
export fn main() void {
const screenWidth = 800;
const screenHeight = 480;
const windowTitle = "funLab";
c.InitWindow(screenWidth, screenHeight, windowTitle);
while (!c.WindowShouldClose()) {
_update();
}
c.CloseWindow();
}
fn _update() void {
const Msg = "Zig x C x RayLib";
c.BeginDrawing();
c.ClearBackground(c.RAYWHITE);
c.DrawText(Msg, 200, 240, 50, c.LIGHTGRAY);
c.EndDrawing();
} Building object file from zig zig build-obj -isystem ~/.mybin/emsdk/upstream/emscripten/cache/sysroot/include src/main.zig -I. -L. -target wasm32-wasi Finish building with emscripten emcc main.o -o index.html -s FULL_ES3 -s USE_GLFW=3 -s ASYNCIFY -s STANDALONE_WASM -s -s EXPORTED_FUNCTIONS=_main -lraylib -I. -L. --no-entry -O3 -Wall -sASSERTIONS --shell-file minshell.html -sSAFE_HEAP I get an Here is the references: |
(deleted my previous comment because it was full of false information, see end of the comment for current workarounds) Current state in pacman.zig with the final zig-0.11.0 version is: Trying to build Zig code with
Trying to build Zig code which has a
What I'm doing now in pacman.zig for the 'web browser target':
PS: for reference: https://github.com/floooh/pacman.zig/blob/a2ce623da2a564beea5aea4512c48e9e1d260fc7/build.zig#L48-L134 |
I was able to get emscripten builds working for my own projects with guidance from the gist linked above and the build files from projects such as raylib-zig. From there, I ended up testing out some additions to the standard library that would add basic support for the I copied the supported portions of To build a native project for the web you should just need to use |
It's been possible to target emscripten as the "OS" of zig for a while now. Any improvements to the standard library to support this OS are welcome. |
Btw, confirmed that this is working now. Nice :) |
Not sure where I should note my Emscripten findings but I'm gonna dump them here for now so others can discover them.
// Force allocator to use c allocator for emscripten
pub const os = if ( builtin.os.tag != .wasi) std.os else struct {
pub const heap = struct {
pub const page_allocator = std.heap.c_allocator;
};
};
emcc_command.addArgs(&[_][]const u8{
"-g", // adds debugging information to build, ie. get symbol names for functions where crash occurred, you need "strip = false" in your build
"-o",
emccOutputDir ++ emccOutputFile,
"-sFULL-ES3=1",
"-sUSE_GLFW=3",
"-sINITIAL_MEMORY=512Mb", // increased default heap memory
"-sSTACK_SIZE=1000000", // 1mb, raised to what Windows has to prevent a crash in my app
// Debug options
"-sSAFE_HEAP=1",
"-sASSERTIONS=1", // note(jae): ASSERTIONS=2 crashes due to not rounding down mouse position for SDL2, https://github.com/emscripten-core/emscripten/issues/19655
"-sSTACK_OVERFLOW_CHECK=1",
"-sUSE_OFFSET_CONVERTER=1", // not sure if this is necessary anymore for Zig builds.
"-sMALLOC='dlmalloc'",
"-sASYNCIFY",
"-sASYNCIFY_STACK_SIZE=5120000", // I increased this randomly to stop problems, not sure how necessary this change was.
"-O0", // "-O3", //-Og = debug
"--emrun",
});
link_step.addArg("--embed-file");
link_step.addArg("src/assets@/wasm_data"); // "src/assets" is a real folder in the code repo, "wasm_data" is the directory it maps to when embedded. // belongs in your main.zig
pub const std_options: std.Options = .{ .wasiCwd = if (builtin.os.tag == .wasi) defaultWasiCwd else std.fs.defaultWasiCwd };
var default_wasi_dir = if (builtin.os.tag == .wasi) std.fs.defaultWasiCwd() else void;
pub fn defaultWasiCwd() std.os.wasi.fd_t {
// Expect the first preopen to be current working directory.
return default_wasi_dir;
}
pub fn main() !void {
if (builtin.os.tag == .wasi) {
const dir = try std.fs.cwd().openDir("/wasm_data", .{});
default_wasi_dir = dir.fd;
}
} NOTE: If you're wondering why I did this, it's because even if you do the following when mapping your embed file, to be able to see files in emscripten/web builds, you'd need to prefix each file open with link_step.addArg("--embed-file");
link_step.addArg("src/assets@"); |
(Background: This reddit post where @andrewrk suggested I file this issue.)
I think it would be useful for Zig to allow compiling the same code both natively and to the Web.
Zig already supports the Web to some extent with the wasi and freestanding wasm targets. The difference with Emscripten is in API support: Emscripten allows common native APIs to be compiled to the Web, such as SDL2, OpenGL, OpenAL, pthreads, and so forth. A very common use case for Emscripten is to port a game engine, and that's why Unity and many others use it. As game engines begin to be written in Zig, allowing the same there as for C++ would be nice I think. (Of course, this isn't limited to games: Photoshop, Figma, AutoCAD, etc. also use Emscripten.)
What this means concretely: this gist has an example of a C program that uses GLES3 and GLFW to render, and relies on Asyncify to handle the Web event loop. That exact same program can be compiled with gcc or clang natively, or with emcc to the Web - that's the big benefit here.
The gist also has an example of a Zig program that I managed to do the same with, with some hacks. With small (I hope) improvements to Zig I think the hacks could be avoided, namely to allow setting the Emscripten target triple, and to get the full Zig stdlib compiling with that triple (hopefully simple, as the differences between the Emscripten wasm triple and the WASI wasm triple are very small). Emscripten itself would do all the rest, when the user runs it on the object files emitted from Zig (see details in that gist; there may also be more interesting Zig build system integration opportunities, but I don't know enough about that).
Why is it useful to compile the exact same code both natively and to the Web? You don't have to, of course - you can have separate codebases for the two platforms. For example, you could add a new rendering backend to your game engine to use WebGL, you can rewrite all your event handling logic to be async like the Web requires, rethink how you handle files, etc., and all the other stuff that is different on the Web. Such rewrites may have benefits, sometimes. But it makes porting harder, it makes debugging harder, and you may end up handling the mismatches between native and the Web in a sub-optimal way. Reusing the large effort put into Emscripten here can be useful.
The text was updated successfully, but these errors were encountered: