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

View file

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

View file

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

View file

@ -11,7 +11,7 @@ mod mpd;
use anyhow::{Context, Error, Result};
use async_io::{block_on, Timer};
use crossbeam_channel::unbounded;
use crossbeam_queue::SegQueue;
use crossterm::{
event::{
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers,
@ -24,7 +24,18 @@ use dirs_next::config_dir;
use structopt::StructOpt;
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::{
app::{Command, Opts, State},
@ -133,43 +144,46 @@ async fn run() -> Result<()> {
let seek_forwards = seek_forwards.as_bytes();
let update_interval = Duration::from_secs_f32(1.0 / opts.ups.unwrap_or(cfg.ups));
let (tx, rx) = unbounded();
let tx1 = tx.clone();
let tx2 = tx.clone();
let tx3 = tx.clone();
let t1 = thread::current();
let t2 = t1.clone();
let t3 = t1.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 || {
let tx = tx1;
loop {
let changed = block_on(idle_cl.idle()).or_die();
if changed.0 {
tx.send(Command::UpdateStatus).or_die();
}
if changed.1 {
tx.send(Command::UpdateQueue).or_die();
}
tx.send(Command::UpdateFrame).or_die();
}
thread::spawn(move || loop {
updates1.fetch_or(
match block_on(idle_cl.idle()).or_die() {
(true, true) => 0b111,
(true, false) => 0b101,
(false, true) => 0b011,
_ => continue,
},
Ordering::Relaxed,
);
t1.unpark();
});
thread::spawn(move || {
let tx = tx2;
loop {
let timer = Timer::after(update_interval);
tx.send(Command::UpdateStatus).or_die();
tx.send(Command::UpdateFrame).or_die();
block_on(timer);
}
thread::spawn(move || loop {
let timer = Timer::after(update_interval);
updates2.fetch_or(0b101, Ordering::Relaxed);
t2.unpark();
block_on(timer);
});
thread::spawn(move || {
let mut searching = false;
let tx = tx3;
while let Ok(ev) = event::read() {
tx.send(match ev {
cmds1.push(match ev {
Event::Mouse(MouseEvent::ScrollDown(..)) => Command::Down,
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 {
KeyCode::Char('q') if modifiers.contains(KeyModifiers::CONTROL) => {
Command::Quit
@ -218,257 +232,267 @@ async fn run() -> Result<()> {
_ => 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 {
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);
Some(cmd) => match cmd {
Command::Quit => break,
Command::ToggleRepeat => {
cl.command(if s.status.repeat {
b"repeat 0\n"
} else {
b"repeat 1\n"
})
.await
.context("Failed to toggle repeat")?;
updates |= 0b101;
}
}
Command::ToggleRepeat => {
cl.command(if s.status.repeat {
b"repeat 0\n"
} else {
b"repeat 1\n"
})
.await
.context("Failed to toggle repeat")?;
s.status = cl.status().await?;
render(&mut term, &cfg.layout, &mut s)?;
}
Command::ToggleRandom => {
cl.command(if s.status.random {
b"random 0\n"
} else {
b"random 1\n"
})
.await
.context("Failed to toggle random")?;
s.status = cl.status().await?;
render(&mut term, &cfg.layout, &mut s)?;
}
Command::ToggleSingle => {
cl.command(if s.status.single == Some(true) {
b"single 0\n"
} else {
b"single 1\n"
})
.await
.context("Failed to toggle single")?;
s.status = cl.status().await?;
render(&mut term, &cfg.layout, &mut s)?;
}
Command::ToggleOneshot => {
cl.command(
s.status
.single
.map_or(b"single 0\n", |_| b"single oneshot\n"),
)
.await
.context("Failed to toggle oneshot")?;
s.status = cl.status().await?;
render(&mut term, &cfg.layout, &mut s)?;
}
Command::ToggleConsume => {
cl.command(if s.status.consume {
b"consume 0\n"
} else {
b"consume 1\n"
})
.await
.context("Failed to toggle consume")?;
s.status = cl.status().await?;
render(&mut term, &cfg.layout, &mut s)?;
}
Command::TogglePause => {
cl.command(s.status.state.map_or(b"play\n", |_| b"pause\n"))
Command::ToggleRandom => {
cl.command(if s.status.random {
b"random 0\n"
} else {
b"random 1\n"
})
.await
.context("Failed to toggle pause")?;
s.status = cl.status().await?;
render(&mut term, &cfg.layout, &mut s)?;
}
Command::Stop => {
cl.command(b"stop\n")
.context("Failed to toggle random")?;
updates |= 0b101;
}
Command::ToggleSingle => {
cl.command(if s.status.single == Some(true) {
b"single 0\n"
} else {
b"single 1\n"
})
.await
.context("Failed to stop playing")?;
s.status = cl.status().await?;
render(&mut term, &cfg.layout, &mut s)?;
}
Command::SeekBackwards => {
cl.command(seek_backwards)
.context("Failed to toggle single")?;
updates |= 0b101;
}
Command::ToggleOneshot => {
cl.command(
s.status
.single
.map_or(b"single 0\n", |_| b"single oneshot\n"),
)
.await
.context("Failed to seek backwards")?;
s.status = cl.status().await?;
render(&mut term, &cfg.layout, &mut s)?;
}
Command::SeekForwards => {
cl.command(seek_forwards)
.context("Failed to toggle oneshot")?;
updates |= 0b101;
}
Command::ToggleConsume => {
cl.command(if s.status.consume {
b"consume 0\n"
} else {
b"consume 1\n"
})
.await
.context("Failed to seek forwards")?;
s.status = cl.status().await?;
render(&mut term, &cfg.layout, &mut s)?;
}
Command::Previous => {
cl.command(b"previous\n")
.await
.context("Failed to play previous song")?;
s.status = cl.status().await?;
render(&mut term, &cfg.layout, &mut s)?;
}
Command::Next => {
cl.command(b"next\n")
.await
.context("Failed to play next song")?;
s.status = cl.status().await?;
render(&mut term, &cfg.layout, &mut s)?;
}
Command::Play => {
cl.play(if s.query.is_empty() {
if s.selected < s.queue.len() {
s.selected
.context("Failed to toggle consume")?;
updates |= 0b101;
}
Command::TogglePause => {
cl.command(s.status.state.map_or(b"play\n", |_| b"pause\n"))
.await
.context("Failed to toggle pause")?;
updates |= 0b101;
}
Command::Stop => {
cl.command(b"stop\n")
.await
.context("Failed to stop playing")?;
updates |= 0b101;
}
Command::SeekBackwards => {
cl.command(seek_backwards)
.await
.context("Failed to seek backwards")?;
updates |= 0b101;
}
Command::SeekForwards => {
cl.command(seek_forwards)
.await
.context("Failed to seek forwards")?;
updates |= 0b101;
}
Command::Previous => {
cl.command(b"previous\n")
.await
.context("Failed to play previous song")?;
updates |= 0b101;
}
Command::Next => {
cl.command(b"next\n")
.await
.context("Failed to play next song")?;
updates |= 0b101;
}
Command::Play => {
cl.play(if s.query.is_empty() {
if s.selected < s.queue.len() {
s.selected
} else {
continue;
}
} else if let Some(&x) = s.filtered.get(s.selected) {
x
} else {
continue;
})
.await
.context("Failed to play the selected song")?;
updates |= 0b100;
if clear_query_on_play {
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 {
updates |= 0b001;
}
} else if let Some(&x) = s.filtered.get(s.selected) {
x
} else {
continue;
})
.await
.context("Failed to play the selected song")?;
s.status = cl.status().await?;
if clear_query_on_play {
tx.send(Command::QuitSearch)?;
} else {
render(&mut term, &cfg.layout, &mut s)?;
}
}
Command::Reselect => {
s.selected = s.status.song.map_or(0, |song| song.pos);
s.liststate.select(Some(s.selected));
render(&mut term, &cfg.layout, &mut s)?;
}
Command::Down => {
let len = if s.query.is_empty() {
s.queue.len()
} else {
s.filtered.len()
};
if s.selected >= len {
s.selected = s.status.song.map_or(0, |song| song.pos);
} else if s.selected == len - 1 {
if cycle {
s.selected = 0;
}
} else {
s.selected += 1;
}
s.liststate.select(Some(s.selected));
render(&mut term, &cfg.layout, &mut s)?;
}
Command::Up => {
let len = if s.query.is_empty() {
s.queue.len()
} else {
s.filtered.len()
};
if s.selected >= len {
s.selected = s.status.song.map_or(0, |song| song.pos);
} else if s.selected == 0 {
if cycle {
s.selected = len - 1;
}
} else {
s.selected -= 1;
}
s.liststate.select(Some(s.selected));
render(&mut term, &cfg.layout, &mut s)?;
}
Command::JumpDown => {
let len = if s.query.is_empty() {
s.queue.len()
} else {
s.filtered.len()
};
s.selected = if s.selected >= len {
s.status.song.map_or(0, |song| song.pos)
} else if cycle {
(s.selected + jump_lines) % len
} else {
min(s.selected + jump_lines, len - 1)
};
s.liststate.select(Some(s.selected));
render(&mut term, &cfg.layout, &mut s)?;
}
Command::JumpUp => {
let len = if s.query.is_empty() {
s.queue.len()
} else {
s.filtered.len()
};
s.selected = if s.selected >= len {
s.status.song.map_or(0, |song| song.pos)
} else if cycle {
((s.selected as isize - jump_lines as isize) % len as isize) as usize
} else if s.selected < jump_lines {
0
} else {
s.selected - jump_lines
};
s.liststate.select(Some(s.selected));
render(&mut term, &cfg.layout, &mut s)?;
}
Command::InputSearch(c) => {
if s.query.is_empty() {
s.query.push(c);
s.update_search(&queue_strings);
} else {
s.query.push(c);
let query = s.query.to_lowercase();
s.filtered.retain(|&i| queue_strings[i].contains(&query));
}
render(&mut term, &cfg.layout, &mut s)?;
}
Command::BackspaceSearch => {
let c = s.query.pop();
if !s.query.is_empty() {
s.update_search(&queue_strings);
} else if c.is_some() {
Command::Reselect => {
s.selected = s.status.song.map_or(0, |song| song.pos);
s.liststate.select(Some(s.selected));
updates |= 0b001;
}
render(&mut term, &cfg.layout, &mut s)?;
}
Command::QuitSearch => {
s.searching = false;
if !s.query.is_empty() {
s.query.clear();
s.selected = s.status.song.map_or(0, |song| song.pos);
Command::Down => {
let len = if s.query.is_empty() {
s.queue.len()
} else {
s.filtered.len()
};
if s.selected >= len {
s.selected = s.status.song.map_or(0, |song| song.pos);
} else if s.selected == len - 1 {
if cycle {
s.selected = 0;
}
} else {
s.selected += 1;
}
s.liststate.select(Some(s.selected));
updates |= 0b001;
}
render(&mut term, &cfg.layout, &mut s)?;
}
Command::Searching(x) => {
s.searching = x;
render(&mut term, &cfg.layout, &mut s)?;
Command::Up => {
let len = if s.query.is_empty() {
s.queue.len()
} else {
s.filtered.len()
};
if s.selected >= len {
s.selected = s.status.song.map_or(0, |song| song.pos);
} else if s.selected == 0 {
if cycle {
s.selected = len - 1;
}
} else {
s.selected -= 1;
}
s.liststate.select(Some(s.selected));
updates |= 0b001;
}
Command::JumpDown => {
let len = if s.query.is_empty() {
s.queue.len()
} else {
s.filtered.len()
};
s.selected = if s.selected >= len {
s.status.song.map_or(0, |song| song.pos)
} else if cycle {
(s.selected + jump_lines) % len
} else {
min(s.selected + jump_lines, len - 1)
};
s.liststate.select(Some(s.selected));
updates |= 0b001;
}
Command::JumpUp => {
let len = if s.query.is_empty() {
s.queue.len()
} else {
s.filtered.len()
};
s.selected = if s.selected >= len {
s.status.song.map_or(0, |song| song.pos)
} else if cycle {
((s.selected as isize - jump_lines as isize) % len as isize) as usize
} else if s.selected < jump_lines {
0
} else {
s.selected - jump_lines
};
s.liststate.select(Some(s.selected));
updates |= 0b001;
}
Command::InputSearch(c) => {
if s.query.is_empty() {
s.query.push(c);
s.update_search(&queue_strings);
} else {
s.query.push(c);
let query = s.query.to_lowercase();
s.filtered.retain(|&i| queue_strings[i].contains(&query));
}
updates |= 0b001;
}
Command::BackspaceSearch => {
let c = s.query.pop();
if !s.query.is_empty() {
s.update_search(&queue_strings);
} else if c.is_some() {
s.selected = s.status.song.map_or(0, |song| song.pos);
s.liststate.select(Some(s.selected));
}
updates |= 0b001;
}
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;
}
Command::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)?;
}
if empty {
thread::park();
}
}
Ok(())