Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Encapsulate application setup
Erik Kundt committed 2 years ago
commit f929fd8fe57f078b36857fe3c8ce6025896ae3d6
parent f1df2ea2a92557e8846fa2ab3cf45dc3d2bef492
2 files changed +62 -11
modified src/lib.rs
@@ -8,9 +8,15 @@ pub mod task;
pub mod terminal;
pub mod ui;

+
use std::fmt::Debug;
+

use anyhow::Result;

use serde::ser::{Serialize, SerializeStruct, Serializer};
+
use store::State;
+
use task::Interrupted;
+
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
+
use ui::{widget::Widget, Frontend};

/// An optional return value.
#[derive(Clone, Debug)]
@@ -97,3 +103,45 @@ impl<T> PageStack<T> {
        }
    }
}
+

+
/// A multi-producer, single-consumer message channel.
+
pub struct Channel<A> {
+
    pub tx: UnboundedSender<A>,
+
    pub rx: UnboundedReceiver<A>,
+
}
+

+
impl<A> Default for Channel<A> {
+
    fn default() -> Self {
+
        let (tx, rx) = mpsc::unbounded_channel();
+
        Self { tx: tx.clone(), rx }
+
    }
+
}
+

+
/// Initialize a `Store` with the `State` given and a `Frontend` with the `Widget` given,
+
/// and run their main loops concurrently. Connect them to the `Channel` and also to
+
/// an interrupt broadcast channel also initialized in this function.
+
pub async fn run<S, A, W, P>(channel: Channel<A>, state: S, root: W) -> Result<Option<P>>
+
where
+
    S: State<P, Action = A> + Clone + Debug + Send + Sync + 'static,
+
    W: Widget<State = S, Action = A>,
+
    P: Clone + Debug + Send + Sync + 'static,
+
{
+
    let (terminator, mut interrupt_rx) = task::create_termination();
+

+
    let (store, state_rx) = store::Store::<A, S, P>::new();
+
    let frontend = Frontend::<A>::new(channel.tx.clone());
+

+
    tokio::try_join!(
+
        store.main_loop(state, terminator, channel.rx, interrupt_rx.resubscribe()),
+
        frontend.main_loop(Some(root), state_rx, interrupt_rx.resubscribe()),
+
    )?;
+

+
    if let Ok(reason) = interrupt_rx.recv().await {
+
        match reason {
+
            Interrupted::User { payload } => Ok(payload),
+
            Interrupted::OsSignal => anyhow::bail!("exited because of an os sig int"),
+
        }
+
    } else {
+
        anyhow::bail!("exited because of an unexpected error");
+
    }
+
}
modified src/ui.rs
@@ -10,7 +10,8 @@ use std::fmt::Debug;
use std::time::Duration;

use tokio::sync::broadcast;
-
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
+
use tokio::sync::mpsc;
+
use tokio::sync::mpsc::UnboundedReceiver;

use crate::ui::widget::RenderProps;

@@ -34,16 +35,10 @@ pub struct Frontend<A> {

impl<A> Frontend<A> {
    /// Create a new `Frontend` storing the sending end of a message channel.
-
    pub fn new() -> (Self, UnboundedSender<A>, UnboundedReceiver<A>) {
-
        let (action_tx, action_rx) = mpsc::unbounded_channel();
-

-
        (
-
            Self {
-
                action_tx: action_tx.clone(),
-
            },
-
            action_tx,
-
            action_rx,
-
        )
+
    pub fn new(action_tx: mpsc::UnboundedSender<A>) -> Self {
+
        Self {
+
            action_tx: action_tx.clone(),
+
        }
    }

    /// By calling `main_loop`, the `Frontend` will wait for new messages being sent
@@ -115,4 +110,12 @@ impl<A> Frontend<A> {

        result
    }
+

+
    // pub fn action_tx(&self) -> UnboundedSender<A> {
+
    //     self.action_tx.clone()
+
    // }
+

+
    // pub fn action_rx(&self) -> UnboundedReceiver<A> {
+
    //     self.action_rx.clone()
+
    // }
}