Skip to main content

common/
event.rs

1// Generated by Qleany v1.11.0 from common_event.tera
2
3use crate::types::EntityId;
4use flume::{Receiver, Sender, unbounded};
5use serde::Serialize;
6use std::{
7    sync::{Arc, Mutex},
8    thread,
9};
10
11#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
12pub enum EntityEvent {
13    Created,
14    Updated,
15    Removed,
16}
17
18#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
19pub enum AllEvent {
20    Reset,
21}
22
23#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
24pub enum UndoRedoEvent {
25    Undone,
26    Redone,
27    /// A stack's history changed other than by undoing or redoing it — a
28    /// command pushed, a command merged into the one below it, a stack cleared.
29    ///
30    /// Without this a UI cannot know that "can undo" just became true: the
31    /// manager announced only the *consumption* of history, never its creation,
32    /// so an Undo menu row had nothing to react to and had to be polled.
33    /// Carries the stack id in `Event::data`, like every other variant here, so
34    /// a process holding several stacks can tell whose history moved.
35    StackChanged,
36    BeginComposite,
37    EndComposite,
38    CancelComposite,
39}
40
41#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
42pub enum LongOperationEvent {
43    Started,
44    Progress,
45    Cancelled,
46    Completed,
47    Failed,
48}
49
50#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
51pub enum DirectAccessEntity {
52    All(AllEvent),
53
54    Root(EntityEvent),
55    Workspace(EntityEvent),
56    System(EntityEvent),
57    Entity(EntityEvent),
58    Field(EntityEvent),
59    Feature(EntityEvent),
60    File(EntityEvent),
61    UseCase(EntityEvent),
62    Dto(EntityEvent),
63    DtoField(EntityEvent),
64    Global(EntityEvent),
65    Relationship(EntityEvent),
66    UserInterface(EntityEvent),
67}
68
69#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
70pub enum HandlingAppLifecycleEvent {
71    InitializeApp,
72    CleanUpBeforeExit,
73}
74
75#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
76pub enum HandlingManifestEvent {
77    Load,
78    Save,
79    Create,
80    Close,
81    ExportToMermaid,
82    Check,
83}
84
85#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
86pub enum RustFileGenerationEvent {
87    FillRustFiles,
88    FillCodeInRustFiles,
89    GenerateRustCode,
90    GenerateRustFiles,
91    GenerateRustPrompt,
92}
93
94#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
95pub enum CppQtFileGenerationEvent {
96    FillCppQtFiles,
97    GenerateCppQtCode,
98    GenerateCppQtFiles,
99    FillCodeInCppQtFiles,
100    GenerateCppQtPrompt,
101}
102
103#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
104pub enum FileGenerationSharedStepsEvent {
105    FillStatusInFiles,
106    GetFileDiff,
107}
108
109#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
110pub enum Origin {
111    DirectAccess(DirectAccessEntity),
112    UndoRedo(UndoRedoEvent),
113    LongOperation(LongOperationEvent),
114
115    HandlingAppLifecycle(HandlingAppLifecycleEvent),
116    HandlingManifest(HandlingManifestEvent),
117    RustFileGeneration(RustFileGenerationEvent),
118    CppQtFileGeneration(CppQtFileGenerationEvent),
119    FileGenerationSharedSteps(FileGenerationSharedStepsEvent),
120}
121
122#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
123pub struct Event {
124    pub origin: Origin,
125    pub ids: Vec<EntityId>,
126    pub data: Option<String>,
127}
128
129impl Event {
130    pub fn origin_string(&self) -> String {
131        match &self.origin {
132            Origin::DirectAccess(entity) => match entity {
133                DirectAccessEntity::All(event) => format!("direct_access_all_{:?}", event),
134                // entities
135                DirectAccessEntity::Root(event) => format!("direct_access_root_{:?}", event),
136                DirectAccessEntity::Workspace(event) => {
137                    format!("direct_access_workspace_{:?}", event)
138                }
139                DirectAccessEntity::System(event) => format!("direct_access_system_{:?}", event),
140                DirectAccessEntity::Entity(event) => format!("direct_access_entity_{:?}", event),
141                DirectAccessEntity::Field(event) => format!("direct_access_field_{:?}", event),
142                DirectAccessEntity::Feature(event) => format!("direct_access_feature_{:?}", event),
143                DirectAccessEntity::File(event) => format!("direct_access_file_{:?}", event),
144                DirectAccessEntity::UseCase(event) => format!("direct_access_use_case_{:?}", event),
145                DirectAccessEntity::Dto(event) => format!("direct_access_dto_{:?}", event),
146                DirectAccessEntity::DtoField(event) => {
147                    format!("direct_access_dto_field_{:?}", event)
148                }
149                DirectAccessEntity::Global(event) => format!("direct_access_global_{:?}", event),
150                DirectAccessEntity::Relationship(event) => {
151                    format!("direct_access_relationship_{:?}", event)
152                }
153                DirectAccessEntity::UserInterface(event) => {
154                    format!("direct_access_user_interface_{:?}", event)
155                }
156            },
157            Origin::UndoRedo(event) => format!("undo_redo_{:?}", event),
158            Origin::LongOperation(event) => format!("long_operation_{:?}", event),
159            // features
160            Origin::HandlingAppLifecycle(event) => format!("handling_app_lifecycle_{:?}", event),
161            Origin::HandlingManifest(event) => format!("handling_manifest_{:?}", event),
162            Origin::RustFileGeneration(event) => format!("rust_file_generation_{:?}", event),
163            Origin::CppQtFileGeneration(event) => format!("cpp_qt_file_generation_{:?}", event),
164            Origin::FileGenerationSharedSteps(event) => {
165                format!("file_generation_shared_steps_{:?}", event)
166            }
167        }
168        .to_lowercase()
169    }
170}
171/// Thread-safe event buffer for deferring event emissions during transactions.
172///
173/// Repositories push events into this buffer instead of sending them directly
174/// to the EventHub. On commit(), the UoW drains the buffer and sends all events.
175/// On rollback(), the buffer is discarded. This prevents the UI from seeing
176/// phantom state from failed transactions.
177///
178/// This is the Rust equivalent of SignalBuffer in the C++/Qt target.
179pub struct EventBuffer {
180    buffering: bool,
181    pending: Vec<Event>,
182}
183
184impl EventBuffer {
185    pub fn new() -> Self {
186        Self {
187            buffering: false,
188            pending: Vec::new(),
189        }
190    }
191
192    /// Start buffering. Clears any stale events from a previous cycle.
193    pub fn begin_buffering(&mut self) {
194        self.buffering = true;
195        self.pending.clear();
196    }
197
198    /// Queue an event for deferred delivery.
199    ///
200    /// If buffering is not active, the event is silently dropped.
201    /// (Callers should only push during an active transaction.)
202    pub fn push(&mut self, event: Event) {
203        if self.buffering {
204            self.pending.push(event);
205        }
206    }
207
208    /// Drain all pending events and stop buffering.
209    /// The caller is responsible for sending them to the EventHub.
210    pub fn flush(&mut self) -> Vec<Event> {
211        self.buffering = false;
212        std::mem::take(&mut self.pending)
213    }
214
215    /// Discard all pending events and stop buffering.
216    pub fn discard(&mut self) {
217        self.buffering = false;
218        self.pending.clear();
219    }
220
221    pub fn is_buffering(&self) -> bool {
222        self.buffering
223    }
224}
225
226impl Default for EventBuffer {
227    fn default() -> Self {
228        Self::new()
229    }
230}
231
232pub type Queue = Arc<Mutex<Vec<Event>>>;
233
234/// Central event hub for managing subscriptions and dispatching events
235#[derive(Debug)]
236pub struct EventHub {
237    sender: Sender<Event>,
238    receiver: Receiver<Event>,
239    queue: Queue,
240}
241
242impl Default for EventHub {
243    fn default() -> Self {
244        Self::new()
245    }
246}
247
248impl EventHub {
249    /// Create a new event hub
250    pub fn new() -> Self {
251        let (sender, receiver) = unbounded();
252        EventHub {
253            sender,
254            receiver,
255            queue: Arc::new(Mutex::new(Vec::new())),
256        }
257    }
258
259    /// Start the event processing loop.
260    ///
261    /// Returns a `JoinHandle` so the caller can join the thread on
262    /// shutdown. The thread blocks on a `flume::Selector` that wakes
263    /// only when an event arrives or `shutdown_rx` disconnects — zero
264    /// CPU while idle. Drop the matching `Sender<()>` to stop the
265    /// thread.
266    pub fn start_event_loop(&self, shutdown_rx: Receiver<()>) -> thread::JoinHandle<()> {
267        let receiver = self.receiver.clone();
268        let queue = self.queue.clone();
269        thread::spawn(move || {
270            loop {
271                let outcome: Result<Option<Event>, ()> = flume::Selector::new()
272                    .recv(&receiver, |r| r.map(Some).map_err(|_| ()))
273                    .recv(&shutdown_rx, |_| Ok(None))
274                    .wait();
275                match outcome {
276                    Ok(Some(event)) => {
277                        let mut queue = crate::long_operation::lock_or_recover(&queue);
278                        queue.push(event);
279                    }
280                    Ok(None) | Err(()) => break,
281                }
282            }
283        })
284    }
285
286    /// Send an event to the queue
287    pub fn send_event(&self, event: Event) {
288        if let Err(e) = self.sender.send(event) {
289            eprintln!("EventHub: failed to send event (receiver dropped): {e}");
290        }
291    }
292
293    pub fn get_queue(&self) -> Queue {
294        self.queue.clone()
295    }
296
297    /// Get a direct event receiver.
298    ///
299    /// The receiver blocks on `recv()` until an event arrives — no polling needed.
300    /// **Important**: flume uses MPMC semantics — each event is delivered to exactly
301    /// one receiver. Multiple cloned receivers compete for events rather than each
302    /// receiving a copy. Ensure only one consumer calls `subscribe_receiver()`.
303    pub fn subscribe_receiver(&self) -> Receiver<Event> {
304        self.receiver.clone()
305    }
306}
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311
312    #[test]
313    fn test_event_hub_send_and_receive() {
314        let event_hub = EventHub::new();
315        let (shutdown_tx, shutdown_rx) = flume::bounded::<()>(1);
316        let handle = event_hub.start_event_loop(shutdown_rx);
317
318        let event = Event {
319            origin: Origin::DirectAccess(DirectAccessEntity::All(AllEvent::Reset)),
320            ids: vec![EntityId::default()],
321            data: Some("test_data".to_string()),
322        };
323
324        event_hub.send_event(event.clone());
325
326        thread::sleep(std::time::Duration::from_millis(100));
327
328        let queue = event_hub.get_queue();
329        let queue = crate::long_operation::lock_or_recover(&queue);
330        assert_eq!(queue.len(), 1);
331        assert_eq!(queue[0], event);
332
333        // Drop the sender to wake the loop thread and exit cleanly.
334        drop(shutdown_tx);
335        handle.join().unwrap();
336    }
337}