diff --git a/README.md b/README.md index ff74a93..341cfe9 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,9 @@ want to run an example, say `basic_window` run `zig build basic_window` ### Using raylib-zig's template -* Execute `project_setup.sh project_name`, this will create a folder with the name specified -* You can copy that folder anywhere you want and edit the source -* Run `zig build run` at any time to test your project +- Execute `project_setup.sh project_name`, this will create a folder with the name specified +- You can copy that folder anywhere you want and edit the source +- Run `zig build run` at any time to test your project ### In an existing project (e.g. created with `zig init`) @@ -88,8 +88,8 @@ exe.root_module.addImport("raylib", raylib); exe.root_module.addImport("raygui", raygui); ``` -If you additionally want to support Web as a platform with emscripten, you will need to use `emcc.zig` by importing -raylib-zig's build script with `const rlz = @import("raylib_zig");` and then accessing its functions with `rlz.emcc`. +If you additionally want to support Web as a platform with emscripten, you will need to use `emsdk` by importing +raylib-zig's build script with `const rlz = @import("raylib_zig");` and then accessing like described here [Exporting for web](https://github.com/raylib-zig/raylib-zig?tab=readme-ov-file#exporting-for-web). Refer to raylib-zig's project template on how to use them. ### Passing build options @@ -118,16 +118,50 @@ raylib_artifact.root_module.addCMacro("SUPPORT_FILEFORMAT_JPG", ""); ## Exporting for web -To export your project for the web, first install emsdk. -Once emsdk is installed, set it up by running +To export your project for the web, first add emsdk to your dependencies. +Its also possible to use a local emsdk folder. -`emsdk install latest` +`zig fetch --save git+https://github.com/emscripten-core/emsdk#4.0.9` -Find the folder where it's installed and run +Add this to your build method to build for the web -`zig build -Dtarget=wasm32-emscripten --sysroot [path to emsdk]/upstream/emscripten` +```zig +if (target.query.os_tag == .emscripten) { + const emsdk = rlz.emsdk; + const wasm = b.addLibrary(.{ + .name = , + .root_module = exe_mod, + }); -once that is finished, the exported project should be located at `zig-out/htmlout` + const install_dir: std.Build.InstallDir = .{ .custom = "web" }; + const emcc_flags = emsdk.emccDefaultFlags(b.allocator, .{ .optimize = optimize }); + const emcc_settings = emsdk.emccDefaultSettings(b.allocator, .{ .optimize = optimize }); + + const emcc_step = emsdk.emccStep(b, raylib_artifact, wasm, .{ + .optimize = optimize, + .flags = emcc_flags, + .settings = emcc_settings, + .install_dir = install_dir, + }); + b.getInstallStep().dependOn(emcc_step); + + const html_filename = try std.fmt.allocPrint(b.allocator, "{s}.html", .{wasm.name}); + const emrun_step = emsdk.emrunStep( + b, + b.getInstallPath(install_dir, html_filename), + &.{}, + ); + + emrun_step.dependOn(emcc_step); + run_step.dependOn(emrun_step); +} +``` + +then you can run + +`zig build -Dtarget=wasm32-emscripten` + +once that is finished, the exported project should be located at `zig-out/web` ### When is the binding updated? @@ -136,6 +170,6 @@ implementation stuff should be updatable with some hacks on your side. ### What needs to be done? -+ _(Done)_ Set up a proper package build and a build script for the examples -+ Port all the examples -+ Member functions/initialisers +- _(Done)_ Set up a proper package build and a build script for the examples +- Port all the examples +- Member functions/initialisers diff --git a/build.zig b/build.zig index 36ffc28..dbdd812 100644 --- a/build.zig +++ b/build.zig @@ -3,8 +3,7 @@ const std = @import("std"); const this = @This(); const rl = @import("raylib"); - -pub const emcc = @import("emcc.zig"); +pub const emsdk = rl.emsdk; pub const Options = rl.Options; pub const OpenglVersion = rl.OpenglVersion; @@ -27,7 +26,7 @@ fn getRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.buil .rtext = options.rtext, .rtextures = options.rtextures, .platform = options.platform, - .shared = options.shared, + .linkage = options.linkage, .linux_display_backend = options.linux_display_backend, .opengl_version = options.opengl_version, .android_api_version = options.android_api_version, @@ -390,33 +389,54 @@ pub fn build(b: *std.Build) !void { const examples_step = b.step("examples", "Builds all the examples"); for (examples) |ex| { + const mod = b.createModule(.{ + .root_source_file = b.path(ex.path), + .target = target, + .optimize = optimize, + }); + if (target.query.os_tag == .emscripten) { - const exe_lib = try emcc.compileForEmscripten(b, ex.name, ex.path, target, optimize); - exe_lib.root_module.addImport("raylib", raylib); - exe_lib.root_module.addImport("raygui", raygui); + const wasm = b.addLibrary(.{ + .name = ex.name, + .root_module = mod, + }); + wasm.root_module.addImport("raylib", raylib); + wasm.root_module.addImport("raygui", raygui); + wasm.linkLibrary(raylib_artifact); - // Note that raylib itself isn't actually added to the exe_lib - // output file, so it also needs to be linked with emscripten. - exe_lib.linkLibrary(raylib_artifact); - const link_step = try emcc.linkWithEmscripten(b, &[_]*std.Build.Step.Compile{ exe_lib, raylib_artifact }); - link_step.addArg("--emrun"); - link_step.addArg("--embed-file"); - link_step.addArg("resources/"); + const install_dir: std.Build.InstallDir = .{ .custom = "web" }; + const emcc_flags = emsdk.emccDefaultFlags(b.allocator, .{ + .optimize = optimize, + .asyncify = !std.mem.endsWith(u8, ex.name, "web"), + }); + const emcc_settings = emsdk.emccDefaultSettings(b.allocator, .{ + .optimize = optimize, + }); + + const emcc_step = emsdk.emccStep(b, raylib_artifact, wasm, .{ + .optimize = optimize, + .flags = emcc_flags, + .settings = emcc_settings, + .shell_file_path = emsdk.shell(b), + .install_dir = install_dir, + .embed_paths = &.{.{ .src_path = "resources/" }}, + }); + + const html_filename = try std.fmt.allocPrint(b.allocator, "{s}.html", .{wasm.name}); + const emrun_step = emsdk.emrunStep( + b, + b.getInstallPath(install_dir, html_filename), + &.{}, + ); + emrun_step.dependOn(emcc_step); - const run_step = try emcc.emscriptenRunStep(b); - run_step.step.dependOn(&link_step.step); const run_option = b.step(ex.name, ex.desc); - - run_option.dependOn(&run_step.step); - examples_step.dependOn(&exe_lib.step); + run_option.dependOn(emrun_step); + examples_step.dependOn(emcc_step); } else { const exe = b.addExecutable(.{ .name = ex.name, - .root_module = b.createModule(.{ - .root_source_file = b.path(ex.path), - .target = target, - .optimize = optimize, - }), + .root_module = mod, }); exe.linkLibrary(raylib_artifact); exe.root_module.addImport("raylib", raylib); diff --git a/build.zig.zon b/build.zig.zon index 31512e1..62dd879 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,13 +4,17 @@ .fingerprint = 0xc4cfa8c610114f28, .dependencies = .{ .raylib = .{ - .url = "git+https://github.com/raysan5/raylib?ref=master#f83c5cb6e1b3462c9b723c0ab3b479951aa48458", - .hash = "raylib-5.5.0-whq8uBlJxwRAvOHHNQIi8WS0QTbQJcdx7FbjSSOnPn6n", + .url = "git+https://github.com/raysan5/raylib#82d65e110a2caba862b31a3f1c11ca7a8ba60e3b", + .hash = "raylib-5.6.0-dev-whq8uHRjyARJNFHTJ8fBtYU71IrnB-rTHJwuasjUnRWt", }, .raygui = .{ .url = "git+https://github.com/raysan5/raygui#6530ee136b3c5af86c5640151f07837a604308ec", .hash = "N-V-__8AAOQabwCjOjMI2uUTw4Njc0tAUOO6Lw2kCydLbvVG", }, + .emsdk = .{ + .url = "git+https://github.com/emscripten-core/emsdk#4.0.9", + .hash = "N-V-__8AAJl1DwBezhYo_VE6f53mPVm00R-Fk28NPW7P14EQ", + }, }, .minimum_zig_version = "0.15.1", .paths = .{ diff --git a/emcc.zig b/emcc.zig deleted file mode 100644 index 32d5bed..0000000 --- a/emcc.zig +++ /dev/null @@ -1,126 +0,0 @@ -// raylib-zig (c) Nikolas Wipper 2020-2024 - -const std = @import("std"); -const builtin = @import("builtin"); - -const emccOutputDir = "zig-out" ++ std.fs.path.sep_str ++ "htmlout" ++ std.fs.path.sep_str; -const emccOutputFile = "index.html"; -pub fn emscriptenRunStep(b: *std.Build) !*std.Build.Step.Run { - // If compiling on windows , use emrun.bat. - const emrunExe = switch (builtin.os.tag) { - .windows => "emrun.bat", - else => "emrun", - }; - var emrun_run_arg = try b.allocator.alloc(u8, b.sysroot.?.len + emrunExe.len + 1); - defer b.allocator.free(emrun_run_arg); - - if (b.sysroot == null) { - emrun_run_arg = try std.fmt.bufPrint(emrun_run_arg, "{s}", .{emrunExe}); - } else { - emrun_run_arg = try std.fmt.bufPrint(emrun_run_arg, "{s}" ++ std.fs.path.sep_str ++ "{s}", .{ b.sysroot.?, emrunExe }); - } - - const run_cmd = b.addSystemCommand(&[_][]const u8{ emrun_run_arg, emccOutputDir ++ emccOutputFile }); - return run_cmd; -} - -// Creates the static library to build a project for Emscripten. -pub fn compileForEmscripten( - b: *std.Build, - name: []const u8, - root_source_file: []const u8, - target: std.Build.ResolvedTarget, - optimize: std.builtin.OptimizeMode, -) !*std.Build.Step.Compile { - // TODO: It might be a good idea to create a custom compile step, that does - // both the compile to static library and the link with emcc by overidding - // the make function of the step. However it might also be a bad idea since - // it messes with the build system itself. - - // The project is built as a library and linked later. - const lib = b.addLibrary(.{ - .name = name, - .linkage = .static, - .root_module = b.createModule(.{ - .root_source_file = b.path(root_source_file), - .target = target, - .optimize = optimize, - }), - }); - - const emscripten_headers = try std.fs.path.join(b.allocator, &.{ b.sysroot.?, "cache", "sysroot", "include" }); - defer b.allocator.free(emscripten_headers); - lib.addIncludePath(.{ .cwd_relative = emscripten_headers }); - return lib; -} - -// Links a set of items together using emscripten. -// -// Will accept objects and static libraries as items to link. As for files to -// include, it is recomended to have a single resources directory and just pass -// the entire directory instead of passing every file individually. The entire -// path given will be the path to read the file within the program. So, if -// "resources/image.png" is passed, your program will use "resources/image.png" -// as the path to load the file. -// -// TODO: Test if shared libraries are accepted, I don't remember if emcc can -// link a shared library with a project or not. -// TODO: Add a parameter that allows a custom output directory. -pub fn linkWithEmscripten( - b: *std.Build, - itemsToLink: []const *std.Build.Step.Compile, -) !*std.Build.Step.Run { - const emccExe = switch (builtin.os.tag) { - .windows => "emcc.bat", - else => "emcc", - }; - var emcc_run_arg = try b.allocator.alloc(u8, b.sysroot.?.len + emccExe.len + 1); - defer b.allocator.free(emcc_run_arg); - - if (b.sysroot == null) { - emcc_run_arg = try std.fmt.bufPrint(emcc_run_arg, "{s}", .{emccExe}); - } else { - emcc_run_arg = try std.fmt.bufPrint( - emcc_run_arg, - "{s}" ++ std.fs.path.sep_str ++ "{s}", - .{ b.sysroot.?, emccExe }, - ); - } - - // Create the output directory because emcc can't do it. - const mkdir_command = switch (builtin.os.tag) { - .windows => b.addSystemCommand(&.{ "cmd.exe", "/c", "if", "not", "exist", emccOutputDir, "mkdir", emccOutputDir }), - else => b.addSystemCommand(&.{ "mkdir", "-p", emccOutputDir }), - }; - - // Actually link everything together. - const emcc_command = b.addSystemCommand(&[_][]const u8{emcc_run_arg}); - - for (itemsToLink) |item| { - emcc_command.addFileArg(item.getEmittedBin()); - emcc_command.step.dependOn(&item.step); - } - // This puts the file in zig-out/htmlout/index.html. - emcc_command.step.dependOn(&mkdir_command.step); - emcc_command.addArgs(&[_][]const u8{ - "-o", - emccOutputDir ++ emccOutputFile, - "-sUSE_OFFSET_CONVERTER", - "-sFULL-ES3=1", - "-sUSE_GLFW=3", - "-sASYNCIFY", - "-O3", - "-fsanitize=undefined", - }); - return emcc_command; -} - -// TODO: See if zig's standard library already has somehing like this. -fn lastIndexOf(string: []const u8, character: u8) usize { - // Interestingly, Zig has no nice way of iterating a slice backwards. - for (0..string.len) |i| { - const index = string.len - i - 1; - if (string[index] == character) return index; - } - return string.len - 1; -} diff --git a/examples/core/basic_window_web.zig b/examples/core/basic_window_web.zig index 66e85f7..9d91a56 100644 --- a/examples/core/basic_window_web.zig +++ b/examples/core/basic_window_web.zig @@ -1,11 +1,10 @@ // A raylib-zig port of https://github.com/raysan5/raylib/blob/master/examples/core/core_basic_window_web.c +const std = @import("std"); const rl = @import("raylib"); const builtin = @import("builtin"); -const c = if (builtin.os.tag == .emscripten) @cImport({ - @cInclude("emscripten/emscripten.h"); -}); +const emscripten = std.os.emscripten; //---------------------------------------------------------------------------------- // Global Variables Definition @@ -24,7 +23,7 @@ pub fn main() anyerror!void { defer rl.closeWindow(); // Close window and OpenGL context if (builtin.os.tag == .emscripten) { - c.emscripten_set_main_loop(@ptrCast(&updateDrawFrame), 0, 1); + emscripten.emscripten_set_main_loop(@ptrCast(&updateDrawFrame), 0, 1); } else { rl.setTargetFPS(60); // Set our game to run at 60 frames-per-second diff --git a/project_setup.ps1 b/project_setup.ps1 index 8a78322..14da57a 100644 --- a/project_setup.ps1 +++ b/project_setup.ps1 @@ -19,60 +19,83 @@ const rlz = @import("raylib_zig"); pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - + const raylib_dep = b.dependency("raylib_zig", .{ .target = target, .optimize = optimize, }); - const raylib = raylib_dep.module("raylib"); const raylib_artifact = raylib_dep.artifact("raylib"); + const exe_mod = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + exe_mod.addImport("raylib", raylib); + exe_mod.linkLibrary(raylib_artifact); + + const run_step = b.step("run", "Run the app"); + //web exports are completely separate if (target.query.os_tag == .emscripten) { - const exe_lib = try rlz.emcc.compileForEmscripten(b, "$PROJECT_NAME", "src/main.zig", target, optimize); + const emsdk = rlz.emsdk; + const wasm = b.addLibrary(.{ + .name = "$PROJECT_NAME", + .root_module = exe_mod, + }); - exe_lib.linkLibrary(raylib_artifact); - exe_lib.root_module.addImport("raylib", raylib); - - // Note that raylib itself is not actually added to the exe_lib output file, so it also needs to be linked with emscripten. - const link_step = try rlz.emcc.linkWithEmscripten(b, &[_]*std.Build.Step.Compile{ exe_lib, raylib_artifact }); - //this lets your program access files like "resources/my-image.png": - link_step.addArg("--emrun"); - link_step.addArg("--embed-file"); - link_step.addArg("resources/"); - - b.getInstallStep().dependOn(&link_step.step); - const run_step = try rlz.emcc.emscriptenRunStep(b); - run_step.step.dependOn(&link_step.step); - const run_option = b.step("run", "Run $PROJECT_NAME"); - run_option.dependOn(&run_step.step); - return; - } - - const exe = b.addExecutable(.{ - .name = "'$PROJECT_NAME'", - .root_module = b.createModule(.{ - .root_source_file = b.path("src/main.zig"), + const install_dir: std.Build.InstallDir = .{ .custom = "web" }; + const emcc_flags = emsdk.emccDefaultFlags(b.allocator, .{ .optimize = optimize, - .target = target - }), - }); + }); + const emcc_settings = emsdk.emccDefaultSettings(b.allocator, .{ + .optimize = optimize, + }); - exe.linkLibrary(raylib_artifact); - exe.root_module.addImport("raylib", raylib); + const emcc_step = emsdk.emccStep(b, raylib_artifact, wasm, .{ + .optimize = optimize, + .flags = emcc_flags, + .settings = emcc_settings, + .shell_file_path = emsdk.shell(raylib_dep.builder), + .embed_paths = &.{ + .{ + .src_path = b.pathJoin(&.{ module_subpath, "resources" }), + .virtual_path = "resources", + }, + }, + .install_dir = install_dir, + }); + b.getInstallStep().dependOn(emcc_step); - const run_cmd = b.addRunArtifact(exe); - const run_step = b.step("run", "Run $PROJECT_NAME"); - run_step.dependOn(&run_cmd.step); + const html_filename = try std.fmt.allocPrint(b.allocator, "{s}.html", .{wasm.name}); + const emrun_step = emsdk.emrunStep( + b, + b.getInstallPath(install_dir, html_filename), + &.{}, + ); - b.installArtifact(exe); + emrun_step.dependOn(emcc_step); + run_step.dependOn(emrun_step); + } else { + const exe = b.addExecutable(.{ + .name = "$PROJECT_NAME", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + run_step.dependOn(&run_cmd.step); + } } "@ New-Item -Name "build.zig" -ItemType "file" -Value $BUILD_DOT_ZIG -Force -zig fetch --save git+https://github.com/raylib-zig/raylib-zig#devel +zig fetch --save git+https://github.com/Not-Nik/raylib-zig#devel +zig fetch --save git+https://github.com/emscripten-core/emsdk#4.0.9 New-Item -Name "resources" -ItemType "directory" New-Item -Name "resources/placeholder.txt" -ItemType "file" -Value "" -Force diff --git a/project_setup.sh b/project_setup.sh index e5a83a9..cb63053 100755 --- a/project_setup.sh +++ b/project_setup.sh @@ -20,57 +20,76 @@ const rlz = @import("raylib_zig"); pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - + const raylib_dep = b.dependency("raylib_zig", .{ .target = target, .optimize = optimize, }); - const raylib = raylib_dep.module("raylib"); const raylib_artifact = raylib_dep.artifact("raylib"); + const exe_mod = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + exe_mod.addImport("raylib", raylib); + exe_mod.linkLibrary(raylib_artifact); + + const run_step = b.step("run", "Run the app"); + //web exports are completely separate if (target.query.os_tag == .emscripten) { - const exe_lib = try rlz.emcc.compileForEmscripten(b, "'$PROJECT_NAME'", "src/main.zig", target, optimize); + const emsdk = rlz.emsdk; + const wasm = b.addLibrary(.{ + .name = "'$PROJECT_NAME'", + .root_module = exe_mod, + }); - exe_lib.linkLibrary(raylib_artifact); - exe_lib.root_module.addImport("raylib", raylib); + const install_dir: std.Build.InstallDir = .{ .custom = "web" }; + const emcc_flags = emsdk.emccDefaultFlags(b.allocator, .{ .optimize = optimize }); + const emcc_settings = emsdk.emccDefaultSettings(b.allocator, .{ .optimize = optimize }); - // Note that raylib itself is not actually added to the exe_lib output file, so it also needs to be linked with emscripten. - const link_step = try rlz.emcc.linkWithEmscripten(b, &[_]*std.Build.Step.Compile{ exe_lib, raylib_artifact }); - //this lets your program access files like "resources/my-image.png": - link_step.addArg("--emrun"); - link_step.addArg("--embed-file"); - link_step.addArg("resources/"); - - b.getInstallStep().dependOn(&link_step.step); - const run_step = try rlz.emcc.emscriptenRunStep(b); - run_step.step.dependOn(&link_step.step); - const run_option = b.step("run", "Run '$PROJECT_NAME'"); - run_option.dependOn(&run_step.step); - return; - } - - const exe = b.addExecutable(.{ - .name = "'$PROJECT_NAME'", - .root_module = b.createModule(.{ - .root_source_file = b.path("src/main.zig"), + const emcc_step = emsdk.emccStep(b, raylib_artifact, wasm, .{ .optimize = optimize, - .target = target - }), - }); + .flags = emcc_flags, + .settings = emcc_settings, + .shell_file_path = emsdk.shell(raylib_dep.builder), + .embed_paths = &.{ + .{ + .src_path = b.pathJoin(&.{ module_subpath, "resources" }), + .virtual_path = "resources", + }, + }, + .install_dir = install_dir, + }); + b.getInstallStep().dependOn(emcc_step); - exe.linkLibrary(raylib_artifact); - exe.root_module.addImport("raylib", raylib); + const html_filename = try std.fmt.allocPrint(b.allocator, "{s}.html", .{wasm.name}); + const emrun_step = emsdk.emrunStep( + b, + b.getInstallPath(install_dir, html_filename), + &.{}, + ); - const run_cmd = b.addRunArtifact(exe); - const run_step = b.step("run", "Run '$PROJECT_NAME'"); - run_step.dependOn(&run_cmd.step); + emrun_step.dependOn(emcc_step); + run_step.dependOn(emrun_step); + } else { + const exe = b.addExecutable(.{ + .name = "'$PROJECT_NAME'", + .root_module = exe_mod, + }); + b.installArtifact(exe); - b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + run_step.dependOn(&run_cmd.step); + } }' >> build.zig -zig fetch --save git+https://github.com/raylib-zig/raylib-zig#devel +zig fetch --save git+https://github.com/Not-Nik/raylib-zig#devel +zig fetch --save git+https://github.com/emscripten-core/emsdk#4.0.9 mkdir resources touch resources/placeholder.txt