mecomp_tui/state/
mod.rs

1use std::sync::Arc;
2
3use action::Action;
4use mecomp_core::{
5    rpc::{MusicPlayerClient, SearchResult},
6    state::{library::LibraryFull, StateAudio},
7};
8use tokio::sync::{
9    broadcast,
10    mpsc::{self, UnboundedReceiver, UnboundedSender},
11};
12
13use crate::{
14    termination::{Interrupted, Terminator},
15    ui::{components::content_view::ActiveView, widgets::popups::PopupType},
16};
17
18pub mod action;
19pub mod audio;
20pub mod component;
21pub mod library;
22pub mod popup;
23pub mod search;
24pub mod view;
25
26/// an all-in-one dispactcher for managing state updates.
27pub struct Dispatcher {
28    audio: audio::AudioState,
29    search: search::SearchState,
30    library: library::LibraryState,
31    view: view::ViewState,
32    popup: popup::PopupState,
33    component: component::ComponentState,
34}
35
36/// a struct that centralized the senders for all the state stores.
37struct Senders {
38    pub audio: UnboundedSender<action::AudioAction>,
39    pub search: UnboundedSender<String>,
40    pub library: UnboundedSender<action::LibraryAction>,
41    pub view: UnboundedSender<action::ViewAction>,
42    pub popup: UnboundedSender<action::PopupAction>,
43    pub component: UnboundedSender<action::ComponentAction>,
44}
45
46/// a struct that centralized the receivers for all the state stores.
47pub struct Receivers {
48    pub audio: UnboundedReceiver<StateAudio>,
49    pub search: UnboundedReceiver<SearchResult>,
50    pub library: UnboundedReceiver<LibraryFull>,
51    pub view: UnboundedReceiver<ActiveView>,
52    pub popup: UnboundedReceiver<Option<PopupType>>,
53    pub component: UnboundedReceiver<component::ActiveComponent>,
54}
55
56impl Dispatcher {
57    #[must_use]
58    pub fn new() -> (Self, Receivers) {
59        let (audio, audio_rx) = audio::AudioState::new();
60        let (search, search_rx) = search::SearchState::new();
61        let (library, library_rx) = library::LibraryState::new();
62        let (view, view_rx) = view::ViewState::new();
63        let (popup, popup_rx) = popup::PopupState::new();
64        let (active_component, active_component_rx) = component::ComponentState::new();
65
66        let dispatcher = Self {
67            audio,
68            search,
69            library,
70            view,
71            popup,
72            component: active_component,
73        };
74        let state_receivers = Receivers {
75            audio: audio_rx,
76            search: search_rx,
77            library: library_rx,
78            view: view_rx,
79            popup: popup_rx,
80            component: active_component_rx,
81        };
82
83        (dispatcher, state_receivers)
84    }
85
86    /// the main loop for the dispatcher.
87    ///
88    /// the dispatcher will run until the user exits the application.
89    ///
90    /// # Errors
91    ///
92    /// if any of the state stores fail to run.
93    pub async fn main_loop(
94        &self,
95        daemon: Arc<MusicPlayerClient>,
96        terminator: Terminator,
97        action_rx: UnboundedReceiver<Action>,
98        mut interrupt_rx: broadcast::Receiver<Interrupted>,
99    ) -> anyhow::Result<Interrupted> {
100        let (audio_action_tx, audio_action_rx) = mpsc::unbounded_channel();
101        let (search_action_tx, search_action_rx) = mpsc::unbounded_channel();
102        let (library_action_tx, library_action_rx) = mpsc::unbounded_channel();
103        let (view_action_tx, view_action_rx) = mpsc::unbounded_channel();
104        let (popup_action_tx, popup_action_rx) = mpsc::unbounded_channel();
105        let (component_action_tx, component_action_rx) = mpsc::unbounded_channel();
106
107        // run multiple tasks in parallel, and wait for all of them to finish.
108        // the tasks are:
109        // - the audio state store
110        // - ...
111        // - the action dispatcher
112        tokio::try_join!(
113            // the audio state store
114            self.audio
115                .main_loop(daemon.clone(), audio_action_rx, interrupt_rx.resubscribe()),
116            // the search state store
117            self.search
118                .main_loop(daemon.clone(), search_action_rx, interrupt_rx.resubscribe()),
119            // the library state store
120            self.library.main_loop(
121                daemon.clone(),
122                library_action_rx,
123                interrupt_rx.resubscribe()
124            ),
125            // the view store
126            self.view
127                .main_loop(view_action_rx, interrupt_rx.resubscribe()),
128            // the popup store
129            self.popup
130                .main_loop(popup_action_rx, interrupt_rx.resubscribe()),
131            // the active component store
132            self.component
133                .main_loop(component_action_rx, interrupt_rx.resubscribe()),
134            // the action dispatcher
135            Self::action_dispatcher(
136                terminator,
137                action_rx,
138                Senders {
139                    audio: audio_action_tx,
140                    search: search_action_tx,
141                    library: library_action_tx,
142                    view: view_action_tx,
143                    popup: popup_action_tx,
144                    component: component_action_tx,
145                },
146            ),
147        )?;
148
149        Ok(interrupt_rx.recv().await?)
150    }
151
152    async fn action_dispatcher(
153        mut terminator: Terminator,
154        mut action_rx: UnboundedReceiver<Action>,
155        senders: Senders,
156    ) -> anyhow::Result<()> {
157        while let Some(action) = action_rx.recv().await {
158            match action {
159                Action::Audio(action) => {
160                    senders.audio.send(action)?;
161                }
162                Action::Search(query) => {
163                    senders.search.send(query)?;
164                }
165                Action::General(action) => match action {
166                    action::GeneralAction::Exit => {
167                        let _ = terminator.terminate(Interrupted::UserInt);
168
169                        break;
170                    }
171                },
172                Action::Library(action) => {
173                    senders.library.send(action)?;
174                }
175                Action::ActiveView(action) => {
176                    senders.view.send(action)?;
177                }
178                Action::Popup(popup) => senders.popup.send(popup)?,
179                Action::ActiveComponent(action) => {
180                    senders.component.send(action)?;
181                }
182            }
183        }
184
185        Ok(())
186    }
187}