bunnymark_ecs/
bunnymark_ecs.rs1use ponko::*;
16
17struct Vel(Vec2);
18
19struct Rng(u64);
20impl Rng {
21 fn next_f32(&mut self) -> f32 {
22 self.0 ^= self.0 << 13;
23 self.0 ^= self.0 >> 7;
24 self.0 ^= self.0 << 17;
25 (self.0 >> 40) as f32 / (1u64 << 24) as f32
26 }
27}
28
29struct Bench {
30 crab: Texture,
31 rng: Rng,
32 report_at: f64,
33 frames: u32,
34 worst_ms: f32,
35 frame_start: Instant,
36 quit_after: f64,
37 scale: f32,
38}
39
40fn env_number(name: &str, default: f64) -> f64 {
41 std::env::var(name).ok().and_then(|v| v.parse().ok()).unwrap_or(default)
42}
43
44fn spawn(ctx: &mut Ctx, n: usize, at: Vec2) {
45 let b = ctx.world.resource_mut::<Bench>();
46 let crab = b.crab;
47 let mut batch = Vec::with_capacity(n);
48 for _ in 0..n {
49 let r = &mut b.rng;
50 let vel = vec2(r.next_f32() * 400.0 - 200.0, r.next_f32() * 400.0 - 300.0);
51 let color = Color::rgb(
52 0.6 + r.next_f32() * 0.4,
53 0.6 + r.next_f32() * 0.4,
54 0.6 + r.next_f32() * 0.4,
55 );
56 batch.push((
57 Transform2D {
58 scale: Vec2::splat(b.scale),
59 ..Transform2D::at(at)
60 },
61 Sprite::new(crab).with_color(color),
62 Vel(vel),
63 ));
64 }
65 ctx.world.spawn_batch(batch);
66}
67
68fn bounce(ctx: &mut Ctx) {
69 let dt = ctx.dt();
70 let bounds = ctx.window_size();
71 let gravity = 600.0;
72 ctx.world.query::<(&mut Transform2D, &mut Vel)>().for_each(|(t, v)| {
73 let (p, v) = (&mut t.position, &mut v.0);
74 v.y += gravity * dt;
75 *p += *v * dt;
76 if p.x < 0.0 {
77 p.x = 0.0;
78 v.x = v.x.abs();
79 }
80 if p.x > bounds.x {
81 p.x = bounds.x;
82 v.x = -v.x.abs();
83 }
84 if p.y > bounds.y {
85 p.y = bounds.y;
86 v.y = -v.y.abs() * 0.85;
87 }
88 if p.y < 0.0 {
89 p.y = 0.0;
90 v.y = 0.0;
91 }
92 });
93}
94
95fn report(ctx: &mut Ctx) {
96 if ctx.mouse_down(MouseButton::Left) {
97 let at = ctx.mouse_pos();
98 spawn(ctx, 1000, at);
99 }
100 let systems_ms: f32 = ctx
103 .system_timings()
104 .iter()
105 .filter(|t| !t.name.ends_with("report"))
106 .map(|t| t.avg_ms)
107 .sum();
108 let (time, count, render) = (ctx.time(), ctx.world.len(), ctx.render_stats());
109 let b = ctx.world.resource_mut::<Bench>();
110 let now = Instant::now();
111 let frame_ms = now.duration_since(b.frame_start).as_secs_f32() * 1000.0;
112 b.frame_start = now;
113 b.worst_ms = b.worst_ms.max(frame_ms);
114 b.frames += 1;
115 if time >= b.report_at {
116 println!(
117 "crabs: {:>8} fps: {:>6.1} avg frame: {:>6.2} ms worst: {:>6.2} ms update+draw calls: {:>6.2} ms render cpu: {:>5.2} ms gpu: {}",
118 count,
119 b.frames,
120 1000.0 / b.frames as f64,
121 b.worst_ms,
122 systems_ms,
123 render.cpu_ms,
124 render.gpu_ms.map_or("n/a".into(), |g| format!("{g:.2} ms")),
125 );
126 b.report_at += 1.0;
127 b.frames = 0;
128 b.worst_ms = 0.0;
129 }
130 let quit_after = b.quit_after;
131 if ctx.key_pressed(Key::Escape) || (quit_after > 0.0 && time >= quit_after) {
132 ctx.quit();
133 }
134}
135
136fn main() {
137 let start_count = env_number("CRABS", 10_000.0) as usize;
138 let quit_after = env_number("SECONDS", 0.0);
139
140 Game::new("Crabby Engine: bunnymark (ECS)", 1280, 720)
141 .vsync(false)
142 .system(bounce)
143 .system(report)
144 .run(move |ctx| {
145 let crab = ctx.load_texture("crab.png");
146 ctx.world.insert_resource(Bench {
147 crab,
148 rng: Rng(0x9E37_79B9_7F4A_7C15),
149 report_at: 1.0,
150 frames: 0,
151 worst_ms: 0.0,
152 frame_start: Instant::now(),
153 quit_after,
154 scale: env_number("SCALE", 1.0) as f32,
155 });
156 spawn(ctx, start_count, vec2(20.0, 20.0));
157 });
158}