zng_app/event/
events.rs
1use std::collections::HashSet;
2use zng_app_context::app_local;
3use zng_time::INSTANT_APP;
4use zng_txt::Txt;
5
6use crate::update::{UPDATES, UpdatesTrace};
7
8use super::*;
9
10app_local! {
11 pub(crate) static EVENTS_SV: EventsService = const { EventsService::new() };
12}
13
14pub(crate) struct EventsService {
15 updates: Mutex<Vec<EventUpdate>>, commands: CommandSet,
17 register_commands: Vec<Command>,
18 l10n: EventsL10n,
19}
20enum EventsL10n {
21 Pending(Vec<([&'static str; 3], Command, &'static str, CommandMetaVar<Txt>)>),
22 Init(Box<dyn Fn([&'static str; 3], Command, &'static str, CommandMetaVar<Txt>) + Send + Sync>),
23}
24impl EventsService {
25 const fn new() -> Self {
26 Self {
27 updates: Mutex::new(vec![]),
28 commands: HashSet::with_hasher(BuildFxHasher),
29 register_commands: vec![],
30 l10n: EventsL10n::Pending(vec![]),
31 }
32 }
33
34 pub(super) fn register_command(&mut self, command: Command) {
35 if self.register_commands.is_empty() {
36 UPDATES.update(None);
37 }
38 self.register_commands.push(command);
39 }
40
41 pub(super) fn sender<A>(&mut self, event: Event<A>) -> EventSender<A>
42 where
43 A: EventArgs + Send,
44 {
45 EventSender {
46 sender: UPDATES.sender(),
47 event,
48 }
49 }
50
51 pub(crate) fn has_pending_updates(&mut self) -> bool {
52 !self.updates.get_mut().is_empty()
53 }
54}
55
56#[derive(Clone, Default)]
58pub struct BuildFxHasher;
59impl std::hash::BuildHasher for BuildFxHasher {
60 type Hasher = rustc_hash::FxHasher;
61
62 fn build_hasher(&self) -> Self::Hasher {
63 rustc_hash::FxHasher::default()
64 }
65}
66
67pub type CommandSet = HashSet<Command, BuildFxHasher>;
69
70pub struct EVENTS;
72impl EVENTS {
73 pub fn commands(&self) -> CommandSet {
81 EVENTS_SV.read().commands.clone()
82 }
83
84 pub fn notify(&self, update: EventUpdate) {
86 UpdatesTrace::log_event(update.event);
87 EVENTS_SV.write().updates.get_mut().push(update);
88 UPDATES.send_awake();
89 }
90
91 #[must_use]
92 pub(crate) fn apply_updates(&self) -> Vec<EventUpdate> {
93 let _s = tracing::trace_span!("EVENTS").entered();
94
95 let mut ev = EVENTS_SV.write();
96 ev.commands.retain(|c| c.update_state());
97
98 {
99 let ev = &mut *ev;
100 for cmd in ev.register_commands.drain(..) {
101 if cmd.update_state() && !ev.commands.insert(cmd) {
102 tracing::error!("command `{cmd:?}` is already registered")
103 }
104 }
105 }
106
107 let mut updates: Vec<_> = ev.updates.get_mut().drain(..).collect();
108 drop(ev);
109
110 if !updates.is_empty() {
111 let _t = INSTANT_APP.pause_for_update();
112
113 for u in &mut updates {
114 let ev = u.event;
115 ev.on_update(u);
116 }
117 }
118 updates
119 }
120}
121
122#[expect(non_camel_case_types)]
124pub struct EVENTS_L10N;
125impl EVENTS_L10N {
126 pub(crate) fn init_meta_l10n(&self, file: [&'static str; 3], cmd: Command, meta_name: &'static str, txt: CommandMetaVar<Txt>) {
127 {
128 let sv = EVENTS_SV.read();
129 if let EventsL10n::Init(f) = &sv.l10n {
130 f(file, cmd, meta_name, txt);
131 return;
132 }
133 }
134
135 let mut sv = EVENTS_SV.write();
136 match &mut sv.l10n {
137 EventsL10n::Pending(a) => a.push((file, cmd, meta_name, txt)),
138 EventsL10n::Init(f) => f(file, cmd, meta_name, txt),
139 }
140 }
141
142 pub fn init_l10n(&self, localize: impl Fn([&'static str; 3], Command, &'static str, CommandMetaVar<Txt>) + Send + Sync + 'static) {
152 let mut sv = EVENTS_SV.write();
153 match &mut sv.l10n {
154 EventsL10n::Pending(a) => {
155 for (f, k, a, t) in a.drain(..) {
156 localize(f, k, a, t);
157 }
158 }
159 EventsL10n::Init(_) => panic!("EVENTS_L10N already has a localizer"),
160 }
161 sv.l10n = EventsL10n::Init(Box::new(localize));
162 }
163}