diff --git a/README.md b/README.md new file mode 100644 index 0000000..cfd0eec --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Gaem + +## Run + +Linux and Windows: +```sh +zig build run +``` + +Web: +```sh +zig build -Dtarget=wasm32-emscripten run +``` + +Cross-compile for Windows from Linux: +```sh +zig build -Dtarget=x86_64-windows +``` diff --git a/build.zig b/build.zig index 2cb0dbd..a8aa2f7 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const sokol = @import("sokol"); pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); @@ -7,11 +8,17 @@ pub fn build(b: *std.Build) !void { const is_wasm = target.result.cpu.arch.isWasm(); const is_debug = (optimize == .Debug); - const has_tracy = b.option(bool, "tracy", "Tracy integration") orelse is_debug; const has_imgui = b.option(bool, "imgui", "ImGui integration") orelse is_debug; + var has_tracy = false; + if (!is_wasm) { + has_tracy = b.option(bool, "tracy", "Tracy integration") orelse is_debug; + } + const exe_mod = b.createModule(.{ .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, }); const build_options = b.addOptions(); @@ -53,11 +60,26 @@ pub fn build(b: *std.Build) !void { } exe_mod.addImport("tracy", dep_tracy.module("tracy")); + var run_cmd_step: *std.Build.Step = undefined; if (is_wasm) { - // TODO: + if (true) { + @panic("TODO: test in 0.17"); + } + + const build_step = buildWasm(b, .{ + .name = "index", + .root_module = exe_mod, + .dep_sokol = dep_sokol, + }); + b.getInstallStep().dependOn(build_step); + + const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{}); + const emrun_step = sokol.emRunStep(b, .{ .name = "index", .emsdk = dep_emsdk }); + emrun_step.step.dependOn(build_step); + run_cmd_step = &emrun_step.step; + + // TODO: Create a zip archive of all of the files. Would be useful for easier itch.io upload } else { - exe_mod.resolved_target = target; - exe_mod.optimize = optimize; const exe = buildNative(b, .{ .name = "sokol_template_v2", .root_module = exe_mod @@ -66,13 +88,10 @@ pub fn build(b: *std.Build) !void { { const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } - - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); + run_cmd_step = &run_cmd.step; } { @@ -83,6 +102,9 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(&run_exe_tests.step); } } + + const run_step = b.step("run", "Run game"); + run_step.dependOn(run_cmd_step); } const BuildNativeOptions = struct { @@ -202,3 +224,87 @@ const AddExecutableIcon = struct { } } }; + +const BuildWasmOptions = struct { + name: []const u8, + root_module: *std.Build.Module, + dep_sokol: *std.Build.Dependency, +}; + +fn buildWasm(b: *std.Build, opts: BuildWasmOptions) *std.Build.Step { + opts.root_module.sanitize_c = .off; + + // build the main file into a library, this is because the WASM 'exe' + // needs to be linked in a separate build step with the Emscripten linker + const main_lib = b.addLibrary(.{ + .name = opts.name, + .root_module = opts.root_module, + }); + + const dep_sokol = opts.dep_sokol; + const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{}); + + // TODO: + // const emsdk_install_step = sokol.emSdkInstallStep(b, dep_emsdk, .{}); + // main_lib.step.dependOn(emsdk_install_step); + + patchWasmIncludeDirs( + opts.root_module, + dep_emsdk.path("upstream/emscripten/cache/sysroot/include"), + &(dep_sokol.artifact("sokol_clib").step) + ); + + const link_step = sokol.emLinkStep(b, .{ + .lib_main = main_lib, + .target = opts.root_module.resolved_target.?, + .optimize = opts.root_module.optimize.?, + .emsdk = dep_emsdk, + .use_webgl2 = true, + .use_emmalloc = true, + .use_filesystem = false, + .shell_file_path = b.path("src/engine/shell.html"), + }) catch unreachable; + + return &link_step.step; +} + +fn patchWasmIncludeDirs( + module: *std.Build.Module, + path: std.Build.LazyPath, + depend_step: *std.Build.Step +) void { + if (module.link_libc != null and module.link_libc.?) { + // need to inject the Emscripten system header include path into + // the cimgui C library otherwise the C/C++ code won't find + // C stdlib headers + module.addSystemIncludePath(path); + } + + for (module.import_table.values()) |imported_module| { + patchWasmIncludeDirs(imported_module, path, depend_step); + } + + for (module.link_objects.items) |link_object| { + if (link_object != .other_step) { + continue; + } + const lib = link_object.other_step; + if (&lib.step == depend_step) { + continue; + } + + if (lib.root_module.link_libc != null and lib.root_module.link_libc.?) { + // need to inject the Emscripten system header include path into + // the cimgui C library otherwise the C/C++ code won't find + // C stdlib headers + lib.root_module.addSystemIncludePath(path); + + // all C libraries need to depend on the sokol library, when building for + // WASM this makes sure that the Emscripten SDK has been setup before + // C compilation is attempted (since the sokol C library depends on the + // Emscripten SDK setup step) + lib.step.dependOn(depend_step); + } + patchWasmIncludeDirs(lib.root_module, path, depend_step); + } +} diff --git a/src/platform.zig b/src/platform.zig index db880ff..3715e41 100644 --- a/src/platform.zig +++ b/src/platform.zig @@ -22,7 +22,6 @@ const ig = @import("cimgui"); const Color = @import("./color.zig"); fn PlatformType(App: type) type { - _ = App; // autofix return struct { const Self = @This(); @@ -31,6 +30,8 @@ fn PlatformType(App: type) type { show_first_window: bool = true, + app: App, + fn sokolInit(user_data: ?*anyopaque) callconv(.c) void { const self: *Self = @ptrCast(@alignCast(user_data)); sg.setup(.{ @@ -44,8 +45,8 @@ fn PlatformType(App: type) type { if (@hasDecl(ig, "ImGuiConfigFlags_DockingEnable")) { ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable; + ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_ViewportsEnable; } - ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_ViewportsEnable; self.pass_action.colors[0] = .{ .load_action = .CLEAR, @@ -68,8 +69,8 @@ fn PlatformType(App: type) type { //=== UI CODE STARTS HERE { const viewport = ig.igGetMainViewport(); - ig.igSetNextWindowPos(viewport[0].WorkPos, ig.ImGuiCond_Always); - ig.igSetNextWindowSize(viewport[0].WorkSize, ig.ImGuiCond_Always); + ig.igSetNextWindowPos(viewport[0].Pos, ig.ImGuiCond_Always); + ig.igSetNextWindowSize(viewport[0].Size, ig.ImGuiCond_Always); ig.igSetNextWindowViewport(viewport[0].ID); const window_flags = @@ -138,14 +139,12 @@ fn PlatformType(App: type) type { const self: *Self = @ptrCast(@alignCast(user_data)); _ = self; // autofix - // forward input events to sokol-imgui _ = simgui.handleEvent(ev.*); } fn sokolCleanup(user_data: ?*anyopaque) callconv(.c) void { const self: *Self = @ptrCast(@alignCast(user_data)); _ = self; // autofix - // self.deinit(&self.exposed); simgui.shutdown(); sg.shutdown(); } @@ -168,7 +167,8 @@ pub fn run(App: type, opts: RunOptions) void { const Platform = PlatformType(App); const plt = arena.create(Platform) catch @panic("OOM"); plt.* = Platform{ - .cli_args = opts.init.minimal.args.toSlice(arena) catch @panic("OOM") + .cli_args = opts.init.minimal.args.toSlice(arena) catch @panic("OOM"), + .app = undefined }; // var init = Platform.Init{ @@ -186,7 +186,8 @@ pub fn run(App: type, opts: RunOptions) void { // std.process.exit(1); // } - if (builtin.os.tag == .linux) { + const is_wasm = builtin.cpu.arch.isWasm(); + if (builtin.os.tag == .linux and !is_wasm) { var sa: std.posix.Sigaction = .{ .handler = .{ .handler = posixSignalHandler }, .mask = std.posix.sigemptyset(),