[Feat] Split Daemon & TUI
This commit is contained in:
parent
f82500ea7d
commit
36a70e82a0
35 changed files with 970 additions and 239 deletions
|
|
@ -3,16 +3,26 @@ name = "iota-cli"
|
|||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
legacy-commands = [
|
||||
"dep:iota-logger",
|
||||
"dep:iota-storage",
|
||||
"dep:iota-util",
|
||||
"dep:mtp",
|
||||
"dep:omikron-connector",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
iota-logger = { path = "../iota-logger" }
|
||||
iota-logger = { path = "../iota-logger", optional = true }
|
||||
iota-state = { path = "../iota-state" }
|
||||
iota-storage = { path = "../iota-storage" }
|
||||
iota-storage = { path = "../iota-storage", optional = true }
|
||||
iota-terms = { path = "../iota-terms" }
|
||||
iota-util = { path = "../iota-util" }
|
||||
omikron-connector = { path = "../omikron-connector" }
|
||||
iota-util = { path = "../iota-util", optional = true }
|
||||
iota-ipc = { path = "../iota-ipc" }
|
||||
omikron-connector = { path = "../omikron-connector", optional = true }
|
||||
|
||||
mtp = { git = "https://git.methanium.net/Methanium/mtp.git", optional = true }
|
||||
|
||||
mtp = { git = "https://git.methanium.net/Methanium/mtp.git" }
|
||||
|
||||
actix-web = { version = "4", features = ["rustls-0_23"] }
|
||||
actix-web-actors = "4"
|
||||
|
|
@ -56,7 +66,6 @@ sysinfo = "0.38.3"
|
|||
tokio = { version = "1.50.0", features = ["full"] }
|
||||
tokio-tungstenite = { version = "*", features = ["native-tls"] }
|
||||
tungstenite = "*"
|
||||
uuid = { version = "*", features = ["v4"] }
|
||||
walkdir = "2.5.0"
|
||||
warp = "*"
|
||||
x448 = { version = "*" }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use iota_logger::{log, log_command, log_cv};
|
||||
#[cfg(feature = "legacy-commands")]
|
||||
use iota_logger::{log, log_cv};
|
||||
#[cfg(feature = "legacy-commands")]
|
||||
use iota_state::{ACTIVE_TASKS, RELOAD, SHUTDOWN};
|
||||
#[cfg(feature = "legacy-commands")]
|
||||
use iota_storage::users::{user_manager, user_profile::UserProfile};
|
||||
#[cfg(feature = "legacy-commands")]
|
||||
use iota_storage::util::config_util::modify_config;
|
||||
#[cfg(feature = "legacy-commands")]
|
||||
use iota_util::file_util;
|
||||
#[cfg(feature = "legacy-commands")]
|
||||
use mtp::codec::{CommunicationType, CommunicationValue};
|
||||
#[cfg(feature = "legacy-commands")]
|
||||
use omikron_connector::omikron_connection::OMIKRON_CONNECTION;
|
||||
use ratatui::{
|
||||
Frame,
|
||||
|
|
@ -13,7 +20,6 @@ use ratatui::{
|
|||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use std::{
|
||||
any::Any,
|
||||
|
|
@ -25,11 +31,12 @@ use tokio::time::Instant;
|
|||
use crate::{
|
||||
elements::elements::{Element, InteractableElement, JoinableElement},
|
||||
interaction_result::InteractionResult,
|
||||
ui::FPS,
|
||||
ipc_client::IpcClient,
|
||||
util::borders::draw_block_joins,
|
||||
};
|
||||
|
||||
pub struct ConsoleCard {
|
||||
ipc: Arc<IpcClient>,
|
||||
focused: bool,
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
|
|
@ -44,8 +51,9 @@ pub struct ConsoleCard {
|
|||
}
|
||||
|
||||
impl ConsoleCard {
|
||||
pub fn new(title: &str, content: &str) -> Self {
|
||||
pub fn new(title: &str, content: &str, ipc: Arc<IpcClient>) -> Self {
|
||||
ConsoleCard {
|
||||
ipc,
|
||||
focused: false,
|
||||
title: title.to_string(),
|
||||
content: content.to_string(),
|
||||
|
|
@ -290,22 +298,17 @@ impl InteractableElement for ConsoleCard {
|
|||
match key.code {
|
||||
KeyCode::Enter => {
|
||||
if self.content.is_empty() {
|
||||
log!("");
|
||||
return InteractionResult::Handled;
|
||||
}
|
||||
|
||||
let command = self.content.clone();
|
||||
let id = Uuid::new_v4();
|
||||
let id = id.to_string();
|
||||
let id = id.split_at(8).0;
|
||||
let task_id = format!("command_{}_{}", command, id);
|
||||
ACTIVE_TASKS.insert(task_id.clone());
|
||||
|
||||
log_command!("{}", command);
|
||||
|
||||
let ipc = self.ipc.clone();
|
||||
let seq = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as u64;
|
||||
tokio::spawn(async move {
|
||||
run_command(&command).await;
|
||||
ACTIVE_TASKS.remove(&task_id);
|
||||
let _ = ipc.send_command(seq, command).await;
|
||||
});
|
||||
|
||||
self.content.clear();
|
||||
|
|
@ -379,6 +382,7 @@ impl InteractableElement for ConsoleCard {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "legacy-commands")]
|
||||
pub async fn run_command(command: &str) {
|
||||
let parts = command.split(" ").collect::<Vec<&str>>();
|
||||
|
||||
|
|
@ -503,6 +507,7 @@ pub async fn run_command(command: &str) {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "legacy-commands")]
|
||||
pub async fn ping(time: u64) {
|
||||
let conn = OMIKRON_CONNECTION.clone();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use crossterm::event::KeyEvent;
|
||||
use iota_state::APP_STATE;
|
||||
use iota_state::ClientState;
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::Rect,
|
||||
|
|
@ -34,11 +34,29 @@ impl GRAPHS {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_graph(&self) -> Vec<(f64, f64)> {
|
||||
pub fn get_graph(&self, state: &ClientState) -> Vec<(f64, f64)> {
|
||||
match self {
|
||||
GRAPHS::Ram => APP_STATE.lock().unwrap().with_width(28).ram.clone(),
|
||||
GRAPHS::Cpu => APP_STATE.lock().unwrap().with_width(28).cpu.clone(),
|
||||
GRAPHS::Ping => APP_STATE.lock().unwrap().with_width(28).ping.clone(),
|
||||
GRAPHS::Ram => state
|
||||
.app
|
||||
.lock()
|
||||
.unwrap_or_else(|error| error.into_inner())
|
||||
.with_width(28)
|
||||
.ram
|
||||
.clone(),
|
||||
GRAPHS::Cpu => state
|
||||
.app
|
||||
.lock()
|
||||
.unwrap_or_else(|error| error.into_inner())
|
||||
.with_width(28)
|
||||
.cpu
|
||||
.clone(),
|
||||
GRAPHS::Ping => state
|
||||
.app
|
||||
.lock()
|
||||
.unwrap_or_else(|error| error.into_inner())
|
||||
.with_width(28)
|
||||
.ping
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,6 +72,7 @@ impl GRAPHS {
|
|||
#[allow(unused)]
|
||||
pub struct GraphCard {
|
||||
ui: Arc<UI>,
|
||||
state: ClientState,
|
||||
graph_type: GRAPHS,
|
||||
|
||||
focused: bool,
|
||||
|
|
@ -66,9 +85,10 @@ pub struct GraphCard {
|
|||
}
|
||||
|
||||
impl GraphCard {
|
||||
pub fn new(ui: Arc<UI>, graph_type: GRAPHS, title: String) -> Self {
|
||||
pub fn new(ui: Arc<UI>, state: ClientState, graph_type: GRAPHS, title: String) -> Self {
|
||||
Self {
|
||||
ui,
|
||||
state,
|
||||
graph_type,
|
||||
focused: false,
|
||||
title,
|
||||
|
|
@ -93,7 +113,7 @@ impl Element for GraphCard {
|
|||
|
||||
fn render(&self, f: &mut Frame, r: Rect) {
|
||||
if self.open {
|
||||
let graph = self.graph_type.get_graph();
|
||||
let graph = self.graph_type.get_graph(&self.state);
|
||||
let unit = self.graph_type.get_unit();
|
||||
let min_x = graph.first().map(|(x, _)| *x).unwrap_or(0.0);
|
||||
let max_x = graph.last().map(|(x, _)| *x).unwrap_or(100.0);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use crate::app_state::APP_STATE;
|
||||
use crate::elements::elements::{Element, InteractableElement, JoinableElement};
|
||||
use crate::interaction_result::InteractionResult;
|
||||
use crate::util::borders::draw_block_joins;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use iota_logger::PrintType;
|
||||
use iota_state::{ClientState, UiLogEntry};
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::Rect,
|
||||
|
|
@ -12,60 +11,9 @@ use ratatui::{
|
|||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
use std::any::Any;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UiLogEntry {
|
||||
pub timestamp_ms: u128,
|
||||
pub sender: PrintType,
|
||||
pub message: String,
|
||||
pub is_error: bool,
|
||||
}
|
||||
|
||||
impl UiLogEntry {
|
||||
pub fn format_timestamp(&self) -> String {
|
||||
let secs = (self.timestamp_ms / 1000) as i64;
|
||||
let hours = (secs / 3600) % 24;
|
||||
let minutes = (secs / 60) % 60;
|
||||
let seconds = secs % 60;
|
||||
format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LogEntry> for UiLogEntry {
|
||||
fn from(entry: LogEntry) -> Self {
|
||||
Self {
|
||||
timestamp_ms: entry.timestamp_ms,
|
||||
sender: entry.sender,
|
||||
message: entry.message,
|
||||
is_error: entry.is_error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LogEntry {
|
||||
pub timestamp_ms: u128,
|
||||
pub sender: PrintType,
|
||||
pub message: String,
|
||||
pub is_error: bool,
|
||||
}
|
||||
|
||||
impl LogEntry {
|
||||
pub fn new(sender: PrintType, message: String, is_error: bool) -> Self {
|
||||
Self {
|
||||
timestamp_ms: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis(),
|
||||
sender,
|
||||
message,
|
||||
is_error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LogCard {
|
||||
state: ClientState,
|
||||
focused: bool,
|
||||
selected: bool,
|
||||
scroll_offset: usize,
|
||||
|
|
@ -76,8 +24,9 @@ pub struct LogCard {
|
|||
}
|
||||
|
||||
impl LogCard {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(state: ClientState) -> Self {
|
||||
Self {
|
||||
state,
|
||||
focused: false,
|
||||
selected: false,
|
||||
scroll_offset: 0,
|
||||
|
|
@ -89,21 +38,17 @@ impl LogCard {
|
|||
}
|
||||
|
||||
fn get_logs(&self) -> Vec<UiLogEntry> {
|
||||
let state = APP_STATE.lock().unwrap();
|
||||
let state = self
|
||||
.state
|
||||
.app
|
||||
.lock()
|
||||
.unwrap_or_else(|error| error.into_inner());
|
||||
state
|
||||
.get_logs()
|
||||
.iter()
|
||||
.map(|e| UiLogEntry {
|
||||
timestamp_ms: e.timestamp_ms,
|
||||
sender: match e.sender.as_str() {
|
||||
"Call" => PrintType::Call,
|
||||
"Client" => PrintType::Client,
|
||||
"Iota" => PrintType::Iota,
|
||||
"Omikron" => PrintType::Omikron,
|
||||
"Omega" => PrintType::Omega,
|
||||
"Command" => PrintType::Command,
|
||||
_ => PrintType::General,
|
||||
},
|
||||
sender: e.sender.clone(),
|
||||
message: e.message.clone(),
|
||||
is_error: e.is_error,
|
||||
})
|
||||
|
|
@ -196,12 +141,24 @@ impl LogCard {
|
|||
line.push_str(×tamp);
|
||||
}
|
||||
|
||||
result.push((line, entry.sender.prefix_color(), entry.is_error));
|
||||
result.push((line, Self::sender_color(&entry.sender), entry.is_error));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn sender_color(sender: &str) -> Color {
|
||||
match sender {
|
||||
"Call" => Color::Magenta,
|
||||
"Client" => Color::Green,
|
||||
"Iota" => Color::Yellow,
|
||||
"Omikron" => Color::Blue,
|
||||
"Omega" => Color::Cyan,
|
||||
"Command" => Color::LightGreen,
|
||||
_ => Color::LightCyan,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_all_lines(
|
||||
&self,
|
||||
entries: Vec<UiLogEntry>,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
use crate::ui::UI;
|
||||
use crossterm::event::{Event, KeyEvent, KeyEventKind, KeyModifiers, poll, read};
|
||||
use iota_state::{RELOAD, SHUTDOWN, UNIQUE};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn setup_input_handler(ui: Arc<UI>) {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
if *SHUTDOWN.read().await {
|
||||
if ui.is_shutdown() {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +25,6 @@ pub fn setup_input_handler(ui: Arc<UI>) {
|
|||
match event_result {
|
||||
Ok(Some(key_event)) => {
|
||||
handle_input(key_event, ui.clone()).await;
|
||||
UNIQUE.store(true, Ordering::Relaxed);
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
|
|
@ -43,11 +40,11 @@ pub async fn handle_input(key: KeyEvent, ui: Arc<UI>) {
|
|||
match (key.code, key.modifiers) {
|
||||
(crossterm::event::KeyCode::Char('q'), KeyModifiers::CONTROL)
|
||||
| (crossterm::event::KeyCode::Char('c'), KeyModifiers::CONTROL) => {
|
||||
*SHUTDOWN.write().await = true;
|
||||
ui.request_shutdown();
|
||||
}
|
||||
(crossterm::event::KeyCode::Char('r'), KeyModifiers::CONTROL) => {
|
||||
*RELOAD.write().await = true;
|
||||
*SHUTDOWN.write().await = true;
|
||||
let _ = ui.send_restart().await;
|
||||
ui.request_shutdown();
|
||||
}
|
||||
_ => {
|
||||
ui.handle_input(key).await;
|
||||
|
|
|
|||
83
iota-cli/src/ipc_client.rs
Normal file
83
iota-cli/src/ipc_client.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use iota_ipc::{ClientMessage, DaemonMessage, read_msg, write_msg};
|
||||
use iota_state::{ClientState, UiLogEntry};
|
||||
use std::io::Result;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::UnixStream;
|
||||
use tokio::net::unix::OwnedWriteHalf;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/* The TUI owns this cache. IPC updates replace daemon snapshots and append
|
||||
* logs, so rendering never reaches into daemon-owned storage or connections. */
|
||||
pub struct IpcClient {
|
||||
state: ClientState,
|
||||
writer: Mutex<OwnedWriteHalf>,
|
||||
}
|
||||
|
||||
impl IpcClient {
|
||||
pub async fn connect(path: impl AsRef<Path>) -> Result<Arc<Self>> {
|
||||
let stream = UnixStream::connect(path).await?;
|
||||
let (mut reader, writer) = stream.into_split();
|
||||
let client = Arc::new(Self {
|
||||
state: ClientState::new(),
|
||||
writer: Mutex::new(writer),
|
||||
});
|
||||
let reader_client = client.clone();
|
||||
tokio::spawn(async move {
|
||||
while let Ok(message) = read_msg::<_, DaemonMessage>(&mut reader).await {
|
||||
reader_client.apply(message).await;
|
||||
}
|
||||
});
|
||||
client.send(ClientMessage::Subscribe).await?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn state(&self) -> ClientState {
|
||||
self.state.clone()
|
||||
}
|
||||
|
||||
pub async fn send_command(&self, seq: u64, line: String) -> Result<()> {
|
||||
self.send(ClientMessage::Command { seq, line }).await
|
||||
}
|
||||
|
||||
async fn send(&self, message: ClientMessage) -> Result<()> {
|
||||
let mut writer = self.writer.lock().await;
|
||||
write_msg(&mut *writer, &message).await
|
||||
}
|
||||
|
||||
async fn apply(&self, message: DaemonMessage) {
|
||||
let mut state = self
|
||||
.state
|
||||
.app
|
||||
.lock()
|
||||
.unwrap_or_else(|error| error.into_inner());
|
||||
match message {
|
||||
DaemonMessage::LogEntry(entry) => state.push_log(UiLogEntry {
|
||||
timestamp_ms: entry.timestamp_ms,
|
||||
sender: entry.sender,
|
||||
message: entry.message,
|
||||
is_error: entry.is_error,
|
||||
}),
|
||||
DaemonMessage::StateUpdate(snapshot) => {
|
||||
state.cpu = snapshot.cpu;
|
||||
state.ram = snapshot.ram;
|
||||
state.ping = snapshot.ping;
|
||||
state.net_up = snapshot.net_up;
|
||||
state.net_down = snapshot.net_down;
|
||||
state.sys_info = snapshot.sys_info;
|
||||
}
|
||||
DaemonMessage::CommandResult {
|
||||
success, message, ..
|
||||
} => state.push_log(UiLogEntry {
|
||||
timestamp_ms: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis(),
|
||||
sender: "Command".into(),
|
||||
message,
|
||||
is_error: !success,
|
||||
}),
|
||||
DaemonMessage::Pong { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,5 +18,6 @@ pub mod util {
|
|||
}
|
||||
pub mod app_state;
|
||||
pub mod input_handler;
|
||||
pub mod ipc_client;
|
||||
pub mod interaction_result;
|
||||
pub mod ui;
|
||||
|
|
|
|||
|
|
@ -36,22 +36,23 @@ impl MainScreen {
|
|||
vec![Some(1), Some(4)],
|
||||
];
|
||||
|
||||
let mut log_card = LogCard::new();
|
||||
let state = ui.client_state();
|
||||
let mut log_card = LogCard::new(state.clone());
|
||||
log_card.set_borders(Borders::TOP.union(Borders::RIGHT).union(Borders::LEFT));
|
||||
let mut console_card = ConsoleCard::new("Console", "");
|
||||
let mut console_card = ConsoleCard::new("Console", "", ui.ipc());
|
||||
console_card.set_joins(Borders::TOP);
|
||||
|
||||
elements.push(Box::new(log_card));
|
||||
elements.push(Box::new(console_card));
|
||||
|
||||
let mut ram_graph = GraphCard::new(ui.clone(), GRAPHS::Ram, "RAM".into());
|
||||
let mut ram_graph = GraphCard::new(ui.clone(), state.clone(), GRAPHS::Ram, "RAM".into());
|
||||
ram_graph.set_borders(Borders::TOP.union(Borders::LEFT).union(Borders::RIGHT));
|
||||
elements.push(Box::new(ram_graph));
|
||||
let mut cpu_graph = GraphCard::new(ui.clone(), GRAPHS::Cpu, "CPU".into());
|
||||
let mut cpu_graph = GraphCard::new(ui.clone(), state.clone(), GRAPHS::Cpu, "CPU".into());
|
||||
cpu_graph.set_borders(Borders::TOP.union(Borders::LEFT).union(Borders::RIGHT));
|
||||
cpu_graph.set_joins(Borders::TOP);
|
||||
elements.push(Box::new(cpu_graph));
|
||||
let mut ping_graph = GraphCard::new(ui.clone(), GRAPHS::Ping, "Ping".into());
|
||||
let mut ping_graph = GraphCard::new(ui.clone(), state, GRAPHS::Ping, "Ping".into());
|
||||
ping_graph.set_joins(Borders::TOP);
|
||||
elements.push(Box::new(ping_graph));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
use crate::{
|
||||
input_handler::setup_input_handler, interaction_result::InteractionResult,
|
||||
screens::screens::Screen,
|
||||
ipc_client::IpcClient, screens::screens::Screen,
|
||||
};
|
||||
use crossterm::event::KeyEvent;
|
||||
use iota_state::{ACTIVE_TASKS, SHUTDOWN, UNIQUE};
|
||||
use once_cell::sync::Lazy;
|
||||
use ratatui::{Terminal, backend::CrosstermBackend, init};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
io::Stdout,
|
||||
sync::{Arc, Mutex, atomic::Ordering},
|
||||
sync::{
|
||||
Arc, Mutex,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::{sync::RwLock, time::Instant};
|
||||
|
|
@ -19,14 +21,15 @@ use tokio::{sync::RwLock, time::Instant};
|
|||
pub static FPS: Lazy<RwLock<(f64, f64)>> = Lazy::new(|| RwLock::new((0.0, 0.0)));
|
||||
|
||||
pub struct UI {
|
||||
ipc: Arc<IpcClient>,
|
||||
shutdown: AtomicBool,
|
||||
pub terminal: Arc<Mutex<Terminal<CrosstermBackend<Stdout>>>>,
|
||||
screen_stack: Arc<RwLock<Vec<Box<dyn Screen>>>>,
|
||||
}
|
||||
|
||||
pub fn start_tui() -> Arc<UI> {
|
||||
let ui = Arc::new(UI::new());
|
||||
pub fn start_tui(ipc: Arc<IpcClient>) -> Arc<UI> {
|
||||
let ui = Arc::new(UI::new(ipc));
|
||||
let uic = ui.clone();
|
||||
ACTIVE_TASKS.insert("UI Renderer".to_string());
|
||||
tokio::spawn(async move {
|
||||
let mut last_render = Instant::now();
|
||||
|
||||
|
|
@ -39,11 +42,11 @@ pub fn start_tui() -> Arc<UI> {
|
|||
let mut skipped = 0;
|
||||
|
||||
loop {
|
||||
if *SHUTDOWN.read().await {
|
||||
if uic.is_shutdown() {
|
||||
break;
|
||||
}
|
||||
|
||||
if skipped > 5 || UNIQUE.load(Ordering::Relaxed) {
|
||||
if skipped > 5 {
|
||||
uic.render().await;
|
||||
|
||||
skip_samples.push_back(skipped);
|
||||
|
|
@ -88,27 +91,47 @@ pub fn start_tui() -> Arc<UI> {
|
|||
*FPS.write().await = (avg_fps, avg_skips_percentage);
|
||||
|
||||
last_render = Instant::now();
|
||||
UNIQUE.store(false, Ordering::Relaxed);
|
||||
} else {
|
||||
skipped += 1;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(16)).await;
|
||||
}
|
||||
ACTIVE_TASKS.remove("UI Renderer");
|
||||
ratatui::restore();
|
||||
});
|
||||
setup_input_handler(ui.clone());
|
||||
ui
|
||||
}
|
||||
impl UI {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(ipc: Arc<IpcClient>) -> Self {
|
||||
let terminal = init();
|
||||
Self {
|
||||
ipc,
|
||||
shutdown: AtomicBool::new(false),
|
||||
terminal: Arc::new(Mutex::new(terminal)),
|
||||
screen_stack: Arc::new(RwLock::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ipc(&self) -> Arc<IpcClient> {
|
||||
self.ipc.clone()
|
||||
}
|
||||
|
||||
pub fn client_state(&self) -> iota_state::ClientState {
|
||||
self.ipc.state()
|
||||
}
|
||||
|
||||
pub fn is_shutdown(&self) -> bool {
|
||||
self.shutdown.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn request_shutdown(&self) {
|
||||
self.shutdown.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub async fn send_restart(&self) -> std::io::Result<()> {
|
||||
self.ipc.send_command(0, "restart".into()).await
|
||||
}
|
||||
|
||||
pub async fn set_screen(&self, screen: Box<dyn Screen>) {
|
||||
self.screen_stack.write().await.push(screen);
|
||||
}
|
||||
|
|
@ -140,7 +163,7 @@ impl UI {
|
|||
stack.pop();
|
||||
|
||||
if stack.is_empty() {
|
||||
*SHUTDOWN.write().await = true;
|
||||
self.request_shutdown();
|
||||
}
|
||||
}
|
||||
InteractionResult::Handled => {}
|
||||
|
|
|
|||
Loading…
Reference in a new issue