Hi. I’m trying to make a game with Raylib + emscripten for WASM. The problem is I get an “Uncaught” error in the Browser console. When debugging I noticed the problem seems to be with my readFile function to read my maps. It opens the file successfully but when reading the file to store the data into an array list, it just doesn’t work. The only case I found it worked is by using a fixed buffer but it’s not very useful since I need to specify the size of the buffer. Here’s the code:
fn readFile(allocator: std.mem.Allocator, filePath: []const u8) ![]u8 {
std.log.info("starting to read the map file at {s}", .{filePath});
if (std.fs.cwd().openFile(filePath, .{})) |file| {
std.log.info("No errors after reading the file", .{});
defer file.close();
var buf_reader = std.io.bufferedReader(file.reader());
var stream = buf_reader.reader();
std.log.info("array list initiated for converting data into u8", .{});
const file_size: usize = @intCast((try file.stat()).size);
std.log.info("this is the file usize: {d}", .{file_size});
//THIS DOESN'T PRINT
if (stream.readAllAlloc(allocator, file_size)) |data| {
std.log.info("this is the data of the file: {s}", .{data});
return data;
} else |_| {
std.log.info("Something went wrong with readAllArrayList", .{});
}
return error.SomethingWentWrong;
} else |_| {
std.log.info("Couldn't read the file at {s}", .{filePath});
return error.SomethingWentWrong;
}
}
What I’m expecting here is to see a log with either “this is the data of the file: {s}” or “Something went wrong with readAllArrayList”, but I don’t see anything after readAllAlloc(). Here’s the logs:
raylib-game on HEAD (76cb349) [✘!?] via ↯ v0.14.1 took 50s
✦ ❯ zig build web -Dtarget=wasm32-emscripten --sysroot /usr/lib/emsdk/upstream/emscripten
Now listening at http://0.0.0.0:6931/
Opening in existing browser session.
info: Initiating Window
INFO: Initializing raylib 5.6-dev
INFO: Platform backend: WEB (HTML5)
INFO: Supported raylib modules:
INFO: > rcore:..... loaded (mandatory)
INFO: > rlgl:...... loaded (mandatory)
INFO: > rshapes:... loaded (optional)
INFO: > rtextures:. loaded (optional)
INFO: > rtext:..... loaded (optional)
INFO: > rmodels:... loaded (optional)
INFO: > raudio:.... loaded (optional)
INFO: DISPLAY: Device initialized successfully
INFO: > Display size: 1920 x 1080
INFO: > Screen size: 1920 x 1080
INFO: > Render size: 1920 x 1080
INFO: > Viewport offsets: 0, 0
INFO: GL: Supported extensions count: 73
INFO: GL: OpenGL device information:
INFO: > Vendor: WebKit
INFO: > Renderer: WebKit WebGL
INFO: > Version: OpenGL ES 2.0 (WebGL 1.0 (OpenGL ES 2.0 Chromium))
INFO: > GLSL: OpenGL ES GLSL ES 1.00 (WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium))
INFO: GL: VAO extension detected, VAO functions loaded successfully
WARNING: GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)
INFO: GL: DXT compressed textures supported
INFO: GL: ETC1 compressed textures supported
INFO: PLATFORM: WEB: Initialized successfully
INFO: TEXTURE: [ID 2] Texture loaded successfully (1x1 | R8G8B8A8 | 1 mipmaps)
INFO: TEXTURE: [ID 2] Default texture loaded successfully
INFO: SHADER: [ID 3] Vertex shader compiled successfully
INFO: SHADER: [ID 4] Fragment shader compiled successfully
INFO: SHADER: [ID 5] Program shader loaded successfully
INFO: SHADER: [ID 5] Default shader loaded successfully
INFO: RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)
INFO: RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)
INFO: RLGL: Default OpenGL state initialized successfully
INFO: TEXTURE: [ID 12] Texture loaded successfully (128x128 | GRAY_ALPHA | 1 mipmaps)
INFO: FONT: Default font loaded successfully (224 glyphs)
INFO: SYSTEM: Working Directory: /
--------------------------------------------------- LOGS FROM main.zig
info: Initiating ArenaAllocator
info: Parsing the TiledMap
info: starting to parse the map
----------------------------------------------- LOGS FROM THE READFILE FUNCTION
info: starting to read the map file at ./assets/maps/export/initial.json
info: No errors after reading the file
info: array list initiated for converting data into u8
info: this is the file usize: 85888
----------------------------------------------- PROGRAM STILL RUNNING BUT NOTHING LOGGED
…this basically makes synchronous fopen/fread/fclose work on Emscripten by storing the files in memory before main() is called - and I guess in Zig this works because Emscripten exposes the underlying syscalls to the Zig stdlib.
As for why this unreachable happens, no idea unfortunately - might be a Zig @panic or unreachable somewhere in the stdlib because something isn’t implemented in the Emscripten runtime layer.
In C I cobbled together a debugging environment via the VSCode WASM DWARF extension so that it’s possible to step through the code in the browser runtime environment, but I didn’t get around yet making this work for Zig too: WASM Debugging with Emscripten and VSCode
…the important parts are the .vscode/launch.json and .vscode/tasks.json snippets towards the end of the blog post.
Thanks for the response. No, I wasn’t using that but as I read more about emscripten it seems I will have to refactor my code to use its API here and there to have good support for the web version. I will take my time to read that page and a bit more about emscripten. Do you know maybe other areas aside from filesystem stuff where I will also probably need to use the emscripten API?
I think you can work around that by directling using the emscripten_set_main_loop() or emscripten_request_animation_frame_loop() approach (set-main-loop is higher level than request-animation-frame-loop)
For async file loading you could also look at the Zig bindings for sokol_fetch.h in the sokol-zig package:
…I must admit though that I haven’t used this yet in Zig code, only in C/C++ projects.
E.g. here’s a sample which asynchronously loads a texture vis sokol_fetch.h stb_image.h:
Click on the “src” link bottom left to look at the C code and search for:
From my experiments a while back it seems that allocations only really work if you enable shared memory.
However shared memory needs additional CORS headers to be set, because by default browsers disable shared memory, because it can potentially be used to create side channel/timing attacks. This is basically so that a random ad embedded into a website isn’t allowed to use shared memory.
A whole other way is to just not allocate, this only works if you could instead use @embedFile to directly embed the files into your program (only makes sense if they are tiny enough and don’t change too often) and if your other allocations can be turned into fixed size allocations.
I am not entirely sure, but I think it is also possible to use a fixed-size allocator if you can estimate an upper bound for max needed memory.
That said, I think if you need dynamically growing memory in the browser, you will need to enable shared memory.
So basically I think you aren’t even getting to the point where you are trying to load the file, instead you probably already error trying to allocate memory.
Another possibility would be to use emscriptens --embed-file path option which also can be used with a directory to embed a whole directory of assets into your generated webassembly output.
The benefit with that is that it can make it so that your app works pretty similar in desktop and web (not requiring many changes), because emscripten basically packages the file somewhere into its virtual file system and just delivers them alongside your webassembly (I think it ends up in a .data file alongside the .wasm)
IME heap allocation works without shared memory (although I had to specifically verify because so far my code mostly allocates on the C side), e.g. this works:
However, when linking with emcc and trying to run that in the browser you get a runtime error with a message:
Aborted(Cannot use convertFrameToPC (needed by __builtin_return_address) without -sUSE_OFFSET_CONVERTER)
…e.g. Zig allocators use the builtin @returnAddress, and this isn’t supported by default by Emscripten. To make that work you have to invoke the emcc link step with the cmdline arg -sUSE_OFFSET_CONVERTER and after that it works fine.
(it looks like this is only a problem in debug mode though)