Skip to main content

mecomp_tui/state/
mod.rs

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