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> {
27 let mut kinds: Vec<PlugKind> = default_kinds()
28 .into_iter()
29 .filter(|k| k.name != "dm")
30 .collect();
31 kinds.push(PlugKind {
32 name: "dm",
33 doc: "数据管理器(KlineManager;real_time 更新门开,行情板/K线页取 eBarUpdate./eBar.)",
34 build: Arc::new(move || -> Arc<dyn Plugin> {
35 Arc::new(ek_dm::plugin::DmPlugin::new(DmConfig {
36 real_time: dm_rt,
37 ..DmConfig::minute("dm")
38 }))
39 }),
40 });
41 kinds.push(PlugKind {
42 name: crate::sim::SIM_NAME,
43 doc: "纸面网关(四口契约同 ek-ctp;eBar./eTick. 驱动 FIFO 撮合,成交上总线)",
44 build: std::sync::Arc::new(|| std::sync::Arc::new(crate::sim::SimGatewayPlugin::new())),
45 });
46 kinds.push(PlugKind {
47 name: UI_NAME,
48 doc: "常驻观测界面(eLog + eTimer 1s 快照;关窗即 stop)",
49 build: {
50 let shared = Arc::clone(shared);
51 Arc::new(move || -> Arc<dyn Plugin> { Arc::new(UiPlugin::new(Arc::clone(&shared))) })
52 },
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 ek_station::boot(&engine, &ui_kinds(&shared, opts.dm_rt), cfg)
124 .map_err(|e| format!("boot: {e:?}"))?;
125 engine.start().map_err(|e| format!("start: {e:?}"))?;
126
127 if let Some(marker) = &opts.ready_marker {
128 use std::io::Write;
129 if let Ok(mut f) = std::fs::File::create(marker) {
130 let _ = f.write_all(crate::launch::READY_TEXT.as_bytes());
131 } else {
132 eprintln!("ek-ui: ready marker write failed: {}", marker.display());
133 }
134 }
135
136 let watcher = spawn_host_watcher(&engine, &shared);
137
138 let opts = eframe::NativeOptions {
139 viewport: egui::ViewportBuilder::default()
140 .with_inner_size([1280.0, 860.0])
141 .with_title("ek-station"),
142 ..Default::default()
143 };
144 let result = eframe::run_native(
146 "ek-station",
147 opts,
148 Box::new(|cc| {
149 crate::fonts::apply_cjk_fonts(&cc.egui_ctx); Ok(Box::new(StationApp {
151 shared: Arc::clone(&shared),
152 }) as Box<dyn eframe::App>)
153 }),
154 );
155 shared.request_stop();
156 let _ = watcher.join();
157 result.map_err(|e| format!("ui: {e:?}"))
158}