aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNic Gaffney <gaffney_nic@protonmail.com>2025-10-14 22:23:59 -0500
committerNic Gaffney <gaffney_nic@protonmail.com>2025-10-14 22:23:59 -0500
commitdd3ca084640f794c59427a507686bbd9bec1ed6b (patch)
tree4524af0c7a857a923387c07d58794de6d34a6b19
parentec2527a65c9609dc6098b55312a8286e30f9ba46 (diff)
downloadparticle-sim-dd3ca084640f794c59427a507686bbd9bec1ed6b.tar.gz
Started work on emscripten, fixed single frame leak caused by infinite splits
-rw-r--r--build.zig35
-rw-r--r--build.zig.zon8
-rw-r--r--src/config.zig1
-rw-r--r--src/main.zig36
-rw-r--r--src/particle.zig11
-rw-r--r--src/quad.zig24
6 files changed, 98 insertions, 17 deletions
diff --git a/build.zig b/build.zig
index a333a8a..b41b644 100644
--- a/build.zig
+++ b/build.zig
@@ -1,4 +1,5 @@
const std = @import("std");
+const raylib = @import("raylib");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
@@ -11,6 +12,7 @@ pub fn build(b: *std.Build) !void {
});
const raylib_artifact = raylib_zig.artifact("raylib");
+
const zgui = b.dependency("zgui", .{
.shared = false,
.with_implot = true,
@@ -34,7 +36,6 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize,
}),
});
- // exe.linkLibCpp();
exe.linkLibrary(raylib_artifact);
exe.linkLibrary(zgui.artifact("imgui"));
exe.addIncludePath(zgui.path("libs/imgui"));
@@ -61,6 +62,38 @@ pub fn build(b: *std.Build) !void {
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
+ if (target.query.os_tag == .emscripten) {
+ const emsdk = raylib.emsdk;
+ const wasm = b.addLibrary(.{
+ .name = "particle-sim-web",
+ .root_module = exe.root_module,
+ });
+
+ wasm.linkLibCpp();
+
+ 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);
+ }
+
// const exe_unit_tests = b.addTest(.{
// .root_source_file = b.path("src/quad.zig"),
// .target = target,
diff --git a/build.zig.zon b/build.zig.zon
index 5b59387..e0f767e 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -4,6 +4,10 @@
.version = "0.3.0",
.paths = .{ "src", "vendor" },
.dependencies = .{
+ .raylib = .{
+ .url = "git+https://github.com/raysan5/raylib#8ada37d9671682f420a2be1f1afd4b06173b81ad",
+ .hash = "raylib-5.6.0-dev-whq8uCg2ywTzCiX3VEP9RuCMXR6_VnDBmkj8GjL_p5QN",
+ },
.rlimgui = .{ .path = "vendor/rlImGui" },
.raylib_zig = .{
.url = "git+https://github.com/raylib-zig/raylib-zig?ref=devel#163b1ef2e993fe7cc7c76bb3213d98612f4b7676",
@@ -13,5 +17,9 @@
.url = "git+https://github.com/nic-gaffney/zgui#d90f353a733ab34f1c62e42c01ee87993075fa7e",
.hash = "zgui-0.6.0-dev--L6sZBrubQAfBA0cnGPYocwjKh_ass7FrUkTadgicanG",
},
+ .emsdk = .{
+ .url = "git+https://github.com/emscripten-core/emsdk?ref=4.0.9#3bcf1dcd01f040f370e10fe673a092d9ed79ebb5",
+ .hash = "N-V-__8AAJl1DwBezhYo_VE6f53mPVm00R-Fk28NPW7P14EQ",
+ },
},
}
diff --git a/src/config.zig b/src/config.zig
index 793fde7..3fea589 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -10,6 +10,7 @@ pub const colorAmnt = colors.len;
pub const numThreads = 16;
pub const minQuadSize = 1;
pub const quadSplitLimit = 64;
+pub var leafCapacityMod: u32 = 1;
pub var particleCount: i32 = initialParticles;
pub var minDistance: i32 = 20;
pub var friction: f32 = 0.95;
diff --git a/src/main.zig b/src/main.zig
index 4d21dae..aa410a1 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,6 +1,7 @@
const std = @import("std");
const rl = @import("raylib");
const z = @import("zgui");
+const builtin = @import("builtin");
const part = @import("particle.zig");
const cfg = @import("config.zig");
const img = @import("imgui.zig");
@@ -16,8 +17,28 @@ pub fn main() !void {
cfg.colors = cfg.customColors();
cfg.rules = rules.ruleMatrix(true, true);
rules.printRules(cfg.rules);
+ var gpa: std.heap.DebugAllocator(.{
+ .safety = true,
+ .thread_safe = true,
+ }) = undefined;
+ defer {
+ if (builtin.mode == .Debug) {
+ _=gpa.detectLeaks();
+ _=gpa.deinit();
+ }}
+
+
+ const allocator = allocblk: {
+ if (builtin.mode == .Debug) {
+ gpa = std.heap.DebugAllocator(.{ .safety = true, .thread_safe = true, }){};
+ break :allocblk gpa.allocator();
+ }
+ if (builtin.target.os.tag == .emscripten)
+ break :allocblk std.heap.c_allocator
+ else
+ break :allocblk std.heap.smp_allocator;
+ };
- const allocator = std.heap.smp_allocator;
// defer {
// const leaked = smp.deinit();
// if (leaked == .leak) {
@@ -58,6 +79,7 @@ pub fn main() !void {
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);
+ var particleArrEMCC = try std.ArrayList(part.particle).initCapacity(allocator, cfg.particleMax);
defer allocator.free(pool);
while (!rl.windowShouldClose()) {
@@ -87,12 +109,16 @@ pub fn main() !void {
}
rl.clearBackground(rl.getColor(0x1E1E2EFF));
- for (pool, 0..) |*thread, i| {
- thread.* = try std.Thread.spawn(.{}, part.updateVelocities, .{ particles, quadTree, i, &particleArrs[i] });
- }
+ if (builtin.target.os.tag != .emscripten) {
+ for (pool, 0..) |*thread, i| {
+ thread.* = try std.Thread.spawn(.{}, part.updateVelocities, .{ particles, quadTree, i, &particleArrs[i] });
+ }
- for (pool) |thread|
+ for (pool) |thread|
thread.join();
+ } else {
+ try part.updateVelocities(particles, quadTree, 0, &particleArrEMCC);
+ }
quadTree.deinit();
diff --git a/src/particle.zig b/src/particle.zig
index 14748dc..03de9ae 100644
--- a/src/particle.zig
+++ b/src/particle.zig
@@ -1,3 +1,4 @@
+const builtin = @import("builtin");
const cfg = @import("config.zig");
const std = @import("std");
const rl = @import("raylib");
@@ -24,12 +25,18 @@ pub fn initParticles(allocator: std.mem.Allocator, amnt: u32) !std.array_list.Ma
pub fn updateVelocities(
particles: std.array_list.Managed(particle),
qtree: quad.Quad(particle, cfg.quadSplitLimit),
- threadidx: u64,
+ threadidx: usize,
particlesInRange: *std.ArrayList(particle),
) !void {
const rules = cfg.rules;
var i = threadidx;
- while (i < particles.items.len) : (i += cfg.numThreads) {
+ while (i < particles.items.len) : (i += iterval: {
+ if (builtin.target.os.tag == .emscripten)
+ break :iterval 1
+ else
+ break :iterval cfg.numThreads;
+ }) {
+
var p: *particle = &(particles.items[i]);
defer particlesInRange.clearRetainingCapacity();
const radius = cfg.radius[p.colorId];
diff --git a/src/quad.zig b/src/quad.zig
index db24589..c7177dc 100644
--- a/src/quad.zig
+++ b/src/quad.zig
@@ -26,7 +26,7 @@ pub fn Quad(T: type, comptime splitLimit: usize) type {
pub fn init(allocator: std.mem.Allocator, tl: Point, br: Point) !Self {
return Quad(T, splitLimit){
.allocator = allocator,
- .nodes = try std.ArrayList(Node(T)).initCapacity(allocator, splitLimit),
+ .nodes = try std.ArrayList(Node(T)).initCapacity(allocator, splitLimit * cfg.leafCapacityMod),
.topLeft = tl,
.bottomRight = br,
.children = [4]?*Quad(T, splitLimit){ null, null, null, null },
@@ -44,12 +44,12 @@ pub fn Quad(T: type, comptime splitLimit: usize) type {
}
fn shouldSplit(self: Self) bool {
- if (@abs(self.topLeft.x - self.bottomRight.x) <= 8 and
- @abs(self.topLeft.y - self.bottomRight.y) <= 8) {
+ if (@abs(self.topLeft.x - self.bottomRight.x) <= cfg.minQuadSize and
+ @abs(self.topLeft.y - self.bottomRight.y) <= cfg.minQuadSize) {
return false;
}
if (self.nodes) |nodes|
- return nodes.len >= cfg.quadSplitLimit;
+ return nodes.items.len >= cfg.quadSplitLimit;
return false;
}
@@ -116,11 +116,17 @@ pub fn Quad(T: type, comptime splitLimit: usize) type {
return;
}
if (self.nodes) |*nodes| {
- nodes.appendBounded(node) catch {
- try self.split();
- const quadrant = self.getQuadrant(node.pos);
- try self.children[quadrant].?.insert(node);
- };
+ if (!shouldSplit(self.*)) {
+ nodes.appendBounded(node) catch {
+ cfg.leafCapacityMod += 1;
+ try nodes.ensureTotalCapacity(self.allocator, splitLimit * cfg.leafCapacityMod);
+ try nodes.appendBounded(node);
+ };
+ return;
+ }
+ try self.split();
+ const quadrant = self.getQuadrant(node.pos);
+ try self.children[quadrant].?.insert(node);
}
}