diff --git a/musicdb-client/Cargo.toml b/musicdb-client/Cargo.toml index deb13f8..fd9f302 100755 --- a/musicdb-client/Cargo.toml +++ b/musicdb-client/Cargo.toml @@ -12,9 +12,20 @@ musicdb-lib = { version = "0.1.0", path = "../musicdb-lib" } regex = "1.9.3" speedy2d = { version = "1.12.0", optional = true } toml = "0.7.6" -mers_lib = { version = "0.3.1", optional = true } +mers_lib = { version = "0.3.2", optional = true } +musicdb-mers = { version = "0.1.0", path = "../musicdb-mers", optional = true } [features] -default = ["speedy2d"] +default = ["gui"] +# gui: +# enables the gui mode +# merscfg: +# allows using mers to configure the gui +# mers: +# enables the run-mers mode +# playback: +# enables Symcplayer modes, where the client mirrors the server's playback +gui = ["speedy2d"] +merscfg = ["mers_lib", "musicdb-mers", "speedy2d"] +mers = ["mers_lib", "musicdb-mers"] playback = ["musicdb-lib/playback"] -merscfg = ["mers_lib"] diff --git a/musicdb-client/src/gui.rs b/musicdb-client/src/gui.rs index 1c85c58..1636f66 100755 --- a/musicdb-client/src/gui.rs +++ b/musicdb-client/src/gui.rs @@ -39,6 +39,8 @@ use crate::{ pub enum GuiEvent { Refresh, + #[cfg(feature = "merscfg")] + RefreshMers, UpdatedQueue, UpdatedLibrary, Exit, @@ -213,7 +215,7 @@ pub fn main( let sender = window.create_user_event_sender(); window.run_loop(Gui::new( font, - database, + Arc::clone(&database), connection, Arc::new(Mutex::new(get_con)), event_sender_arc, @@ -258,7 +260,7 @@ pub fn main( ), ], #[cfg(feature = "merscfg")] - merscfg: crate::merscfg::MersCfg::new(config_dir.join("dynamic_config.mers")), + merscfg: crate::merscfg::MersCfg::new(config_dir.join("dynamic_config.mers"), database), }, )); } @@ -316,7 +318,8 @@ impl Gui { scroll_pixels_multiplier: f64, scroll_lines_multiplier: f64, scroll_pages_multiplier: f64, - gui_config: GuiConfig, + #[cfg(not(feature = "merscfg"))] gui_config: GuiConfig, + #[cfg(feature = "merscfg")] mut gui_config: GuiConfig, ) -> Self { let (notif_overlay, notif_sender) = NotifOverlay::new(); let notif_sender_two = notif_sender.clone(); @@ -1195,13 +1198,14 @@ impl WindowHandler for Gui { Rectangle::new(Vec2::ZERO, self.size.into_f32()), Color::BLACK, ); + let mut cfg = self.gui_config.take().unwrap(); + // before the db is locked! + #[cfg(feature = "merscfg")] + MersCfg::run(&mut cfg, self, |m| &m.func_before_draw); let dblock = Arc::clone(&self.database); let mut dblock = dblock.lock().unwrap(); let mut covers = self.covers.take().unwrap(); let mut custom_images = self.custom_images.take().unwrap(); - let mut cfg = self.gui_config.take().unwrap(); - #[cfg(feature = "merscfg")] - MersCfg::run(&mut cfg, self, Some(&mut dblock), |m| &m.func_before_draw); let mut info = DrawInfo { time: draw_start_time, actions: Vec::with_capacity(0), @@ -1450,15 +1454,17 @@ impl WindowHandler for Gui { fn on_user_event(&mut self, helper: &mut WindowHelper, user_event: GuiEvent) { match user_event { GuiEvent::Refresh => helper.request_redraw(), + #[cfg(feature = "merscfg")] + GuiEvent::RefreshMers => { + if let Some(mut cfg) = self.gui_config.take() { + MersCfg::run(&mut cfg, self, |cfg| &crate::merscfg::OptFunc(None)); + self.gui_config = Some(cfg); + } + } GuiEvent::UpdatedLibrary => { #[cfg(feature = "merscfg")] if let Some(mut gc) = self.gui_config.take() { - MersCfg::run( - &mut gc, - self, - self.database.clone().lock().ok().as_mut().map(|v| &mut **v), - |m| &m.func_library_updated, - ); + MersCfg::run(&mut gc, self, |m| &m.func_library_updated); self.gui_config = Some(gc); } else { eprintln!("WARN: Skipping call to merscfg's library_updated because gui_config is not available"); @@ -1469,12 +1475,7 @@ impl WindowHandler for Gui { GuiEvent::UpdatedQueue => { #[cfg(feature = "merscfg")] if let Some(mut gc) = self.gui_config.take() { - MersCfg::run( - &mut gc, - self, - self.database.clone().lock().ok().as_mut().map(|v| &mut **v), - |m| &m.func_queue_updated, - ); + MersCfg::run(&mut gc, self, |m| &m.func_queue_updated); self.gui_config = Some(gc); } else { eprintln!("WARN: Skipping call to merscfg's queue_updated because gui_config is not available"); diff --git a/musicdb-client/src/main.rs b/musicdb-client/src/main.rs index ab74097..1edd4f1 100755 --- a/musicdb-client/src/main.rs +++ b/musicdb-client/src/main.rs @@ -4,7 +4,6 @@ use std::{ path::PathBuf, sync::{Arc, Mutex}, thread, - time::Duration, }; use clap::{Parser, Subcommand}; @@ -18,8 +17,9 @@ use musicdb_lib::{ CoverId, SongId, }, load::ToFromBytes, - server::{get, Command}, + server::Command, }; +#[cfg(feature = "speedy2d")] use speedy2d::color::Color; #[cfg(feature = "speedy2d")] mod gui; @@ -27,6 +27,7 @@ mod gui; mod gui_anim; #[cfg(feature = "speedy2d")] mod gui_base; +#[cfg(feature = "speedy2d")] mod gui_edit_song; #[cfg(feature = "speedy2d")] mod gui_idle_display; @@ -75,6 +76,8 @@ enum Mode { /// play in sync with the server, and fetch the songs from it too. slower than the local variant for obvious reasons #[cfg(feature = "playback")] SyncplayerNetwork, + #[cfg(feature = "mers")] + RunMers { path: PathBuf }, } fn get_config_file_path() -> PathBuf { @@ -85,6 +88,10 @@ fn get_config_file_path() -> PathBuf { } fn main() { + #[cfg(not(feature = "speedy2d"))] + #[cfg(not(feature = "mers"))] + #[cfg(not(feature = "playback"))] + compile_error!("None of the optional features are enabled. Without at least one of these, the application is useless! See Cargo.toml for info."); // parse args let args = Args::parse(); // start @@ -132,7 +139,8 @@ fn main() { if let Some(player) = &mut player { let mut db = database.lock().unwrap(); if db.is_client_init() { - player.update(&mut db); + // command_sender does nothing. if a song finishes, we don't want to move to the next song, we want to wait for the server to send the NextSong event. + player.update(&mut db, &Arc::new(|_| {})); } } let update = Command::from_bytes(&mut con).unwrap(); @@ -154,7 +162,7 @@ fn main() { { let occasional_refresh_sender = Arc::clone(&sender); thread::spawn(move || loop { - std::thread::sleep(Duration::from_secs(1)); + std::thread::sleep(std::time::Duration::from_secs(1)); if let Some(v) = &*occasional_refresh_sender.lock().unwrap() { v.send_event(GuiEvent::Refresh).unwrap(); } @@ -162,7 +170,7 @@ fn main() { gui::main( database, con, - get::Client::new(BufReader::new( + musicdb_lib::server::get::Client::new(BufReader::new( TcpStream::connect(addr).expect("opening get client connection"), )) .expect("initializing get client connection"), @@ -174,6 +182,38 @@ fn main() { Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork => { con_thread.join().unwrap(); } + #[cfg(feature = "mers")] + Mode::RunMers { path } => { + let mut src = mers_lib::prelude_compile::Source::new_from_file(path).unwrap(); + let srca = Arc::new(src.clone()); + let con = Mutex::new(con); + let (mut i1, mut i2, mut i3) = musicdb_mers::add( + mers_lib::prelude_compile::Config::new().bundle_std(), + &database, + &Arc::new(move |cmd: Command| cmd.to_bytes(&mut *con.lock().unwrap()).unwrap()), + ) + .infos(); + let program = mers_lib::prelude_compile::parse(&mut src, &srca) + .unwrap() + .compile(&mut i1, mers_lib::prelude_compile::CompInfo::default()) + .unwrap(); + match program.check(&mut i3, None) { + Ok(_) => {} + Err(e) => { + eprintln!("{e}"); + std::process::exit(60); + } + }; + // wait until db is synced + let dur = std::time::Duration::from_secs_f32(0.1); + loop { + std::thread::sleep(dur); + if database.lock().unwrap().is_client_init() { + break; + } + } + program.run(&mut i2); + } } } @@ -198,6 +238,7 @@ fn get_cover(song: SongId, database: &Database) -> Option { } } +#[cfg(feature = "speedy2d")] pub(crate) fn color_scale(c: Color, r: f32, g: f32, b: f32, new_alpha: Option) -> Color { Color::from_rgba(c.r() * r, c.g() * g, c.b() * b, new_alpha.unwrap_or(c.a())) } diff --git a/musicdb-client/src/merscfg.rs b/musicdb-client/src/merscfg.rs index 13d5ad2..dd243f5 100644 --- a/musicdb-client/src/merscfg.rs +++ b/musicdb-client/src/merscfg.rs @@ -24,7 +24,7 @@ use crate::{ textcfg::TextBuilder, }; -pub struct OptFunc(Option); +pub struct OptFunc(pub Option); impl OptFunc { pub fn none() -> Self { Self(None) @@ -65,6 +65,7 @@ impl OptFunc { /// - `set_idle_screen_side_text_2_format` pub struct MersCfg { pub source_file: PathBuf, + pub database: Arc>, // - - handler functions - - pub func_before_draw: OptFunc, pub func_library_updated: OptFunc, @@ -75,6 +76,10 @@ pub struct MersCfg { pub var_window_size_in_pixels: Arc>, pub var_idle_screen_cover_aspect_ratio: Arc>, // - - results from running functions - - + pub channel_gui_actions: ( + std::sync::mpsc::Sender, + std::sync::mpsc::Receiver, + ), pub updated_playing_status: Arc, pub updated_idle_status: Arc, pub updated_idle_screen_cover_pos: Arc>>, @@ -90,9 +95,10 @@ pub struct MersCfg { } impl MersCfg { - pub fn new(path: PathBuf) -> Self { + pub fn new(path: PathBuf, database: Arc>) -> Self { Self { source_file: path, + database, func_before_draw: OptFunc::none(), func_library_updated: OptFunc::none(), @@ -110,6 +116,7 @@ impl MersCfg { mers_lib::data::float::Float(0.0), ))), + channel_gui_actions: std::sync::mpsc::channel(), updated_playing_status: Arc::new(AtomicU8::new(0)), updated_idle_status: Arc::new(AtomicU8::new(0)), updated_idle_screen_cover_pos: Arc::new(Updatable::new()), @@ -127,12 +134,19 @@ impl MersCfg { fn custom_globals( &self, cfg: mers_lib::prelude_extend_config::Config, + db: &Arc>, event_sender: Arc>, notif_sender: Sender< Box (Box, NotifInfo) + Send>, >, ) -> mers_lib::prelude_extend_config::Config { - cfg.add_var_arc( + let cmd_es = event_sender.clone(); + let cmd_ga = self.channel_gui_actions.0.clone(); + musicdb_mers::add(cfg, db, &Arc::new(move |cmd| { + cmd_ga.send(cmd).unwrap(); + cmd_es.send_event(GuiEvent::RefreshMers).unwrap(); + })) + .add_var_arc( "is_playing".to_owned(), Arc::clone(&self.var_is_playing), self.var_is_playing.read().unwrap().get().as_type(), @@ -545,14 +559,11 @@ impl MersCfg { // ])))) } - pub fn run( - gui_cfg: &mut GuiConfig, - gui: &mut Gui, - mut db: Option<&mut Database>, - run: impl FnOnce(&Self) -> &OptFunc, - ) { - // prepare vars - if let Some(db) = &mut db { + pub fn run(gui_cfg: &mut GuiConfig, gui: &mut Gui, run: impl FnOnce(&Self) -> &OptFunc) { + { + let mut db = gui_cfg.merscfg.database.lock().unwrap(); + let db = &mut db; + // prepare vars *gui_cfg.merscfg.var_is_playing.write().unwrap() = mers_lib::data::Data::new(mers_lib::data::bool::Bool(db.playing)); } @@ -572,6 +583,14 @@ impl MersCfg { // run run(&gui_cfg.merscfg).run(); + loop { + if let Ok(a) = gui_cfg.merscfg.channel_gui_actions.1.try_recv() { + gui.exec_gui_action(GuiAction::SendToServer(a)); + } else { + break; + } + } + // apply updates match gui_cfg @@ -701,6 +720,7 @@ impl MersCfg { let (mut i1, mut i2, mut i3) = self .custom_globals( mers_lib::prelude_extend_config::Config::new().bundle_std(), + &self.database, event_sender, notif_sender, ) diff --git a/musicdb-lib/src/data/database.rs b/musicdb-lib/src/data/database.rs index 0277fc3..2afc7f9 100755 --- a/musicdb-lib/src/data/database.rs +++ b/musicdb-lib/src/data/database.rs @@ -1,6 +1,5 @@ use std::{ - collections::{BTreeSet, HashMap, HashSet}, - convert::identity, + collections::{BTreeSet, HashMap}, fs::{self, File}, io::{BufReader, Read, Write}, path::PathBuf,