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 name_validator: None,
43 });
44 kinds.push(PlugKind {
45 name: UI_NAME,
46 doc: "常驻观测界面(eLog + eTimer 1s 快照;关窗即 stop)",
47 build: {
48 let shared = Arc::clone(shared);
49 Arc::new(move || -> Arc<dyn Plugin> { Arc::new(UiPlugin::new(Arc::clone(&shared))) })
50 },
51 build_named: None,
52 name_validator: None,
53 });
54 kinds
55}
56
57pub fn spawn_host_watcher(
61 engine: &Arc<MainEngine>,
62 shared: &Arc<UiShared>,
63) -> std::thread::JoinHandle<()> {
64 let engine = Arc::clone(engine);
65 let shared = Arc::clone(shared);
66 std::thread::spawn(move || {
67 while !shared.is_stop() {
68 std::thread::sleep(Duration::from_millis(120));
69 }
70 shared.log_line("[host] 窗口关闭,close_all …");
71 engine.close_all(Duration::from_secs(3));
72 shared.log_line("[host] 已收场");
73 })
74}
75
76struct StationApp {
78 shared: Arc<UiShared>,
79}
80
81impl eframe::App for StationApp {
82 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
83 crate::render_body(ctx, &self.shared);
85 ctx.request_repaint_after(Duration::from_secs(1));
86 }
87}
88
89#[derive(Debug, Clone)]
92pub struct RunOpts {
93 pub ready_marker: Option<std::path::PathBuf>,
95 pub dm_rt: bool,
97}
98
99impl Default for RunOpts {
100 fn default() -> Self {
103 Self {
104 ready_marker: None,
105 dm_rt: true,
106 }
107 }
108}
109
110impl RunOpts {
111 pub fn plain() -> Self {
113 Self::default()
114 }
115}
116
117pub fn run_station_ui(cfg: &StationConfig, opts: &RunOpts) -> Result<(), String> {
120 let engine = Arc::new(MainEngine::new(None));
121 let shared = Arc::new(UiShared::new());
122
123 let store = ek_cfg::Store::eco();
128 let boot = match &store {
129 Ok(s) => ek_station::boot_with_cfg(&engine, &ui_kinds(&shared, opts.dm_rt), cfg, Some(s)),
130 Err(e) => {
131 let _ = e; ek_station::boot(&engine, &ui_kinds(&shared, opts.dm_rt), cfg)
133 }
134 };
135 boot.map_err(|e| format!("boot: {e:?}"))?;
136 if let Err(e) = engine.start() {
141 engine.close_all(Duration::from_secs(3));
142 return Err(format!("start: {e:?}"));
143 }
144
145 if let Some(marker) = &opts.ready_marker {
146 use std::io::Write;
147 if let Ok(mut f) = std::fs::File::create(marker) {
148 let _ = f.write_all(crate::launch::READY_TEXT.as_bytes());
149 } else {
150 eprintln!("ek-ui: ready marker write failed: {}", marker.display());
151 }
152 }
153
154 let watcher = spawn_host_watcher(&engine, &shared);
155
156 let opts = eframe::NativeOptions {
157 viewport: egui::ViewportBuilder::default()
158 .with_inner_size([1280.0, 860.0])
159 .with_title("ek-station"),
160 ..Default::default()
161 };
162 let result = eframe::run_native(
164 "ek-station",
165 opts,
166 Box::new(|cc| {
167 crate::fonts::apply_cjk_fonts(&cc.egui_ctx); Ok(Box::new(StationApp {
169 shared: Arc::clone(&shared),
170 }) as Box<dyn eframe::App>)
171 }),
172 );
173 shared.request_stop();
174 let _ = watcher.join();
175 result.map_err(|e| format!("ui: {e:?}"))
176}