Skip to main content

text_document_frontend/
app_context.rs

1// Generated by Qleany v1.7.3 from frontend_app_context.tera
2
3use common::database::db_context::DbContext;
4use common::event::EventHub;
5use common::long_operation::LongOperationManager;
6use common::undo_redo::UndoRedoManager;
7use flume::{Receiver, Sender};
8use parking_lot::Mutex;
9use std::sync::Arc;
10
11/// Application context that holds all shared state.
12///
13/// Shutdown lifecycle: when [`AppContext::shutdown`] is called the shared
14/// `shutdown_tx` is dropped, which makes every cloned `shutdown_rx`
15/// (held by `EventHubClient::start` threads) see `Disconnected` on its
16/// next `recv()` and exit. No polling and no atomic flag needed.
17#[derive(Clone, Debug)]
18pub struct AppContext {
19    pub db_context: DbContext,
20    pub event_hub: Arc<EventHub>,
21    /// Receiver clones are handed to background event-hub threads.
22    /// Threads exit when the matching sender is dropped via
23    /// [`AppContext::shutdown`].
24    pub shutdown_rx: Receiver<()>,
25    /// Shared single sender. `shutdown()` takes it out, dropping
26    /// the only live `Sender` and unblocking every receiver clone.
27    /// Wrapped in `Mutex<Option<…>>` so `shutdown()` stays `&self`
28    /// (the alternative — store the sender directly in `AppContext`
29    /// — would force every clone to hold its own sender clone and
30    /// `Disconnect` would only fire after the last clone dropped,
31    /// defeating the explicit `shutdown()` API).
32    shutdown_tx: Arc<Mutex<Option<Sender<()>>>>,
33    pub undo_redo_manager: Arc<Mutex<UndoRedoManager>>,
34    pub long_operation_manager: Arc<Mutex<LongOperationManager>>,
35}
36
37impl AppContext {
38    pub fn new() -> Self {
39        let db_context = DbContext::new().expect("Failed to create database context");
40
41        let event_hub = Arc::new(EventHub::new());
42        // Bounded(1) is enough — we only ever drop the sender, never
43        // actually send a unit on this channel. The bound keeps the
44        // allocation tiny.
45        let (shutdown_tx, shutdown_rx) = flume::bounded(1);
46
47        let undo_redo_manager = Arc::new(Mutex::new(UndoRedoManager::new()));
48        let long_operation_manager = Arc::new(Mutex::new(LongOperationManager::new()));
49
50        // Inject event hub into long_operation_manager
51        {
52            let mut lom = long_operation_manager.lock();
53            lom.set_event_hub(&event_hub);
54        }
55
56        // And into undo_redo_manager, for the same reason and at the same
57        // moment. It used to be injected lazily, by whichever undo/redo command
58        // happened to run first — which meant the *push* events never reached
59        // anyone: nothing pushes through those commands, so on a context where
60        // the user had only ever edited, `StackChanged` was emitted into a
61        // `None` hub and dropped. A subscriber cannot learn that "can undo" just
62        // became true from an event that is only delivered after the first undo.
63        {
64            let mut urm = undo_redo_manager.lock();
65            urm.set_event_hub(&event_hub);
66        }
67
68        Self {
69            db_context,
70            event_hub,
71            shutdown_rx,
72            shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))),
73            undo_redo_manager,
74            long_operation_manager,
75        }
76    }
77
78    /// A context that shares another's event hub, shutdown channel and
79    /// long-operation manager, with a **private** store and undo stack.
80    ///
81    /// What may be shared and what may not is decided by undo. Every
82    /// repository's `snapshot`/`restore` takes and puts back the *whole* store
83    /// (see `Transaction::snapshot_store`), so two documents in one store would
84    /// undo and roll each other back. The store and the undo manager therefore
85    /// stay private to each document.
86    ///
87    /// The event hub can be shared, and that is where the cost is: draining one
88    /// costs an OS thread, so a hub per document is a thread per document. A
89    /// stream over a book-length manuscript opens more than a hundred.
90    ///
91    /// Because the hub is shared, so is the shutdown channel: a document that
92    /// stopped the pump on its own way out would stop it for every sibling. The
93    /// owner of the shared context is what decides when the pump ends.
94    pub fn new_sharing(other: &AppContext) -> Self {
95        let db_context = DbContext::new().expect("Failed to create database context");
96        let undo_redo_manager = Arc::new(Mutex::new(UndoRedoManager::new()));
97        {
98            let mut urm = undo_redo_manager.lock();
99            urm.set_event_hub(&other.event_hub);
100        }
101        Self {
102            db_context,
103            event_hub: Arc::clone(&other.event_hub),
104            shutdown_rx: other.shutdown_rx.clone(),
105            shutdown_tx: Arc::clone(&other.shutdown_tx),
106            undo_redo_manager,
107            long_operation_manager: Arc::clone(&other.long_operation_manager),
108        }
109    }
110
111    /// Signal background event-hub threads to stop. Idempotent: the second call
112    /// is a no-op because the sender has already been taken.
113    pub fn shutdown(&self) {
114        let _ = self.shutdown_tx.lock().take();
115    }
116}
117
118impl Default for AppContext {
119    fn default() -> Self {
120        Self::new()
121    }
122}