mirror of
https://github.com/figsoda/mmtc
synced 2024-11-10 18:24:13 +00:00
rewrite event loop and replace channel with queue
This commit is contained in:
parent
2cb4a22188
commit
b488293bd3
4 changed files with 289 additions and 268 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
180
src/main.rs
180
src/main.rs
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue