diff options
| -rw-r--r-- | .gitmodules | 3 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | build.zig | 53 | ||||
| -rw-r--r-- | build.zig.zon | 18 | ||||
| -rw-r--r-- | src/imgui.zig | 4 | ||||
| -rw-r--r-- | src/main.zig | 50 | ||||
| -rw-r--r-- | src/particle.zig | 34 | ||||
| -rw-r--r-- | src/quad.zig | 24 | ||||
| -rw-r--r-- | src/rules.zig | 42 | ||||
| m--------- | vendor/rlImGui | 0 | ||||
| m--------- | vendor/zgui | 0 |
11 files changed, 127 insertions, 103 deletions
diff --git a/.gitmodules b/.gitmodules index 9ce7459..a277ea8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "vendor/zgui"] path = vendor/zgui url = https://github.com/zig-gamedev/zgui.git +[submodule "rlimgui"] + path = vendor/rlImGui + url = https://github.com/raylib-extras/rlImGui.git @@ -1,7 +1,7 @@ # Particle Simulator This is a simple particle simulator written in zig using [Raylib](https://www.raylib.com) ## Dependencies -- slightly modified rlImgui bindings (commented out 3 random lines) +- slightly modified rlImgui bindings (commented out lines 295-298) - zgui - raylib ## Running @@ -4,35 +4,42 @@ pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast }); - const exe = b.addExecutable(.{ - .name = "particle-sim", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); - exe.linkLibCpp(); - const raylib_zig = b.dependency("raylib-zig", .{ + const raylib_zig = b.dependency("raylib_zig", .{ .target = target, .optimize = optimize, }); - const raylib = raylib_zig.module("raylib"); const raylib_artifact = raylib_zig.artifact("raylib"); - exe.linkLibrary(raylib_artifact); - exe.root_module.addImport("raylib", raylib); const zgui = b.dependency("zgui", .{ .shared = false, .with_implot = true, }); - exe.root_module.addImport("zgui", zgui.module("root")); - exe.linkLibrary(zgui.artifact("imgui")); - exe.addIncludePath(b.path("vendor/zgui/libs/imgui")); const rlimgui = b.dependency("rlimgui", .{ .target = target, .optimize = optimize, }); + + const exe = b.addExecutable(.{ + .name = "particle-sim", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .link_libcpp = true, + .imports = &.{ + .{ .name = "raylib", .module = raylib_zig.module("raylib") }, + .{ .name = "zgui", .module = zgui.module("root") }, + }, + .target = target, + .optimize = optimize, + }), + }); + // exe.linkLibCpp(); + exe.linkLibrary(raylib_artifact); + exe.linkLibrary(zgui.artifact("imgui")); + exe.addIncludePath(zgui.path("libs/imgui")); + + exe.root_module.addCSourceFile(.{ .file = rlimgui.path("rlImGui.cpp"), .flags = &.{ @@ -54,14 +61,14 @@ pub fn build(b: *std.Build) !void { const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); - const exe_unit_tests = b.addTest(.{ - .root_source_file = b.path("src/quad.zig"), - .target = target, - .optimize = optimize, - }); - - const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + // const exe_unit_tests = b.addTest(.{ + // .root_source_file = b.path("src/quad.zig"), + // .target = target, + // .optimize = optimize, + // }); - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&run_exe_unit_tests.step); + // const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + // + // const test_step = b.step("test", "Run unit tests"); + // test_step.dependOn(&run_exe_unit_tests.step); } diff --git a/build.zig.zon b/build.zig.zon index 61aaa5e..5b59387 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,13 +1,17 @@ .{ - .name = "particle-sim", - .version = "0.2.1", + .name = .particle_sim, + .fingerprint = 0x88cd171d5a7354dc, + .version = "0.3.0", .paths = .{ "src", "vendor" }, .dependencies = .{ - .@"raylib-zig" = .{ - .url = "https://github.com/Not-Nik/raylib-zig/archive/devel.tar.gz", - .hash = "1220fc554f109a45a77ee5c58b4a847936dc0b24dcbed818b65a02de1b58500041dc", - }, .rlimgui = .{ .path = "vendor/rlImGui" }, - .zgui = .{ .path = "vendor/zgui" }, + .raylib_zig = .{ + .url = "git+https://github.com/raylib-zig/raylib-zig?ref=devel#163b1ef2e993fe7cc7c76bb3213d98612f4b7676", + .hash = "raylib_zig-5.6.0-dev-KE8REGNJBQDSjjyCmf4ATVQtk_OaBpY0B7kBxwSOPEGZ", + }, + .zgui = .{ + .url = "git+https://github.com/nic-gaffney/zgui#d90f353a733ab34f1c62e42c01ee87993075fa7e", + .hash = "zgui-0.6.0-dev--L6sZBrubQAfBA0cnGPYocwjKh_ass7FrUkTadgicanG", + }, }, } diff --git a/src/imgui.zig b/src/imgui.zig index 688e935..f086f2a 100644 --- a/src/imgui.zig +++ b/src/imgui.zig @@ -30,13 +30,13 @@ pub fn update(alloc: std.mem.Allocator, buf: [:0]u8) !void { } if (z.collapsingHeader("Radius", .{ .default_open = true })) { for (&cfg.radius, 0..) |*r, i| { - const str = rul.colorToStringZ(i); + const str = rul.colorToStringZ(i, "", " Radius"); _ = z.sliderInt(str, .{ .v = r, .min = cfg.minDistance, .max = 500 }); } } if (z.collapsingHeader("Speed", .{ .default_open = true })) { for (&cfg.speed, 0..) |*s, i| { - const str = rul.colorToStringZ(i); + const str = rul.colorToStringZ(i, "", " Speed"); _ = z.sliderInt(str, .{ .v = s, .min = 1, .max = 1000 }); } } diff --git a/src/main.zig b/src/main.zig index f7ec0a0..4d21dae 100644 --- a/src/main.zig +++ b/src/main.zig @@ -17,18 +17,18 @@ pub fn main() !void { cfg.rules = rules.ruleMatrix(true, true); rules.printRules(cfg.rules); - var gpa = std.heap.GeneralPurposeAllocator(.{ - // .safety = true, - // .thread_safe = true, - // .verbose_log = false, - }){}; - defer { - const leaked = gpa.deinit(); - if (leaked == .leak) { - std.debug.print("LEAKY PROGRAM\n", .{}); - } - } - const allocator = gpa.allocator(); + const allocator = std.heap.smp_allocator; + // defer { + // const leaked = smp.deinit(); + // if (leaked == .leak) { + // std.debug.print("LEAKY PROGRAM\n", .{}); + // } + // } + // gpa.setRequestedMemoryLimit(8000000); + // const allocator = smp.allocator(); + // var buffer: [80000000]u8 = undefined; + // var fbuffer = std.heap.FixedBufferAllocator.init(&buffer); + // const allocator = fbuffer.threadSafeAllocator(); rl.initWindow(cfg.screenWidth, cfg.screenHeight, "Particle Simulator"); defer rl.closeWindow(); @@ -49,18 +49,20 @@ pub fn main() !void { var particles = try part.initParticles(allocator, cfg.initialParticles); defer particles.deinit(); var quadTree: quad.Quad(part.particle, cfg.quadSplitLimit) = undefined; + // defer quadTree.deinit(); const buf = try allocator.allocSentinel(u8, 128, 0); std.mem.copyForwards(u8, buf, "Absolute File Path" ++ .{0}); defer allocator.free(buf); const pool = try allocator.alloc(std.Thread, cfg.numThreads); + var particleArrs: [cfg.numThreads]std.ArrayList(part.particle) = undefined; + for (0..cfg.numThreads) |i| + particleArrs[i] = try std.ArrayList(part.particle).initCapacity(allocator, comptime @divFloor(cfg.particleMax, cfg.numThreads) + cfg.numThreads); defer allocator.free(pool); - var frameCounter: u32 = 0; while (!rl.windowShouldClose()) { if (particles.items.len < cfg.particleCount) { for (0..@intCast(cfg.particleCount - @as(i32, @intCast(particles.items.len)))) |_| { - //std.debug.print("without this print statement it breaks on arm idk why {d}\n", .{cfg.particleCount}); _ = cfg.particleCount; try particles.append(part.createParticle()); } @@ -70,22 +72,28 @@ pub fn main() !void { particles.shrinkRetainingCapacity(@intCast(cfg.particleCount)); } - quadTree = quad.Quad(part.particle, cfg.quadSplitLimit).init(allocator, - .{ .x = 0, .y = 0 }, .{ .x = rl.getScreenWidth(), .y = rl.getScreenHeight()}); - defer quadTree.deinit(); + + quadTree = try quad.Quad(part.particle, cfg.quadSplitLimit).init(allocator, + .{ .x = 0, .y = 0 }, + .{ .x = rl.getScreenWidth(), .y = rl.getScreenHeight()}); + // defer quadTree.deinit(); for (particles.items) |p| try quadTree.insert(.{ .pos = p.pos, .data = p}); rl.beginDrawing(); defer rl.endDrawing(); - if (rl.isKeyPressed(rl.KeyboardKey.key_q)) break; + if (rl.isKeyPressed(rl.KeyboardKey.q)) { + quadTree.deinit(); + break; + } rl.clearBackground(rl.getColor(0x1E1E2EFF)); - for (pool, 0..) |*thread, i| - thread.* = try std.Thread.spawn(.{}, part.updateVelocities, .{ particles, quadTree, i }); + for (pool, 0..) |*thread, i| { + thread.* = try std.Thread.spawn(.{}, part.updateVelocities, .{ particles, quadTree, i, &particleArrs[i] }); + } for (pool) |thread| thread.join(); - frameCounter += 1; + quadTree.deinit(); part.updatePosition(&particles); diff --git a/src/particle.zig b/src/particle.zig index d33e7ce..14748dc 100644 --- a/src/particle.zig +++ b/src/particle.zig @@ -9,9 +9,10 @@ pub const particle = struct { xvel: f32, yvel: f32, }; -/// Initialize a MultiArrayList of size amnt with particles created by createParticle -pub fn initParticles(allocator: std.mem.Allocator, amnt: u32) !std.ArrayList(particle) { - var particles = std.ArrayList(particle).init(allocator); + +/// Initialize an array_list.Managed of size amnt with particles created by createParticle +pub fn initParticles(allocator: std.mem.Allocator, amnt: u32) !std.array_list.Managed(particle) { + var particles = std.array_list.Managed(particle).init(allocator); try particles.ensureTotalCapacity(cfg.particleMax); for (0..amnt) |_| try particles.append(createParticle()); @@ -21,38 +22,38 @@ pub fn initParticles(allocator: std.mem.Allocator, amnt: u32) !std.ArrayList(par /// Applies forces from the ruleset to each particle pub fn updateVelocities( - particles: std.ArrayList(particle), + particles: std.array_list.Managed(particle), qtree: quad.Quad(particle, cfg.quadSplitLimit), threadidx: u64, + particlesInRange: *std.ArrayList(particle), ) !void { const rules = cfg.rules; - var particlesInRange = std.ArrayList(particle).init(qtree.allocator); - defer particlesInRange.deinit(); var i = threadidx; while (i < particles.items.len) : (i += cfg.numThreads) { var p: *particle = &(particles.items[i]); defer particlesInRange.clearRetainingCapacity(); const radius = cfg.radius[p.colorId]; - try qtree.radiusSearchWrapping(p.pos, @intCast(radius), &particlesInRange, rl.getScreenWidth(), rl.getScreenHeight()); + try qtree.radiusSearchWrapping(p.pos, @intCast(radius), particlesInRange, rl.getScreenWidth(), rl.getScreenHeight()); var forceX: f32 = 0.0; var forceY: f32 = 0.0; const floatRadius = @as(f32, @floatFromInt(radius)); const floattMinDistance = @as(f32, @floatFromInt(cfg.minDistance)); for (particlesInRange.items) |p2| { if (p.pos.x == p2.pos.x and p.pos.y == p2.pos.y) continue; + // distance calculations const distance_x: f32 = @floatFromInt(p.pos.x - p2.pos.x); const distance_y: f32 = @floatFromInt(p.pos.y - p2.pos.y); var distance = @sqrt(distance_x * distance_x + distance_y * distance_y); if (distance == 0) distance = 0.01; + // force calculations const f = -force(distance, floatRadius, rules[p.colorId][p2.colorId]); forceX += (distance_x / distance) * f; forceY += (distance_y / distance) * f; } + // update velocity forceX = forceX * floattMinDistance / floatRadius; - // forceX = std.math.clamp(forceX, -10.0, 10.0); forceY = forceY * floattMinDistance / floatRadius; - // forceY = std.math.clamp(forceY, -10.0, 10.0); - p.xvel *= cfg.friction ; + p.xvel *= cfg.friction; p.xvel += forceX; p.yvel *= cfg.friction; p.yvel += forceY; @@ -60,18 +61,15 @@ pub fn updateVelocities( } /// Applies the particles velocity and updates position -pub fn updatePosition(particles: *std.ArrayList(particle)) void { +pub fn updatePosition(particles: *std.array_list.Managed(particle)) void { for (particles.items) |*p| { - const maxVel: f32 = 4096.0; - const posYplusVel: f32 = @as(f32, @floatFromInt(p.pos.y)) + std.math.clamp(p.yvel, -maxVel, maxVel); - const posXplusVel: f32 = @as(f32, @floatFromInt(p.pos.x)) + std.math.clamp(p.xvel, -maxVel, maxVel); - p.pos.y = @mod(@as(i32, @intFromFloat(posYplusVel)), rl.getScreenHeight()); - p.pos.x = @mod(@as(i32, @intFromFloat(posXplusVel)), rl.getScreenWidth()); + p.pos.y = @mod(@as(i32, @intFromFloat(@round(@as(f32, @floatFromInt(p.pos.y)) + (@as(f32, @floatFromInt(cfg.speed[p.colorId])) / 1000.0) * p.yvel))), rl.getScreenHeight()); + p.pos.x = @mod(@as(i32, @intFromFloat(@round(@as(f32, @floatFromInt(p.pos.x)) + (@as(f32, @floatFromInt(cfg.speed[p.colorId])) / 1000.0) * p.xvel))), rl.getScreenWidth()); } } /// Draw the particles onto the screen using raylib -pub fn draw(particles: std.ArrayList(particle)) void { +pub fn draw(particles: std.array_list.Managed(particle)) void { for (particles.items) |p| rl.drawRectangle(p.pos.x, p.pos.y, 5, 5, cfg.colors[p.colorId]); } @@ -88,7 +86,7 @@ fn force(distance: f32, radius: f32, attraction: f32) f32 { pub fn createParticle() particle { const seed = @as(u64, @truncate(@as(u128, @bitCast(std.time.nanoTimestamp())))); - var prng = std.rand.DefaultPrng.init(seed); + var prng = std.Random.DefaultPrng.init(seed); const x = prng.random().uintLessThan(u32, @intCast(rl.getScreenWidth())); const y = prng.random().uintLessThan(u32, @intCast(rl.getScreenHeight())); const color = prng.random().uintLessThan(u32, cfg.colorAmnt); diff --git a/src/quad.zig b/src/quad.zig index 0d4174e..db24589 100644 --- a/src/quad.zig +++ b/src/quad.zig @@ -16,17 +16,17 @@ pub fn Node(T: type) type { pub fn Quad(T: type, comptime splitLimit: usize) type { return struct { allocator: std.mem.Allocator, - nodes: ?std.BoundedArray(Node(T), splitLimit), + nodes: ?std.ArrayList(Node(T)), topLeft: Point, bottomRight: Point, children: [4]?*Quad(T, splitLimit), const Self = @This(); - pub fn init(allocator: std.mem.Allocator, tl: Point, br: Point) Quad(T, splitLimit) { + pub fn init(allocator: std.mem.Allocator, tl: Point, br: Point) !Self { return Quad(T, splitLimit){ .allocator = allocator, - .nodes = std.BoundedArray(Node(T), splitLimit).init(0) catch unreachable, + .nodes = try std.ArrayList(Node(T)).initCapacity(allocator, splitLimit), .topLeft = tl, .bottomRight = br, .children = [4]?*Quad(T, splitLimit){ null, null, null, null }, @@ -89,8 +89,8 @@ pub fn Quad(T: type, comptime splitLimit: usize) type { 3 => self.bottomRight, else => unreachable, }; - self.children[quadrant] = try self.allocator.create(Quad(T, splitLimit)); - self.children[quadrant].?.* = Quad(T, splitLimit).init(self.allocator, tl, br); + self.children[quadrant] = try self.allocator.create(Self); + self.children[quadrant].?.* = try Self.init(self.allocator, tl, br); } fn split(self: *Quad(T, splitLimit)) !void { @@ -98,7 +98,7 @@ pub fn Quad(T: type, comptime splitLimit: usize) type { for (0..4) |i| if (self.children[i] == null) try self.createChild(i); - const nodesToRedistribute = self.nodes.?.slice(); + const nodesToRedistribute = self.nodes.?.items; for (nodesToRedistribute) |node| { const quadrant = self.getQuadrant(node.pos); try self.children[quadrant].?.insert(node); @@ -116,7 +116,7 @@ pub fn Quad(T: type, comptime splitLimit: usize) type { return; } if (self.nodes) |*nodes| { - nodes.append(node) catch { + nodes.appendBounded(node) catch { try self.split(); const quadrant = self.getQuadrant(node.pos); try self.children[quadrant].?.insert(node); @@ -128,7 +128,7 @@ pub fn Quad(T: type, comptime splitLimit: usize) type { if (!self.inBoundry(p)) return null; if (self.nodes) |nodes| { - for (nodes.slice()) |node| + for (nodes.items) |node| if (node.pos.x == p.x and node.pos.y == p.y) return node; return null; @@ -144,9 +144,9 @@ pub fn Quad(T: type, comptime splitLimit: usize) type { if (!self.intersectsCircle(center, radius)) return; if (self.nodes) |nodes| { - for (nodes.slice()) |node| + for (nodes.items) |node| if (locationInRadius(center, node.pos, radius)) { - try results.append(node.data); + try results.appendBounded(node.data); }; return; } @@ -245,7 +245,9 @@ pub fn Quad(T: type, comptime splitLimit: usize) type { return self.inRadius(center, radius) or self.inBoundry(center); } - pub fn deinit(self: *Quad(T, splitLimit)) void { + pub fn deinit(self: *Self) void { + if (self.nodes) |*n| + n.deinit(self.allocator); for (self.children) |child| { if (child) |c| { c.deinit(); diff --git a/src/rules.zig b/src/rules.zig index 4e513eb..fac1999 100644 --- a/src/rules.zig +++ b/src/rules.zig @@ -4,7 +4,7 @@ const std = @import("std"); /// Generate the set of rules the particles will abide by pub fn ruleMatrix(radius: bool, speed: bool) [cfg.colorAmnt][cfg.colorAmnt]f32 { const seed = @as(u64, @truncate(@as(u128, @bitCast(std.time.nanoTimestamp())))); - var prng = std.rand.DefaultPrng.init(seed); + var prng = std.Random.DefaultPrng.init(seed); var rules: [cfg.colorAmnt][cfg.colorAmnt]f32 = undefined; for (0..cfg.colorAmnt) |i| { for (0..cfg.colorAmnt) |j| { @@ -39,36 +39,37 @@ pub fn printRules(rules: [cfg.colorAmnt][cfg.colorAmnt]f32) void { /// Loads rules from a csv pub fn loadRules(allocator: std.mem.Allocator, absolutePath: [:0]u8) !void { + var buffer: [256]u8 = undefined; const file = try std.fs.openFileAbsoluteZ(absolutePath, .{ .mode = .read_only }); defer file.close(); - var reader = file.reader(); + var reader = file.reader(&buffer).interface; for (&cfg.rules) |*row| { for (row) |*col| { - const buf = try reader.readUntilDelimiterAlloc(allocator, ',', 16); + const buf = try reader.takeDelimiterExclusive(','); defer allocator.free(buf); col.* = try std.fmt.parseFloat(f32, buf); } - try reader.skipBytes(1, .{}); + reader.toss(1); } for (&cfg.speed) |*s| { - const buf = try reader.readUntilDelimiterAlloc(allocator, ',', 16); + const buf = try reader.takeDelimiterExclusive(','); defer allocator.free(buf); s.* = try std.fmt.parseInt(i32, buf, 10); } - try reader.skipBytes(1, .{}); + reader.toss(1); for (&cfg.radius) |*r| { - const buf = try reader.readUntilDelimiterAlloc(allocator, ',', 16); + const buf = try reader.takeDelimiterExclusive(','); defer allocator.free(buf); r.* = try std.fmt.parseInt(i32, buf, 10); } - try reader.skipBytes(1, .{}); + reader.toss(1); { - const buf = try reader.readUntilDelimiterAlloc(allocator, ',', 16); + const buf = try reader.takeDelimiterExclusive(','); defer allocator.free(buf); cfg.minDistance = try std.fmt.parseInt(i32, buf, 10); } { - const buf = try reader.readUntilDelimiterAlloc(allocator, ',', 16); + const buf = try reader.takeDelimiterExclusive(','); defer allocator.free(buf); cfg.friction = try std.fmt.parseFloat(f32, buf); } @@ -76,9 +77,10 @@ pub fn loadRules(allocator: std.mem.Allocator, absolutePath: [:0]u8) !void { /// Save rules to a csv pub fn saveRules(absolutePath: [:0]u8) !void { + var buffer: [256]u8 = undefined; const file = try std.fs.createFileAbsoluteZ(absolutePath, .{ .read = true }); defer file.close(); - var writer = file.writer(); + var writer = file.writer(&buffer).interface; for (cfg.rules) |row| { for (row) |col| { try writer.print("{d:.3},", .{col}); @@ -112,16 +114,16 @@ pub inline fn colorToString(c: usize) []const u8 { }; } -pub inline fn colorToStringZ(c: usize) [:0]const u8 { +pub inline fn colorToStringZ(c: usize, comptime prepend: []const u8, comptime append: []const u8) [:0]const u8 { return switch (c) { - 0 => "Red", - 1 => "Green", - 2 => "Blue", - 3 => "Yellow", - 4 => "Magenta", - 5 => "Brown", - 6 => "Orange", - 7 => "Gray", + 0 => prepend ++ "Red" ++ append, + 1 => prepend ++ "Green" ++ append, + 2 => prepend ++ "Blue" ++ append, + 3 => prepend ++ "Yellow" ++ append, + 4 => prepend ++ "Magenta" ++ append, + 5 => prepend ++ "Brown" ++ append, + 6 => prepend ++ "Orange" ++ append, + 7 => prepend ++ "Gray" ++ append, else => " ", }; } diff --git a/vendor/rlImGui b/vendor/rlImGui new file mode 160000 +Subproject 4d8a61842903978bc42adf3347cd34f4e6524ef diff --git a/vendor/zgui b/vendor/zgui deleted file mode 160000 -Subproject 7fa8081c208885b85e3fdfc043cd9d9cb955912 |
