Files
Zag/src/tests/fireworks.zig
T
Loïc Grégoire 6cd9117a05 Remove bad tests
2026-10-07 18:02:17 -04:00

322 lines
8.0 KiB
Zig

//! Fireworks test ported from the Boron operating system.
const std = @import("std");
const r = @import("root");
const ke = r.ke;
const mm = r.mm;
const ps = r.ps;
const bv = r.bv;
var pixel_buffer: [*]u8 = undefined;
var fb_width: usize = 0;
var fb_height: usize = 0;
var fb_pitch: usize = 0;
var fb_bpp: usize = 0;
var inited = std.atomic.Value(bool).init(false);
var particle_count = std.atomic.Value(usize).init(0);
const background_color = 0x09090F;
fn fill_screen(color: u32) void {
const buf = @as([*]u32, @ptrCast(@alignCast(&pixel_buffer)));
for (0..fb_height) |y| {
for (0..fb_width) |x| {
const pixel_offset = y * fb_pitch + x * fb_bpp;
buf[pixel_offset] = color;
}
}
}
fn plot_pixel(x: i64, y: i64, color: u32) void {
if (x >= fb_width or y >= fb_height or x < 0 or y < 0) return;
const y_u: u64 = @intCast(y);
const x_u: u64 = @intCast(x);
const pixel_offset = y_u * fb_pitch + x_u * fb_bpp;
@as(*u32, @ptrCast(@alignCast(&pixel_buffer[pixel_offset]))).* = color;
}
fn rand_tsc_based() u32 {
const tsc = ke.time.read_time().value;
const lo: u32 = @truncate(tsc);
const hi: u32 = @truncate(tsc >> 32);
return lo ^ hi;
}
var rand_gen: u32 = 0x9521af17;
fn rand() i64 {
rand_gen +%= 0xe120fc15;
var tmp: u64 = @as(u64, rand_gen) *% 0x4a39b70d;
const m1: u32 = @truncate((tmp >> 32) ^ tmp);
tmp = @as(u64, m1) *% 0x12fad5c9;
return @as(u32, @truncate((tmp >> 32) ^ tmp));
}
const fixed_point_shift = 16;
fn fp_to_int(fp: i64) i64 {
return fp >> fixed_point_shift;
}
fn int_to_fp(i: i64) i64 {
return i << fixed_point_shift;
}
fn mul_fp_fp(a: i64, b: i64) i64 {
return (a * b) >> fixed_point_shift;
}
fn rand_fp() i64 {
return rand() & 0xFFFF;
}
fn rand_fp_sign() i64 {
const v = rand();
const m = -((v >> 16) & 1);
return ((v & 0xFFFF) ^ m) - m;
}
fn sin(angle: i64) i64 {
const x: i32 = @as(i16, @truncate(angle));
const mask = x >> 31;
const ax = (x ^ mask) - mask;
return (x * (32768 - ax)) >> 12;
}
fn cos(angle: i64) i64 {
return sin(angle + 16384);
}
const FireworkData = struct {
x: i64,
y: i64,
color: u32,
act_x: i64,
act_y: i64,
vel_x: i64,
vel_y: i64,
explosion_range: i32,
expire_in: i64,
i: u32,
sleep_start: u64,
};
fn get_random_color() u32 {
return @intCast((rand() + 0x808080) & 0xFFFFFF);
}
fn sleep(ms: usize, continuation: ?ke.Continuation) void {
_ = ke.wait.wait_any(&.{}, "sleep", .{
.timeout = .from(r.Milliseconds.init(ms)),
.continuation = continuation,
}) catch {
return;
};
@panic("Not a timeout?");
}
const particle_delay = 16;
var worst_delay = std.atomic.Value(u64).init(0);
fn particle_continuation(param: ?*anyopaque) void {
const data: *FireworkData = @ptrCast(@alignCast(param));
const sleep_start = data.sleep_start;
data.sleep_start = ke.time.read_time().value;
const delay = ((data.sleep_start - sleep_start) / std.time.ns_per_us);
const p_delay_us = std.time.us_per_ms * particle_delay;
plot_pixel(@intCast(data.x), @intCast(data.y), background_color);
if (delay > p_delay_us and delay > worst_delay.load(.monotonic)) {
worst_delay.store(delay - p_delay_us, .monotonic);
}
data.i += particle_delay;
if (data.i >= data.expire_in) {
mm.zone.gpa.destroy(data);
_ = particle_count.fetchSub(1, .monotonic);
ps.thread.exit();
}
data.act_x += @divTrunc(data.vel_x * particle_delay, 1000);
data.act_y += @divTrunc(data.vel_y * particle_delay, 1000);
data.x = fp_to_int(data.act_x);
data.y = fp_to_int(data.act_y);
data.vel_y += @divTrunc(int_to_fp(10) * particle_delay, 1000);
plot_pixel(@intCast(data.x), @intCast(data.y), data.color);
sleep(particle_delay, .{
.func = particle_continuation,
.arg = data,
});
}
fn particle(param: ?*anyopaque) void {
const parent_data: *FireworkData = @ptrCast(@alignCast(param));
const data = mm.zone.gpa.create(FireworkData) catch
{
// This will wait for free memory.
unreachable;
};
data.* = std.mem.zeroes(FireworkData);
_ = particle_count.fetchAdd(1, .monotonic);
data.x = parent_data.x;
data.y = parent_data.y;
data.act_x = parent_data.act_x;
data.act_y = parent_data.act_y;
const explosion_range = parent_data.explosion_range;
mm.zone.gpa.destroy(parent_data);
const angle = rand();
const speed = @max(rand_fp(), rand_fp());
data.vel_x = mul_fp_fp(cos(angle), speed) * explosion_range;
data.vel_y = mul_fp_fp(sin(angle), speed) * explosion_range;
const expire_in = 2000 + (@rem(rand(), 1000));
data.expire_in = expire_in;
data.i = 0;
data.color = get_random_color();
plot_pixel(@intCast(data.x), @intCast(data.y), data.color);
data.sleep_start = ke.time.read_time().value;
sleep(particle_delay, .{
.func = particle_continuation,
.arg = data,
});
@panic("shouldn't happen");
}
fn spawn_particle(arg: ?*anyopaque) void {
const t = ps.thread.create_kernel(
.Default,
.{ .func = &particle, .arg = arg },
false,
) catch @panic("OOM");
ke.sched.enqueue(&t.kern);
}
fn spawn_explodeable() void {
const t = ps.thread.create_kernel(
.Default,
.{ .func = &explodeable, .arg = null },
false,
) catch @panic("OOM");
ke.sched.enqueue(&t.kern);
}
fn explodeable(_: ?*anyopaque) void {
var data: FireworkData = undefined;
const offset_x: i64 = @intCast(fb_width * 400 / 1024);
data.x = @intCast(fb_width / 2);
data.y = @intCast(fb_height - 1);
data.act_x = int_to_fp(data.x);
data.act_y = int_to_fp(data.y);
data.vel_y = -int_to_fp(400 + @rem(rand(), 400));
data.vel_x = offset_x * rand_fp_sign();
data.color = get_random_color();
data.explosion_range = @intCast(100 + (@rem(rand(), 100)));
const expire_in = 500 + (@rem(rand(), 500));
var t: i32 = 0;
var i: i32 = 0;
while (i < expire_in) {
plot_pixel(data.x, data.y, data.color);
const delay: i32 = @as(u8, 16) + @intFromBool(t != 0);
sleep(@intCast(delay), null);
i += delay;
t += 1;
if (t == 3)
t = 0;
plot_pixel(data.x, data.y, background_color);
data.act_x += @divTrunc(data.vel_x * delay, 1000);
data.act_y += @divTrunc(data.vel_y * delay, 1000);
data.x = fp_to_int(data.act_x);
data.y = fp_to_int(data.act_y);
data.vel_y += @divTrunc(int_to_fp(10) * delay, 1000);
}
const part_count: usize = @intCast(@rem(rand(), 100) + 100);
for (0..part_count) |_| {
const param: *FireworkData = mm.zone.gpa.create(FireworkData) catch
{
// This will wait for free memory.
unreachable;
};
param.* = data;
spawn_particle(param);
}
ps.thread.exit();
}
fn spawning_loop(_: ?*anyopaque) void {
const spawn_count: usize = @intCast(@rem(rand(), 20) + 1);
for (0..spawn_count) |_| {
spawn_explodeable();
}
std.log.info("async: {}, sync: {}, usable memory: {} KiB, {} stacks for {} particles {}us worst delay", .{
mm.private.tlb.async_shootdowns.load(.monotonic),
mm.private.tlb.sync_shootdowns.load(.monotonic),
mm.private.phys.usable_memory.load(.monotonic) / 1024,
ke.private.thread.stacks_count(),
particle_count.load(.monotonic),
worst_delay.load(.monotonic),
});
sleep(2000, .{ .func = spawning_loop, .arg = null });
}
pub fn start(param: ?*anyopaque) void {
const boot_info: *r.BootInfo = @ptrCast(@alignCast(param));
rand_gen ^= rand_tsc_based();
bv.disable();
if (boot_info.framebuffer) |fb| {
fb_bpp = fb.bpp / 8;
fb_pitch = fb.pitch;
fb_width = fb.width;
fb_height = fb.height;
pixel_buffer = @ptrCast(
@alignCast(@as(*anyopaque, @ptrFromInt(fb.address))),
);
}
fill_screen(0x09090F);
spawning_loop(null);
}