diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..37ba9d6 Binary files /dev/null and b/assets/icon.png differ diff --git a/build.zig b/build.zig index 40d34fb..b5156b5 100644 --- a/build.zig +++ b/build.zig @@ -6,9 +6,57 @@ pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const dep_stb = b.dependency("stb", .{}); + const ctx = Context.init(b, dep_stb.module("stb_image")); + + var run_cmd_step: *std.Build.Step = undefined; + switch (try buildProject(ctx, target, optimize)) { + .native => |exe| { + b.installArtifact(exe); + + { + const run_cmd = b.addRunArtifact(exe); + if (b.args) |args| { + run_cmd.addArgs(args); + } + run_cmd_step = &run_cmd.step; + } + + // { + // const exe_tests = b.addTest(.{ .root_module = exe_mod, }); + // const run_exe_tests = b.addRunArtifact(exe_tests); + // + // const test_step = b.step("test", "Run tests"); + // test_step.dependOn(&run_exe_tests.step); + // } + }, + .wasm => { + // TODO: + // 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; + } + } + + + const run_step = b.step("run", "Run game"); + run_step.dependOn(run_cmd_step); +} + +const ProjectResult = union(enum) { + native: *std.Build.Step.Compile, + wasm: *std.Build.Step, +}; + +fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !ProjectResult { + const b = ctx.b; + const name = "sokol_template_v2"; - const assets_dir = "assets"; + const assets_dir = b.path("assets"); const asset_files = [_][]const u8{ "sokoban/sokoban_tilesheet.png", "sokoban/sokoban_tilesheet@2.png", @@ -18,10 +66,8 @@ pub fn build(b: *std.Build) !void { "impact-sounds/footstep_concrete_000.ogg" }; - const asset_dir_realpath = try b.build_root.join(b.graph.arena, &.{ assets_dir }); - const is_wasm = target.result.cpu.arch.isWasm(); - const is_debug = (optimize == .Debug); + const is_debug = optimize == .Debug; const has_imgui = b.option(bool, "imgui", "ImGui integration") orelse is_debug; const assets_from_filesystem = b.option(bool, "assets-filesystem", "Allow reading assets from filesystem") orelse is_debug; @@ -31,6 +77,9 @@ pub fn build(b: *std.Build) !void { has_tracy = b.option(bool, "tracy", "Tracy integration") orelse is_debug; } + const win32_has_console = null; + const win32_icon_png: ?Build.LazyPath = b.path("assets/icon.png"); + const exe_mod = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, @@ -52,10 +101,10 @@ pub fn build(b: *std.Build) !void { { const build_options = b.addOptions(); build_options.addOption(bool, "has_imgui", has_imgui); - build_options.addOption(?[]const u8, "assets_dir", if (assets_from_filesystem) asset_dir_realpath else null); + build_options.addOption(?[]const u8, "assets_dir", if (assets_from_filesystem) assets_dir.getPath(b) else null); platform_mod.addOptions("build_options", build_options); - platform_mod.addImport("embedded_assets", try createEmbeddedAssets(b, assets_dir, &asset_files)); + platform_mod.addImport("embedded_assets", try createAssetsModule(b, assets_dir, &asset_files)); const math_mod = b.createModule(.{ .root_source_file = b.path("src/platform/math.zig") @@ -118,206 +167,121 @@ pub fn build(b: *std.Build) !void { platform_mod.addImport("tracy", dep_tracy.module("tracy")); } - var run_cmd_step: *std.Build.Step = undefined; if (is_wasm) { - @panic("TODO: Test wasm support on 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 + return ProjectResult{ + .wasm = buildWasm(b, .{ + .name = "index", + .root_module = exe_mod, + .dep_sokol = dep_sokol, + }) + }; } else { - const exe = buildNative(b, .{ + const exe = b.addExecutable(.{ .name = name, - .root_module = exe_mod + .root_module = exe_mod, }); - b.installArtifact(exe); - { - const run_cmd = b.addRunArtifact(exe); - if (b.args) |args| { - run_cmd.addArgs(args); + if (target.result.os.tag == .windows) { + const has_console = win32_has_console orelse (optimize == .Debug); + exe.subsystem = if (has_console) .Console else .Windows; + + if (win32_icon_png) |icon_png| { + buildWin32ResourceFile(ctx, exe.root_module, .{ + .icon_png = icon_png + }); } - run_cmd_step = &run_cmd.step; } - { - const exe_tests = b.addTest(.{ .root_module = exe_mod, }); - const run_exe_tests = b.addRunArtifact(exe_tests); - - const test_step = b.step("test", "Run tests"); - test_step.dependOn(&run_exe_tests.step); - } + return ProjectResult{ + .native = exe + }; } - - const run_step = b.step("run", "Run game"); - run_step.dependOn(run_cmd_step); } -fn createEmbeddedAssets( +fn createAssetsModule( b: *std.Build, - assets_dir: []const u8, + assets_dir: Build.LazyPath, asset_files: []const []const u8 ) !*std.Build.Module { const write_files = b.addWriteFiles(); - var embedded_assets: std.ArrayList(u8) = .empty; - defer embedded_assets.deinit(b.allocator); + var mod_contents: std.ArrayList(u8) = .empty; + defer mod_contents.deinit(b.allocator); - try embedded_assets.appendSlice(b.allocator, "pub const files = .{\n"); - const mod_embedded_assets = b.createModule(.{}); + try mod_contents.appendSlice(b.allocator, "pub const files = .{\n"); + const mod = b.createModule(.{}); for (asset_files) |asset_file| { - mod_embedded_assets.addAnonymousImport(asset_file, .{ - .root_source_file = b.path(b.pathJoin(&.{ assets_dir, asset_file })) + mod.addAnonymousImport(asset_file, .{ + .root_source_file = assets_dir.path(b, asset_file) }); - try embedded_assets.appendSlice(b.allocator, " .{ "); + try mod_contents.appendSlice(b.allocator, " .{ "); - try embedded_assets.appendSlice(b.allocator, ".name = \""); - try embedded_assets.appendSlice(b.allocator, asset_file); - try embedded_assets.appendSlice(b.allocator, "\""); + try mod_contents.appendSlice(b.allocator, ".name = \""); + try mod_contents.appendSlice(b.allocator, asset_file); + try mod_contents.appendSlice(b.allocator, "\""); - try embedded_assets.appendSlice(b.allocator, ", "); + try mod_contents.appendSlice(b.allocator, ", "); - try embedded_assets.appendSlice(b.allocator, ".contents = @embedFile(\""); - try embedded_assets.appendSlice(b.allocator, asset_file); - try embedded_assets.appendSlice(b.allocator, "\")"); + try mod_contents.appendSlice(b.allocator, ".contents = @embedFile(\""); + try mod_contents.appendSlice(b.allocator, asset_file); + try mod_contents.appendSlice(b.allocator, "\")"); - try embedded_assets.appendSlice(b.allocator, " },\n"); + try mod_contents.appendSlice(b.allocator, " },\n"); } - try embedded_assets.appendSlice(b.allocator, "};\n"); - mod_embedded_assets.root_source_file = write_files.add("embedded_assets.zig", embedded_assets.items); + try mod_contents.appendSlice(b.allocator, "};\n"); + mod.root_source_file = write_files.add("assets.zig", mod_contents.items); - return mod_embedded_assets; + return mod; } -const BuildNativeOptions = struct { - name: []const u8, - root_module: *std.Build.Module, - win32_has_console: ?bool = null, - win32_png_icon: ?std.Build.LazyPath = null +const Context = struct { + b: *std.Build, + png_to_icon: *Build.Step.Compile, + create_win32_resource: *Build.Step.Compile, + + pub fn init(b: *std.Build, stb_image: *Build.Module) Context { + return Context{ + .b = b, + .png_to_icon = b.addExecutable(.{ + .name = "png-to-icon", + .root_module = b.createModule(.{ + .target = b.graph.host, + .root_source_file = b.path("scripts/png-to-icon.zig"), + .imports = &.{ + .{ .name = "stb_image", .module = stb_image } + } + }) + }), + .create_win32_resource = b.addExecutable(.{ + .name = "create-win32-resource", + .root_module = b.createModule(.{ + .target = b.graph.host, + .root_source_file = b.path("scripts/create-win32-resource.zig"), + }) + }) + }; + } }; -fn buildNative( - b: *std.Build, - opts: BuildNativeOptions, -) *std.Build.Step.Compile { - const exe = b.addExecutable(.{ - .name = opts.name, - .root_module = opts.root_module, - }); - - const target = opts.root_module.resolved_target.?; - if (target.result.os.tag == .windows) { - const optimize = opts.root_module.optimize.?; - const has_console = opts.win32_has_console orelse (optimize == .Debug); - exe.subsystem = if (has_console) .Console else .Windows; - - if (opts.win32_png_icon) |win32_png_icon| { - const png_to_icon_tool = buildPngToIconTool(b, b.graph.host, .ReleaseSafe); - - const png_to_icon_step = b.addRunArtifact(png_to_icon_tool); - png_to_icon_step.addFileArg(win32_png_icon); - const icon_file = png_to_icon_step.addOutputFileArg("icon.ico"); - - const add_icon_step = AddExecutableIcon.init(exe, icon_file); - exe.step.dependOn(&add_icon_step.step); - } - } - - return exe; -} - -fn buildPngToIconTool( - b: *std.Build, - target: std.Build.ResolvedTarget, - optimize: std.builtin.OptimizeMode, -) *std.Build.Step.Compile { - const mod = b.createModule(.{ - .target = target, - .optimize = optimize, - .root_source_file = b.path("tools/png-to-icon.zig"), - }); - - const dep_stb = b.dependency("stb", .{}); - mod.addImport("stb_image", dep_stb.module("stb_image")); - - return b.addExecutable(.{ - .name = "png-to-icon", - .root_module = mod, - }); -} - -const AddExecutableIcon = struct { - obj: *std.Build.Step.Compile, - step: std.Build.Step, - icon_file: std.Build.LazyPath, - resource_file: std.Build.LazyPath, - - fn init(obj: *std.Build.Step.Compile, icon_file: std.Build.LazyPath) *AddExecutableIcon { - const b = obj.step.owner; - const self = b.allocator.create(AddExecutableIcon) catch @panic("OOM"); - - self.obj = obj; - self.step = std.Build.Step.init(.{ - .id = .custom, - .name = "add executable icon", - .owner = b, - .makeFn = make - }); - - self.icon_file = icon_file; - icon_file.addStepDependencies(&self.step); - - const write_files = b.addWriteFiles(); - self.resource_file = write_files.add("resource-file.rc", ""); - self.step.dependOn(&write_files.step); - - self.obj.root_module.addWin32ResourceFile(.{ - .file = self.resource_file, - }); - - return self; - } - - fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void { - const b = step.owner; - const self: *AddExecutableIcon = @fieldParentPtr("step", step); - - const relative_icon_path = try std.fs.path.relative( - b.allocator, - "", - null, - self.resource_file.dirname().getPath(b), - self.icon_file.getPath(b) - ); - std.mem.replaceScalar(u8, relative_icon_path, '\\', '/'); - - { - const cwd = std.Io.Dir.cwd(); - const resource_file = try cwd.createFile(b.graph.io, self.resource_file.getPath(b), .{ }); - defer resource_file.close(b.graph.io); - - var buff: [4*4096]u8 = undefined; - var file_writer = resource_file.writer(b.graph.io, &buff); - const w = &file_writer.interface; - - try w.writeAll("IDI_ICON ICON \""); - try w.writeAll(relative_icon_path); - try w.writeAll("\""); - } - } +const Win32ResourceFileOptions = struct { + icon_png: Build.LazyPath }; +fn buildWin32ResourceFile(ctx: Context, mod: *Build.Module, opts: Win32ResourceFileOptions) void { + const b = ctx.b; + + const png_to_icon_step = b.addRunArtifact(ctx.png_to_icon); + png_to_icon_step.addFileArg(opts.icon_png); + const icon_file = png_to_icon_step.addOutputFileArg("icon.ico"); + + const resource_file_step = b.addRunArtifact(ctx.create_win32_resource); + resource_file_step.addFileArg(icon_file); + const resource_file = resource_file_step.addOutputFileArg("resource-file.rc"); + + mod.addWin32ResourceFile(.{ .file = resource_file }); +} + const BuildWasmOptions = struct { name: []const u8, root_module: *std.Build.Module, diff --git a/scripts/create-win32-resource.zig b/scripts/create-win32-resource.zig new file mode 100644 index 0000000..45a81a4 --- /dev/null +++ b/scripts/create-win32-resource.zig @@ -0,0 +1,31 @@ +const std = @import("std"); +const Io = std.Io; + +pub fn main(init: std.process.Init) !void { + const io = init.io; + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); + + if (args.len != 3) { + std.debug.print("Usage: ./create-win32-resource-file ", .{}); + std.process.exit(1); + } + + const icon_file_path = args[1]; + const output_file_path = args[2]; + + const cwd = Io.Dir.cwd(); + + const output_file = try cwd.createFile(io, output_file_path, .{}); + defer output_file.close(io); + + var buff: [4*4096]u8 = undefined; + var file_writer = output_file.writer(io, &buff); + const writer = &file_writer.interface; + + try writer.writeAll("IDI_ICON ICON \""); + try writer.writeAll(icon_file_path); + try writer.writeAll("\""); + + try writer.flush(); +} diff --git a/scripts/png-to-icon.zig b/scripts/png-to-icon.zig index 0638586..dc226c7 100644 --- a/scripts/png-to-icon.zig +++ b/scripts/png-to-icon.zig @@ -1,27 +1,26 @@ const std = @import("std"); +const Io = std.Io; const STBImage = @import("stb_image"); // https://en.wikipedia.org/wiki/ICO_(file_format)#Icon_file_structure -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - defer _ = gpa.deinit(); - - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); +pub fn main(init: std.process.Init) !void { + const io = init.io; + const gpa = init.gpa; + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); if (args.len != 3) { std.debug.print("Usage: ./png-to-icon ", .{}); std.process.exit(1); } - const cwd = std.fs.cwd(); + const cwd = Io.Dir.cwd(); const input_png_path = args[1]; const output_ico_path = args[2]; - const input_png_data = try cwd.readFileAlloc(allocator, input_png_path, 1024 * 1024 * 5); - defer allocator.free(input_png_data); + const input_png_data = try cwd.readFileAlloc(io, input_png_path, gpa, .unlimited); + defer gpa.free(input_png_data); const png_image = try STBImage.load(input_png_data); defer png_image.deinit(); @@ -29,29 +28,30 @@ pub fn main() !void { std.debug.assert(png_image.width > 0 and png_image.width <= 256); std.debug.assert(png_image.height > 0 and png_image.height <= 256); - const output_ico_file = try std.fs.cwd().createFile(output_ico_path, .{ }); - defer output_ico_file.close(); + const output_ico_file = try cwd.createFile(io, output_ico_path, .{}); + defer output_ico_file.close(io); var buffer: [4096 * 4]u8 = undefined; - var writer = output_ico_file.writer(&buffer); + var file_writer = output_ico_file.writer(io, &buffer); + const writer = &file_writer.interface; // ICONDIR structure - try writer.interface.writeInt(u16, 0, .little); // Must always be zero - try writer.interface.writeInt(u16, 1, .little); // Image type. 1 for .ICO - try writer.interface.writeInt(u16, 1, .little); // Number of images + try writer.writeInt(u16, 0, .little); // Must always be zero + try writer.writeInt(u16, 1, .little); // Image type. 1 for .ICO + try writer.writeInt(u16, 1, .little); // Number of images // ICONDIRENTRY structure - try writer.interface.writeInt(u8, @truncate(png_image.width), .little); // Image width - try writer.interface.writeInt(u8, @truncate(png_image.height), .little); // Image height - try writer.interface.writeInt(u8, 0, .little); // Number of colors in color pallete. 0 means that color pallete is not used - try writer.interface.writeInt(u8, 0, .little); // Must always be zero - try writer.interface.writeInt(u16, 0, .little); // Color plane - try writer.interface.writeInt(u16, 32, .little); // Bits per pixel - try writer.interface.writeInt(u32, @intCast(input_png_data.len), .little); // Image size in bytes - try writer.interface.writeInt(u32, 22, .little); // Offset to image data from the start + try writer.writeInt(u8, @truncate(png_image.width), .little); // Image width + try writer.writeInt(u8, @truncate(png_image.height), .little); // Image height + try writer.writeInt(u8, 0, .little); // Number of colors in color pallete. 0 means that color pallete is not used + try writer.writeInt(u8, 0, .little); // Must always be zero + try writer.writeInt(u16, 0, .little); // Color plane + try writer.writeInt(u16, 32, .little); // Bits per pixel + try writer.writeInt(u32, @intCast(input_png_data.len), .little); // Image size in bytes + try writer.writeInt(u32, 22, .little); // Offset to image data from the start // PNG image data - try writer.interface.writeAll(input_png_data); - try writer.interface.flush(); + try writer.writeAll(input_png_data); + try writer.flush(); }