use std::{
env,
sync::{Arc, OnceLock},
};
use super::{ClientError, Scope};
use eframe::egui;
use egui_plot::{Legend, Plot, PlotUi};
use interface::UniqueIdentifier;
use tokio::sync::broadcast;
const PLOT_SIZE: (f32, f32) = (600f32, 500f32);
const MAX_WINDOW_SIZE: (f32, f32) = (1200f32, 1000f32);
#[derive(Debug, thiserror::Error)]
pub enum GridScopeError {
#[error("failed to create the scope within the grid")]
Pin(#[from] ClientError),
}
pub type Result<T> = std::result::Result<T, GridScopeError>;
struct NodeScope {
indices: (usize, usize),
scope: Scope,
}
pub struct GridScope {
size: (usize, usize),
scopes: Vec<NodeScope>,
server_ip: String,
client_address: String,
n_sample: Option<usize>,
egui_ctx: Arc<OnceLock<egui::Context>>,
}
impl GridScope {
pub fn new(size: (usize, usize)) -> Self {
Self {
size,
scopes: vec![],
server_ip: env::var("SCOPE_SERVER_IP").unwrap_or(crate::SERVER_IP.into()),
client_address: crate::CLIENT_ADDRESS.into(),
n_sample: None,
egui_ctx: Arc::new(OnceLock::new()),
}
}
pub fn n_sample(mut self, n_sample: usize) -> Self {
self.n_sample = Some(n_sample);
self
}
pub fn server_ip<S: Into<String>>(mut self, server_ip: S) -> Self {
self.server_ip = server_ip.into();
self
}
pub fn client_address<S: Into<String>>(mut self, client_address: S) -> Self {
self.client_address = client_address.into();
self
}
fn window_size(&self) -> (f32, f32) {
let (rows, cols) = self.size;
let width = MAX_WINDOW_SIZE.0.min(PLOT_SIZE.0 * cols as f32) / cols as f32;
let height = MAX_WINDOW_SIZE.1.min(PLOT_SIZE.1 * rows as f32) / rows as f32;
(width * cols as f32, height * rows as f32)
}
pub fn pin<U>(mut self, indices: (usize, usize)) -> Result<Self>
where
U: UniqueIdentifier + 'static,
{
let (rows, cols) = self.size;
let (row, col) = indices;
assert!(
row < rows,
"The row index in the scopes grid must be less than {}",
rows
);
assert!(
col < cols,
"The columm index in the scopes grid must be less than {}",
cols
);
if let Some(node) = self.scopes.iter_mut().find(|node| node.indices == indices) {
node.scope.as_mut_signal::<U>()?;
} else {
self.scopes.push(NodeScope {
indices,
scope: Scope::new()
.server_ip(&self.server_ip)
.client_address(&self.client_address)
.signal::<U>()?,
});
}
Ok(self)
}
pub fn pin_with_legends<U>(
mut self,
indices: (usize, usize),
items: &[String],
rx: broadcast::Receiver<Vec<String>>,
) -> Result<Self>
where
U: UniqueIdentifier + 'static,
{
let (rows, cols) = self.size;
let (row, col) = indices;
assert!(
row < rows,
"The row index in the scopes grid must be less than {}",
rows
);
assert!(
col < cols,
"The columm index in the scopes grid must be less than {}",
cols
);
if let Some(node) = self.scopes.iter_mut().find(|node| node.indices == indices) {
node.scope.as_mut_signal::<U>()?;
} else {
self.scopes.push(NodeScope {
indices,
scope: Scope::new()
.server_ip(&self.server_ip)
.client_address(&self.client_address)
.rx(rx)
.signal_with_legends::<U>(items.to_vec())?,
});
}
Ok(self)
}
pub fn egui_ctx(&self) -> Arc<OnceLock<egui::Context>> {
self.egui_ctx.clone()
}
pub fn show(mut self) {
for node in self.scopes.iter_mut() {
node.scope.n_sample = self.n_sample.clone();
let monitor = node.scope.monitor.take().unwrap();
tokio::spawn(async move {
match monitor.join().await {
Ok(_) => println!("*** data streaming complete ***"),
Err(e) => println!("!!! data streaming error with {:?} !!!", e),
}
});
}
let native_options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size(egui::Vec2::from(self.window_size())),
..Default::default()
};
let _ = eframe::run_native(
"GMT DOS Actors Scope",
native_options,
Box::new(|cc| {
for node in self.scopes.iter_mut() {
let scope = &mut node.scope;
scope.run(cc.egui_ctx.clone());
}
Ok(Box::new(self))
}),
);
}
}
impl eframe::App for GridScope {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let _ = self.egui_ctx.set(ctx.clone());
egui::CentralPanel::default().show(ctx, |ui| {
let (rows, cols) = self.size;
let style = ui.style_mut();
style.spacing.item_spacing = egui::vec2(0.0, 0.0);
let available_size = ui.available_size();
let plot_width = available_size.x / cols as f32;
let plot_height = available_size.y / rows as f32;
for row in 0..rows {
ui.horizontal(|ui| {
for col in 0..cols {
self.scopes
.iter_mut()
.find(|node| node.indices == (row, col))
.map(|node| {
let plot = Plot::new(format!("Scope_{}_{}", row, col))
.legend(Legend::default().position(egui_plot::Corner::LeftTop))
.width(plot_width)
.height(plot_height)
.set_margin_fraction(egui::Vec2::from((0.05, 0.05)))
.link_axis("x_axis_link", [true, false]);
plot.show(ui, |plot_ui: &mut PlotUi| {
for signal in &mut node.scope.signals {
signal.plot_ui(plot_ui, node.scope.n_sample)
}
});
});
}
});
}
});
for NodeScope { scope, .. } in &mut self.scopes {
if let Some(rx) = scope.rx.as_mut()
&& let Ok(items) = rx.try_recv()
{
for signal in &mut scope.signals {
signal.set_hidden(items.clone());
}
}
}
}
}