mecomp_tui/state/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
use std::sync::Arc;

use action::Action;
use mecomp_core::{
    rpc::{MusicPlayerClient, SearchResult},
    state::{library::LibraryFull, StateAudio},
};
use tokio::sync::{
    broadcast,
    mpsc::{self, UnboundedReceiver, UnboundedSender},
};

use crate::{
    termination::{Interrupted, Terminator},
    ui::{components::content_view::ActiveView, widgets::popups::PopupType},
};

pub mod action;
pub mod audio;
pub mod component;
pub mod library;
pub mod popup;
pub mod search;
pub mod view;

/// an all-in-one dispactcher for managing state updates.
pub struct Dispatcher {
    audio: audio::AudioState,
    search: search::SearchState,
    library: library::LibraryState,
    view: view::ViewState,
    popup: popup::PopupState,
    component: component::ComponentState,
}

/// a struct that centralized the senders for all the state stores.
struct Senders {
    pub audio: UnboundedSender<action::AudioAction>,
    pub search: UnboundedSender<String>,
    pub library: UnboundedSender<action::LibraryAction>,
    pub view: UnboundedSender<ActiveView>,
    pub popup: UnboundedSender<action::PopupAction>,
    pub component: UnboundedSender<action::ComponentAction>,
}

/// a struct that centralized the receivers for all the state stores.
pub struct Receivers {
    pub audio: UnboundedReceiver<StateAudio>,
    pub search: UnboundedReceiver<SearchResult>,
    pub library: UnboundedReceiver<LibraryFull>,
    pub view: UnboundedReceiver<ActiveView>,
    pub popup: UnboundedReceiver<Option<PopupType>>,
    pub component: UnboundedReceiver<component::ActiveComponent>,
}

impl Dispatcher {
    #[must_use]
    pub fn new() -> (Self, Receivers) {
        let (audio, audio_rx) = audio::AudioState::new();
        let (search, search_rx) = search::SearchState::new();
        let (library, library_rx) = library::LibraryState::new();
        let (view, view_rx) = view::ViewState::new();
        let (popup, popup_rx) = popup::PopupState::new();
        let (active_component, active_component_rx) = component::ComponentState::new();

        let dispatcher = Self {
            audio,
            search,
            library,
            view,
            popup,
            component: active_component,
        };
        let state_receivers = Receivers {
            audio: audio_rx,
            search: search_rx,
            library: library_rx,
            view: view_rx,
            popup: popup_rx,
            component: active_component_rx,
        };

        (dispatcher, state_receivers)
    }

    /// the main loop for the dispatcher.
    ///
    /// the dispatcher will run until the user exits the application.
    ///
    /// # Errors
    ///
    /// if any of the state stores fail to run.
    pub async fn main_loop(
        &self,
        daemon: Arc<MusicPlayerClient>,
        terminator: Terminator,
        action_rx: UnboundedReceiver<Action>,
        mut interrupt_rx: broadcast::Receiver<Interrupted>,
    ) -> anyhow::Result<Interrupted> {
        let (audio_action_tx, audio_action_rx) = mpsc::unbounded_channel();
        let (search_action_tx, search_action_rx) = mpsc::unbounded_channel();
        let (library_action_tx, library_action_rx) = mpsc::unbounded_channel();
        let (view_action_tx, view_action_rx) = mpsc::unbounded_channel();
        let (popup_action_tx, popup_action_rx) = mpsc::unbounded_channel();
        let (component_action_tx, component_action_rx) = mpsc::unbounded_channel();

        // run multiple tasks in parallel, and wait for all of them to finish.
        // the tasks are:
        // - the audio state store
        // - ...
        // - the action dispatcher
        tokio::try_join!(
            // the audio state store
            self.audio
                .main_loop(daemon.clone(), audio_action_rx, interrupt_rx.resubscribe()),
            // the search state store
            self.search
                .main_loop(daemon.clone(), search_action_rx, interrupt_rx.resubscribe()),
            // the library state store
            self.library.main_loop(
                daemon.clone(),
                library_action_rx,
                interrupt_rx.resubscribe()
            ),
            // the view store
            self.view
                .main_loop(view_action_rx, interrupt_rx.resubscribe()),
            // the popup store
            self.popup
                .main_loop(popup_action_rx, interrupt_rx.resubscribe()),
            // the active component store
            self.component
                .main_loop(component_action_rx, interrupt_rx.resubscribe()),
            // the action dispatcher
            Self::action_dispatcher(
                terminator,
                action_rx,
                Senders {
                    audio: audio_action_tx,
                    search: search_action_tx,
                    library: library_action_tx,
                    view: view_action_tx,
                    popup: popup_action_tx,
                    component: component_action_tx,
                },
            ),
        )?;

        Ok(interrupt_rx.recv().await?)
    }

    async fn action_dispatcher(
        mut terminator: Terminator,
        mut action_rx: UnboundedReceiver<Action>,
        senders: Senders,
    ) -> anyhow::Result<()> {
        while let Some(action) = action_rx.recv().await {
            match action {
                Action::Audio(action) => {
                    senders.audio.send(action)?;
                }
                Action::Search(query) => {
                    senders.search.send(query)?;
                }
                Action::General(action) => match action {
                    action::GeneralAction::Exit => {
                        let _ = terminator.terminate(Interrupted::UserInt);

                        break;
                    }
                },
                Action::Library(action) => {
                    senders.library.send(action)?;
                }
                Action::SetCurrentView(view) => {
                    senders.view.send(view)?;
                }
                Action::Popup(popup) => senders.popup.send(popup)?,
                Action::ActiveComponent(action) => {
                    senders.component.send(action)?;
                }
            }
        }

        Ok(())
    }
}