hydrate_model/editor/
undo.rs1use hydrate_data::DataSetResult;
2use slotmap::DenseSlotMap;
3use std::sync::mpsc;
4use std::sync::mpsc::{Receiver, Sender};
5
6use crate::edit_context::EditContext;
7use crate::{AssetId, DataSet, DataSetDiffSet, EditContextKey, HashSet};
8
9#[derive(PartialEq)]
18pub enum EndContextBehavior {
19 Finish,
20 AllowResume,
21}
22
23pub struct CompletedUndoContextMessage {
24 edit_context_key: EditContextKey,
25 diff_set: DataSetDiffSet,
26}
27
28pub struct UndoStack {
29 undo_chain: Vec<CompletedUndoContextMessage>,
30 current_undo_index: usize,
34 completed_undo_context_tx: Sender<CompletedUndoContextMessage>,
35 completed_undo_context_rx: Receiver<CompletedUndoContextMessage>,
36}
37
38impl Default for UndoStack {
39 fn default() -> Self {
40 let (tx, rx) = mpsc::channel();
41 UndoStack {
42 undo_chain: Default::default(),
43 current_undo_index: 0,
44 completed_undo_context_tx: tx,
45 completed_undo_context_rx: rx,
46 }
47 }
48}
49
50impl UndoStack {
51 fn drain_rx(&mut self) {
55 while let Ok(diff) = self.completed_undo_context_rx.try_recv() {
56 self.undo_chain.truncate(self.current_undo_index);
57 self.undo_chain.push(diff);
58 self.current_undo_index += 1;
59 }
60 }
61
62 pub fn undo(
63 &mut self,
64 edit_contexts: &mut DenseSlotMap<EditContextKey, EditContext>,
65 ) -> DataSetResult<()> {
66 for (_, edit_context) in edit_contexts.iter_mut() {
68 edit_context.commit_pending_undo_context();
69 }
70
71 self.drain_rx();
73
74 if self.current_undo_index > 0 {
78 if let Some(current_step) = self.undo_chain.get(self.current_undo_index - 1) {
79 let edit_context = edit_contexts
80 .get_mut(current_step.edit_context_key)
81 .unwrap();
82
83 let result = edit_context.apply_diff(¤t_step.diff_set.revert_diff);
84 self.current_undo_index -= 1;
85 return result;
86 }
87 }
88
89 Ok(())
90 }
91
92 pub fn redo(
93 &mut self,
94 edit_contexts: &mut DenseSlotMap<EditContextKey, EditContext>,
95 ) -> DataSetResult<()> {
96 self.drain_rx();
98
99 if let Some(current_step) = self.undo_chain.get(self.current_undo_index) {
103 let edit_context = edit_contexts
104 .get_mut(current_step.edit_context_key)
105 .unwrap();
106 edit_context.cancel_pending_undo_context()?;
108 let result = edit_context.apply_diff(¤t_step.diff_set.apply_diff);
109 self.current_undo_index += 1;
110 return result;
111 }
112
113 Ok(())
114 }
115}
116
117pub struct UndoContext {
120 edit_context_key: EditContextKey,
121 before_state: DataSet,
122 tracked_assets: HashSet<AssetId>,
123 context_name: Option<&'static str>,
124 completed_undo_context_tx: Sender<CompletedUndoContextMessage>,
125}
126
127impl UndoContext {
128 pub(crate) fn new(
129 undo_stack: &UndoStack,
130 edit_context_key: EditContextKey,
131 ) -> Self {
132 UndoContext {
133 edit_context_key,
134 before_state: Default::default(),
135 tracked_assets: Default::default(),
136 context_name: Default::default(),
137 completed_undo_context_tx: undo_stack.completed_undo_context_tx.clone(),
138 }
139 }
140
141 pub(crate) fn track_new_asset(
143 &mut self,
144 asset_id: AssetId,
145 ) {
146 if self.context_name.is_some() {
147 self.tracked_assets.insert(asset_id);
148 }
149 }
150
151 pub(crate) fn track_existing_asset(
153 &mut self,
154 before_state: &DataSet,
155 asset_id: AssetId,
156 ) -> DataSetResult<()> {
157 if self.context_name.is_some() {
158 if !self.tracked_assets.contains(&asset_id) {
160 self.tracked_assets.insert(asset_id);
161 self.before_state.copy_from(&before_state, asset_id)?;
162 }
163 }
164
165 Ok(())
166 }
167
168 pub(crate) fn has_open_context(&self) -> bool {
169 self.context_name.is_some()
170 }
171
172 pub(crate) fn begin_context(
173 &mut self,
174 after_state: &DataSet,
175 name: &'static str,
176 ) {
177 if self.context_name == Some(name) {
178 } else {
180 if self.context_name.is_some() {
182 self.commit_context(after_state);
184 }
185
186 self.context_name = Some(name);
187 }
188 }
189
190 pub(crate) fn end_context(
191 &mut self,
192 after_state: &DataSet,
193 end_context_behavior: EndContextBehavior,
194 ) {
195 if end_context_behavior != EndContextBehavior::AllowResume {
196 self.commit_context(after_state);
198 }
199 }
200
201 pub(crate) fn cancel_context(
202 &mut self,
203 after_state: &mut DataSet,
204 ) -> DataSetResult<()> {
205 let mut first_error = None;
206
207 if !self.tracked_assets.is_empty() {
208 let keys_to_delete: Vec<_> = after_state
210 .assets()
211 .keys()
212 .filter(|x| {
213 self.tracked_assets.contains(x) && !self.before_state.assets().contains_key(x)
214 })
215 .copied()
216 .collect();
217
218 for key_to_delete in keys_to_delete {
219 if let Err(e) = after_state.delete_asset(key_to_delete) {
220 if first_error.is_none() {
221 first_error = Some(Err(e));
222 }
223 }
224 }
225
226 for (asset_id, _asset) in self.before_state.assets() {
229 if let Err(e) = after_state.copy_from(&self.before_state, *asset_id) {
230 if first_error.is_none() {
231 first_error = Some(Err(e));
232 }
233 }
234 }
235
236 self.tracked_assets.clear();
238 }
239
240 self.before_state = Default::default();
241 self.context_name = None;
242
243 first_error.unwrap_or(Ok(()))
244 }
245
246 pub(crate) fn commit_context(
247 &mut self,
248 after_state: &DataSet,
249 ) {
250 if !self.tracked_assets.is_empty() {
251 let diff_set = DataSetDiffSet::diff_data_set(
253 &self.before_state,
254 &after_state,
255 &self.tracked_assets,
256 );
257 if diff_set.has_changes() {
258 self.completed_undo_context_tx
262 .send(CompletedUndoContextMessage {
263 edit_context_key: self.edit_context_key,
264 diff_set,
265 })
266 .unwrap();
267 }
268
269 self.tracked_assets.clear();
270 }
271
272 self.before_state = Default::default();
273 self.context_name = None;
274 }
275}