From cdd294d08c12f66531d96a77ab39b114b03d36ae Mon Sep 17 00:00:00 2001 From: Huck Boles Date: Fri, 2 Jun 2023 14:29:43 -0500 Subject: [PATCH] refactored: render loop into main loop --- Cargo.lock | 77 ++++++++++++++++++++++ Cargo.toml | 1 + src/cells.rs | 11 ++-- src/graphics.rs | 62 ++---------------- src/graphics/actions.rs | 29 +++++++-- src/graphics/keys.rs | 10 +++ src/graphics/render.rs | 135 +++++++++++++++++++++++++++++++++++++++ src/graphics/selector.rs | 3 +- src/main.rs | 7 +- src/music.rs | 1 + src/music/scale.rs | 1 + src/music/transport.rs | 1 + src/state.rs | 12 +++- src/state/transport.rs | 1 + 14 files changed, 276 insertions(+), 75 deletions(-) create mode 100644 src/graphics/render.rs diff --git a/Cargo.lock b/Cargo.lock index 4d18abd..e52137c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" name = "cellseq" version = "0.1.0" dependencies = [ + "crossbeam", "crossterm", "eyre", "ndarray", @@ -30,6 +31,73 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "crossbeam" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossterm" version = "0.26.1" @@ -114,6 +182,15 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + [[package]] name = "mio" version = "0.8.8" diff --git a/Cargo.toml b/Cargo.toml index c5e719c..6dc1a49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ ndarray = "0.15" rand = "0.8" crossterm = "0.26" eyre = "0.6" +crossbeam = "0.8.2" diff --git a/src/cells.rs b/src/cells.rs index 36d3473..1506ebe 100644 --- a/src/cells.rs +++ b/src/cells.rs @@ -16,14 +16,14 @@ pub enum State { Alive, } -#[derive(Clone, Debug)] +#[derive(Debug, Clone)] pub struct World { pub map: Array2>, } impl World { pub fn new(area: Area) -> Self { - let map = Array2::from_elem((area.width(), area.height()), Cell::from(State::Dead)); + let map = Array2::from_elem((area.width(), area.height()), Cell::new(State::Dead)); Self { map } } @@ -31,9 +31,9 @@ impl World { let mut rng = thread_rng(); for cell in self.map.iter() { if rng.gen::() > val { - cell.set(State::Alive) + cell.set(State::Alive); } else { - cell.set(State::Dead) + cell.set(State::Dead); } } } @@ -42,7 +42,7 @@ impl World { macro_rules! wrap { ($portal:expr,$wall:expr) => { for (portal, wall) in $portal.iter().zip($wall) { - portal.set(wall.get()) + portal.set(wall.get()); } }; } @@ -110,6 +110,7 @@ impl Map> for World { } false } + fn get_point(&self, point: Point) -> Option> { self.get((point.y, point.x)).cloned() } diff --git a/src/graphics.rs b/src/graphics.rs index b35f082..9fbf217 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -4,6 +4,7 @@ mod keys; mod layout; mod map; mod point; +mod render; mod selector; pub use actions::*; @@ -12,13 +13,14 @@ pub use keys::*; pub use layout::*; pub use map::*; pub use point::*; +pub use render::*; pub use selector::*; use super::*; use crossterm::{ cursor::MoveTo, - event::{poll, read, Event}, + event::{read, Event}, execute, queue, style::{Print, SetAttributes, SetColors}, terminal, @@ -31,62 +33,6 @@ use std::{ time::Duration, }; -pub fn render_loop(state: &mut GlobalState) -> Result<()> { - execute!(stdout(), terminal::Clear(terminal::ClearType::All))?; - - state.layout.draw_outlines()?; - - let world = Arc::new(Mutex::new(state.world.clone())); - let state_arc = Arc::new(Mutex::new(state)); - - loop { - let arc = Arc::clone(&state_arc); - let tick = arc.lock().unwrap().tick(); - let timer = std::thread::spawn(move || thread::sleep(tick)); - - let event = std::thread::spawn(move || { - if poll(tick).unwrap() { - match read().unwrap() { - Event::Key(key) => key_event(key), - _ => Action::None, - } - } else { - Action::None - } - }); - - let mut maps = std::thread::spawn(|| {}); - if arc.lock().unwrap().transport.running { - let map = world.clone(); - let mut mask = arc.lock().unwrap().mask.clone(); - let cell_area = arc.lock().unwrap().layout.cells; - let mask_area = arc.lock().unwrap().layout.mask; - - maps = std::thread::spawn(move || { - let mut map = map.lock().unwrap(); - map.update(); - let tmp = map.clone(); - draw_map(&tmp, &cell_area).unwrap(); - mask[0].update(); - let tmp = mask[0].clone(); - draw_map(&tmp, &mask_area).unwrap(); - }); - } - - let area = arc.lock().unwrap().layout.transport; - - arc.lock().unwrap().transport.render(area)?; - - action(event.join().unwrap(), arc.clone())?; - maps.join().unwrap(); - - arc.lock().unwrap().cursor.render()?; - - timer.join().unwrap(); - stdout().flush()?; - } -} - pub fn draw_map(map: &impl Map, area: &Area) -> Result<()> { let ((x_zero, y_zero), (x_max, y_max)) = area.to_u16()?; @@ -127,6 +73,8 @@ pub fn draw_map(map: &impl Map, area: &Area) -> Result<()> { area.outline_area()?; + stdout().flush()?; + Ok(()) } diff --git a/src/graphics/actions.rs b/src/graphics/actions.rs index 3c67dbb..15f734c 100644 --- a/src/graphics/actions.rs +++ b/src/graphics/actions.rs @@ -33,26 +33,24 @@ pub enum Direction { Right, } -type AMmGS<'a> = Arc>; - -pub fn action(action: Action, state: AMmGS) -> Result<()> { +pub fn action(action: Action, state: ArcState) -> Result<()> { match action { Action::None => Ok(()), Action::Exit => exit(), Action::Move(direction) => move_cursor(direction, state), Action::Clock(clock) => transport_action(clock, state), - Action::Channel(_) => Ok(()), + Action::Channel(n) => change_channel(n, state), Action::Select => Ok(()), Action::SelectArea => Ok(()), Action::Reload => Ok(()), Action::Randomize => Ok(()), Action::Help => Ok(()), Action::Edit => Ok(()), - Action::SwitchPanel => Ok(()), + Action::SwitchPanel => switch_panel(state), } } -fn transport_action(clock: Clock, state: AMmGS) -> Result<()> { +fn transport_action(clock: Clock, state: ArcState) -> Result<()> { let mut state = state.lock().unwrap(); match clock { Clock::Stop => state.transport.running = false, @@ -64,7 +62,7 @@ fn transport_action(clock: Clock, state: AMmGS) -> Result<()> { Ok(()) } -fn move_cursor(direction: Direction, state: AMmGS) -> Result<()> { +fn move_cursor(direction: Direction, state: ArcState) -> Result<()> { let mut state = state.lock().unwrap(); match direction { Direction::Up => { @@ -90,3 +88,20 @@ fn move_cursor(direction: Direction, state: AMmGS) -> Result<()> { } Ok(()) } + +fn switch_panel(state: ArcState) -> Result<()> { + let mut mutex = state.lock().unwrap(); + mutex.cursor.area = if mutex.cursor.area == mutex.layout.mask { + mutex.layout.cells + } else { + mutex.layout.mask + }; + Ok(()) +} + +fn change_channel(channel: usize, state: ArcState) -> Result<()> { + let mut mutex = state.lock().unwrap(); + mutex.channels.0 = channel; + draw_map(&mutex.mask[channel], &mutex.layout.mask)?; + Ok(()) +} diff --git a/src/graphics/keys.rs b/src/graphics/keys.rs index ee3f4c2..b91ee0f 100644 --- a/src/graphics/keys.rs +++ b/src/graphics/keys.rs @@ -34,6 +34,16 @@ pub fn match_normal_key(key: KeyCode) -> Action { 'k' => Action::Move(Direction::Up), 'h' => Action::Move(Direction::Left), 'l' => Action::Move(Direction::Right), + '1' => Action::Channel(1), + '2' => Action::Channel(2), + '3' => Action::Channel(3), + '4' => Action::Channel(4), + '5' => Action::Channel(5), + '6' => Action::Channel(6), + '7' => Action::Channel(7), + '8' => Action::Channel(8), + '9' => Action::Channel(9), + '0' => Action::Channel(10), _ => Action::None, }, _ => Action::None, diff --git a/src/graphics/render.rs b/src/graphics/render.rs new file mode 100644 index 0000000..b911bc5 --- /dev/null +++ b/src/graphics/render.rs @@ -0,0 +1,135 @@ +use super::*; + +use crossbeam::channel::{bounded, Receiver, Sender}; + +pub type ArcState = Arc>; + +pub fn main_loop(state: GlobalState) -> Result<()> { + execute!(stdout(), terminal::Clear(terminal::ClearType::All))?; + + state.layout.draw_outlines()?; + + let (clock, sync) = bounded::<()>(0); + let state = Arc::new(Mutex::new(state)); + + let timer_state = Arc::clone(&state); + let world_state = Arc::clone(&state); + let action_state = Arc::clone(&state); + // let cursor_state = Arc::clone(&state); + + let timer = std::thread::spawn(move || timer_loop(timer_state, clock)); + let world = std::thread::spawn(move || world_loop(world_state, sync)); + let action = std::thread::spawn(move || action_loop(action_state)); + // let cursor = std::thread::spawn(move || cursor_loop(cursor_state)); + + timer.join().unwrap()?; + world.join().unwrap()?; + action.join().unwrap()?; + // cursor.join().unwrap()?; + + Ok(()) +} + +fn timer_loop(state: ArcState, clock: Sender<()>) -> Result<()> { + loop { + let tick = state.lock().unwrap().tick(); + thread::sleep(tick); + clock.send(())?; + } +} + +pub fn world_loop(state: ArcState, sync: Receiver<()>) -> Result<()> { + loop { + let mutex = state.lock().unwrap(); + if mutex.transport.running { + let mut world = mutex.world.clone(); + let mask = mutex.mask[mutex.channels.0].clone(); + let world_layout = mutex.layout.cells; + let mask_layout = mutex.layout.mask; + drop(mutex); + + world.update(); + draw_map(&world, &world_layout)?; + draw_map(&mask, &mask_layout)?; + sync.recv()?; + state.lock().as_mut().unwrap().world = world; + } else { + drop(mutex); + sync.recv()?; + } + } +} + +pub fn action_loop(state: ArcState) -> Result<()> { + while let Ok(event) = read() { + match event { + Event::Key(key) => action(key_event(key), state.clone())?, + Event::Resize(_, _) => state.lock().unwrap().layout = Layout::build()?, + _ => (), + } + } + Ok(()) +} + +pub fn cursor_loop(state: ArcState) -> Result<()> { + loop { + state.lock().unwrap().cursor.render()?; + std::thread::sleep(Duration::from_millis(100)) + } +} + +// pub fn render_loop(state: &mut GlobalState) -> Result<()> { +// execute!(stdout(), terminal::Clear(terminal::ClearType::All))?; + +// state.layout.draw_outlines()?; + +// let world = Arc::new(Mutex::new(state.world.clone())); +// let state_arc = Arc::new(Mutex::new(state)); + +// loop { +// let arc = Arc::clone(&state_arc); +// let tick = arc.lock().unwrap().tick(); +// let timer = std::thread::spawn(move || thread::sleep(tick)); + +// let event = std::thread::spawn(move || { +// if poll(tick).unwrap() { +// match read().unwrap() { +// Event::Key(key) => key_event(key), +// _ => Action::None, +// } +// } else { +// Action::None +// } +// }); + +// let mut maps = std::thread::spawn(|| {}); +// if arc.lock().unwrap().transport.running { +// let map = world.clone(); +// let mut mask = arc.lock().unwrap().mask.clone(); +// let cell_area = arc.lock().unwrap().layout.cells; +// let mask_area = arc.lock().unwrap().layout.mask; + +// maps = std::thread::spawn(move || { +// let mut map = map.lock().unwrap(); +// map.update(); +// let tmp = map.clone(); +// draw_map(&tmp, &cell_area).unwrap(); +// mask[0].update(); +// let tmp = mask[0].clone(); +// draw_map(&tmp, &mask_area).unwrap(); +// }); +// } + +// let area = arc.lock().unwrap().layout.transport; + +// arc.lock().unwrap().transport.render(area)?; + +// action(event.join().unwrap(), arc.clone())?; +// maps.join().unwrap(); + +// arc.lock().unwrap().cursor.render()?; + +// timer.join().unwrap(); +// stdout().flush()?; +// } +// } diff --git a/src/graphics/selector.rs b/src/graphics/selector.rs index 8e7785f..becfb27 100644 --- a/src/graphics/selector.rs +++ b/src/graphics/selector.rs @@ -11,7 +11,7 @@ use std::{ use super::*; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Cursor { pub position: Point, pub color: Colors, @@ -58,6 +58,7 @@ impl Cursor { cursor::MoveTo(offset.x.try_into().unwrap(), offset.y.try_into().unwrap()), style::SetAttributes(self.style), style::SetColors(self.color), + cursor::Show, style::Print(self.char.to_string()) )?; Ok(()) diff --git a/src/main.rs b/src/main.rs index 4e9ff64..c53dab3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,18 @@ use cellseq::*; -use crossterm::{cursor, execute, terminal}; +use crossterm::{cursor::Hide, execute, terminal}; use eyre::Result; +use std::io::stdout; fn main() -> Result<()> { terminal::enable_raw_mode()?; - execute!(std::io::stdout(), cursor::Hide)?; + execute!(stdout(), Hide)?; let mut state = GlobalState::build()?; state.world.randomize(0.75); state.mask[0].randomize(0.75, Scale::Aeolian); - match render_loop(&mut state) { + match main_loop(state) { Ok(_) => exit(), Err(e) => { eprintln!("{}", e); diff --git a/src/music.rs b/src/music.rs index a5e91c6..c0185e7 100644 --- a/src/music.rs +++ b/src/music.rs @@ -6,6 +6,7 @@ pub use transport::*; use std::fmt::Display; +#[derive(Clone, Copy, Debug)] pub struct TimeSignature { pub top: usize, pub bottom: usize, diff --git a/src/music/scale.rs b/src/music/scale.rs index 33f8773..486bfbd 100644 --- a/src/music/scale.rs +++ b/src/music/scale.rs @@ -5,6 +5,7 @@ use super::{ Note::{A, B, C, D, E, F, G}, }; +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Scale { Ionian, Dorian, diff --git a/src/music/transport.rs b/src/music/transport.rs index 59face8..415f2e2 100644 --- a/src/music/transport.rs +++ b/src/music/transport.rs @@ -1,5 +1,6 @@ use super::*; +#[derive(Clone, Copy, Debug)] pub struct Song { pub key: Option, pub scale: Option, diff --git a/src/state.rs b/src/state.rs index 7b9d5e4..159c265 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,16 +2,20 @@ mod transport; pub use transport::*; +use crossbeam::channel::{bounded, Receiver, Sender}; + use super::*; +#[derive(Debug, Clone)] pub struct GlobalState { pub world: World, pub layout: Layout, pub transport: Transport, pub song: Song, pub mask: Vec, - pub channels: Vec, + pub channels: (usize, Vec), pub cursor: Cursor, + pub update_mask: (Sender, Receiver), } impl GlobalState { @@ -33,14 +37,17 @@ impl GlobalState { let song = Song::new(None, None); + let update_mask = bounded::(0); + Ok(Self { world, layout, transport, song, mask, - channels, + channels: (0, channels), cursor, + update_mask, }) } @@ -49,6 +56,7 @@ impl GlobalState { } } +#[derive(Clone, Copy, Debug)] pub struct MidiChannel { pub num: usize, pub poly_num: usize, diff --git a/src/state/transport.rs b/src/state/transport.rs index f544fe4..b230928 100644 --- a/src/state/transport.rs +++ b/src/state/transport.rs @@ -7,6 +7,7 @@ use crossterm::{ }; use std::io::stdout; +#[derive(Debug, Clone, Copy)] pub struct Transport { pub running: bool, pub bpm: usize, -- 2.45.2