use super::data::{Grid, GridIdx};
use gfx;
use gfx::traits::FactoryExt;
use gfx::Device;
use gfx::Factory;
use gfx_device_gl::{CommandBuffer, Device as GlDevice, Resources};
use gfx_window_glutin;
use glutin;
use glutin::dpi::LogicalSize;
use rayon::prelude::*;
use std::error::Error;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
const WINDOW_TITLE: &str = "Simple Life";
const QUAD_VERTICES: [Vertex; 4] = [
Vertex {
position: [-0.5, 0.5],
},
Vertex {
position: [-0.5, -0.5],
},
Vertex {
position: [0.5, -0.5],
},
Vertex {
position: [0.5, 0.5],
},
];
const QUAD_INDICES: [u16; 6] = [0, 1, 2, 2, 3, 0];
const CLEARING_COLOR: [f32; 4] = [0.1, 0.2, 0.3, 1.0];
const WHITE: [f32; 4] = [1., 1., 1., 1.];
const COLOURED: [f32; 4] = [0.2, 0.4, 0.5, 1.];
const SCALE_TOTAL: f32 = 2.0;
const INSTANCE_PORTION: f32 = 1.8;
pub type ColorFormat = gfx::format::Rgba8;
pub type DepthFormat = gfx::format::DepthStencil;
gfx_defines! {
vertex Vertex {
position: [f32; 2] = "a_Position",
}
vertex Instance {
translate: [f32; 2] = "a_Translate",
colour: [f32; 4] = "a_Color",
}
constant Locals {
scale: [[f32;2];2] = "u_Scale",
}
pipeline pipe {
vertex: gfx::VertexBuffer<Vertex> = (),
instance: gfx::InstanceBuffer<Instance> = (),
scale: gfx::Global<[[f32;2];2]> = "u_Scale",
locals: gfx::ConstantBuffer<Locals> = "Locals",
out: gfx::RenderTarget<ColorFormat> = "Target0",
}
}
fn fill_instances(instances: &mut [Instance], grid: &Grid, size: [[f32; 2]; 2]) -> Vec<Instance> {
let width = grid.width();
let height = grid.height();
let cells = grid.cells();
let size_x = size[0][0];
let size_y = size[1][1];
let scale_remaining = SCALE_TOTAL - INSTANCE_PORTION;
let gap_x = scale_remaining / (width + 1) as f32;
let gap_y = scale_remaining / (height + 1) as f32;
let begin_x = -1. + gap_x + (size_x / 2.);
let begin_y = -1. + gap_y + (size_y / 2.);
let mut translate = [begin_x, begin_y];
let mut v = Vec::with_capacity(grid.area());
let mut index = 0;
for row in cells {
for cell in row {
let colour = if cell.alive() { COLOURED } else { WHITE };
let inst = Instance { translate, colour };
v.push(inst);
instances[index] = inst;
translate[0] += size_x + gap_x;
index += 1;
}
translate[1] += size_y + gap_y;
translate[0] = begin_x;
}
v
}
pub struct App {
grid: Arc<Mutex<Grid>>,
updates_per_second: u16,
window: glutin::WindowedContext,
device: GlDevice,
events_loop: glutin::EventsLoop,
pso: gfx::PipelineState<Resources, pipe::Meta>,
data: pipe::Data<Resources>,
encoder: gfx::Encoder<Resources, CommandBuffer>,
slice: gfx::Slice<Resources>,
upload: gfx::handle::Buffer<Resources, Instance>,
instances: Vec<Instance>,
uploading: bool,
}
impl App {
#[allow(clippy::missing_errors_doc)]
pub fn new(
grid: Grid,
window_width: u32,
window_height: u32,
updates_per_second: u16,
) -> Result<Self, Box<dyn Error>> {
let events_loop = glutin::EventsLoop::new();
let window_size = LogicalSize::new(window_width.into(), window_height.into());
let builder = glutin::WindowBuilder::new()
.with_title(WINDOW_TITLE)
.with_dimensions(window_size);
let context = glutin::ContextBuilder::new().with_vsync(true);
let (window, device, mut factory, main_color, _) =
gfx_window_glutin::init::<ColorFormat, DepthFormat>(builder, context, &events_loop)?;
let encoder = factory.create_command_buffer().into();
let width: u32 = u32::try_from(grid.width())?;
let height: u32 = u32::try_from(grid.height())?;
let area = u32::try_from(grid.area())?;
let size = [
[INSTANCE_PORTION / width as f32, 0.],
[0., INSTANCE_PORTION / height as f32],
];
let upload = factory.create_upload_buffer(area as usize)?;
let insts = {
let mut writer = factory.write_mapping(&upload)?;
fill_instances(&mut writer, &grid, size)
};
let instances = factory.create_buffer(
area as usize,
gfx::buffer::Role::Vertex,
gfx::memory::Usage::Dynamic,
gfx::memory::Bind::TRANSFER_DST,
)?;
let (quad_vertices, mut slice) =
factory.create_vertex_buffer_with_slice(&QUAD_VERTICES, &QUAD_INDICES[..]);
slice.instances = Some((area, 0));
let locals = Locals { scale: size };
Ok(Self {
grid: Arc::new(Mutex::new(grid)),
updates_per_second,
window,
device,
events_loop,
pso: factory.create_pipeline_simple(
include_bytes!("shaders/instancing.glslv"),
include_bytes!("shaders/instancing.glslf"),
pipe::new(),
)?,
encoder,
data: pipe::Data {
vertex: quad_vertices,
instance: instances,
scale: size,
locals: factory.create_buffer_immutable(
&[locals],
gfx::buffer::Role::Constant,
gfx::memory::Bind::empty(),
)?,
out: main_color,
},
instances: insts,
slice,
upload,
uploading: true,
})
}
#[inline]
fn render(&mut self) -> Result<(), Box<dyn Error>> {
if self.uploading {
self.encoder
.copy_buffer(&self.upload, &self.data.instance, 0, 0, self.upload.len())?;
self.uploading = false;
} else {
self.update_instances()?;
self.encoder
.update_buffer(&self.data.instance, &self.instances, 0)?;
}
self.encoder.clear(&self.data.out, CLEARING_COLOR);
self.encoder.draw(&self.slice, &self.pso, &self.data);
self.encoder.flush(&mut self.device);
self.window.swap_buffers()?;
self.device.cleanup();
Ok(())
}
#[allow(clippy::significant_drop_tightening)]
#[doc(hidden)]
#[inline]
pub fn update_instances(&mut self) -> Result<(), Box<dyn Error>> {
let grid = self.grid.lock().map_err(|e| format!("{e}"))?;
let op = |(idx, inst): (usize, &mut Instance)| {
if let Some(cell) = grid.get_idx(&GridIdx(idx)) {
let colour = if cell.alive() { COLOURED } else { WHITE };
inst.colour = colour;
}
};
if grid.area_requires_bool() {
self.instances.par_iter_mut().enumerate().for_each(op);
} else {
for (idx, inst) in self.instances.iter_mut().enumerate() {
op((idx, inst));
}
}
Ok(())
}
#[allow(clippy::missing_errors_doc)]
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
{
let grid = self.grid.clone();
let updates_per_second = self.updates_per_second;
thread::spawn(move || async_update_loop(&grid, updates_per_second));
}
let mut running = true;
while running {
let currently_uploading = self.uploading;
self.events_loop.poll_events(|polled_event| {
if let glutin::Event::WindowEvent { event, .. } = polled_event {
match event {
glutin::WindowEvent::KeyboardInput {
input:
glutin::KeyboardInput {
virtual_keycode: Some(glutin::VirtualKeyCode::Escape),
..
},
..
}
| glutin::WindowEvent::CloseRequested => running = false,
glutin::WindowEvent::Resized(_) => running = currently_uploading,
_ => {}
}
}
});
self.render()?;
}
Ok(())
}
}
fn async_update_loop(grid: &Arc<Mutex<Grid>>, updates_per_second: u16) -> Result<(), String> {
let wait_duration = Duration::from_millis(1000 / u64::from(updates_per_second));
let mut last_updated = Instant::now();
loop {
if last_updated.elapsed() > wait_duration {
grid.lock().map_err(|e| format!("{e}"))?.advance();
last_updated = Instant::now();
}
}
}