Skip to main content

ek_ui/
faces.rs

1//! faces —— 插件界面挂载表(「插件即 App」波 P4)。
2//!
3//! 挂载 = **装配名单驱动**(判据 U-W1:装配了谁挂谁的面,没装配的不出现):
4//! 插件自带界面住在插件 crate(feature `ui` 门控,daemon 根本不编译),本模块
5//! 只做**派发 + 宿主桥**:
6//! - [`CfgBridge`] = [`CfgHost`] 的 ek-ui 实现(写档 = ek-cfg 托底根;显形 =
7//!   UiShared 日志行)—— 插件面经它交回宿主,插件本体永不碰文件与凭据库;
8//! - 派发按注册名走 `MainEngine::get_typed`(类型安全取壳;tq/ctp 的注册名是
9//!   派生网关名,所以逐类型试取,命中即定面);dm/oms/tq 吃各自 `ui_face::render`,
10//!   ctp(自带键位、无 schema)走 [`crate::pluginwin::render_window`] 视图驱动形态。
11
12use std::sync::Arc;
13
14use ek_cfg::LoadOutcome;
15use ek_cfg::Store as CfgStore;
16use ek_core::cfg_face::CfgHost;
17use ek_core::main_engine::MainEngine;
18use ek_core::plugin::Plugin;
19use ek_core::section::SectionMap;
20
21use crate::pluginwin::WinState;
22use crate::state::UiShared;
23
24/// 插件面出口的稳定注册名(0.3.19 惯例:消费方零字面量,提供方导出口)。
25/// ek-tq / ek-ctp 的注册名是派生网关名,故此处只作缺省参考,派发按类型试取。
26const NS_DM: &str = "dm";
27const NS_OMS: &str = "oms";
28
29/// 命中面 kinds(挂载表与派发共用同一张判定)。
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum FaceKind {
32    /// dm 自带面。
33    Dm,
34    /// oms 自带面。
35    Oms,
36    /// tq 自带面。
37    Tq,
38    /// ctp 走通用窗口(视图驱动)。
39    Ctp,
40}
41
42/// 宿主桥:插件面的 `cfg_save`/`cfg_load`/`log` 落点(写档 + 日志显形)。
43pub struct CfgBridge {
44    /// ek-cfg 托底根(`Store::eco()` 成功时才有;失败 = 界面只显形不落盘)。
45    pub cfg: Option<CfgStore>,
46    /// 宿主日志面(窗口右下日志区)。
47    pub shared: Arc<UiShared>,
48}
49
50impl CfgBridge {
51    /// 用当前托底根 + 宿主日志面建桥(`eco()` 失败 = 只显形不落盘)。
52    #[must_use]
53    pub fn new(shared: &Arc<UiShared>) -> Self {
54        Self {
55            cfg: CfgStore::eco().ok(),
56            shared: Arc::clone(shared),
57        }
58    }
59}
60
61impl CfgHost for CfgBridge {
62    fn cfg_save(&self, ns: &str, map: &SectionMap) -> Result<(), String> {
63        match &self.cfg {
64            Some(c) => c.store(ns, map).map_err(|e| e.to_string()),
65            None => Err("档案托底根不可用(Store::eco 失败)".to_string()),
66        }
67    }
68
69    fn cfg_load(&self, ns: &str) -> Result<Option<SectionMap>, String> {
70        match &self.cfg {
71            Some(c) => match c.load(ns) {
72                Ok(LoadOutcome::Loaded { map, .. }) => Ok(Some(map)),
73                Ok(LoadOutcome::Fresh) => Ok(None),
74                Err(e) => Err(e.to_string()),
75            },
76            None => Err("档案托底根不可用(Store::eco 失败)".to_string()),
77        }
78    }
79
80    fn log(&self, line: &str) {
81        self.shared.log_line(line);
82    }
83}
84
85/// 一帧宿主的状态包(各插件面自带态 + 通用窗口态表)。
86#[derive(Default)]
87pub struct FaceStates {
88    /// dm 面态。
89    pub dm: ek_dm::ui_face::DmUiState,
90    /// oms 面态。
91    pub oms: ek_oms::ui_face::OmsUiState,
92    /// tq 面态。
93    pub tq: ek_tq::ui_face::TqUiState,
94    /// 通用窗口态表(注册名 → 窗口态)。
95    pub wins: std::collections::HashMap<String, WinState>,
96}
97
98impl FaceStates {
99    /// 空态表。
100    #[must_use]
101    pub fn new() -> Self {
102        Self::default()
103    }
104}
105
106/// 取壳并 downcast 成真类型(0.9.5 裁 B 路)。
107///
108/// 装配侧把插件包进壳(ek-station `DynPlugin`),注册表的 downcast 底座是壳类型,
109/// `get_typed::<真插件>` 在**产品装配路线**上必然落空(上一波判据用手搭引擎测才没暴露)。
110/// 本口走 `MainEngine::plugin_arc`(core 0.5.3)取 dyn 本体 + 壳转发的 `as_any`(station 0.4.5)
111/// 做 downcast —— 手搭引擎(`get_typed` 直连)与装配壳两条路都通。
112fn with_plugin<T: Plugin + 'static, R>(
113    engine: &MainEngine,
114    name: &str,
115    f: impl FnOnce(&T) -> R,
116) -> Option<R> {
117    let arc = engine.plugin_arc(name)?;
118    let any = arc.as_any()?;
119    any.downcast_ref::<T>().map(f)
120}
121
122/// 有面判据:能 downcast 成真类型且有受理面。
123fn face_probe<T: Plugin + 'static>(engine: &MainEngine, name: &str) -> bool {
124    with_plugin(engine, name, |p: &T| p.as_configurable().is_some()).unwrap_or(false)
125}
126
127/// dm 采样档位读数(产品取壳路,判据用;装配壳路线也通)。
128#[must_use]
129pub fn dm_intervals(engine: &MainEngine) -> Option<Vec<i64>> {
130    with_plugin(engine, ek_ui_ns_dm(), |p: &ek_dm::plugin::DmPlugin| {
131        p.intervals_snapshot()
132    })
133}
134
135/// 本模块认的 dm 注册名(与挂载表同源)。
136fn ek_ui_ns_dm() -> &'static str {
137    NS_DM
138}
139
140/// 按注册名定面(逐类型试取:dm/oms 是定名,tq/ctp 是派生网关名)。
141#[must_use]
142pub fn face_of(engine: &MainEngine, name: &str) -> Option<FaceKind> {
143    if face_probe::<ek_dm::plugin::DmPlugin>(engine, name) {
144        return Some(FaceKind::Dm);
145    }
146    if face_probe::<ek_oms::plugin::OmsPlugin>(engine, name) {
147        return Some(FaceKind::Oms);
148    }
149    if face_probe::<ek_tq::plugin::TqPlugin>(engine, name) {
150        return Some(FaceKind::Tq);
151    }
152    if face_probe::<ek_ctp::plugin::CtpPlugin>(engine, name) {
153        return Some(FaceKind::Ctp);
154    }
155    None
156}
157
158/// 挂载表:在册且有面的插件(注册名, 窗口标题)。未装配的不出现(判据 U-W1)。
159#[must_use]
160pub fn mounted(engine: &MainEngine) -> Vec<(String, String)> {
161    let mut out: Vec<(String, String)> = Vec::new();
162    for name in engine.plugin_names() {
163        let title = match face_of(engine, &name) {
164            Some(FaceKind::Dm) => "数据管理器 dm",
165            Some(FaceKind::Oms) => "统一受理 oms",
166            Some(FaceKind::Tq) => "天勤桥 tq",
167            Some(FaceKind::Ctp) => "行情网关 ctp",
168            None => continue,
169        };
170        out.push((name, title.to_string()));
171    }
172    out
173}
174
175/// 按段派发受理(档案包导入用):段名 → 在册插件壳 → `apply_config` 回执。
176/// 取不到壳 = `None`(只收编不受理,导入不炸)。
177pub fn apply_section(engine: &MainEngine, ns: &str, section: &SectionMap) -> Option<String> {
178    // 定名段直取;派生网关名段(ctp/tq)按 ns 扫在册壳的 config_ns 对上号。
179    let rep = match ns {
180        NS_DM => with_plugin(engine, ns, |p: &ek_dm::plugin::DmPlugin| {
181            p.as_configurable()?.apply_config(section).ok()
182        })??,
183        NS_OMS => with_plugin(engine, ns, |p: &ek_oms::plugin::OmsPlugin| {
184            p.as_configurable()?.apply_config(section).ok()
185        })??,
186        _ => {
187            // 扫在册插件:谁的 config_ns == ns 就派谁(tq/ctp 的派生名段)
188            let mut found = None;
189            for name in engine.plugin_names() {
190                if let Some(kind) = face_of(engine, &name) {
191                    let hit = match kind {
192                        FaceKind::Tq => {
193                            with_plugin(engine, &name, |p: &ek_tq::plugin::TqPlugin| {
194                                let c = p.as_configurable()?;
195                                (c.config_ns() == ns).then(|| c.apply_config(section).ok())
196                            })
197                        }
198                        FaceKind::Ctp => {
199                            with_plugin(engine, &name, |p: &ek_ctp::plugin::CtpPlugin| {
200                                let c = p.as_configurable()?;
201                                (c.config_ns() == ns).then(|| c.apply_config(section).ok())
202                            })
203                        }
204                        _ => None,
205                    };
206                    // 三层压平:取壳(Option) × 命中判断(Option) × 受理回执(Option)
207                    if let Some(r) = hit.flatten().flatten() {
208                        found = Some(r);
209                        break;
210                    }
211                }
212            }
213            found?
214        }
215    };
216    Some(format!(
217        "受理 采纳{}/待重启{}",
218        rep.adopted.len(),
219        rep.deferred.len()
220    ))
221}
222
223/// 派发一个插件的面。返回 true = 本帧有交互(宿主按需重绘)。
224pub fn render_face(
225    ui: &mut egui::Ui,
226    engine: &MainEngine,
227    name: &str,
228    host: &dyn CfgHost,
229    fs: &mut FaceStates,
230) -> bool {
231    match face_of(engine, name) {
232        Some(FaceKind::Dm) => {
233            let Some(hit) = with_plugin(engine, name, |p: &ek_dm::plugin::DmPlugin| {
234                ek_dm::ui_face::render(ui, p, host, &mut fs.dm)
235            }) else {
236                ui.label("dm 壳取不到(未装配?)");
237                return false;
238            };
239            hit
240        }
241        Some(FaceKind::Oms) => {
242            let Some(hit) = with_plugin(engine, name, |p: &ek_oms::plugin::OmsPlugin| {
243                ek_oms::ui_face::render(ui, p, host, &mut fs.oms)
244            }) else {
245                ui.label("oms 壳取不到(未装配?)");
246                return false;
247            };
248            hit
249        }
250        Some(FaceKind::Tq) => {
251            let Some(hit) = with_plugin(engine, name, |p: &ek_tq::plugin::TqPlugin| {
252                ek_tq::ui_face::render(ui, p, host, &mut fs.tq)
253            }) else {
254                ui.label("tq 壳取不到(未装配?)");
255                return false;
256            };
257            hit
258        }
259        Some(FaceKind::Ctp) => {
260            // ctp 走通用视图驱动形貌(键位表 + 只读秘面引用),直取 dyn 本体即可。
261            let Some(arc) = engine.plugin_arc(name) else {
262                ui.label("ctp 壳取不到(未装配?)");
263                return false;
264            };
265            let st = fs.wins.entry(name.to_string()).or_default();
266            crate::pluginwin::render_window(ui, arc.as_ref(), host, st)
267        }
268        None => {
269            ui.label(format!("{name}:无自带面(不在挂载表)"));
270            false
271        }
272    }
273}