re_global_context/
command_sender.rs

1use re_chunk::{EntityPath, Timeline};
2use re_chunk_store::external::re_chunk::Chunk;
3use re_data_source::DataSource;
4use re_log_types::{ResolvedTimeRangeF, StoreId};
5use re_ui::{UICommand, UICommandSender};
6
7use crate::RecordingOrTable;
8
9// ----------------------------------------------------------------------------
10
11/// Commands used by internal system components
12// TODO(jleibs): Is there a better crate for this?
13#[derive(strum_macros::IntoStaticStr)]
14pub enum SystemCommand {
15    /// Make this the active application.
16    ActivateApp(re_log_types::ApplicationId),
17
18    /// Close this app and all its recordings.
19    CloseApp(re_log_types::ApplicationId),
20
21    /// Load some data.
22    LoadDataSource(DataSource),
23
24    /// Clear everything that came from this source, and close the source.
25    ClearSourceAndItsStores(re_smart_channel::SmartChannelSource),
26
27    /// Add a new receiver for log messages.
28    AddReceiver(re_smart_channel::Receiver<re_log_types::LogMsg>),
29
30    /// Add a new server to the redap browser.
31    AddRedapServer(re_uri::Origin),
32
33    ChangeDisplayMode(crate::DisplayMode),
34
35    /// Reset the `Viewer` to the default state
36    ResetViewer,
37
38    /// Clear the active blueprint.
39    ///
40    /// This may have two outcomes:
41    /// - If a default blueprint is set, it will be used.
42    /// - Otherwise, the heuristics will be enabled.
43    ///
44    /// To force using the heuristics, use [`Self::ClearActiveBlueprintAndEnableHeuristics`].
45    ///
46    /// UI note: because of the above ambiguity, controls for this command should only be enabled if
47    /// a default blueprint is set or the behavior is explicitly explained.
48    ClearActiveBlueprint,
49
50    /// Clear the active blueprint and enable heuristics.
51    ///
52    /// The final outcome of this is to set the active blueprint to the heuristics. This command
53    /// does not affect the default blueprint if any was set.
54    ClearActiveBlueprintAndEnableHeuristics,
55
56    /// Switch to this [`RecordingOrTable`].
57    ActivateRecordingOrTable(RecordingOrTable),
58
59    /// Close an [`RecordingOrTable`] and free its memory.
60    CloseRecordingOrTable(RecordingOrTable),
61
62    /// Close all stores and show the welcome screen again.
63    CloseAllEntries,
64
65    /// Add more data to a store (blueprint or recording).
66    ///
67    /// Edit recordings with case: we generally regard recordings as immutable.
68    ///
69    /// For blueprints,the [`StoreId`] should generally be the currently selected blueprint.
70    ///
71    /// Instead of using this directly, consider using `save_blueprint_archetype` or similar.
72    AppendToStore(StoreId, Vec<Chunk>),
73
74    UndoBlueprint {
75        blueprint_id: StoreId,
76    },
77    RedoBlueprint {
78        blueprint_id: StoreId,
79    },
80
81    /// Drop a specific entity from a store.
82    ///
83    /// Also drops all recursive children.
84    ///
85    /// The [`StoreId`] should generally be the currently selected blueprint
86    /// but is tracked manually to ensure self-consistency if the blueprint
87    /// is both modified and changed in the same frame.
88    DropEntity(StoreId, EntityPath),
89
90    /// Show a timeline of the blueprint data.
91    #[cfg(debug_assertions)]
92    EnableInspectBlueprintTimeline(bool),
93
94    /// Set the item selection.
95    SetSelection(crate::Item),
96
97    /// Set the active timeline and time for the given recording.
98    SetActiveTime {
99        rec_id: StoreId,
100        timeline: re_chunk::Timeline,
101        time: Option<re_log_types::TimeReal>,
102    },
103
104    /// Set the loop selection for the given timeline.
105    ///
106    /// This also sets the active timeline and activates the loop selection.
107    SetLoopSelection {
108        rec_id: StoreId,
109        timeline: Timeline,
110        time_range: ResolvedTimeRangeF,
111    },
112
113    /// Sets the focus to the given item.
114    ///
115    /// The focused item is cleared out every frame.
116    /// Focusing is triggered either explicitly by ui-elements saying so
117    /// or by double-clicking on a button representing an item.
118    ///
119    /// Unlike item selection, item focusing is not global state.
120    /// It may however have stateful effects in certain views,
121    /// e.g. the 3D view may follow the last focused item as it moves,
122    /// or a frame may be highlighted for a few frames.
123    ///
124    /// Just like selection highlighting, the exact behavior of focusing is up to the receiving views.
125    SetFocus(crate::Item),
126
127    /// Add a task, run on a background thread, that saves something to disk.
128    #[cfg(not(target_arch = "wasm32"))]
129    FileSaver(Box<dyn FnOnce() -> anyhow::Result<std::path::PathBuf> + Send + 'static>),
130}
131
132impl std::fmt::Debug for SystemCommand {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        // not all variant contents can be made `Debug`, so we only output the variant name
135        f.write_str(self.into())
136    }
137}
138
139/// Interface for sending [`SystemCommand`] messages.
140pub trait SystemCommandSender {
141    fn send_system(&self, command: SystemCommand);
142}
143
144// ----------------------------------------------------------------------------
145
146/// Sender that queues up the execution of commands.
147#[derive(Clone)]
148pub struct CommandSender {
149    system_sender: std::sync::mpsc::Sender<SystemCommand>,
150    ui_sender: std::sync::mpsc::Sender<UICommand>,
151}
152
153/// Receiver for the [`CommandSender`]
154pub struct CommandReceiver {
155    system_receiver: std::sync::mpsc::Receiver<SystemCommand>,
156    ui_receiver: std::sync::mpsc::Receiver<UICommand>,
157}
158
159impl CommandReceiver {
160    /// Receive a [`SystemCommand`] to be executed if any is queued.
161    pub fn recv_system(&self) -> Option<SystemCommand> {
162        // The only way this can fail (other than being empty)
163        // is if the sender has been dropped.
164        self.system_receiver.try_recv().ok()
165    }
166
167    /// Receive a [`UICommand`] to be executed if any is queued.
168    pub fn recv_ui(&self) -> Option<UICommand> {
169        // The only way this can fail (other than being empty)
170        // is if the sender has been dropped.
171        self.ui_receiver.try_recv().ok()
172    }
173}
174
175/// Creates a new command channel.
176pub fn command_channel() -> (CommandSender, CommandReceiver) {
177    let (system_sender, system_receiver) = std::sync::mpsc::channel();
178    let (ui_sender, ui_receiver) = std::sync::mpsc::channel();
179    (
180        CommandSender {
181            system_sender,
182            ui_sender,
183        },
184        CommandReceiver {
185            system_receiver,
186            ui_receiver,
187        },
188    )
189}
190
191// ----------------------------------------------------------------------------
192
193impl SystemCommandSender for CommandSender {
194    /// Send a command to be executed.
195    fn send_system(&self, command: SystemCommand) {
196        // The only way this can fail is if the receiver has been dropped.
197        self.system_sender.send(command).ok();
198    }
199}
200
201impl UICommandSender for CommandSender {
202    /// Send a command to be executed.
203    fn send_ui(&self, command: UICommand) {
204        // The only way this can fail is if the receiver has been dropped.
205        self.ui_sender.send(command).ok();
206    }
207}