diff --git a/src/config/cg.rs b/src/config/cg.rs index 3cc12f6..0797840 100644 --- a/src/config/cg.rs +++ b/src/config/cg.rs @@ -1,7 +1,6 @@ -use std::collections::HashMap; -use std::fs; -use serde::{Deserialize, Serialize}; use crate::config::ENGINE_CONFIG; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, fs}; lazy_static::lazy_static! { pub static ref CG_LENGTH: CgLength = load_cg(); @@ -51,20 +50,18 @@ impl CgLength { } fn load_cg() -> CgLength { - let content = fs::read_to_string(format!( - "{}length.toml", - ENGINE_CONFIG.cg_path(), - )) - .unwrap(); + let content = fs::read_to_string(format!("{}length.toml", ENGINE_CONFIG.cg_path(),)).unwrap(); let name_item: LengthWrapper = toml::from_str(&content).unwrap(); let index_item = name_item.cast.clone(); CgLength { - cg_by_name: name_item.cast + cg_by_name: name_item + .cast .into_iter() .map(|length| (length.name, (length.index, length.length))) .collect(), - cg_by_id: index_item.into_iter() + cg_by_id: index_item + .into_iter() .map(|length| (length.index, (length.name, length.length))) .collect(), } -} \ No newline at end of file +} diff --git a/src/config/character_volume.rs b/src/config/character_volume.rs index 13be2d2..d137ea0 100644 --- a/src/config/character_volume.rs +++ b/src/config/character_volume.rs @@ -1,11 +1,9 @@ -use std::collections::HashMap; -use std::rc::Rc; +use crate::config::{user::USER_CONFIG, ENGINE_CONFIG}; +use crate::executors::executor::Executor; +use crate::ui::initialize::{CharacterVolume, MainWindow}; use serde::{Deserialize, Serialize}; use slint::{Model, Weak}; -use crate::config::ENGINE_CONFIG; -use crate::config::user::USER_CONFIG; -use crate::executor::executor::Executor; -use crate::ui::ui::{CharacterVolume, MainWindow}; +use std::{collections::HashMap, rc::Rc}; #[derive(Debug, Deserialize, Serialize)] pub(crate) struct CharacterVolumeConfig { @@ -57,9 +55,7 @@ impl Executor { }) .collect(); - window.set_character_volumes( - Rc::new(slint::VecModel::from(volumes)).into() - ); + window.set_character_volumes(Rc::new(slint::VecModel::from(volumes)).into()); } } } diff --git a/src/config/extra.rs b/src/config/extra.rs index f53d46a..fbc7b23 100644 --- a/src/config/extra.rs +++ b/src/config/extra.rs @@ -1,9 +1,8 @@ -use std::fs; -use serde::{Deserialize, Serialize}; -use crate::config::cg::CgConfig; -use crate::config::ENGINE_CONFIG; +use crate::config::{cg::CgConfig, ENGINE_CONFIG}; use crate::error::{EngineError, SaveError}; -use crate::executor::executor::Executor; +use crate::executors::executor::Executor; +use serde::{Deserialize, Serialize}; +use std::fs; lazy_static::lazy_static! { pub static ref EXTRA_CONFIG: ExtraConfig = load_extra_config(); @@ -34,11 +33,11 @@ fn load_extra_config() -> ExtraConfig { pub fn save_extra_config(cg: u64) -> Result<(), EngineError> { let path = format!("{}/extra.toml", ENGINE_CONFIG.save_path()); - let content = toml::to_string(&ExtraConfig{cg: CgConfig::new(cg)}).map_err(SaveError::from)?; - fs::write(&path, content).map_err(|e| SaveError::Write { - path, - source: e, - })?; + let content = toml::to_string(&ExtraConfig { + cg: CgConfig::new(cg), + }) + .map_err(SaveError::from)?; + fs::write(&path, content).map_err(|e| SaveError::Write { path, source: e })?; Ok(()) } diff --git a/src/config/figure.rs b/src/config/figure.rs index ffdce7a..472f1d9 100644 --- a/src/config/figure.rs +++ b/src/config/figure.rs @@ -1,7 +1,6 @@ use crate::config::ENGINE_CONFIG; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fs; +use std::{collections::HashMap, fs}; lazy_static::lazy_static! { pub static ref FIGURE_CONFIG: FigureConfig = load_figure(); @@ -42,6 +41,12 @@ struct FigureRead { offset: Offset, } +type FigureConfigRef<'a> = ( + Option<&'a HashMap>, + Option<&'a HashMap>, + Option<&'a f32>, +); + #[derive(Debug, Deserialize, Serialize)] pub struct FigureConfig { body_list: HashMap>, @@ -50,14 +55,7 @@ pub struct FigureConfig { } impl FigureConfig { - pub fn find( - &self, - name: &str, - ) -> ( - Option<&HashMap>, - Option<&HashMap>, - Option<&f32>, - ) { + pub fn find(&self, name: &str) -> FigureConfigRef<'_> { ( self.body_list.get(name), self.face_list.get(name), diff --git a/src/config/initialize.rs b/src/config/initialize.rs index d3396a7..42a8643 100644 --- a/src/config/initialize.rs +++ b/src/config/initialize.rs @@ -21,7 +21,7 @@ impl Character { pub(crate) fn list(&self) -> &HashMap { &self.0 } - + pub(crate) fn name_list(&self) -> HashSet<&String> { self.0.keys().collect() } diff --git a/src/config/mod.rs b/src/config/mod.rs index 5b55f48..822d4be 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,20 +1,22 @@ -use std::collections::{HashMap, HashSet}; use crate::config::initialize::{Character, InitializeConfig}; use serde::{Deserialize, Serialize}; -use std::fs; +use std::{ + collections::{HashMap, HashSet}, + fs, +}; pub mod figure; pub mod initialize; pub mod save_load; +pub mod cg; +pub mod character_volume; +pub mod extra; pub mod system; pub mod text; pub mod user; pub mod voice; pub mod volume; -pub mod extra; -pub mod cg; -pub mod character_volume; lazy_static::lazy_static! { pub static ref ENGINE_CONFIG: EngineConfig = load_engine_config(); @@ -34,7 +36,7 @@ impl EngineConfig { pub fn background_path(&self) -> &str { &self.initialize.background_path } - + pub fn cg_path(&self) -> &str { &self.initialize.cg_path } @@ -66,13 +68,13 @@ impl EngineConfig { pub fn character_name_list(&self) -> HashSet<&String> { self.character.name_list() } - + pub fn character_full_name_list(&self) -> HashSet<&String> { self.character.full_name_list() } - + pub fn character_list(&self) -> &HashMap { - &self.character.list() + self.character.list() } } diff --git a/src/config/save_load.rs b/src/config/save_load.rs index 7cd1ec8..6b8a22c 100644 --- a/src/config/save_load.rs +++ b/src/config/save_load.rs @@ -1,11 +1,9 @@ use crate::config::ENGINE_CONFIG; use crate::error::{EngineError, SaveError}; -use crate::executor::executor::Executor; +use crate::executors::executor::Executor; use serde::{Deserialize, Serialize}; use slint::{Image, ToSharedString, VecModel}; -use std::fs; -use std::path::Path; -use std::rc::Rc; +use std::{fs, path::Path, rc::Rc}; #[derive(Serialize, Deserialize, Debug)] pub struct SaveData { @@ -46,8 +44,7 @@ impl Executor { path: path.clone(), source: e, })?; - let image = - Image::load_from_path(Path::new(&image_path)).unwrap_or(Image::default()); + let image = Image::load_from_path(Path::new(&image_path)).unwrap_or_default(); load_items.push(( image, explain.to_shared_string(), diff --git a/src/config/system.rs b/src/config/system.rs index b31a8fb..9569642 100644 --- a/src/config/system.rs +++ b/src/config/system.rs @@ -1,6 +1,6 @@ use crate::config::user::USER_CONFIG; -use crate::executor::executor::Executor; -use crate::ui::ui::MainWindow; +use crate::executors::executor::Executor; +use crate::ui::initialize::MainWindow; use serde::{Deserialize, Serialize}; use slint::Weak; @@ -12,7 +12,10 @@ pub struct AutoConfig { impl Default for AutoConfig { fn default() -> Self { - AutoConfig { delay: 3.5, is_wait: true } + AutoConfig { + delay: 3.5, + is_wait: true, + } } } diff --git a/src/config/text.rs b/src/config/text.rs index bf0cb0a..d44917f 100644 --- a/src/config/text.rs +++ b/src/config/text.rs @@ -1,6 +1,6 @@ use crate::config::user::USER_CONFIG; -use crate::executor::executor::Executor; -use crate::ui::ui::MainWindow; +use crate::executors::executor::Executor; +use crate::ui::initialize::MainWindow; use serde::{Deserialize, Serialize}; use slint::Weak; @@ -12,7 +12,10 @@ pub struct TextConfig { impl Default for TextConfig { fn default() -> Self { - TextConfig { speed: 50.0, opacity: 0.8 } + TextConfig { + speed: 50.0, + opacity: 0.8, + } } } impl TextConfig { diff --git a/src/config/user.rs b/src/config/user.rs index a0b2357..ace36aa 100644 --- a/src/config/user.rs +++ b/src/config/user.rs @@ -1,13 +1,12 @@ -use crate::config::system::AutoConfig; -use crate::config::text::TextConfig; -use crate::config::volume::VolumeConfig; -use crate::config::ENGINE_CONFIG; +use crate::config::{ + character_volume::CharacterVolumeConfig, system::AutoConfig, text::TextConfig, + volume::VolumeConfig, ENGINE_CONFIG, +}; use crate::error::{EngineError, SaveError}; -use crate::ui::ui::MainWindow; +use crate::ui::initialize::MainWindow; use serde::{Deserialize, Serialize}; use slint::Weak; use std::fs; -use crate::config::character_volume::CharacterVolumeConfig; lazy_static::lazy_static! { pub static ref USER_CONFIG: UserConfig = load_user_config(); @@ -60,7 +59,7 @@ impl UserConfig { } pub fn character_volume(&self, name: &str) -> f32 { - self.character_volume.volumes.get(name).unwrap().clone() + *self.character_volume.volumes.get(name).unwrap() } pub fn from_weak(weak: Weak) -> Self { @@ -99,7 +98,7 @@ fn load_user_config() -> UserConfig { pub fn save_user_config(weak: Weak) -> Result<(), EngineError> { let path = format!("{}/user.toml", ENGINE_CONFIG.save_path()); write_config(&path, &UserConfig::from_weak(weak))?; - + Ok(()) } diff --git a/src/config/voice.rs b/src/config/voice.rs index 95002f9..0fec612 100644 --- a/src/config/voice.rs +++ b/src/config/voice.rs @@ -1,7 +1,6 @@ use crate::config::ENGINE_CONFIG; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::collections::HashMap; -use std::fs; +use serde::{Deserialize, Deserializer, Serialize}; +use std::{collections::HashMap, fs}; use tokio::time::Duration; lazy_static::lazy_static! { @@ -60,10 +59,3 @@ where let seconds = u64::deserialize(deserializer)?; Ok(Duration::from_secs(seconds)) } - -pub fn serialize_duration_as_secs(duration: &Duration, serializer: S) -> Result -where - S: Serializer, -{ - serializer.serialize_u64(duration.as_secs()) -} diff --git a/src/config/volume.rs b/src/config/volume.rs index 179a3d6..9a8401c 100644 --- a/src/config/volume.rs +++ b/src/config/volume.rs @@ -1,6 +1,6 @@ use crate::config::user::USER_CONFIG; -use crate::executor::executor::Executor; -use crate::ui::ui::MainWindow; +use crate::executors::executor::Executor; +use crate::ui::initialize::MainWindow; use serde::{Deserialize, Serialize}; use slint::Weak; @@ -13,7 +13,11 @@ pub(crate) struct VolumeConfig { impl Default for VolumeConfig { fn default() -> Self { - VolumeConfig { main: 100.0, bgm: 100.0, voice: 100.0 } + VolumeConfig { + main: 100.0, + bgm: 100.0, + voice: 100.0, + } } } diff --git a/src/error.rs b/src/error.rs index 2e8ffda..4bc396d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,7 +5,7 @@ pub enum EngineError { #[error("script error: {0}")] Script(#[from] ScriptError), - #[error("executor error: {0}")] + #[error("executors error: {0}")] Executor(#[from] ExecutorError), #[error("save error: {0}")] @@ -68,7 +68,7 @@ pub enum ExecutorError { CgMetadataMissing(u64), #[allow(dead_code)] - #[error("invalid executor state: {0}")] + #[error("invalid executors state: {0}")] InvalidState(&'static str), } diff --git a/src/executor/auto_executor.rs b/src/executors/auto_executor.rs similarity index 77% rename from src/executor/auto_executor.rs rename to src/executors/auto_executor.rs index 09f7ec0..437dc9d 100644 --- a/src/executor/auto_executor.rs +++ b/src/executors/auto_executor.rs @@ -1,10 +1,13 @@ -use crate::executor::executor::Executor; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; -use std::sync::mpsc::Receiver; -use std::sync::Arc; -use tokio::sync::mpsc::{channel, Sender}; -use tokio::time::{Duration, Sleep}; +use crate::executors::executor::Executor; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + mpsc::Receiver, + Arc, +}; +use tokio::{ + sync::mpsc::{channel, Sender}, + time::{Duration, Sleep}, +}; pub struct AutoExecutor { timer: slint::Timer, @@ -36,7 +39,7 @@ impl AutoExecutor { let is_auto_clone = is_auto.clone(); tokio::spawn(async move { let mut start = true; - while let Some(_) = rx.recv().await { + while rx.recv().await.is_some() { if start { is_auto_clone.store(true, Ordering::Relaxed); start = false; @@ -95,17 +98,14 @@ impl AutoExecutor { slint::TimerMode::Repeated, Duration::from_millis(100), move || { - if is_auto.load(Ordering::Relaxed) { - if let Ok(_) = rx.try_recv() { - - let mut executor = executor.clone(); - slint::spawn_local(async move { - if let Err(e) = executor.execute_script().await { - eprintln!("auto execute_script failed: {e}"); - } - }) - .expect("auto-play timer: no slint event loop"); - } + if is_auto.load(Ordering::Relaxed) && rx.try_recv().is_ok() { + let mut executor = executor.clone(); + slint::spawn_local(async move { + if let Err(e) = executor.execute_script().await { + eprintln!("auto execute_script failed: {e}"); + } + }) + .expect("auto-play timer: no slint event loop"); } }, ); diff --git a/src/executor/delay_executor.rs b/src/executors/delay_executor.rs similarity index 62% rename from src/executor/delay_executor.rs rename to src/executors/delay_executor.rs index 26eac4f..53ed35c 100644 --- a/src/executor/delay_executor.rs +++ b/src/executors/delay_executor.rs @@ -1,10 +1,14 @@ -use crate::executor::executor::Executor; -use crate::parser::parser::Command; -use std::collections::VecDeque; -use std::sync::Arc; -use std::sync::RwLock; -use tokio::sync::mpsc::Sender; -use tokio::time::{sleep, Duration}; +use crate::error::EngineError; +use crate::executors::executor::Executor; +use crate::parser::script_parser::Command; +use std::{ + collections::VecDeque, + sync::{Arc, RwLock}, +}; +use tokio::{ + sync::mpsc::Sender, + time::{sleep, Duration}, +}; #[derive(Clone)] pub struct DelayTX { @@ -13,28 +17,74 @@ pub struct DelayTX { clear_tx: Sender<()>, } -impl DelayTX { - pub fn delay_tx(tx: &Option) -> Sender { - if let Some(tx) = tx { - tx.delay_tx.clone() - } else { - unreachable!() - } +#[derive(Clone)] +pub struct DelayChannels { + pub delay_tx: DelayTX, + pub delay_move_tx: DelayTX, + pub loop_move_tx: DelayTX, +} + +impl DelayChannels { + pub async fn send_delay(&self, fg: &Command) -> Result<(), EngineError> { + self.delay_tx.delay_tx.send(fg.clone()).await?; + Ok(()) } - pub fn skip_tx(tx: &Option) -> Sender<()> { - if let Some(tx) = tx { - tx.skip_tx.clone() - } else { - unreachable!() - } + pub async fn send_move(&self, fg_move: Command) -> Result<(), EngineError> { + self.delay_move_tx.delay_tx.send(fg_move).await?; + Ok(()) + } + + pub fn send_loop(&self, fg_move: Command, fg_back: Command) { + try_send_loop(&self.delay_move_tx.delay_tx, fg_back); + try_send_loop(&self.loop_move_tx.delay_tx, fg_move); } - pub fn clear_tx(tx: &Option) -> Sender<()> { - if let Some(tx) = tx { - tx.clear_tx.clone() - } else { - unreachable!() + pub fn clear_all(&self) { + self.delay_tx + .clear_tx + .try_send(()) + .expect("clear_delay_tx send fali"); + self.delay_move_tx + .clear_tx + .try_send(()) + .expect("clear_delay_move_tx send fali"); + self.loop_move_tx + .clear_tx + .try_send(()) + .expect("clear_loop_move_tx send fali"); + } + + pub fn skip_all(&self) { + self.delay_tx + .skip_tx + .try_send(()) + .expect("skip_delay_tx send fali"); + self.delay_move_tx + .skip_tx + .try_send(()) + .expect("skip_delay_move_tx send fali"); + self.loop_move_tx + .skip_tx + .try_send(()) + .expect("skip_loop_move_tx send fali"); + } +} + +fn try_send_loop(tx: &Sender, cmd: Command) { + match tx.try_send(cmd) { + Ok(_) => {} + Err(tokio::sync::mpsc::error::TrySendError::Full(cmd)) => { + // 通道满了:把发送任务交给 tokio 等待 + let tx_clone = tx.clone(); + tokio::spawn(async move { + if let Err(e) = tx_clone.send(cmd).await { + eprintln!("delay tx send failed: {e:?}"); + } + }); + } + Err(e) => { + eprintln!("try_send other error: {e:?}"); } } } @@ -136,7 +186,7 @@ impl DelayExecutor { Ok(()) }; if let Err(e) = result { - eprintln!("delay executor failed: {e}"); + eprintln!("delay executors failed: {e}"); } }) .expect("Delay panicked"); diff --git a/src/executor/executor.rs b/src/executors/executor.rs similarity index 73% rename from src/executor/executor.rs rename to src/executors/executor.rs index 946c96e..6d43991 100644 --- a/src/executor/executor.rs +++ b/src/executors/executor.rs @@ -1,28 +1,29 @@ -use crate::media::player::PreBgm::Play; -use crate::media::player::{Player, PreBgm}; -use crate::media::video_player::VideoPlayer; -use crate::config::figure::FIGURE_CONFIG; -use crate::config::save_load::SaveData; -use crate::config::user::save_user_config; -use crate::config::voice::VOICE_LENGTH; -use crate::config::ENGINE_CONFIG; +use crate::config::{ + cg::CG_LENGTH, extra::save_extra_config, figure::FIGURE_CONFIG, save_load::SaveData, + user::save_user_config, voice::VOICE_LENGTH, ENGINE_CONFIG, +}; use crate::error::{EngineError, ExecutorError, SaveError}; -use crate::executor::delay_executor::DelayTX; -use crate::executor::text_executor::DisplayText; -use crate::parser::parser::{Command, Commands}; +use crate::executors::{ + delay_executor::{DelayChannels, DelayTX}, + text_executor::{DisplayText, TextTX}, +}; +use crate::media::{ + player::{MediaPlayer, Player, PreBgm, PreBgm::Play}, + video_player::{VideoContext, VideoPlayer}, +}; +use crate::parser::script_parser::{Command, Commands}; use crate::script::{Label, Script}; -use crate::ui::ui::MainWindow; +use crate::ui::initialize::{CharacterVolume, MainWindow}; use slint::{Image, Model, SharedString, ToSharedString, VecModel, Weak}; -use std::cell::RefCell; -use std::fs; -use std::path::Path; -use std::rc::Rc; -use std::sync::{Arc, RwLock}; -use std::time::Duration; +use std::{ + cell::RefCell, + fs, + path::Path, + rc::Rc, + sync::{Arc, RwLock}, + time::Duration, +}; use tokio::sync::mpsc::Sender; -use crate::config::cg::CG_LENGTH; -use crate::config::extra::save_extra_config; -use crate::ui::ui::CharacterVolume; pub(crate) enum Jump { Label(Label), @@ -37,72 +38,41 @@ fn face_default() -> (Image, f32, f32) { (Image::default(), 0.0, 0.0) } +#[derive(Clone)] pub struct Executor { script: Rc>, - bgm_player: Rc>, - voice_player: Rc>, + media_player: Rc>, pub(crate) cg: Rc>, weak: Weak, text: Arc>, choose_lock: Rc>, - /// 视频播放:当前是否正在播放(与 UI `is-video` 同步),用于阻止剧情推进。 - video_lock: Rc>, - /// 当前活动的视频播放器;为 `None` 表示无视频播放。 - video_player: Rc>>, - /// 视频帧轮询定时器(持有以保活)。 - video_timer: Rc>>, - text_tx: Option>>>, + video_context: Rc>, + text_tx: Option, auto_tx: Option>, - delay_tx: Option, - delay_move_tx: Option, - loop_move_tx: Option, -} - -impl Clone for Executor { - fn clone(&self) -> Executor { - Executor { - script: self.script.clone(), - bgm_player: self.bgm_player.clone(), - voice_player: self.voice_player.clone(), - cg: self.cg.clone(), - weak: self.weak.clone(), - text: self.text.clone(), - choose_lock: self.choose_lock.clone(), - video_lock: self.video_lock.clone(), - video_player: self.video_player.clone(), - video_timer: self.video_timer.clone(), - text_tx: self.text_tx.clone(), - auto_tx: self.auto_tx.clone(), - delay_tx: self.delay_tx.clone(), - delay_move_tx: self.delay_move_tx.clone(), - loop_move_tx: self.loop_move_tx.clone(), - } - } + delay_channels: Option, } impl Executor { pub fn new( script: Rc>, - bgm_player: Rc>, - voice_player: Rc>, + bgm_player: Player, + voice_player: Player, weak: Weak, ) -> Executor { Executor { script, - bgm_player, - voice_player, + media_player: Rc::new(RefCell::new(MediaPlayer { + bgm_player, + voice_player, + })), cg: Rc::new(RefCell::new(0)), weak, text: Arc::new(RwLock::new(DisplayText::new())), choose_lock: Rc::new(RefCell::new(false)), - video_lock: Rc::new(RefCell::new(false)), - video_player: Rc::new(RefCell::new(None)), - video_timer: Rc::new(RefCell::new(None)), + video_context: Rc::new(RefCell::new(VideoContext::default())), text_tx: None, - delay_tx: None, auto_tx: None, - delay_move_tx: None, - loop_move_tx: None, + delay_channels: None, } } @@ -114,20 +84,21 @@ impl Executor { self.text_tx = Some(text_tx); } - pub fn set_delay_tx(&mut self, delay_tx: DelayTX) { - self.delay_tx = Some(delay_tx); - } - pub fn set_auto_tx(&mut self, auto_tx: Sender) { self.auto_tx = Some(auto_tx); } - pub fn set_delay_move_tx(&mut self, delay_move_tx: DelayTX) { - self.delay_move_tx = Some(delay_move_tx); - } - - pub fn set_loop_move_tx(&mut self, loop_move_tx: DelayTX) { - self.loop_move_tx = Some(loop_move_tx); + pub fn set_delay_channels( + &mut self, + delay_tx: DelayTX, + delay_move_tx: DelayTX, + loop_move_tx: DelayTX, + ) { + self.delay_channels = Some(DelayChannels { + delay_tx, + delay_move_tx, + loop_move_tx, + }); } pub fn unlock(&mut self, index: usize) { @@ -208,7 +179,6 @@ impl Executor { })?; } window.set_save_items(Rc::new(VecModel::from(save_items)).into()); - } Ok(()) @@ -230,7 +200,7 @@ impl Executor { pub async fn execute_get_ex(&self) -> Result<(), EngineError> { let mut ex_items = Vec::with_capacity(16); - + let cgs = *self.cg.borrow(); let mut i = 1; while i <= 63 { @@ -238,28 +208,25 @@ impl Executor { if let Some((_, length)) = CG_LENGTH.find_by_id(i) { let (mut images, mut l, is_lock) = (Vec::new(), *length, false); for j in 1..=*length { - if cgs & (1 << j + i - 1) != 0 { + if cgs & (1 << (j + i - 1)) != 0 { if let Some((name, _)) = CG_LENGTH.find_by_id(j + i - 1) { - images.push(Image::load_from_path(Path::new(&format!( - "{}{}.png", - ENGINE_CONFIG.cg_path(), - name - ))) - .unwrap()); - } - else { + images.push( + Image::load_from_path(Path::new(&format!( + "{}{}.png", + ENGINE_CONFIG.cg_path(), + name + ))) + .unwrap(), + ); + } else { return Err(ExecutorError::CgMetadataMissing(j + i - 1).into()); } } else { l -= 1; } } - i = i + *length; - ex_items.push(( - Rc::new(VecModel::from(images)).into(), - l as i32, - is_lock, - )) + i += *length; + ex_items.push((Rc::new(VecModel::from(images)).into(), l as i32, is_lock)) } else { return Err(ExecutorError::CgMetadataMissing(i).into()); } @@ -268,11 +235,11 @@ impl Executor { ex_items.push(( Rc::new(VecModel::from(vec![Image::default()])).into(), 0, - true - )) + true, + )) } } - + if let Some(window) = self.weak.upgrade() { window.set_ex_items(Rc::new(VecModel::from(ex_items)).into()); } @@ -282,7 +249,7 @@ impl Executor { pub async fn execute_bgm_volume(&mut self) -> Result<(), EngineError> { if let Some(window) = self.weak.upgrade() { - let bgm_player = self.bgm_player.borrow_mut(); + let bgm_player = &self.media_player.borrow_mut().bgm_player; let volume = window.get_main_volume() / 100.0; let bgm_volume = window.get_bgm_volume() / 100.0; bgm_player.change_volume(volume * bgm_volume); @@ -293,7 +260,7 @@ impl Executor { pub async fn execute_voice_volume(&mut self) -> Result<(), EngineError> { if let Some(window) = self.weak.upgrade() { - let voice_player = self.voice_player.borrow_mut(); + let voice_player = &self.media_player.borrow().voice_player; let volume = window.get_main_volume() / 100.0; let voice_volume = window.get_voice_volume() / 100.0; voice_player.change_volume(volume * voice_volume); @@ -341,54 +308,55 @@ impl Executor { } pub async fn execute_jump(&mut self, label: Jump) -> Result<(), EngineError> { - let mut script = self.script.borrow_mut(); - let current_bgm = script.current_bgm().to_string(); - let mut pre_bg = None; - let mut pre_bgm = PreBgm::None; - let mut pre_figures = None; - let backlog = script.to_owned().take_backlog(); - let jump_index = match label { - Jump::Label((name, label)) => { - if name != script.name() { - let mut scr = Script::new(); - scr.with_name(&name)?; - scr.set_backlog(backlog); - *script = scr; - } - script.find_label(&label).map(|index| *index) - } - Jump::Index((name, index)) => { - if name != script.name() { - let mut scr = Script::new(); - scr.with_name(&name)?; - scr.set_backlog(backlog); - *script = scr; + { + let mut script = self.script.borrow_mut(); + let current_bgm = script.current_bgm().to_string(); + let mut pre_bg = None; + let mut pre_bgm = PreBgm::None; + let mut pre_figures = None; + let backlog = script.to_owned().take_backlog(); + let jump_index = match label { + Jump::Label((name, label)) => { + if name != script.name() { + let mut scr = Script::new(); + scr.with_name(&name)?; + scr.set_backlog(backlog); + *script = scr; + } + script.find_label(&label).copied() + } + Jump::Index((name, index)) => { + if name != script.name() { + let mut scr = Script::new(); + scr.with_name(&name)?; + scr.set_backlog(backlog); + *script = scr; + } + Some(index as usize) } - Some(index as usize) - } - }; + }; - let mut current_block = script.index(); - if let Some(index) = jump_index { - current_block = index; - if let Some((_, bgm)) = script.get_bgm(index) { - if ¤t_bgm != bgm { - pre_bgm = Play(bgm.to_string()); + let mut current_block = script.index(); + if let Some(index) = jump_index { + current_block = index; + if let Some((_, bgm)) = script.get_bgm(index) { + if ¤t_bgm != bgm { + pre_bgm = Play(bgm.to_string()); + } + } else if script.get_bgm(index).is_none() { + pre_bgm = PreBgm::Stop; + } + { + pre_bg = script.get_background(index).map(|(_, bg)| bg.clone()); + pre_figures = script.get_figures(index).map(|(_, fg)| fg.clone()); } - } else if script.get_bgm(index).is_none() { - pre_bgm = PreBgm::Stop; - } - { - pre_bg = script.get_background(index).map(|(_, bg)| bg.clone()); - pre_figures = script.get_figures(index).map(|(_, fg)| fg.clone()); } + script.set_pre_bg(pre_bg); + script.set_pre_bgm(pre_bgm); + script.set_pre_figures(pre_figures); + script.set_index(current_block); } - script.set_pre_bg(pre_bg); - script.set_pre_bgm(pre_bgm); - self.clean_fg("All", "All").await?; - script.set_pre_figures(pre_figures); - script.set_index(current_block); Ok(()) } @@ -432,33 +400,33 @@ impl Executor { { let scr = self.script.clone(); let scr = scr.borrow(); - if scr.clear.get(&scr.index()).is_some() { - DelayTX::clear_tx(&self.delay_tx).try_send(()).expect("clear_delay_tx send fali"); - DelayTX::clear_tx(&self.delay_move_tx).try_send(()).expect("clear_delay_move_tx send fali"); - DelayTX::clear_tx(&self.loop_move_tx).try_send(()).expect("clear_loop_move_tx send fali"); + if scr.clear.contains(&scr.index()) { + self.delay_channels.as_ref().unwrap().clear_all(); } else { - DelayTX::skip_tx(&self.delay_tx).try_send(()).expect("skip_delay_tx send fali"); - DelayTX::skip_tx(&self.delay_move_tx).try_send(()).expect("skip_delay_move_tx send fali"); - DelayTX::skip_tx(&self.loop_move_tx).try_send(()).expect("skip_loop_move_tx send fali"); + self.delay_channels.as_ref().unwrap().skip_all(); } } - { - + let res = { let mut text = self.text.write().unwrap(); if text.is_running { text.end(); - if let Some(window) = self.weak.upgrade() { - if window.get_is_auto() { - self.auto_tx - .clone() - .unwrap() - .send(Duration::from_secs(2)) - .await?; - } + true + } else { + false + } + }; + if res { + if let Some(window) = self.weak.upgrade() { + if window.get_is_auto() { + self.auto_tx + .clone() + .unwrap() + .send(Duration::from_secs(2)) + .await?; } - return Ok(()); } + return Ok(()); } let mut duration = Duration::default(); @@ -474,8 +442,7 @@ impl Executor { return Ok(()); } - if *self.video_lock.borrow() { - // 视频播放中,禁止推进剧情;UI 端也不会触发 clicked,这是双保险。 + if self.video_context.try_borrow().is_err() { return Ok(()); } @@ -531,7 +498,7 @@ impl Executor { if let Play(bgm) = pre_bgm { self.play_bgm(bgm).await?; } else if let PreBgm::Stop = pre_bgm { - let bgm_player = self.bgm_player.borrow_mut(); + let bgm_player = &self.media_player.borrow().bgm_player; bgm_player.stop(); } if let Some(figures) = pre_fg { @@ -543,9 +510,16 @@ impl Executor { match command { Command::Background { .. } => self.show_bg(&command).await?, Command::PlayBgm(bgm) => { - let mut script = self.script.borrow_mut(); - if bgm != script.current_bgm() { - script.set_current_bgm(bgm.clone()); + let needs_play = { + let mut script = self.script.borrow_mut(); + if bgm != script.current_bgm() { + script.set_current_bgm(bgm.clone()); + true + } else { + false + } + }; + if needs_play { self.play_bgm(bgm).await?; } } @@ -553,7 +527,7 @@ impl Executor { *self.choose_lock.borrow_mut() = true; let mut script = self.script.borrow_mut(); - script.set_explain(&format!("选择支:{}", explain)); + script.set_explain(&format!("选择支:{explain}")); let mut choose_branch = Vec::with_capacity(choices.len()); for (index, choice) in choices.iter().enumerate() { choose_branch.push((index as i32, SharedString::from(choice.0.clone()))); @@ -563,9 +537,11 @@ impl Executor { window.set_current_choose(choices.len() as i32); } Command::Dialogue { speaker, text } => { - let mut script = self.script.borrow_mut(); - script.set_explain(&text); - script.push_backlog(speaker.to_shared_string(), text.to_shared_string()); + { + let mut script = self.script.borrow_mut(); + script.set_explain(&text); + script.push_backlog(speaker.to_shared_string(), text.to_shared_string()); + } window.set_speaker(SharedString::from(speaker)); { let mut send_text = self.text.write().unwrap(); @@ -579,23 +555,32 @@ impl Executor { ref voice, } => { if let Some(length) = VOICE_LENGTH.find(name) { - let voice_player = self.voice_player.borrow_mut(); + let voice_player = &self.media_player.borrow().voice_player; let volume = window.get_main_volume() / 100.0; let voice_volume = window.get_voice_volume() / 100.0; let character_volumes = window.get_character_volumes(); { let full_name = ENGINE_CONFIG.character_list().get(name).unwrap(); - for CharacterVolume {name: ch_name, volume: ch_volume} in character_volumes.iter() { + for CharacterVolume { + name: ch_name, + volume: ch_volume, + } in character_volumes.iter() + { if ch_name == full_name { voice_player.play_voice( - &format!("{}/{}/{}.ogg", ENGINE_CONFIG.voice_path(), name, voice), + &format!( + "{}/{}/{}.ogg", + ENGINE_CONFIG.voice_path(), + name, + voice + ), volume * voice_volume * ch_volume / 100.0, )?; break; } } } - duration += length.get(voice).unwrap().clone(); + duration += *length.get(voice).unwrap(); } } Command::PlayVideo(name) => { @@ -611,8 +596,7 @@ impl Executor { Command::Jump(jump) => { self.execute_jump(Jump::Label(jump)).await?; } - Command::Label(_) => (), - Command::Empty => (), + Command::Label => (), } }; @@ -623,7 +607,7 @@ impl Executor { let weak = self.weak.clone(); if let Some(window) = weak.upgrade() { - let bgm_player = self.bgm_player.borrow_mut(); + let bgm_player = &self.media_player.borrow().bgm_player; let volume = window.get_main_volume() / 100.0; let bgm_volume = window.get_bgm_volume() / 100.0; bgm_player.play_loop( @@ -651,7 +635,7 @@ impl Executor { let path = if *is_cg { if let Some((index, _)) = CG_LENGTH.find_by_name(name) { self.unlock(*index); - save_extra_config(self.cg.borrow().clone())?; + save_extra_config(*self.cg.borrow())?; } ENGINE_CONFIG.cg_path() } else { @@ -659,12 +643,7 @@ impl Executor { }; if let Some(window) = weak.upgrade() { - let image = Image::load_from_path(Path::new(&format!( - "{}{}.png", - path, - name - ))) - .unwrap(); + let image = Image::load_from_path(Path::new(&format!("{path}{name}.png"))).unwrap(); window.set_bg(( image, x_offset.unwrap_or(0.0), @@ -691,11 +670,11 @@ impl Executor { }; if let Some(window) = weak.upgrade() { - if let Some(_) = delay { - DelayTX::delay_tx(&self.delay_tx).send(fg.clone()).await?; + if delay.is_some() { + self.delay_channels.as_ref().unwrap().send_delay(fg).await?; return Ok(()); } - if let (Some(body_para), Some(face_para), Some(offset)) = FIGURE_CONFIG.find(&name) { + if let (Some(body_para), Some(face_para), Some(offset)) = FIGURE_CONFIG.find(name) { let body_exist = match (&position[..], &distance[..]) { ("-1", "z1") => window.get_fg_z1__1(), ("-2", "z1") => window.get_fg_z1__2(), @@ -773,9 +752,11 @@ impl Executor { }; if let Some(window) = weak.upgrade() { - if let Some(_) = delay { - DelayTX::delay_tx(&self.delay_tx) - .send(fg_move.clone()) + if delay.is_some() { + self.delay_channels + .as_ref() + .unwrap() + .send_delay(fg_move) .await?; return Ok(()); } @@ -783,7 +764,6 @@ impl Executor { let offset: (f32, f32) = match &action[..] { "to2" => { if *repeat != 1 { - let tx = DelayTX::delay_tx(&self.loop_move_tx); let back = fg_move.back(); let action = Command::Move { name: name.to_string(), @@ -795,11 +775,13 @@ impl Executor { repeat: if *repeat > 1 { *repeat - 1 } else { -1 }, delay: Some("301".to_string()), }; - send_loop(tx.clone(), back); - send_loop(tx, action); + self.delay_channels + .as_ref() + .unwrap() + .send_loop(action, back); } else { - let tx = DelayTX::delay_tx(&self.delay_move_tx); - tx.send(Command::Figure { + let tx = self.delay_channels.as_ref().unwrap(); + tx.send_move(Command::Figure { name: name.to_string(), distance: distance.to_string(), body: body.to_string(), @@ -808,7 +790,7 @@ impl Executor { delay: Some("150".to_string()), }) .await?; - tx.send(fg_move.back_and_clean()).await?; + tx.send_move(fg_move.back_and_clean()).await?; } match (&position[..], &distance[..]) { ("-1", "z1") => (window.get_container_width() * 0.5, 0.0), @@ -819,8 +801,8 @@ impl Executor { } } "to0" => { - let tx = DelayTX::delay_tx(&self.delay_move_tx); - tx.send(Command::Figure { + let tx = self.delay_channels.as_ref().unwrap(); + tx.send_move(Command::Figure { name: name.to_string(), distance: distance.to_string(), body: body.to_string(), @@ -829,7 +811,7 @@ impl Executor { delay: Some("150".to_string()), }) .await?; - tx.send(fg_move.back_and_clean()).await?; + tx.send_move(fg_move.back_and_clean()).await?; match (&position[..], &distance[..]) { ("-1", "z1") => (window.get_container_width() * 0.33, 0.0), @@ -841,7 +823,6 @@ impl Executor { } "nod" => { if *repeat != 1 { - let tx = DelayTX::delay_tx(&self.loop_move_tx); let back = fg_move.back(); let nod = Command::Move { name: name.to_string(), @@ -853,27 +834,26 @@ impl Executor { repeat: if *repeat > 1 { *repeat - 1 } else { -1 }, delay: Some("301".to_string()), }; - send_loop(tx.clone(), back); - send_loop(tx, nod); + self.delay_channels.as_ref().unwrap().send_loop(nod, back); } else { - let tx = DelayTX::delay_tx(&self.delay_move_tx); - tx.send(Command::Move { - name: name.to_string(), - distance: distance.to_string(), - body: body.to_string(), - face: face.to_string(), - position: position.to_string(), - action: "back".to_string(), - repeat: *repeat, - delay: Some("150".to_string()), - }) - .await?; + self.delay_channels + .as_ref() + .unwrap() + .send_move(Command::Move { + name: name.to_string(), + distance: distance.to_string(), + body: body.to_string(), + face: face.to_string(), + position: position.to_string(), + action: "back".to_string(), + repeat: *repeat, + delay: Some("150".to_string()), + }) + .await?; } (0.0, window.get_container_height() / 40.0) } - "back" => { - (0.0, 0.0) - } + "back" => (0.0, 0.0), "back_and_clean" => { match (&position[..], &distance[..]) { ("-1", "z1") => { @@ -919,7 +899,7 @@ impl Executor { async fn clean_fg(&self, distance: &str, position: &str) -> Result<(), EngineError> { let weak = self.weak.clone(); if let Some(window) = weak.upgrade() { - match (&position[..], &distance[..]) { + match (position, distance) { ("-2", "z1") => { window.set_fg_z1__2(figure_default()); window.set_face_z1__2(face_default()); @@ -961,12 +941,11 @@ impl Executor { ENGINE_CONFIG.video_extension() ); - self.bgm_player.borrow().stop(); - self.voice_player.borrow().stop(); + self.media_player.borrow().stop_all(); let player = VideoPlayer::play(&path)?; - *self.video_player.borrow_mut() = Some(player); - *self.video_lock.borrow_mut() = true; + let mut video_context = self.video_context.borrow_mut(); + video_context.set_video_player(player); if let Some(window) = self.weak.upgrade() { window.set_is_video(true); @@ -974,14 +953,14 @@ impl Executor { let timer = slint::Timer::default(); let weak = self.weak.clone(); - let video_player = self.video_player.clone(); + let video_player = self.video_context.clone(); let executor_for_finish = self.clone(); timer.start( slint::TimerMode::Repeated, Duration::from_millis(16), move || { let mut finished = false; - if let Some(vp) = video_player.borrow().as_ref() { + if let Some(vp) = video_player.borrow().get_video_player_ref() { if let Some(window) = weak.upgrade() { if let Some(frame) = vp.take_latest_frame() { window.set_video_frame(frame); @@ -1000,23 +979,24 @@ impl Executor { } }, ); - *self.video_timer.borrow_mut() = Some(timer); + video_context.set_video_timer(timer); Ok(()) } pub async fn execute_stop_video(&self) -> Result<(), EngineError> { - if !*self.video_lock.borrow() { - return Ok(()); - } + { + let mut video_context = self.video_context.borrow_mut(); - if let Some(player) = self.video_player.borrow_mut().take() { - player.stop(); - } + if let Some(player) = video_context.get_video_player() { + player.stop(); + } else { + return Ok(()); + } - self.video_timer.borrow_mut().take(); + video_context.get_video_timer(); + } - *self.video_lock.borrow_mut() = false; if let Some(window) = self.weak.upgrade() { window.set_is_video(false); window.set_video_frame(Image::default()); @@ -1026,21 +1006,3 @@ impl Executor { this.execute_script().await } } - -fn send_loop(tx: Sender, cmd: Command) { - match tx.try_send(cmd) { - Ok(_) => {} - Err(tokio::sync::mpsc::error::TrySendError::Full(cmd)) => { - // 通道满了:把发送任务交给 tokio 等待 - let tx_clone = tx.clone(); - tokio::spawn(async move { - if let Err(e) = tx_clone.send(cmd).await { - eprintln!("delay tx send failed: {:?}", e); - } - }); - } - Err(e) => { - eprintln!("try_send other error: {:?}", e); - } - } -} diff --git a/src/executor/mod.rs b/src/executors/mod.rs similarity index 71% rename from src/executor/mod.rs rename to src/executors/mod.rs index 917e779..b346f0c 100644 --- a/src/executor/mod.rs +++ b/src/executors/mod.rs @@ -1,9 +1,8 @@ use crate::error::EngineError; -use crate::executor::auto_executor::AutoExecutor; -use crate::executor::delay_executor::DelayExecutor; -use crate::executor::executor::Executor; -use crate::executor::skip_executor::SkipExecutor; -use crate::executor::text_executor::TextExecutor; +use crate::executors::{ + auto_executor::AutoExecutor, delay_executor::DelayExecutor, executor::Executor, + skip_executor::SkipExecutor, text_executor::TextExecutor, +}; use tokio::sync::mpsc::Sender; pub mod auto_executor; @@ -38,27 +37,26 @@ pub fn load_data(executor: &mut Executor) -> Result { executor.set_text_tx(text_tx); let (mut delay_executor, delay_tx) = DelayExecutor::new(executor.clone()); - executor.set_delay_tx(delay_tx.clone()); - delay_executor.executor.set_delay_tx(delay_tx); - let (mut delay_move_executor, delay_move_tx) = DelayExecutor::new(executor.clone()); - executor.set_delay_move_tx(delay_move_tx.clone()); - delay_executor - .executor - .set_delay_move_tx(delay_move_tx.clone()); - delay_move_executor - .executor - .set_delay_move_tx(delay_move_tx); - let (mut loop_move_executor, loop_move_tx) = DelayExecutor::new(executor.clone()); - executor.set_loop_move_tx(loop_move_tx.clone()); - delay_executor - .executor - .set_loop_move_tx(loop_move_tx.clone()); - delay_move_executor + executor.set_delay_channels( + delay_tx.clone(), + delay_move_tx.clone(), + loop_move_tx.clone(), + ); + delay_executor.executor.set_delay_channels( + delay_tx.clone(), + delay_move_tx.clone(), + loop_move_tx.clone(), + ); + delay_move_executor.executor.set_delay_channels( + delay_tx.clone(), + delay_move_tx.clone(), + loop_move_tx.clone(), + ); + loop_move_executor .executor - .set_loop_move_tx(loop_move_tx.clone()); - loop_move_executor.executor.set_loop_move_tx(loop_move_tx); + .set_delay_channels(delay_tx, delay_move_tx, loop_move_tx); let (mut auto_executor, auto_tx, auto_delay_tx) = AutoExecutor::new(executor.clone()); executor.set_auto_tx(auto_delay_tx.clone()); diff --git a/src/executor/skip_executor.rs b/src/executors/skip_executor.rs similarity index 89% rename from src/executor/skip_executor.rs rename to src/executors/skip_executor.rs index 20a9280..b991ee1 100644 --- a/src/executor/skip_executor.rs +++ b/src/executors/skip_executor.rs @@ -1,8 +1,11 @@ -use crate::executor::executor::Executor; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; -use std::sync::Arc; -use std::time::Duration; +use crate::executors::executor::Executor; +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, +}; use tokio::sync::mpsc::{channel, Sender}; pub struct SkipExecutor { @@ -29,7 +32,7 @@ impl SkipExecutor { let is_skip_clone = is_skip.clone(); tokio::spawn(async move { let mut start = true; - while let Some(_) = rx.recv().await { + while (rx.recv().await).is_some() { if start { is_skip_clone.store(true, Ordering::Relaxed); start = false; @@ -52,7 +55,6 @@ impl SkipExecutor { Duration::from_millis(100), move || { if is_skip.load(Ordering::Relaxed) { - let mut executor = executor.clone(); slint::spawn_local(async move { if let Err(e) = executor.execute_script().await { diff --git a/src/executor/text_executor.rs b/src/executors/text_executor.rs similarity index 95% rename from src/executor/text_executor.rs rename to src/executors/text_executor.rs index 83f74d2..47ddd70 100644 --- a/src/executor/text_executor.rs +++ b/src/executors/text_executor.rs @@ -1,9 +1,7 @@ -use crate::executor::executor::Executor; -use crate::ui::ui::MainWindow; +use crate::executors::executor::Executor; +use crate::ui::initialize::MainWindow; use slint::{ToSharedString, Weak}; -use std::sync::mpsc::Receiver; -use std::sync::Arc; -use std::sync::RwLock; +use std::sync::{mpsc::Receiver, Arc, RwLock}; use tokio::sync::mpsc::{channel, Sender}; use tokio::time::{sleep, Duration}; @@ -84,6 +82,8 @@ impl TextExecutor { } } +pub type TextTX = Sender>>; + pub struct DisplayText { pub(crate) full_text: String, pub(crate) speed: Duration, diff --git a/src/main.rs b/src/main.rs index d4aec61..ee1b976 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ mod config; mod error; -mod executor; +mod executors; mod media; mod parser; mod run; diff --git a/src/media/player.rs b/src/media/player.rs index 5fda064..56ce372 100644 --- a/src/media/player.rs +++ b/src/media/player.rs @@ -6,6 +6,18 @@ use std::{ sync::{Arc, Mutex}, }; +pub struct MediaPlayer { + pub bgm_player: Player, + pub voice_player: Player, +} + +impl MediaPlayer { + pub fn stop_all(&self) { + self.bgm_player.stop(); + self.voice_player.stop(); + } +} + pub struct Player { sink: Arc>>, _stream: OutputStream, diff --git a/src/media/video_player.rs b/src/media/video_player.rs index 754a283..6527c65 100644 --- a/src/media/video_player.rs +++ b/src/media/video_player.rs @@ -1,37 +1,26 @@ -//! 视频播放器(基于 ffmpeg-next 的实现) -//! -//! 设计: -//! - `play(path)` 打开文件,启动一个**单独的解码 std::thread** -//! (避免 ffmpeg 阻塞调用阻塞 tokio runtime)。 -//! 线程内部完成:解封装 → 视频解码 → swscale 到 RGBA8 → 推到 latest_frame; -//! 音频解码 → swresample → rodio Sink 播放。 -//! - 同步策略:以**视频 PTS 时钟**为基准。线程持有自播放起始的 `Instant`, -//! 每解出一帧后 sleep 至该帧 `pts` 对应的目标显示时刻再写入 `latest_frame`。 -//! 音频独立喂入 rodio sink,由 sink 自带定时驱动播放节奏; -//! 视频追音频实现的"严格 A/V 同步"在此场景下被简化(GalGame 视频 -//! 通常较短,5~30 秒级片头/CG),可接受少许漂移。 -//! - 取消:`cancel: AtomicBool` 在每次循环开头检查;UI 端 `stop()` 会置位 -//! 并立即停止 audio sink,解码线程下一次循环退出。 -//! - 完成:解封装 EOF + 解码缓冲 flush 完毕后置 `finished = true`。 -//! -//! 资源生命周期:`VideoPlayer` 被 drop 时自动 `stop()`,确保 ffmpeg 资源释放。 - use crate::error::MediaError; use ffmpeg_next as ffmpeg; use ffmpeg_next::format::{input, Pixel}; use ffmpeg_next::media::Type as MediaType; -use ffmpeg_next::software::resampling::context::Context as ResamplingContext; -use ffmpeg_next::software::scaling::{context::Context as ScalingContext, flag::Flags}; -use ffmpeg_next::util::format::sample::{Sample as SampleFormat, Type as SampleType}; -use ffmpeg_next::util::frame::{audio::Audio as AudioFrame, video::Video as VideoFrame}; -use ffmpeg_next::util::rational::Rational; -use rodio::buffer::SamplesBuffer; -use rodio::{OutputStream, Sink}; +use ffmpeg_next::software::{ + resampling::context::Context as ResamplingContext, + scaling::{context::Context as ScalingContext, flag::Flags}, +}; +use ffmpeg_next::util::{ + format::sample::{Sample as SampleFormat, Type as SampleType}, + frame::{audio::Audio as AudioFrame, video::Video as VideoFrame}, + rational::Rational, +}; +use rodio::{buffer::SamplesBuffer, OutputStream, Sink}; use slint::{Image, Rgba8Pixel, SharedPixelBuffer}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex, Once}; -use std::thread; -use std::time::{Duration, Instant}; +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, Once, + }, + thread, + time::{Duration, Instant}, +}; type FrameBuffer = SharedPixelBuffer; @@ -46,6 +35,40 @@ fn ensure_ffmpeg_initialized() { }); } +pub struct VideoContext { + video_player: Option, + video_timer: Option, +} + +impl VideoContext { + pub fn default() -> Self { + VideoContext { + video_player: None, + video_timer: None, + } + } + + pub fn set_video_player(&mut self, player: VideoPlayer) { + self.video_player = Some(player); + } + + pub fn set_video_timer(&mut self, timer: slint::Timer) { + self.video_timer = Some(timer); + } + + pub fn get_video_player_ref(&self) -> Option<&VideoPlayer> { + self.video_player.as_ref() + } + + pub fn get_video_player(&mut self) -> Option { + self.video_player.take() + } + + pub fn get_video_timer(&mut self) -> Option { + self.video_timer.take() + } +} + /// 一段视频的播放句柄。 pub struct VideoPlayer { path: String, @@ -142,13 +165,13 @@ fn decode_loop( })?; // 视频流 - let video_stream = ictx - .streams() - .best(MediaType::Video) - .ok_or_else(|| MediaError::DecodeVideo { - path: path.to_string(), - reason: "no video stream".into(), - })?; + let video_stream = + ictx.streams() + .best(MediaType::Video) + .ok_or_else(|| MediaError::DecodeVideo { + path: path.to_string(), + reason: "no video stream".into(), + })?; let video_stream_index = video_stream.index(); let video_time_base: Rational = video_stream.time_base(); @@ -157,10 +180,13 @@ fn decode_loop( path: path.to_string(), reason: format!("video codec ctx: {e}"), })?; - let mut video_decoder = video_ctx.decoder().video().map_err(|e| MediaError::DecodeVideo { - path: path.to_string(), - reason: format!("video decoder: {e}"), - })?; + let mut video_decoder = video_ctx + .decoder() + .video() + .map_err(|e| MediaError::DecodeVideo { + path: path.to_string(), + reason: format!("video decoder: {e}"), + })?; let src_w = video_decoder.width(); let src_h = video_decoder.height(); let mut scaler = ScalingContext::get( @@ -179,7 +205,10 @@ fn decode_loop( // 音频流 let audio_setup = setup_audio(&ictx).map_err(|mut e| { - if let MediaError::DecodeVideo { path: ref mut p, .. } = e { + if let MediaError::DecodeVideo { + path: ref mut p, .. + } = e + { if p.is_empty() { *p = path.to_string(); } @@ -268,8 +297,8 @@ fn drain_video_frames( } if let Some(pts) = decoded.pts() { - let pts_secs = pts as f64 * f64::from(time_base.numerator()) - / f64::from(time_base.denominator()); + let pts_secs = + pts as f64 * f64::from(time_base.numerator()) / f64::from(time_base.denominator()); let target = playback_start + Duration::from_secs_f64(pts_secs.max(0.0)); let now = Instant::now(); if target > now { @@ -328,9 +357,7 @@ struct AudioSetup { _stream: OutputStream, } -fn setup_audio( - ictx: &ffmpeg::format::context::Input, -) -> Result, MediaError> { +fn setup_audio(ictx: &ffmpeg::format::context::Input) -> Result, MediaError> { let audio_stream = match ictx.streams().best(MediaType::Audio) { Some(s) => s, None => return Ok(None), @@ -342,10 +369,13 @@ fn setup_audio( path: String::new(), reason: format!("audio codec ctx: {e}"), })?; - let decoder = audio_ctx.decoder().audio().map_err(|e| MediaError::DecodeVideo { - path: String::new(), - reason: format!("audio decoder: {e}"), - })?; + let decoder = audio_ctx + .decoder() + .audio() + .map_err(|e| MediaError::DecodeVideo { + path: String::new(), + reason: format!("audio decoder: {e}"), + })?; let target_format = SampleFormat::I16(SampleType::Packed); let target_rate = decoder.rate(); diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 67c567f..537311c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1 +1 @@ -pub mod parser; +pub mod script_parser; diff --git a/src/parser/parser.rs b/src/parser/script_parser.rs similarity index 92% rename from src/parser/parser.rs rename to src/parser/script_parser.rs index f5c74c9..43097ba 100644 --- a/src/parser/parser.rs +++ b/src/parser/script_parser.rs @@ -49,8 +49,7 @@ pub enum Command { Clear(String, String), Choice((String, HashMap)), Jump(Label), - Label(String), - Empty, + Label, } impl Command { @@ -161,7 +160,7 @@ impl Script { let mut block_commands = Vec::new(); - for (index, (line_num, line)) in lines.into_iter().enumerate() { + for (index, (line_num, line)) in lines.iter().enumerate() { if let Some(line) = line.strip_prefix('@') { if let Some((cmd, arg)) = line.split_once(' ') { let cmd = match cmd { @@ -172,7 +171,7 @@ impl Script { x_offset: parts.next().and_then(|s| s.parse::().ok()), y_offset: parts.next().and_then(|s| s.parse::().ok()), zoom: parts.next().and_then(|s| s.parse::().ok()), - is_cg: if cmd == "cg" {true} else {false} + is_cg: cmd == "cg", }; self.backgrounds.insert(*block_index, bg.clone()); bg @@ -185,8 +184,9 @@ impl Script { let num = arg.parse::().map_err(ScriptError::from)?; let mut choose_branch = HashMap::with_capacity(num); let explain = lines[index + 1].1.clone(); - for i in index + 2..=index + num + 1 { - if let Some((choice, script)) = lines[i].1.split_once(' ') { + for (i, line) in lines.iter().take(index + num + 1 + 1).skip(index + 2) + { + if let Some((choice, script)) = line.split_once(' ') { let (choice, label) = match script.split_once(":") { Some((name, label)) if !name.is_empty() && !label.is_empty() => @@ -213,9 +213,9 @@ impl Script { choose_branch.insert(choice.clone(), label.clone()); self.choices.insert(choice, label); } else { - return Err(EngineError::from(ScriptError::Choice( - format!("Invalid choice at line {}: {}", lines[i].0, lines[i].1), - ))); + return Err(EngineError::from(ScriptError::Choice(format!( + "Invalid choice at line {i}: {line}" + )))); } } block_commands.push(Choice((explain, choose_branch))); @@ -282,11 +282,13 @@ impl Script { delay: delay.map(|d| d.to_string()), } } - _ => return Err(EngineError::from(ScriptError::ArgsTooShort { - cmd: "fg".to_string(), - line: *line_num, - content: line.to_string(), - })), + _ => { + return Err(EngineError::from(ScriptError::ArgsTooShort { + cmd: "fg".to_string(), + line: *line_num, + content: line.to_string(), + })) + } } } "move" => { @@ -330,26 +332,26 @@ impl Script { } command } - _ => return Err(EngineError::from(ScriptError::ArgsTooShort { - cmd: "move".to_string(), - line: *line_num, - content: line.to_string(), - })), + _ => { + return Err(EngineError::from(ScriptError::ArgsTooShort { + cmd: "move".to_string(), + line: *line_num, + content: line.to_string(), + })) + } } } "clear" => { self.clear.insert(*block_index); if let Some((dis, pos)) = arg.split_once("|") { Clear(dis.to_string(), pos.to_string()) + } else if arg == "All" { + Clear(arg.to_string(), arg.to_string()) } else { - if arg == "All" { - Clear(arg.to_string(), arg.to_string()) - } else { - return Err(EngineError::from(ScriptError::InvalidCommand { - line: *line_num, - content: line.to_string(), - })); - } + return Err(EngineError::from(ScriptError::InvalidCommand { + line: *line_num, + content: line.to_string(), + })); } } "jump" => match arg.split_once(":") { @@ -365,7 +367,7 @@ impl Script { }, "label" => { self.labels.insert(arg.to_string(), *block_index); - Label(arg.to_string()) + Label } _ => { return Err(EngineError::from(ScriptError::InvalidCommand { @@ -402,7 +404,7 @@ impl Script { content: line.to_string(), })); } - } else if let Some(_) = line.strip_prefix('#') { + } else if line.strip_prefix('#').is_some() { continue; } else if let Some((speaker, text)) = line.split_once("“") { if let Some(text) = text.strip_suffix("”") { diff --git a/src/run.rs b/src/run.rs index 5374749..9a68c7b 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,16 +1,13 @@ -use crate::media::player::Player; use crate::error::EngineError; +use crate::media::player::Player; use crate::script::Script; -use crate::ui::ui::ui; -use std::cell::RefCell; -use std::rc::Rc; +use crate::ui::initialize::ui; +use std::{cell::RefCell, rc::Rc}; pub async fn build() -> Result<(), EngineError> { let mut script = Script::new(); script.with_name("ky01")?; let script = Rc::new(RefCell::new(script)); - let bgm_player = Rc::new(RefCell::new(Player::new()?)); - let voice_player = Rc::new(RefCell::new(Player::new()?)); - ui(script, bgm_player, voice_player).await?; + ui(script, Player::new()?, Player::new()?).await?; Ok(()) } diff --git a/src/script.rs b/src/script.rs index 3fb1b4d..e876870 100644 --- a/src/script.rs +++ b/src/script.rs @@ -1,10 +1,12 @@ -use crate::media::player::PreBgm; use crate::config::ENGINE_CONFIG; use crate::error::{EngineError, ScriptError}; -use crate::parser::parser::{Command, Commands}; +use crate::media::player::PreBgm; +use crate::parser::script_parser::{Command, Commands}; use slint::{SharedString, ToSharedString}; -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::fs; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + fs, +}; pub type Label = (String, String); @@ -77,8 +79,8 @@ impl Script { command } - pub fn set_explain(&mut self, explain: &String) { - let mut explain = &explain[..]; + pub fn set_explain(&mut self, explain: &str) { + let mut explain = explain; if explain.len() > 18 { explain = &explain[0..18]; } @@ -125,7 +127,7 @@ impl Script { ) { self.figures .entry(index) - .or_insert_with(Figure::default) + .or_default() .push(distance, position, command); } @@ -218,11 +220,11 @@ impl Script { } pub fn change_figure(&mut self, index: usize, distance: &str, position: &str) -> Command { - let pos = format!("{}{}", distance, position); + let pos = format!("{distance}{position}"); let mut idx = 0; for i in (0..=index).rev() { if let Some(fg) = self.figures.get(&i) { - if fg.0.get(&pos).is_some() { + if fg.0.contains_key(&pos) { idx = i; break; } @@ -233,17 +235,11 @@ impl Script { } } -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] pub struct Figure(pub HashMap); -impl Default for Figure { - fn default() -> Self { - Figure(HashMap::new()) - } -} - impl Figure { fn push(&mut self, distance: &str, position: &str, command: Command) { - self.0.insert(format!("{}{}", distance, position), command); + self.0.insert(format!("{distance}{position}"), command); } } diff --git a/src/ui/ui.rs b/src/ui/initialize.rs similarity index 96% rename from src/ui/ui.rs rename to src/ui/initialize.rs index 87eb934..89899e8 100644 --- a/src/ui/ui.rs +++ b/src/ui/initialize.rs @@ -1,17 +1,15 @@ -use crate::media::player::Player; use crate::error::EngineError; -use crate::executor::executor::Executor; -use crate::executor::load_data; +use crate::executors::{executor::Executor, load_data}; +use crate::media::player::Player; use crate::script::Script; -use std::cell::RefCell; -use std::rc::Rc; +use std::{cell::RefCell, rc::Rc}; slint::include_modules!(); pub async fn ui( script: Rc>, - bgm_player: Rc>, - voice_player: Rc>, + bgm_player: Player, + voice_player: Player, ) -> Result<(), EngineError> { let window = MainWindow::new()?; let weak = window.as_weak(); @@ -56,7 +54,7 @@ pub async fn ui( window.on_get_ex({ let executor = executor.clone(); move || { - let mut executor = executor.clone(); + let executor = executor.clone(); slint::spawn_local(async move { executor.execute_get_ex().await }) .expect("Get Ex panicked"); } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 6bae95d..c570ae3 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1 +1 @@ -pub mod ui; +pub mod initialize;