mini_gl_fb/examples/game_of_life.rs
LoganDark db2f86ce15
Add wakeup support (#7)
* Add wakeup support

This also fixes a bug (the `glutin_handle_basic_input` callback was never actually given mutable access to the `BasicInput`, so there's no way it could have set `BasicInput::wait` anyway).

Glad I got that before the 0.8 release, phew.

* Fix bug in reschedule_wakeup

* Fix adjust_wakeup
2021-02-21 21:10:27 -05:00

209 lines
6.9 KiB
Rust

#[macro_use]
extern crate mini_gl_fb;
use mini_gl_fb::BufferFormat;
use mini_gl_fb::glutin::event::{VirtualKeyCode, MouseButton};
use mini_gl_fb::glutin::event_loop::EventLoop;
use mini_gl_fb::glutin::dpi::LogicalSize;
use std::time::{Instant, Duration};
use mini_gl_fb::breakout::Wakeup;
const WIDTH: usize = 200;
const HEIGHT: usize = 200;
const NORMAL_SPEED: u64 = 500;
const TURBO_SPEED: u64 = 20;
fn main() {
let mut event_loop = EventLoop::new();
let mut fb = mini_gl_fb::get_fancy(config! {
window_title: String::from("PSA: Conway wants you to appreciate group theory instead"),
window_size: LogicalSize::new(800.0, 800.0),
buffer_size: Some(LogicalSize::new(WIDTH as _, HEIGHT as _))
}, &event_loop);
fb.change_buffer_format::<u8>(BufferFormat::R);
fb.use_post_process_shader(POST_PROCESS);
let mut neighbors = vec![0; WIDTH * HEIGHT];
let mut cells = vec![false; WIDTH * HEIGHT];
cells[5 * WIDTH + 10] = true;
cells[5 * WIDTH + 11] = true;
cells[5 * WIDTH + 12] = true;
cells[50 * WIDTH + 50] = true;
cells[51 * WIDTH + 51] = true;
cells[52 * WIDTH + 49] = true;
cells[52 * WIDTH + 50] = true;
cells[52 * WIDTH + 51] = true;
// ID of the Wakeup which means we should update the board
let mut update_id: Option<u32> = None;
fb.glutin_handle_basic_input(&mut event_loop, |fb, input| {
// We're going to use wakeups to update the grid
input.wait = true;
if update_id.is_none() {
update_id = Some(input.schedule_wakeup(Instant::now() + Duration::from_millis(500)))
} else if let Some(mut wakeup) = input.wakeup {
if Some(wakeup.id) == update_id {
// Time to update our grid
calculate_neighbors(&mut cells, &mut neighbors);
make_some_babies(&mut cells, &mut neighbors);
fb.update_buffer(&cells);
// Reschedule another update
wakeup.when = Instant::now() + Duration::from_millis(
if input.key_is_down(VirtualKeyCode::LShift) {
TURBO_SPEED
} else {
NORMAL_SPEED
}
);
input.reschedule_wakeup(wakeup);
}
// We will get called again after all wakeups are handled
return true;
}
if input.key_is_down(VirtualKeyCode::Escape) {
return false;
}
if input.mouse_is_down(MouseButton::Left) || input.mouse_is_down(MouseButton::Right) {
// Mouse was pressed
let (x, y) = input.mouse_pos;
let x = x.min(WIDTH as f64 - 0.0001).max(0.0).floor() as usize;
let y = y.min(HEIGHT as f64 - 0.0001).max(0.0).floor() as usize;
cells[y * WIDTH + x] = input.mouse_is_down(MouseButton::Left);
fb.update_buffer(&cells);
// Give the user extra time to make something pretty each time they click
if !input.key_is_down(VirtualKeyCode::LShift) {
input.adjust_wakeup(update_id.unwrap(), Wakeup::after_millis(2000));
}
}
// TODO support right shift. Probably by querying modifiers somehow. (modifiers support)
if input.key_pressed(VirtualKeyCode::LShift) {
// immediately update
input.adjust_wakeup(update_id.unwrap(), Wakeup::after_millis(0));
} else if input.key_released(VirtualKeyCode::LShift) {
// immediately stop updating
input.adjust_wakeup(update_id.unwrap(), Wakeup::after_millis(NORMAL_SPEED));
}
true
});
}
fn calculate_neighbors(cells: &mut [bool], neighbors: &mut [u32]) {
// a very basic GOL implementation; assumes outside the grid is dead
for y in 0..HEIGHT {
for x in 0..WIDTH {
let mut n = 0;
// Above
if y > 0 {
let j = y - 1;
if x > 0 && cells[j * WIDTH + x - 1] {
n += 1;
}
if cells[j * WIDTH + x] {
n += 1;
}
if x < (WIDTH - 1) && cells[j * WIDTH + x + 1] {
n += 1;
}
}
// On the same line
if x > 0 && cells[y * WIDTH + x - 1] {
n += 1;
}
if x < (WIDTH - 1) && cells[y * WIDTH + x + 1] {
n += 1;
}
// Below
if y < (HEIGHT - 1) {
let j = y + 1;
if x > 0 && cells[j * WIDTH + x - 1] {
n += 1;
}
if cells[j * WIDTH + x] {
n += 1;
}
if x < (WIDTH - 1) && cells[j * WIDTH + x + 1] {
n += 1;
}
}
let cell = y * WIDTH + x;
neighbors[cell] = n;
}
}
}
fn make_some_babies(cells: &mut [bool], neighbors: &mut [u32]) {
for y in 0..HEIGHT {
for x in 0..WIDTH {
let cell = y * WIDTH + x;
if !cells[cell] {
// if this cell is dead
if neighbors[cell] == 3 {
// and it has three neighbors...
cells[cell] = true;
}
// else it stays dead
continue;
}
// the cell is alive
if neighbors[cell] <= 1 {
// die from under population
cells[cell] = false;
} else if neighbors[cell] > 3 {
// die from over population
cells[cell] = false;
}
// else: survive to the next generation
}
}
}
const POST_PROCESS: &str = "
bool on_grid_line(float pos) {
if (fract(pos) < 0.2) {
return true;
} else {
return false;
}
}
void main_image( out vec4 r_frag_color, in vec2 uv )
{
// A bool is stored as 1 in our image buffer
// OpenGL will map that u8/bool onto the range [0, 1]
// so the u8 1 in the buffer will become 1 / 255 or 0.0
// multiply by 255 to turn 1 / 255 into full intensity and leave 0 as 0
vec3 sample = texture(u_buffer, uv).rrr * 255.0;
// invert it since that's how GOL stuff is typically shown
sample = 1.0 - sample;
// attempt to add some grid lines (assumes width and height of image are 200)...
vec2 grid_pos = uv * 200;
if (on_grid_line(grid_pos.x) || on_grid_line(grid_pos.y)) {
sample = max(sample - 0.4, vec3(0.0, 0.0, 0.0));
}
r_frag_color = vec4(sample, 1.0);
}
";