1use std::sync::Arc;
10use std::time::Duration;
11
12use ek_core::main_engine::MainEngine;
13use ek_core::plugin::Plugin;
14use ek_dm::plugin::DmConfig;
15use ek_station::{PlugKind, StationConfig, default_kinds};
16
17use crate::plugin::{UI_NAME, UiPlugin};
18use crate::state::UiShared;
19
20pub fn ui_kinds(shared: &Arc<UiShared>, dm_rt: bool) -> Vec<PlugKind> {
28 let mut kinds: Vec<PlugKind> = default_kinds()
29 .into_iter()
30 .filter(|k| k.name != "dm")
31 .collect();
32 kinds.push(PlugKind {
33 name: "dm",
34 doc: "数据管理器(KlineManager;real_time 更新门开,行情板/K线页取 eBarUpdate./eBar.)",
35 build: Arc::new(move || -> Arc<dyn Plugin> {
36 Arc::new(ek_dm::plugin::DmPlugin::new(DmConfig {
37 real_time: dm_rt,
38 ..DmConfig::minute("dm")
39 }))
40 }),
41 build_named: None,
42 });
43 kinds.push(PlugKind {
44 name: UI_NAME,
45 doc: "常驻观测界面(eLog + eTimer 1s 快照;关窗即 stop)",
46 build: {
47 let shared = Arc::clone(shared);
48 Arc::new(move || -> Arc<dyn Plugin> { Arc::new(UiPlugin::new(Arc::clone(&shared))) })
49 },
50 build_named: None,
51 });
52 kinds
53}
54
55pub fn spawn_host_watcher(
59 engine: &Arc<MainEngine>,
60 shared: &Arc<UiShared>,
61) -> std::thread::JoinHandle<()> {
62 let engine = Arc::clone(engine);
63 let shared = Arc::clone(shared);
64 std::thread::spawn(move || {
65 while !shared.is_stop() {
66 std::thread::sleep(Duration::from_millis(120));
67 }
68 shared.log_line("[host] 窗口关闭,close_all …");
69 engine.close_all(Duration::from_secs(3));
70 shared.log_line("[host] 已收场");
71 })
72}
73
74struct StationApp {
76 shared: Arc<UiShared>,
77}
78
79impl eframe::App for StationApp {
80 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
81 crate::render_body(ctx, &self.shared);
83 ctx.request_repaint_after(Duration::from_secs(1));
84 }
85}
86
87#[derive(Debug, Clone)]
90pub struct RunOpts {
91 pub ready_marker: Option<std::path::PathBuf>,
93 pub dm_rt: bool,
95}
96
97impl Default for RunOpts {
98 fn default() -> Self {
101 Self {
102 ready_marker: None,
103 dm_rt: true,
104 }
105 }
106}
107
108impl RunOpts {
109 pub fn plain() -> Self {
111 Self::default()
112 }
113}
114
115pub fn run_station_ui(cfg: &StationConfig, opts: &RunOpts) -> Result<(), String> {
118 let engine = Arc::new(MainEngine::new(None));
119 let shared = Arc::new(UiShared::new());
120
121 ek_station::boot(&engine, &ui_kinds(&shared, opts.dm_rt), cfg)
122 .map_err(|e| format!("boot: {e:?}"))?;
123 if let Err(e) = engine.start() {
128 engine.close_all(Duration::from_secs(3));
129 return Err(format!("start: {e:?}"));
130 }
131
132 if let Some(marker) = &opts.ready_marker {
133 use std::io::Write;
134 if let Ok(mut f) = std::fs::File::create(marker) {
135 let _ = f.write_all(crate::launch::READY_TEXT.as_bytes());
136 } else {
137 eprintln!("ek-ui: ready marker write failed: {}", marker.display());
138 }
139 }
140
141 let watcher = spawn_host_watcher(&engine, &shared);
142
143 let opts = eframe::NativeOptions {
144 viewport: egui::ViewportBuilder::default()
145 .with_inner_size([1280.0, 860.0])
146 .with_title("ek-station"),
147 ..Default::default()
148 };
149 let result = eframe::run_native(
151 "ek-station",
152 opts,
153 Box::new(|cc| {
154 crate::fonts::apply_cjk_fonts(&cc.egui_ctx); Ok(Box::new(StationApp {
156 shared: Arc::clone(&shared),
157 }) as Box<dyn eframe::App>)
158 }),
159 );
160 shared.request_stop();
161 let _ = watcher.join();
162 result.map_err(|e| format!("ui: {e:?}"))
163}