rewrite event loop and replace channel with queue

This commit is contained in:
figsoda 2020-11-20 09:20:46 -05:00
parent 2cb4a22188
commit b488293bd3
4 changed files with 289 additions and 268 deletions

8
Cargo.lock generated
View file

@ -183,10 +183,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-queue"
version = "0.5.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" checksum = "6b2a58563f049aa3bae172bc4120f093b5901161c629f280a1f40ba55317d774"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"crossbeam-utils", "crossbeam-utils",
@ -407,7 +407,7 @@ dependencies = [
"anyhow", "anyhow",
"async-io", "async-io",
"async-net", "async-net",
"crossbeam-channel", "crossbeam-queue",
"crossterm", "crossterm",
"dirs-next", "dirs-next",
"expand", "expand",

View file

@ -17,7 +17,7 @@ categories = ["command-line-utilities"]
anyhow = "1.0.34" anyhow = "1.0.34"
async-io = "1.2.0" async-io = "1.2.0"
async-net = "1.5.0" async-net = "1.5.0"
crossbeam-channel = "0.5.0" crossbeam-queue = "0.3.0"
crossterm = "0.18.2" crossterm = "0.18.2"
dirs-next = "2.0.0" dirs-next = "2.0.0"
expand = "0.2.0" expand = "0.2.0"

View file

@ -65,9 +65,6 @@ pub struct State {
#[derive(Debug)] #[derive(Debug)]
pub enum Command { pub enum Command {
Quit, Quit,
UpdateFrame,
UpdateStatus,
UpdateQueue,
ToggleRepeat, ToggleRepeat,
ToggleRandom, ToggleRandom,
ToggleSingle, ToggleSingle,

View file

@ -11,7 +11,7 @@ mod mpd;
use anyhow::{Context, Error, Result}; use anyhow::{Context, Error, Result};
use async_io::{block_on, Timer}; use async_io::{block_on, Timer};
use crossbeam_channel::unbounded; use crossbeam_queue::SegQueue;
use crossterm::{ use crossterm::{
event::{ event::{
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers,
@ -24,7 +24,18 @@ use dirs_next::config_dir;
use structopt::StructOpt; use structopt::StructOpt;
use tui::{backend::CrosstermBackend, widgets::ListState, Terminal}; use tui::{backend::CrosstermBackend, widgets::ListState, Terminal};
use std::{cmp::min, fs, io::stdout, process::exit, thread, time::Duration}; use std::{
cmp::min,
fs,
io::stdout,
process::exit,
sync::{
atomic::{AtomicU8, Ordering},
Arc,
},
thread,
time::Duration,
};
use crate::{ use crate::{
app::{Command, Opts, State}, app::{Command, Opts, State},
@ -133,43 +144,46 @@ async fn run() -> Result<()> {
let seek_forwards = seek_forwards.as_bytes(); let seek_forwards = seek_forwards.as_bytes();
let update_interval = Duration::from_secs_f32(1.0 / opts.ups.unwrap_or(cfg.ups)); let update_interval = Duration::from_secs_f32(1.0 / opts.ups.unwrap_or(cfg.ups));
let (tx, rx) = unbounded(); let t1 = thread::current();
let tx1 = tx.clone(); let t2 = t1.clone();
let tx2 = tx.clone(); let t3 = t1.clone();
let tx3 = tx.clone(); let updates = Arc::new(AtomicU8::new(0b000));
let updates1 = updates.clone();
let updates2 = updates.clone();
let updates3 = updates.clone();
let cmds = Arc::new(SegQueue::new());
let cmds1 = cmds.clone();
thread::spawn(move || { thread::spawn(move || loop {
let tx = tx1; updates1.fetch_or(
loop { match block_on(idle_cl.idle()).or_die() {
let changed = block_on(idle_cl.idle()).or_die(); (true, true) => 0b111,
if changed.0 { (true, false) => 0b101,
tx.send(Command::UpdateStatus).or_die(); (false, true) => 0b011,
} _ => continue,
if changed.1 { },
tx.send(Command::UpdateQueue).or_die(); Ordering::Relaxed,
} );
tx.send(Command::UpdateFrame).or_die(); t1.unpark();
}
}); });
thread::spawn(move || { thread::spawn(move || loop {
let tx = tx2;
loop {
let timer = Timer::after(update_interval); let timer = Timer::after(update_interval);
tx.send(Command::UpdateStatus).or_die(); updates2.fetch_or(0b101, Ordering::Relaxed);
tx.send(Command::UpdateFrame).or_die(); t2.unpark();
block_on(timer); block_on(timer);
}
}); });
thread::spawn(move || { thread::spawn(move || {
let mut searching = false; let mut searching = false;
let tx = tx3;
while let Ok(ev) = event::read() { while let Ok(ev) = event::read() {
tx.send(match ev { cmds1.push(match ev {
Event::Mouse(MouseEvent::ScrollDown(..)) => Command::Down, Event::Mouse(MouseEvent::ScrollDown(..)) => Command::Down,
Event::Mouse(MouseEvent::ScrollUp(..)) => Command::Up, Event::Mouse(MouseEvent::ScrollUp(..)) => Command::Up,
Event::Resize(..) => Command::UpdateFrame, Event::Resize(..) => {
updates3.fetch_or(0b001, Ordering::Relaxed);
continue;
}
Event::Key(KeyEvent { code, modifiers }) => match code { Event::Key(KeyEvent { code, modifiers }) => match code {
KeyCode::Char('q') if modifiers.contains(KeyModifiers::CONTROL) => { KeyCode::Char('q') if modifiers.contains(KeyModifiers::CONTROL) => {
Command::Quit Command::Quit
@ -218,29 +232,18 @@ async fn run() -> Result<()> {
_ => continue, _ => continue,
}, },
_ => continue, _ => continue,
}) });
.or_die(); t3.unpark();
} }
}); });
for cmd in rx { loop {
let mut empty = false;
let cmd = cmds.pop();
let mut updates = updates.swap(0b000, Ordering::SeqCst);
match cmd { match cmd {
Some(cmd) => match cmd {
Command::Quit => break, Command::Quit => break,
Command::UpdateFrame => render(&mut term, &cfg.layout, &mut s)?,
Command::UpdateStatus => {
s.status = cl.status().await?;
}
Command::UpdateQueue => {
let res = cl.queue(s.status.queue_len, &cfg.search_fields).await?;
s.queue = res.0;
queue_strings = res.1;
s.selected = s.status.song.map_or(0, |song| song.pos);
s.liststate = ListState::default();
s.liststate.select(Some(s.selected));
if !s.query.is_empty() {
s.update_search(&queue_strings);
}
}
Command::ToggleRepeat => { Command::ToggleRepeat => {
cl.command(if s.status.repeat { cl.command(if s.status.repeat {
b"repeat 0\n" b"repeat 0\n"
@ -249,8 +252,7 @@ async fn run() -> Result<()> {
}) })
.await .await
.context("Failed to toggle repeat")?; .context("Failed to toggle repeat")?;
s.status = cl.status().await?; updates |= 0b101;
render(&mut term, &cfg.layout, &mut s)?;
} }
Command::ToggleRandom => { Command::ToggleRandom => {
cl.command(if s.status.random { cl.command(if s.status.random {
@ -260,8 +262,7 @@ async fn run() -> Result<()> {
}) })
.await .await
.context("Failed to toggle random")?; .context("Failed to toggle random")?;
s.status = cl.status().await?; updates |= 0b101;
render(&mut term, &cfg.layout, &mut s)?;
} }
Command::ToggleSingle => { Command::ToggleSingle => {
cl.command(if s.status.single == Some(true) { cl.command(if s.status.single == Some(true) {
@ -271,8 +272,7 @@ async fn run() -> Result<()> {
}) })
.await .await
.context("Failed to toggle single")?; .context("Failed to toggle single")?;
s.status = cl.status().await?; updates |= 0b101;
render(&mut term, &cfg.layout, &mut s)?;
} }
Command::ToggleOneshot => { Command::ToggleOneshot => {
cl.command( cl.command(
@ -282,8 +282,7 @@ async fn run() -> Result<()> {
) )
.await .await
.context("Failed to toggle oneshot")?; .context("Failed to toggle oneshot")?;
s.status = cl.status().await?; updates |= 0b101;
render(&mut term, &cfg.layout, &mut s)?;
} }
Command::ToggleConsume => { Command::ToggleConsume => {
cl.command(if s.status.consume { cl.command(if s.status.consume {
@ -293,50 +292,43 @@ async fn run() -> Result<()> {
}) })
.await .await
.context("Failed to toggle consume")?; .context("Failed to toggle consume")?;
s.status = cl.status().await?; updates |= 0b101;
render(&mut term, &cfg.layout, &mut s)?;
} }
Command::TogglePause => { Command::TogglePause => {
cl.command(s.status.state.map_or(b"play\n", |_| b"pause\n")) cl.command(s.status.state.map_or(b"play\n", |_| b"pause\n"))
.await .await
.context("Failed to toggle pause")?; .context("Failed to toggle pause")?;
s.status = cl.status().await?; updates |= 0b101;
render(&mut term, &cfg.layout, &mut s)?;
} }
Command::Stop => { Command::Stop => {
cl.command(b"stop\n") cl.command(b"stop\n")
.await .await
.context("Failed to stop playing")?; .context("Failed to stop playing")?;
s.status = cl.status().await?; updates |= 0b101;
render(&mut term, &cfg.layout, &mut s)?;
} }
Command::SeekBackwards => { Command::SeekBackwards => {
cl.command(seek_backwards) cl.command(seek_backwards)
.await .await
.context("Failed to seek backwards")?; .context("Failed to seek backwards")?;
s.status = cl.status().await?; updates |= 0b101;
render(&mut term, &cfg.layout, &mut s)?;
} }
Command::SeekForwards => { Command::SeekForwards => {
cl.command(seek_forwards) cl.command(seek_forwards)
.await .await
.context("Failed to seek forwards")?; .context("Failed to seek forwards")?;
s.status = cl.status().await?; updates |= 0b101;
render(&mut term, &cfg.layout, &mut s)?;
} }
Command::Previous => { Command::Previous => {
cl.command(b"previous\n") cl.command(b"previous\n")
.await .await
.context("Failed to play previous song")?; .context("Failed to play previous song")?;
s.status = cl.status().await?; updates |= 0b101;
render(&mut term, &cfg.layout, &mut s)?;
} }
Command::Next => { Command::Next => {
cl.command(b"next\n") cl.command(b"next\n")
.await .await
.context("Failed to play next song")?; .context("Failed to play next song")?;
s.status = cl.status().await?; updates |= 0b101;
render(&mut term, &cfg.layout, &mut s)?;
} }
Command::Play => { Command::Play => {
cl.play(if s.query.is_empty() { cl.play(if s.query.is_empty() {
@ -352,17 +344,23 @@ async fn run() -> Result<()> {
}) })
.await .await
.context("Failed to play the selected song")?; .context("Failed to play the selected song")?;
s.status = cl.status().await?; updates |= 0b100;
if clear_query_on_play { if clear_query_on_play {
tx.send(Command::QuitSearch)?; s.searching = false;
if !s.query.is_empty() {
s.query.clear();
s.selected = s.status.song.map_or(0, |song| song.pos);
s.liststate.select(Some(s.selected));
}
updates |= 0b001;
} else { } else {
render(&mut term, &cfg.layout, &mut s)?; updates |= 0b001;
} }
} }
Command::Reselect => { Command::Reselect => {
s.selected = s.status.song.map_or(0, |song| song.pos); s.selected = s.status.song.map_or(0, |song| song.pos);
s.liststate.select(Some(s.selected)); s.liststate.select(Some(s.selected));
render(&mut term, &cfg.layout, &mut s)?; updates |= 0b001;
} }
Command::Down => { Command::Down => {
let len = if s.query.is_empty() { let len = if s.query.is_empty() {
@ -380,7 +378,7 @@ async fn run() -> Result<()> {
s.selected += 1; s.selected += 1;
} }
s.liststate.select(Some(s.selected)); s.liststate.select(Some(s.selected));
render(&mut term, &cfg.layout, &mut s)?; updates |= 0b001;
} }
Command::Up => { Command::Up => {
let len = if s.query.is_empty() { let len = if s.query.is_empty() {
@ -398,7 +396,7 @@ async fn run() -> Result<()> {
s.selected -= 1; s.selected -= 1;
} }
s.liststate.select(Some(s.selected)); s.liststate.select(Some(s.selected));
render(&mut term, &cfg.layout, &mut s)?; updates |= 0b001;
} }
Command::JumpDown => { Command::JumpDown => {
let len = if s.query.is_empty() { let len = if s.query.is_empty() {
@ -414,7 +412,7 @@ async fn run() -> Result<()> {
min(s.selected + jump_lines, len - 1) min(s.selected + jump_lines, len - 1)
}; };
s.liststate.select(Some(s.selected)); s.liststate.select(Some(s.selected));
render(&mut term, &cfg.layout, &mut s)?; updates |= 0b001;
} }
Command::JumpUp => { Command::JumpUp => {
let len = if s.query.is_empty() { let len = if s.query.is_empty() {
@ -432,7 +430,7 @@ async fn run() -> Result<()> {
s.selected - jump_lines s.selected - jump_lines
}; };
s.liststate.select(Some(s.selected)); s.liststate.select(Some(s.selected));
render(&mut term, &cfg.layout, &mut s)?; updates |= 0b001;
} }
Command::InputSearch(c) => { Command::InputSearch(c) => {
if s.query.is_empty() { if s.query.is_empty() {
@ -443,7 +441,7 @@ async fn run() -> Result<()> {
let query = s.query.to_lowercase(); let query = s.query.to_lowercase();
s.filtered.retain(|&i| queue_strings[i].contains(&query)); s.filtered.retain(|&i| queue_strings[i].contains(&query));
} }
render(&mut term, &cfg.layout, &mut s)?; updates |= 0b001;
} }
Command::BackspaceSearch => { Command::BackspaceSearch => {
let c = s.query.pop(); let c = s.query.pop();
@ -453,7 +451,7 @@ async fn run() -> Result<()> {
s.selected = s.status.song.map_or(0, |song| song.pos); s.selected = s.status.song.map_or(0, |song| song.pos);
s.liststate.select(Some(s.selected)); s.liststate.select(Some(s.selected));
} }
render(&mut term, &cfg.layout, &mut s)?; updates |= 0b001;
} }
Command::QuitSearch => { Command::QuitSearch => {
s.searching = false; s.searching = false;
@ -462,12 +460,38 @@ async fn run() -> Result<()> {
s.selected = s.status.song.map_or(0, |song| song.pos); s.selected = s.status.song.map_or(0, |song| song.pos);
s.liststate.select(Some(s.selected)); s.liststate.select(Some(s.selected));
} }
render(&mut term, &cfg.layout, &mut s)?; updates |= 0b001;
} }
Command::Searching(x) => { Command::Searching(x) => {
s.searching = x; s.searching = x;
updates |= 0b001;
}
},
_ => empty = true,
}
if updates & 0b100 == 0b100 {
s.status = cl.status().await?;
}
if updates & 0b010 == 0b010 {
let res = cl.queue(s.status.queue_len, &cfg.search_fields).await?;
s.queue = res.0;
queue_strings = res.1;
s.selected = s.status.song.map_or(0, |song| song.pos);
s.liststate = ListState::default();
s.liststate.select(Some(s.selected));
if !s.query.is_empty() {
s.update_search(&queue_strings);
}
}
if updates & 0b001 == 0b001 {
render(&mut term, &cfg.layout, &mut s)?; render(&mut term, &cfg.layout, &mut s)?;
} }
if empty {
thread::park();
} }
} }