1use std::collections::HashSet;
2use zng_app_context::app_local;
3use zng_txt::Txt;
4use zng_var::VARS;
5
6use crate::update::UPDATES;
7
8use super::*;
9
10app_local! {
11 pub(crate) static EVENTS_SV: EventsService = const { EventsService::new() };
12}
13
14pub(crate) struct EventsService {
15 commands: CommandSet,
16 l10n: EventsL10n,
17}
18enum EventsL10n {
19 Pending(Vec<([&'static str; 3], Command, &'static str, CommandMetaVar<Txt>)>),
20 Init(Box<dyn Fn([&'static str; 3], Command, &'static str, CommandMetaVar<Txt>) + Send + Sync>),
21}
22impl EventsService {
23 const fn new() -> Self {
24 Self {
25 commands: HashSet::with_hasher(BuildFxHasher),
26 l10n: EventsL10n::Pending(vec![]),
27 }
28 }
29}
30
31#[derive(Clone, Default)]
33pub struct BuildFxHasher;
34impl std::hash::BuildHasher for BuildFxHasher {
35 type Hasher = rustc_hash::FxHasher;
36
37 fn build_hasher(&self) -> Self::Hasher {
38 rustc_hash::FxHasher::default()
39 }
40}
41
42pub type CommandSet = HashSet<Command, BuildFxHasher>;
44
45pub struct EVENTS;
47impl EVENTS {
48 pub fn commands(&self) -> CommandSet {
56 EVENTS_SV.read().commands.clone()
57 }
58
59 pub(super) fn register_command(&self, cmd: Command) {
60 tracing::trace!("register {cmd:?}");
61 UPDATES.once_update("register_command", move || {
62 let mut ev = EVENTS_SV.write();
63 if !ev.commands.insert(cmd) {
64 tracing::error!("command `{cmd:?}` is already registered");
65 }
66 });
67 }
68 pub(super) fn unregister_command(&self, cmd: Command) {
69 tracing::trace!("unregister {cmd:?}");
70 UPDATES.once_update("unregister_command", move || {
71 EVENTS_SV.write().commands.remove(&cmd);
72 });
73 }
74
75 pub fn notify(&self, debug_name: &'static str, n: impl FnOnce() + Send + 'static) {
81 VARS.modify(debug_name, n);
82 }
83}
84
85#[expect(non_camel_case_types)]
87pub struct EVENTS_L10N;
88impl EVENTS_L10N {
89 pub(crate) fn init_meta_l10n(&self, file: [&'static str; 3], cmd: Command, meta_name: &'static str, txt: CommandMetaVar<Txt>) {
90 {
91 let sv = EVENTS_SV.read();
92 if let EventsL10n::Init(f) = &sv.l10n {
93 f(file, cmd, meta_name, txt);
94 return;
95 }
96 }
97
98 let mut sv = EVENTS_SV.write();
99 match &mut sv.l10n {
100 EventsL10n::Pending(a) => a.push((file, cmd, meta_name, txt)),
101 EventsL10n::Init(f) => f(file, cmd, meta_name, txt),
102 }
103 }
104
105 pub fn init_l10n(&self, localize: impl Fn([&'static str; 3], Command, &'static str, CommandMetaVar<Txt>) + Send + Sync + 'static) {
115 let mut sv = EVENTS_SV.write();
116 match &mut sv.l10n {
117 EventsL10n::Pending(a) => {
118 for (f, k, a, t) in a.drain(..) {
119 localize(f, k, a, t);
120 }
121 }
122 EventsL10n::Init(_) => panic!("EVENTS_L10N already has a localizer"),
123 }
124 sv.l10n = EventsL10n::Init(Box::new(localize));
125 }
126}