Compare commits

...

3 commits

Author SHA1 Message Date
6c8dd16a97 version 2.0 2026-04-19 15:03:19 +02:00
53a29a3a29 average of 350/360 fps on a AMD Ryzen 7 7840HS on ReleaseFast 2026-04-19 15:02:22 +02:00
c0f3adda16 raylib 2026-04-18 22:35:31 +02:00
6 changed files with 261 additions and 95 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
.zig-cache/ .zig-cache/
zig-out/ zig-out/
zig-pkg/
result result

View file

@ -4,6 +4,16 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const raylib_dep = b.dependency("raylib_zig", .{
.target = target,
.optimize = optimize,
.linux_display_backend = .Wayland,
});
const raylib = raylib_dep.module("raylib");
const raygui = raylib_dep.module("raygui");
const raylib_artifact = raylib_dep.artifact("raylib"); // raylib C library
const mod = b.addModule("sand", .{ const mod = b.addModule("sand", .{
.root_source_file = b.path("src/root.zig"), .root_source_file = b.path("src/root.zig"),
.target = target, .target = target,
@ -17,36 +27,30 @@ pub fn build(b: *std.Build) void {
.optimize = optimize, .optimize = optimize,
.imports = &.{ .imports = &.{
.{ .name = "sand", .module = mod }, .{ .name = "sand", .module = mod },
.{ .name = "raylib", .module = raylib },
.{ .name = "raygui", .module = raygui },
}, },
}), }),
.use_lld = false,
}); });
exe.root_module.linkLibrary(raylib_artifact);
b.installArtifact(exe); b.installArtifact(exe);
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);
run_step.dependOn(&run_cmd.step);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const mod_tests = b.addTest(.{ const mod_tests = b.addTest(.{
.root_module = mod, .root_module = mod,
}); });
const run_mod_tests = b.addRunArtifact(mod_tests); const run_mod_tests = b.addRunArtifact(mod_tests);
const exe_tests = b.addTest(.{ const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
const exe_check = b.addExecutable(.{
.name = "sand",
.root_module = exe.root_module, .root_module = exe.root_module,
}); });
const run_exe_tests = b.addRunArtifact(exe_tests); const check = b.step("check", "Check if foo compiles");
check.dependOn(&exe_check.step);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
test_step.dependOn(&run_exe_tests.step);
} }

View file

@ -1,9 +1,14 @@
.{ .{
.name = .sand, .name = .sand,
.version = "1.0.0", .version = "2.0.0",
.fingerprint = 0xa07a92116fcc91c3, // Changing this has security and trust implications. .fingerprint = 0xa07a92116fcc91c3, // Changing this has security and trust implications.
.minimum_zig_version = "0.16.0", .minimum_zig_version = "0.16.0",
.dependencies = .{}, .dependencies = .{
.raylib_zig = .{
.url = "git+https://github.com/raylib-zig/raylib-zig?ref=devel#4b6a05cd102d51f00523dd5c5e18e628df359d20",
.hash = "raylib_zig-5.6.0-dev-KE8REKNmBQDek2Sz27ULioxYpY9IYR0K0CeIC-iJRLCI",
},
},
.paths = .{ .paths = .{
"build.zig", "build.zig",
"build.zig.zon", "build.zig.zon",

View file

@ -14,31 +14,45 @@
let let
forAllSystems = forAllSystems =
f: f:
builtins.mapAttrs (system: pkgs: f pkgs zig.packages.${system}.zig_0_16_0) nixpkgs.legacyPackages; builtins.foldl' nixpkgs.lib.recursiveUpdate { } (
builtins.map (system: f system nixpkgs.legacyPackages.${system} zig.packages.${system}.zig_0_16_0) (
builtins.attrNames nixpkgs.legacyPackages
)
);
in in
{ forAllSystems (
devShells = forAllSystems ( system: pkgs: zig:
pkgs: zig: { let
default = pkgs.mkShellNoCC { nativeBuildInputs = with pkgs; [
packages = [ wayland-scanner
pkgs.bash pkg-config
zig ];
zig.zls buildInputs = with pkgs; [
]; libGL
}; wayland
} libxkbcommon
); ];
in
{
devShells.${system}.default = pkgs.mkShellNoCC {
packages = [
pkgs.bash
zig
zig.zls
];
inherit buildInputs;
inherit nativeBuildInputs;
};
packages = forAllSystems ( packages.${system}.default = zig.makePackage {
pkgs: zig: { pname = "sand";
default = zig.makePackage { version = "2.0.0";
pname = "sand"; src = ./.;
version = "1.0.0"; zigReleaseMode = "fast";
src = ./.; depsHash = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=";
zigReleaseMode = "fast"; inherit buildInputs;
depsHash = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo="; inherit nativeBuildInputs;
}; };
} }
); );
};
} }

View file

@ -2,28 +2,98 @@ const std = @import("std");
const sand = @import("sand"); const sand = @import("sand");
const raylib = @import("raylib");
const raygui = @import("raygui");
pub fn main(init: std.process.Init) !void { pub fn main(init: std.process.Init) !void {
var grid: sand.Grid = try .init(4, 4, init.gpa); const screenWidth = 600;
var gridCopy: sand.Grid = try .init(4, 4, init.gpa); const screenHeight = 600;
var async: [screenHeight]std.Io.Future(void) = undefined;
var grid: sand.Grid = try .init(screenWidth, screenHeight, init.gpa);
defer grid.deinit(init.gpa); defer grid.deinit(init.gpa);
defer gridCopy.deinit(init.gpa); // defer gridCopy.deinit(init.gpa);
//
// grid.arr[2][0].alive = true;
// grid.arr[2][1].alive = true;
// grid.arr[2][2].alive = true;
// grid.arr[2][3].alive = true;
//
// grid.arr[0][1].alive = true;
// grid.arr[1][1].alive = true;
// grid.arr[1][2].alive = true;
//
// std.debug.print("{f}\n", .{grid});
// try grid.copy(&gridCopy);
// grid.updateAll();
//
// while (!grid.eql(gridCopy)) {
// std.debug.print("{f}\n", .{grid});
// try grid.copy(&gridCopy);
// grid.updateAll();
// }
grid.arr[2][0].alive = true; raylib.initWindow(screenWidth, screenHeight, "sand");
grid.arr[2][1].alive = true; defer raylib.closeWindow();
grid.arr[2][2].alive = true;
grid.arr[2][3].alive = true;
grid.arr[0][1].alive = true; raylib.setTargetFPS(144 * 3);
grid.arr[1][1].alive = true;
grid.arr[1][2].alive = true;
std.debug.print("{f}\n", .{grid}); var timeForUpdate: u64 = 0;
try grid.copy(&gridCopy); var timeForDraw: u64 = 0;
grid.updateAll();
while (!grid.eql(gridCopy)) { // init grid
std.debug.print("{f}\n", .{grid}); var random = std.Random.DefaultPrng.init(@bitCast(std.Io.Timestamp.now(init.io, .real).toMicroseconds()));
try grid.copy(&gridCopy); for (0..300) |y| {
grid.updateAll(); for (y..(600 - y)) |x| {
grid.arr[y][x] = .{ .alive = random.random().int(u8) > 179 };
}
} }
const texture: []raylib.Color = try init.gpa.alloc(raylib.Color, screenHeight * screenWidth);
defer init.gpa.free(texture);
const screenImage: raylib.Image = .{
.data = @ptrCast(texture.ptr),
.width = screenWidth,
.height = screenHeight,
.mipmaps = 1,
.format = .uncompressed_r8g8b8a8,
};
const screenTexture = try raylib.loadTextureFromImage(screenImage);
var fps: struct {
total: f64,
count: f64,
} = .{
.total = 0,
.count = 0,
};
while (!raylib.windowShouldClose()) {
const updateTimer = std.Io.Clock.now(.awake, init.io);
try grid.updateAllConcurrent(init.gpa, init.io, &async);
timeForUpdate += @intCast(updateTimer.durationTo(.now(init.io, .awake)).toMicroseconds());
raylib.beginDrawing();
defer raylib.endDrawing();
const drawTimer = std.Io.Clock.now(.awake, init.io);
for (grid.arr, 0..) |line, y| {
for (line, 0..) |cell, x| {
texture[x + screenWidth * y] = if (cell.alive) .white else .black;
}
}
raylib.updateTexture(screenTexture, @ptrCast(texture));
raylib.drawTexture(screenTexture, 0, 0, .white);
timeForDraw += @intCast(drawTimer.durationTo(.now(init.io, .awake)).toMicroseconds());
fps.count += 1;
fps.total += @floatFromInt(raylib.getFPS());
std.log.info("fps: {}", .{raylib.getFPS()});
}
std.log.debug("time for draw: {}, time for update: {}", .{ timeForDraw, timeForUpdate });
std.log.debug("median fps: {}", .{fps.total / fps.count});
} }

View file

@ -57,18 +57,46 @@ pub const Grid = struct {
allocator.free(self.arr); allocator.free(self.arr);
} }
pub fn updateAll(self: *Grid) void { pub fn updateAll(self: *Grid, allocator: std.mem.Allocator) std.mem.Allocator.Error!void {
var newGrid: Grid = try .init(self.max.x + 1, self.max.y + 1, allocator);
newGrid.leaning = self.leaning.opposite();
for (0..self.arr.len) |y| { for (0..self.arr.len) |y| {
for (0..self.arr[y].len) |x| { for (0..self.arr[y].len) |x| {
update(.{ .y = @truncate(y), .x = @truncate(x) }, self.*); update(.{ .y = @truncate(y), .x = @truncate(x) }, self.*, newGrid);
} }
} }
self.leaning = self.leaning.opposite();
self.deinit(allocator);
self.* = newGrid;
}
pub fn updateAllConcurrent(self: *Grid, allocator: std.mem.Allocator, io: std.Io, async: []std.Io.Future(void)) (std.mem.Allocator.Error || std.Io.ConcurrentError)!void {
var newGrid: Grid = try .init(self.max.x + 1, self.max.y + 1, allocator);
newGrid.leaning = self.leaning.opposite();
for (0..self.arr.len) |y| {
async[y] = try io.concurrent((struct {
pub fn updateLine(s: Grid, ng: Grid, fy: usize) void {
for (0..s.arr[fy].len) |x| {
update(.{ .y = @truncate(fy), .x = @truncate(x) }, s, ng);
}
}
}).updateLine, .{ self.*, newGrid, y });
}
for (async) |*a| {
a.await(io);
}
self.deinit(allocator);
self.* = newGrid;
} }
pub const copyError = error{wrongDimensions}; pub const copyError = error{wrongDimensions};
pub fn copy(self: Grid, dest: *Grid) copyError!void { pub fn copy(self: Grid, dest: *Grid) void {
if (self.arr.len != dest.arr.len or self.arr[0].len != dest.arr[0].len) return copyError.wrongDimensions; std.debug.assert(self.arr.len == dest.arr.len and self.arr[0].len == dest.arr[0].len);
for (dest.arr, self.arr) |d, s| { for (dest.arr, self.arr) |d, s| {
@memcpy(d, s); @memcpy(d, s);
} }
@ -87,43 +115,87 @@ pub const Grid = struct {
} }
}; };
pub fn update(self: position, grid: Grid) void { pub fn update(self: position, grid: Grid, newGrid: Grid) void {
if (self.y == grid.max.y) return; alive: switch (false) {
if (!grid.arr[self.y][self.x].alive) return; false => {
const nextline = grid.arr[self.y + 1]; std.debug.assert(grid.max == newGrid.max);
if (!grid.arr[self.y][self.x].alive) return;
if (self.y == grid.max.y) continue :alive true;
const nextline = grid.arr[self.y + 1];
const newnextline = newGrid.arr[self.y + 1];
if (@cmpxchgStrong(bool, &nextline[self.x].alive, false, true, .seq_cst, .monotonic)) |_| { down: switch (nextline[self.x].alive) {
if (self.x == grid.max.x) { false => {
if (@cmpxchgStrong(bool, &nextline[self.x - 1].alive, false, true, .seq_cst, .monotonic)) |_| {} else { if (@cmpxchgStrong(bool, &newnextline[self.x].alive, false, true, .seq_cst, .monotonic)) |_| {
@atomicStore(bool, &grid.arr[self.y][self.x].alive, false, .seq_cst); continue :down true;
}
} else if (self.x == 0) {
if (@cmpxchgStrong(bool, &nextline[self.x + 1].alive, false, true, .seq_cst, .monotonic)) |_| {} else {
@atomicStore(bool, &grid.arr[self.y][self.x].alive, false, .seq_cst);
}
} else {
switch (grid.leaning) {
.Right => {
if (@cmpxchgStrong(bool, &nextline[self.x + 1].alive, false, true, .seq_cst, .monotonic)) |_| {
if (@cmpxchgStrong(bool, &nextline[self.x - 1].alive, false, true, .seq_cst, .monotonic)) |_| {} else {
@atomicStore(bool, &grid.arr[self.y][self.x].alive, false, .seq_cst);
}
} else { } else {
@atomicStore(bool, &grid.arr[self.y][self.x].alive, false, .seq_cst); @atomicStore(bool, &newGrid.arr[self.y][self.x].alive, false, .seq_cst);
return;
} }
}, },
.Left => { true => {
if (@cmpxchgStrong(bool, &nextline[self.x - 1].alive, false, true, .seq_cst, .monotonic)) |_| { if (self.x == grid.max.x) {
if (@cmpxchgStrong(bool, &nextline[self.x + 1].alive, false, true, .seq_cst, .monotonic)) |_| {} else { if (!nextline[self.x - 1].alive) {
@atomicStore(bool, &grid.arr[self.y][self.x].alive, false, .seq_cst); if (@cmpxchgStrong(bool, &newnextline[self.x - 1].alive, false, true, .seq_cst, .monotonic)) |_| {} else {
@atomicStore(bool, &newGrid.arr[self.y][self.x].alive, false, .seq_cst);
return;
}
}
} else if (self.x == 0) {
if (!nextline[self.x + 1].alive) {
if (@cmpxchgStrong(bool, &newnextline[self.x + 1].alive, false, true, .seq_cst, .monotonic)) |_| {} else {
@atomicStore(bool, &newGrid.arr[self.y][self.x].alive, false, .seq_cst);
return;
}
} }
} else { } else {
@atomicStore(bool, &grid.arr[self.y][self.x].alive, false, .seq_cst); switch (grid.leaning) {
.Right => {
right: switch (nextline[self.x + 1].alive) {
false => {
if (@cmpxchgStrong(bool, &newnextline[self.x + 1].alive, false, true, .seq_cst, .monotonic)) |_| {
continue :right true;
} else {
@atomicStore(bool, &newGrid.arr[self.y][self.x].alive, false, .seq_cst);
return;
}
},
true => {
if (!nextline[self.x - 1].alive) {
if (@cmpxchgStrong(bool, &newnextline[self.x - 1].alive, false, true, .seq_cst, .monotonic)) |_| {} else {
@atomicStore(bool, &newGrid.arr[self.y][self.x].alive, false, .seq_cst);
return;
}
}
},
}
},
.Left => {
left: switch (nextline[self.x - 1].alive) {
false => {
if (@cmpxchgStrong(bool, &newnextline[self.x - 1].alive, false, true, .seq_cst, .monotonic)) |_| {
continue :left true;
} else {
@atomicStore(bool, &newGrid.arr[self.y][self.x].alive, false, .seq_cst);
return;
}
},
true => {
if (!nextline[self.x + 1].alive) {
if (@cmpxchgStrong(bool, &newnextline[self.x + 1].alive, false, true, .seq_cst, .monotonic)) |_| {} else {
@atomicStore(bool, &newGrid.arr[self.y][self.x].alive, false, .seq_cst);
return;
}
}
},
}
},
}
} }
}, },
} }
} continue :alive true;
} else { },
@atomicStore(bool, &grid.arr[self.y][self.x].alive, false, .seq_cst); true => @atomicStore(bool, &newGrid.arr[self.y][self.x].alive, true, .seq_cst),
} }
} }