Hi everyone !
I rework my current UI into ratatui-rs and since my app is async I store my components in Arc<tokio::sync::Mutex>
. So, since terminal.draw
is not sync method, I cannot do smth like this:
terminal.draw(|frame| {
// double mutable usage of terminal anyway will not let me do this, I added this for simplicity
if terminal.check_state(TerminalState::ModalOpened) {
modal.lock().await.render(frame, frame.area());
}
})?;
so currently I do this:
let modal = if terminal.check_state(TerminalState::ModalOpened) {
Some(modal.lock().await)
} else {
None
};
terminal.draw(|frame| {
if let Some(mut modal) = modal {
modal.render(frame, frame.area());
}
})?;
I wrap my MutexGuard
into Option
outside of terminal.draw
because the external scope is async:
let handle_render = || {
let terminal = terminal.clone();
let modal = modal.clone();
tokio::spawn(async move {
loop {
let mut terminal = terminal.lock().await;
if terminal.check_state(TerminalState::Exiting) {
break;
}
let modal = if terminal.check_state(TerminalState::ModalOpened) {
Some(modal.lock().await)
} else {
None
};
terminal.draw(|frame| {
if let Some(mut modal) = modal {
modal.render(frame, frame.area());
}
})?;
tokio::time::sleep(Duration::from_millis(10)).await;
}
ratatui::restore();
Ok(())
})
};
The full code:
#[derive(Default)]
pub struct UI2 {
sender: Option<Sender<HandlerOutput>>,
receiver: Option<Receiver<HandlerOutput>>,
}
impl Feature for UI2 {
fn set_broadcast_channel(
&mut self,
sender: Sender<HandlerOutput>,
receiver: Receiver<HandlerOutput>,
) {
self.sender = Some(sender);
self.receiver = Some(receiver);
}
fn get_tasks(&mut self) -> anyhow::Result<Vec<Task>> {
let mut receiver = self.receiver.as_mut().ok_or(FeatureError::ReceiverNotFound)?.clone();
let terminal = Arc::new(Mutex::new(TerminalWrapper::new()));
let modal = Arc::new(Mutex::new(Modal::default()));
let handle_input = || {
let terminal = terminal.clone();
let modal = modal.clone();
tokio::spawn(async move {
let mut reader = EventStream::new();
loop {
let next_event = reader.next().fuse();
tokio::select! {
Some(Ok(event)) = next_event => {
if let Event::Key(KeyEvent { code, .. }) = event {
match code {
KeyCode::Esc => {
terminal.lock().await.set_state(TerminalState::Exiting);
},
_ => {}
}
}
},
Ok(input) = receiver.recv() => {
match input {
HandlerOutput::SuccessMessage(_msg, _) => {},
HandlerOutput::TransferCharactersList(characters) => {
let data = characters
.into_iter()
.map(|c| vec![
"Name".to_string(),
c.guid.to_string(),
"Guid".to_string(),
c.name,
])
.collect::<Vec<_>>();
*modal.lock().await = Modal::default()
.set_title("SELECT CHARACTER")
.set_list(data);
terminal.lock().await.set_state(TerminalState::ModalOpened);
},
_ => {},
}
},
}
}
})
};
let handle_render = || {
let terminal = terminal.clone();
let modal = modal.clone();
tokio::spawn(async move {
loop {
let mut terminal = terminal.lock().await;
if terminal.check_state(TerminalState::Exiting) {
break;
}
let modal = if terminal.check_state(TerminalState::ModalOpened) {
Some(modal.lock().await)
} else {
None
};
terminal.draw(|frame| {
if let Some(mut modal) = modal {
modal.render(frame, frame.area());
}
})?;
tokio::time::sleep(Duration::from_millis(10)).await;
}
ratatui::restore();
Ok(())
})
};
Ok(vec![
handle_input(),
handle_render(),
])
}
}
struct TerminalWrapper {
_terminal: DefaultTerminal,
_state: TerminalState,
}
impl TerminalWrapper {
pub fn new() -> Self {
Self {
_terminal: ratatui::init(),
_state: TerminalState::Idle,
}
}
pub fn set_state(&mut self, new_state: TerminalState) {
self._state = new_state;
}
pub fn check_state(&mut self, state: TerminalState) -> bool {
self._state == state
}
pub fn draw<F>(&mut self, render_callback: F) -> anyhow::Result<()>
where
F: FnOnce(&mut Frame),
{
self._terminal.draw(render_callback)?;
Ok(())
}
}
#[derive(PartialEq)]
enum TerminalState {
Idle,
ModalOpened,
Exiting,
}
My question is: can my current approach with Option<MutexGuard>
be a good alternative for std::sync::Mutex
or better just to use std::sync::Mutex
for my components. Or probably it is possible to change smth in the code ?
Could you please give me the advices ?