From 7858d05f71a8d641b2fafc714f735271c36dac81 Mon Sep 17 00:00:00 2001 From: Huck Boles Date: Tue, 20 Jun 2023 11:57:58 -0500 Subject: [PATCH] refactor: life + state + map into one struct --- src/lib.rs | 50 +++++++++----------------- src/life.rs | 95 ------------------------------------------------ src/map.rs | 100 +++++++++++++++++++++++++++++++++------------------ src/state.rs | 85 ------------------------------------------- 4 files changed, 82 insertions(+), 248 deletions(-) delete mode 100644 src/life.rs delete mode 100644 src/state.rs diff --git a/src/lib.rs b/src/lib.rs index 03d57cf..ddad6c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,15 +3,17 @@ use iced::theme::{self, Theme}; use iced::time; use iced::widget::{button, column, container, row, slider, text}; use iced::{Alignment, Application, Command, Element, Length, Point, Subscription}; + +use rustc_hash::{FxHashMap, FxHashSet}; use std::time::{Duration, Instant}; -mod life; mod map; mod mask; -mod state; -use life::*; use map::*; +use mask::*; + +pub type CellMap = FxHashSet; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct Cell { @@ -49,16 +51,15 @@ impl Cell { #[derive(Default)] pub struct CellSeq { map: Map, + mask: Mask, is_playing: bool, - queued_ticks: usize, bpm: usize, - next_speed: Option, - version: usize, } #[derive(Debug, Clone)] pub enum Message { - Grid(map::Message, usize), + Map(map::Message), + Mask(mask::Message), Tick(Instant), TogglePlayback, Randomize, @@ -85,41 +86,23 @@ impl Application for CellSeq { } fn title(&self) -> String { - String::from("Game of Life - Iced") + String::from("cellseq") } fn update(&mut self, message: Message) -> Command { match message { - Message::Grid(message, version) => { - if version == self.version { - self.map.update(message); - } - } + Message::Map(message) => self.map.update(message), + Message::Mask(message) => self.mask.update(message), Message::Tick(_) => { - self.queued_ticks = (self.queued_ticks + 1).min(self.bpm); - - if let Some(task) = self.map.tick(self.queued_ticks) { - if let Some(speed) = self.next_speed.take() { - self.bpm = speed; - } - - self.queued_ticks = 0; - - let version = self.version; - - return Command::perform(task, move |message| Message::Grid(message, version)); - } + return Command::perform(self.map.tick(), Message::Map); } Message::TogglePlayback => { self.is_playing = !self.is_playing; } - Message::Clear => { - self.map.clear(); - self.version += 1; - } Message::SpeedChanged(bpm) => { self.bpm = bpm; } + Message::Clear => self.map.clear(), Message::Randomize => self.map.randomize(), Message::Reset => self.map.reset(), Message::Save => self.map.save(), @@ -137,10 +120,9 @@ impl Application for CellSeq { } fn view(&self) -> Element { - let version = self.version; - let selected_speed = self.next_speed.unwrap_or(self.bpm); - let controls = view_controls(self.is_playing, selected_speed); - let map = self.map.view().map(move |m| Message::Grid(m, version)); + let bpm = self.bpm; + let controls = view_controls(self.is_playing, bpm); + let map = self.map.view().map(Message::Map); let content = column![controls, map,]; diff --git a/src/life.rs b/src/life.rs deleted file mode 100644 index f7489f4..0000000 --- a/src/life.rs +++ /dev/null @@ -1,95 +0,0 @@ -use super::*; - -use itertools::Itertools; -use rand::random; -use rustc_hash::{FxHashMap, FxHashSet}; - -#[derive(Clone, Default)] -pub struct Life { - seed: FxHashSet, - cells: FxHashSet, -} - -impl Life { - pub fn contains(&self, cell: &Cell) -> bool { - self.cells.contains(cell) - } - - pub fn populate(&mut self, cell: Cell) { - self.cells.insert(cell); - } - - pub fn unpopulate(&mut self, cell: &Cell) { - self.cells.remove(cell); - } - - pub fn clear(&mut self) { - self.cells = FxHashSet::default(); - } - - pub fn reset(&mut self) { - self.cells = self.seed.clone(); - } - - pub fn save_state(&mut self) { - self.seed = self.cells.clone(); - } - - pub fn randomize(&mut self) { - self.cells.clear(); - for (i, j) in (-32..=32).cartesian_product(-32..=32) { - if random::() > 0.5 { - self.populate(Cell { i, j }) - } - } - self.seed = self.cells.clone(); - } - - pub fn tick(&mut self) { - let mut adjacent_life = FxHashMap::default(); - - for cell in &self.cells { - adjacent_life.entry(*cell).or_insert(0); - - for neighbor in Cell::neighbors(*cell) { - let amount = adjacent_life.entry(neighbor).or_insert(0); - - *amount += 1; - } - } - - for (cell, amount) in adjacent_life.iter() { - match amount { - 2 => {} - 3 => { - self.cells.insert(*cell); - } - _ => { - self.cells.remove(cell); - } - } - } - } - - pub fn iter(&self) -> impl Iterator { - self.cells.iter() - } -} - -impl std::iter::FromIterator for Life { - fn from_iter>(iter: I) -> Self { - let cells: FxHashSet = iter.into_iter().collect(); - Life { - seed: cells.clone(), - cells, - } - } -} - -impl std::fmt::Debug for Life { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Life") - .field("cells", &self.cells.len()) - .finish() - } -} diff --git a/src/map.rs b/src/map.rs index 6e62a5f..9ace573 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,15 +1,20 @@ -use iced::mouse; +use iced::mouse::{self, Button::Left, Event::ButtonPressed}; use iced::widget::canvas; use iced::widget::canvas::event::{self, Event}; use iced::widget::canvas::{Cache, Canvas, Cursor, Geometry, Path}; use iced::{Color, Element, Length, Point, Rectangle, Size, Theme}; -use itertools::Itertools; -use std::future::Future; -use crate::{state::State, Cell, Life}; +use super::*; + +use itertools::Itertools; +use rand::random; +use rustc_hash::FxHashMap; +use std::{fmt::Debug, future::Future}; +#[derive(Default, Debug)] pub struct Map { - state: State, + seed: CellMap, + cells: CellMap, life_cache: Cache, } @@ -17,37 +22,55 @@ pub struct Map { pub enum Message { Populate(Cell), Unpopulate(Cell), - Ticked(Life), -} - -impl Default for Map { - fn default() -> Self { - Self { - state: State::with_life(Life::default()), - life_cache: Cache::default(), - } - } + Ticked(CellMap), } impl Map { - pub fn tick(&mut self, amount: usize) -> Option> { - let tick = self.state.tick(amount)?; + pub fn tick(&self) -> impl Future { + let mut life = self.cells.clone(); + let mut counts = FxHashMap::default(); + + let tick = tokio::task::spawn_blocking(move || { + for cell in &life { + counts.entry(*cell).or_insert(0); + + for neighbor in Cell::neighbors(*cell) { + let amount = counts.entry(neighbor).or_insert(0); + + *amount += 1; + } + } + + for (cell, amount) in counts.iter() { + match amount { + 2 => {} + 3 => { + life.insert(*cell); + } + _ => { + life.remove(cell); + } + } + } - Some(async move { Message::Ticked(tick.await) }) + life + }); + + async move { Message::Ticked(tick.await.unwrap()) } } pub fn update(&mut self, message: Message) { match message { Message::Populate(cell) => { - self.state.populate(cell); + self.cells.insert(cell); self.life_cache.clear(); } Message::Unpopulate(cell) => { - self.state.unpopulate(&cell); + self.cells.remove(&cell); self.life_cache.clear(); } Message::Ticked(life) => { - self.state.update(life); + self.cells = life; self.life_cache.clear(); } } @@ -61,21 +84,32 @@ impl Map { } pub fn clear(&mut self) { - self.state.clear(); + self.cells.clear(); self.life_cache.clear(); } pub fn reset(&mut self) { - self.state.reset(); + self.cells = self.seed.clone(); + self.life_cache.clear(); } pub fn save(&mut self) { - self.state.save(); + self.seed = self.cells.clone(); } pub fn randomize(&mut self) { + self.cells.clear(); + for (i, j) in (-32..=32).cartesian_product(-32..=32) { + if random::() > 0.5 { + self.cells.insert(Cell { i, j }); + } + } + self.seed = self.cells.clone(); self.life_cache.clear(); - self.state.randomize(); + } + + pub fn contains(&self, cell: &Cell) -> bool { + self.cells.contains(cell) } } @@ -89,14 +123,12 @@ impl canvas::Program for Map { bounds: Rectangle, cursor: Cursor, ) -> (event::Status, Option) { - if let Some(pos) = cursor.position_in(&bounds) { - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = event { - let location = Point { x: pos.x, y: pos.y }; - - let cell = Cell::at(location); + if let Some(position) = cursor.position_in(&bounds) { + if let Event::Mouse(ButtonPressed(Left)) = event { + let cell = Cell::at(position); return ( event::Status::Captured, - if self.state.contains(&cell) { + if self.contains(&cell) { Some(Message::Unpopulate(cell)) } else { Some(Message::Populate(cell)) @@ -124,10 +156,10 @@ impl canvas::Program for Map { (0..24) .cartesian_product(0..24) - .filter(|(i, j)| self.state.contains(&Cell { i: *i, j: *j })) - .for_each(|(i, j)| { + .filter(|x| self.contains(&Cell { i: x.1, j: x.0 })) + .for_each(|x| { frame.fill_rectangle( - Point::new(j as f32, i as f32), + Point::new(x.0 as f32, x.1 as f32), Size::UNIT, Color::WHITE, ); diff --git a/src/state.rs b/src/state.rs deleted file mode 100644 index dcb2375..0000000 --- a/src/state.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::{life::Life, Cell}; - -use rustc_hash::FxHashSet; -use std::future::Future; - -#[derive(Default)] -pub struct State { - life: Life, - births: FxHashSet, - is_ticking: bool, -} - -impl State { - pub fn with_life(life: Life) -> Self { - Self { - life, - ..Self::default() - } - } - - pub fn contains(&self, cell: &Cell) -> bool { - self.life.contains(cell) || self.births.contains(cell) - } - - pub fn randomize(&mut self) { - self.life.randomize() - } - - pub fn clear(&mut self) { - self.life.clear(); - } - - pub fn save(&mut self) { - self.life.save_state(); - } - - pub fn reset(&mut self) { - self.life.clear(); - self.life.reset(); - } - - pub fn populate(&mut self, cell: Cell) { - if self.is_ticking { - self.births.insert(cell); - } else { - self.life.populate(cell); - } - } - - pub fn unpopulate(&mut self, cell: &Cell) { - if self.is_ticking { - let _ = self.births.remove(cell); - } else { - self.life.unpopulate(cell); - } - } - - pub fn update(&mut self, mut life: Life) { - // self.births.drain().for_each(|cell| life.populate(cell)); - - self.life = life; - self.is_ticking = false; - } - - pub fn tick(&mut self, amount: usize) -> Option> { - if self.is_ticking { - return None; - } - - self.is_ticking = true; - - let mut life = self.life.clone(); - - Some(async move { - tokio::task::spawn_blocking(move || { - for _ in 0..amount { - life.tick(); - } - life - }) - .await - .unwrap() - }) - } -} -- 2.45.2