diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10538ee..b2e0711 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Zig - uses: mlugg/setup-zig@v1 + uses: mlugg/setup-zig@v2 with: version: master diff --git a/.gitignore b/.gitignore index dca1103..895f4e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ zig-out/ +zig-pkg/ .zig-cache/ diff --git a/build.zig b/build.zig index 3e02c5e..bd20481 100644 --- a/build.zig +++ b/build.zig @@ -1,11 +1,9 @@ const std = @import("std"); -const addBench = @import("zubench").addBench; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const mode = b.standardOptimizeOption(.{}); - const zubench = b.dependency("zubench", .{}).module("zubench"); const strided_arrays_pkg = b.dependency("strided-arrays", .{}); const strided_arrays = strided_arrays_pkg.module("strided-arrays"); @@ -21,9 +19,11 @@ pub fn build(b: *std.Build) void { const exe = b.addExecutable(.{ .name = "zig-wfc", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = mode, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = mode, + }), }); exe.root_module.addImport(strided_arrays_dep.name, strided_arrays_dep.module); b.installArtifact(exe); @@ -38,23 +38,16 @@ pub fn build(b: *std.Build) void { run_step.dependOn(&run_cmd.step); const wfc_tests = b.addTest(.{ - .root_source_file = b.path("src/wfc.zig"), - .target = target, - .optimize = mode, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/wfc.zig"), + .target = target, + .optimize = mode, + }), }); - wfc_tests.root_module.addImport("zubench", zubench); wfc_tests.root_module.addImport(strided_arrays_dep.name, strided_arrays_dep.module); const wfc_tests_run = b.addRunArtifact(wfc_tests); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&wfc_tests_run.step); - - const bench_step = b.step("bench", "Run the benchmarks"); - - inline for (.{ .ReleaseSafe, .ReleaseFast, .ReleaseSmall }) |b_mode| { - const bench_exe = addBench(b, "src/core.zig", target, b_mode, &.{strided_arrays_dep}); - const cmd = b.addRunArtifact(bench_exe); - bench_step.dependOn(&cmd.step); - } } diff --git a/build.zig.zon b/build.zig.zon index 79e9423..aa4538c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,15 +2,18 @@ .name = .zig_wfc, .fingerprint = 0xf451383668953641, .version = "0.0.0", - .paths = .{ "LICENSE", "build.zig", "build.zig.zon", "src", "README.md" }, + .paths = .{ + "LICENSE", + "build.zig", + "build.zig.zon", + "src", + "README.md", + }, + .minimum_zig_version = "0.16.0", .dependencies = .{ .@"strided-arrays" = .{ - .url = "https://github.com/dweiller/zig-strided-arrays/archive/b32e31f767c294a218ff857ac9b6068629e775b7.tar.gz", - .hash = "strided_array-0.0.0-bRJh7EvCAADimJNom3qbzLC3dsiFvAiGRk50x-HoV4KR", - }, - .zubench = .{ - .url = "https://github.com/dweiller/zubench/archive/53140a07a6ee27e28549c46209d900ba92f61ceb.tar.gz", - .hash = "zubench-0.0.0-kIvtMziVAABBkFpJIF0hfbKvx04ZEE9P_G9Xu_xQvb7n", + .url = "git+https://github.com/dweiller/zig-strided-arrays.git#f0151f11c121099ee373ea3d485f835ae6abf74d", + .hash = "strided_array-0.0.0-bRJh7BXCAAAplUBK57yzs5eNRkqlan0Dw1ROcu348Sze", }, }, } diff --git a/src/core.zig b/src/core.zig index 0d39bf7..a61b117 100644 --- a/src/core.zig +++ b/src/core.zig @@ -371,7 +371,7 @@ const Removal = struct { tile_index: TileIndex, coord: Coord, }; -const RemovalStack = std.ArrayList(Removal); +const RemovalStack = std.array_list.Managed(Removal); const EntropyCoord = struct { const Self = @This(); @@ -400,7 +400,7 @@ const EntropyHeap = struct { std.debug.assert(heap_buffer.len <= heap_context.len); self.fba = std.heap.FixedBufferAllocator.init(std.mem.sliceAsBytes(heap_buffer)); self.fba.end_index = self.fba.buffer.len; - self.heap = Heap.init(self.fba.allocator(), heap_context); + self.heap = Heap.initContext(heap_context); self.heap.items = heap_buffer[0..0]; self.heap.cap = heap_buffer.len; } @@ -421,7 +421,7 @@ const EntropyHeap = struct { .entropy = entropy, .coord = item_ind.coord, }; - self.heap.add(item_ind.index) catch { + self.heap.push(self.fba.allocator(), item_ind.index) catch { std.debug.panic( "ran out of memory adding entropy coord {d}\nheap has size {d}", .{ item_ind.index, self.heap.capacity() }, @@ -465,7 +465,7 @@ pub const CoreState = struct { random: std.Random, pub fn chooseCellToCollapse(self: *Self) ?Coord { - const entropy_coord_idx = self.entropy_heap.heap.removeOrNull() orelse return null; + const entropy_coord_idx = self.entropy_heap.heap.pop() orelse return null; const entropy_coord = self.entropy_heap.heap.context[entropy_coord_idx]; return entropy_coord.coord; } @@ -538,9 +538,10 @@ pub const CoreState = struct { index, ) orelse std.debug.panic("could not find tile index {d} in entropy heap", .{index}); - _ = self.entropy_heap.heap.removeIndex(remove_index); + _ = self.entropy_heap.heap.popIndex(remove_index); self.entropy_heap.heap.context[index].entropy = new_entropy; - self.entropy_heap.heap.add(index) catch unreachable; //we just removed one so add() can't fail + // we just removed one so add() can't fail to push + self.entropy_heap.heap.push(self.entropy_heap.fba.allocator(), index) catch unreachable; } } @@ -733,74 +734,3 @@ pub fn tile( test { std.testing.refAllDecls(@This()); } - -const bench_ns = struct { - var bench_gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const bench_allocator = bench_gpa.allocator(); - - const bench_tile_count = 4; - - var bench_edges = edges: { - var adj_0 = [1]TileSet{TileSet.initEmpty()} ** 4; - adj_0[0].set(0); - adj_0[0].set(2); - adj_0[1].set(0); - adj_0[1].set(1); - adj_0[2].set(0); - adj_0[2].set(2); - adj_0[3].set(0); - adj_0[3].set(1); - var adj_1 = [1]TileSet{TileSet.initEmpty()} ** 4; - adj_1[0].set(1); - adj_1[0].set(3); - adj_1[1].set(0); - adj_1[2].set(1); - adj_1[2].set(3); - adj_1[3].set(0); - var adj_2 = [1]TileSet{TileSet.initEmpty()} ** 4; - adj_2[0].set(0); - adj_2[1].set(2); - adj_2[1].set(3); - adj_2[2].set(0); - adj_2[3].set(2); - adj_2[3].set(3); - var adj_3 = [1]TileSet{TileSet.initEmpty()} ** 4; - adj_3[0].set(1); - adj_3[1].set(2); - adj_3[2].set(1); - adj_3[3].set(2); - break :edges [bench_tile_count][4]TileSet{ - adj_0, - adj_1, - adj_2, - adj_3, - }; - }; - - var weights = [_]Weight{ 1, 1, 1, 1 }; - - pub const benchmarks = benchmarks: { - const adjacencies: Adjacencies = .{ .allowed_edges = bench_edges[0..] }; - - const input = GenInput{ - .seed = 0, - .tile_count = bench_tile_count, - .adjacency_rules = adjacencies, - .weights = &weights, - }; - - const output_shape = TileGrid.Indices{ 64, 64 }; - const args = std.meta.ArgsTuple(@TypeOf(generateAlloc)){ - bench_allocator, - bench_allocator, - input, - output_shape, - 1, - }; - break :benchmarks .{ - .@"generateAlloc() 64x64" = @import("zubench").Spec(generateAlloc){ .args = args, .max_samples = 500 }, - }; - }; -}; - -pub const benchmarks = bench_ns.benchmarks; diff --git a/src/main.zig b/src/main.zig index 467f214..61670e1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -25,21 +25,21 @@ const Options = struct { positionals: []const []const u8, }; -fn parseCli(allocator: std.mem.Allocator) !Options { - var args = try std.process.argsWithAllocator(allocator); - std.debug.assert(args.skip()); +fn parseCli(allocator: std.mem.Allocator, args: std.process.Args) !Options { + var iter = try args.iterateAllocator(allocator); + defer iter.deinit(); - var options = GenericOptions{}; + var options: GenericOptions = .{}; var mode: ?Mode = null; - var positionals = std.ArrayList([]const u8).init(allocator); - defer positionals.deinit(); + var positionals: std.ArrayList([]const u8) = .empty; + errdefer positionals.deinit(allocator); - while (args.next()) |arg| { + while (iter.next()) |arg| { if (std.mem.eql(u8, "--seed", arg) or std.mem.eql(u8, "-s", arg)) { - options.seed = try std.fmt.parseInt(usize, args.next() orelse missingArg("seed"), 10); + options.seed = try std.fmt.parseInt(usize, iter.next() orelse missingArg("seed"), 10); } else if (std.mem.eql(u8, "--size", arg) or std.mem.eql(u8, "-o", arg)) { - options.size = try std.fmt.parseInt(u32, args.next() orelse missingArg("size"), 10); + options.size = try std.fmt.parseInt(u32, iter.next() orelse missingArg("size"), 10); } else if (std.mem.eql(u8, "--help", arg) or std.mem.eql(u8, "-h", arg)) { options.help = true; } else if (std.mem.eql(u8, "image", arg)) { @@ -49,38 +49,47 @@ fn parseCli(allocator: std.mem.Allocator) !Options { if (mode != null) unexpectedArg(arg); mode = .@"test"; } else if (mode != null and mode.? == .image and std.mem.eql(u8, "--output-tiles", arg)) { - const owned_arg = try allocator.dupe(u8, args.next() orelse missingArg("output-tiles")); + const owned_arg = try allocator.dupe(u8, iter.next() orelse missingArg("output-tiles")); mode.?.image.@"output-tiles" = owned_arg; } else if (mode != null and mode.? == .image and std.mem.eql(u8, "--filter-size", arg)) { - const number_arg = args.next() orelse missingArg("filter-size"); + const number_arg = iter.next() orelse missingArg("filter-size"); mode.?.image.@"filter-size" = try std.fmt.parseInt( u32, number_arg, 10, ); } else { - try positionals.append(try allocator.dupe(u8, arg)); + try positionals.append(allocator, try allocator.dupe(u8, arg)); } } const result = Options{ .options = options, .mode = mode, - .positionals = try positionals.toOwnedSlice(), + .positionals = try positionals.toOwnedSlice(allocator), }; return result; } -pub fn main() anyerror!void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; +pub fn main(init: std.process.Init.Minimal) anyerror!void { + var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const allocator = gpa.allocator(); - const options = try parseCli(allocator); + var threaded: std.Io.Threaded = .init(allocator, .{}); + const io = threaded.io(); + + const options = try parseCli(allocator, init.args); + defer { + for (options.positionals) |arg| { + allocator.free(arg); + } + allocator.free(options.positionals); + } if (options.mode) |m| { switch (m) { - .@"test" => try testMode(allocator, options.options), + .@"test" => try testMode(io, allocator, options.options), .image => |opts| { if (options.positionals.len != 2) { std.debug.print("Error: 2 arguments expected\n", .{}); @@ -89,6 +98,7 @@ pub fn main() anyerror!void { const in_filename = options.positionals[0]; const out_filename = options.positionals[1]; try imageMode( + io, allocator, options.options, opts, @@ -108,17 +118,19 @@ const Input = struct { }; fn imageMode( + io: std.Io, allocator: std.mem.Allocator, options: GenericOptions, im_opts: ImageOptions, in_filename: []const u8, out_filename: []const u8, ) !void { - const cwd = std.fs.cwd(); + const cwd = std.Io.Dir.cwd(); const image = image: { - const in_file = try cwd.openFile(in_filename, .{}); - defer in_file.close(); - break :image try pnm.readPNM(allocator, in_file.reader()); + const in_file = try cwd.openFile(io, in_filename, .{}); + defer in_file.close(io); + var file_reader = in_file.reader(io, &.{}); + break :image try pnm.readPNM(allocator, &file_reader.interface); }; defer allocator.free(image.raster); @@ -149,9 +161,9 @@ fn imageMode( defer allocator.free(out_image.raster); if (im_opts.@"output-tiles") |dirname| { - try cwd.makeDir(dirname); - var dir = try cwd.openDir(dirname, .{}); - defer dir.close(); + try cwd.createDirPath(io, dirname); + var dir = try cwd.openDir(io, dirname, .{}); + defer dir.close(io); const buf = try allocator.alloc(u8, out_filename.len + "-tile-xxx".len); defer allocator.free(buf); @@ -172,25 +184,28 @@ fn imageMode( try std.fmt.bufPrint(buf, "{s}-tile-{d:0>3}{s}", .{ out_filename[0..idx], n, out_filename[idx..] }) else try std.fmt.bufPrint(buf, "{s}-tile-{d:0>3}", .{ out_filename, n }); - const file = try dir.createFile(filename, .{}); - defer file.close(); + const file = try dir.createFile(io, filename, .{}); + defer file.close(io); - try pnm.writePNM(file.writer(), tile_image); + var file_writer = file.writer(io, &.{}); + try pnm.writePNM(&file_writer.interface, tile_image); } - const out_file = try dir.createFile(out_filename, .{}); - defer out_file.close(); + const out_file = try dir.createFile(io, out_filename, .{}); + defer out_file.close(io); - try pnm.writePNM(out_file.writer(), out_image); + var out_file_writer = out_file.writer(io, &.{}); + try pnm.writePNM(&out_file_writer.interface, out_image); } else { - const out_file = try cwd.createFile(out_filename, .{}); - defer out_file.close(); + const out_file = try cwd.createFile(io, out_filename, .{}); + defer out_file.close(io); - try pnm.writePNM(out_file.writer(), out_image); + var out_file_writer = out_file.writer(io, &.{}); + try pnm.writePNM(&out_file_writer.interface, out_image); } } -fn testMode(allocator: std.mem.Allocator, options: GenericOptions) !void { +fn testMode(io: std.Io, allocator: std.mem.Allocator, options: GenericOptions) !void { const tile_count = 4; const tile_map = [tile_count][]const u8{ " ", "┃", "━", "╋" }; var adj_0 = [1]wfc.TileSet{wfc.TileSet.initEmpty()} ** 4; @@ -241,11 +256,12 @@ fn testMode(allocator: std.mem.Allocator, options: GenericOptions) !void { const tile_grid = try wfc.generateAlloc(allocator, allocator, input, .{ options.size, options.size }, 10); defer allocator.free(tile_grid.items); - try printGrid(tile_grid, tile_map[0..], options.size, options.size); + try printGrid(io, tile_grid, tile_map[0..], options.size, options.size); } -fn printGrid(tile_grid: wfc.TileGrid, tile_map: []const []const u8, rows: usize, cols: usize) !void { - const stdout = std.io.getStdOut().writer(); +fn printGrid(io: std.Io, tile_grid: wfc.TileGrid, tile_map: []const []const u8, rows: usize, cols: usize) !void { + var stdout_writer = std.Io.File.stdout().writer(io, &.{}); + const stdout = &stdout_writer.interface; { try stdout.print("┌", .{}); for (0..cols) |_| { diff --git a/src/overlapping.zig b/src/overlapping.zig index 8d5abae..add7de7 100644 --- a/src/overlapping.zig +++ b/src/overlapping.zig @@ -102,14 +102,19 @@ fn buildAdjacency(adjacencies: Adjacencies, tiles: []TileGrid) void { /// caller own returned memory; call deinit() to free pub fn overlappingTiles(allocator: Allocator, tile_grid: TileGrid, size: u32) !TileInfo { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - const arena_a = arena.allocator(); - var tiles = TilesMap.init(arena_a); + var tiles = TilesMap.init(allocator); + defer tiles.deinit(); + errdefer { + var iter = tiles.keyIterator(); + while (iter.next()) |key| { + allocator.free(key.*); + } + } var tile_index: TileIndex = 0; - var extracted_tiles = std.ArrayList(TileGrid).init(allocator); + var extracted_tiles: std.ArrayList(TileGrid) = .empty; + errdefer extracted_tiles.deinit(allocator); { var iter = tile_grid.iterate(); @@ -127,7 +132,7 @@ pub fn overlappingTiles(allocator: Allocator, tile_grid: TileGrid, size: u32) !T .count = 1, .map_index = @intCast(item.index), }; - try extracted_tiles.append(TileGrid.ofSlicePacked(extract_buf, .{ size, size }) catch unreachable); + try extracted_tiles.append(allocator, TileGrid.ofSlicePacked(extract_buf, .{ size, size }) catch unreachable); tile_index += 1; } } @@ -137,11 +142,14 @@ pub fn overlappingTiles(allocator: Allocator, tile_grid: TileGrid, size: u32) !T return error.TooManyUniqueTiles; const adjacencies = try core.Adjacencies.init(allocator, count); + errdefer adjacencies.deinit(allocator); buildAdjacency(adjacencies, extracted_tiles.items); var map = try allocator.alloc(TileIndex, count); + errdefer allocator.free(map); var weight = try allocator.alloc(Weight, count); + errdefer allocator.free(weight); var iter = tiles.valueIterator(); while (iter.next()) |tile| { @@ -154,7 +162,7 @@ pub fn overlappingTiles(allocator: Allocator, tile_grid: TileGrid, size: u32) !T .adjacencies = adjacencies, .map = map, .weight = weight, - .tiles = try extracted_tiles.toOwnedSlice(), + .tiles = try extracted_tiles.toOwnedSlice(allocator), }; } diff --git a/src/pnm.zig b/src/pnm.zig index 8b4c775..e19ebe8 100644 --- a/src/pnm.zig +++ b/src/pnm.zig @@ -47,8 +47,8 @@ pub const PNM = struct { raster: []u8, }; -pub fn readPNM(allocator: Allocator, reader: anytype) !PNM { - const bytes = try reader.readAllAlloc(allocator, 4096); +pub fn readPNM(allocator: Allocator, reader: *std.Io.Reader) !PNM { + const bytes = try reader.allocRemaining(allocator, .unlimited); defer allocator.free(bytes); const typ: Type = if (std.mem.eql(u8, bytes[0..2], "P5")) @@ -123,8 +123,8 @@ fn readNumber(index: *usize, bytes: []const u8) !usize { return try std.fmt.parseInt(usize, bytes[start..index.*], 10); } -pub fn writePNM(writer: anytype, pnm: PNM) !void { - try writer.print("{[type]s}\n{[width]d} {[height]d}\n{[max]d}\n", pnm.header); +pub fn writePNM(writer: *std.Io.Writer, pnm: PNM) !void { + try writer.print("{[type]t}\n{[width]d} {[height]d}\n{[max]d}\n", pnm.header); try writer.writeAll(pnm.raster); } diff --git a/src/wfc.zig b/src/wfc.zig index 82ff506..4152b52 100644 --- a/src/wfc.zig +++ b/src/wfc.zig @@ -3,7 +3,28 @@ const std = @import("std"); pub const overlapping = @import("overlapping.zig"); pub const constraint = @import("constraint.zig"); -pub usingnamespace @import("core.zig"); +const core = @import("core.zig"); + +pub const TileIndex = core.TileIndex; +pub const Weight = core.Weight; +pub const TileGrid = core.TileGrid; +pub const Coord = core.Coord; +pub const Error = core.Error; +pub const SeedGrid = core.SeedGrid; +pub const CellGrid = core.CellGrid; +pub const TileSet = core.TileSet; +pub const Adjacencies = core.Adjacencies; +pub const GenInput = core.GenInput; +pub const Direction = core.Direction; +pub const directions = core.directions; +pub const CoreState = core.CoreState; + +pub const initSeedGrid = core.initSeedGrid; +pub const neighbouringCoord = core.neighbouringCoord; +pub const generateAlloc = core.generateAlloc; +pub const generateSeededAlloc = core.generateSeededAlloc; +pub const generateSeededCellAlloc = core.generateSeededCellAlloc; +pub const tile = core.tile; comptime { std.testing.refAllDecls(@This());