use hydrate_data::DataSetResult;
use slotmap::DenseSlotMap;
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
use crate::edit_context::EditContext;
use crate::{AssetId, DataSet, DataSetDiffSet, EditContextKey, HashSet};
#[derive(PartialEq)]
pub enum EndContextBehavior {
Finish,
AllowResume,
}
pub struct CompletedUndoContextMessage {
edit_context_key: EditContextKey,
diff_set: DataSetDiffSet,
}
pub struct UndoStack {
undo_chain: Vec<CompletedUndoContextMessage>,
current_undo_index: usize,
completed_undo_context_tx: Sender<CompletedUndoContextMessage>,
completed_undo_context_rx: Receiver<CompletedUndoContextMessage>,
}
impl Default for UndoStack {
fn default() -> Self {
let (tx, rx) = mpsc::channel();
UndoStack {
undo_chain: Default::default(),
current_undo_index: 0,
completed_undo_context_tx: tx,
completed_undo_context_rx: rx,
}
}
}
impl UndoStack {
fn drain_rx(&mut self) {
while let Ok(diff) = self.completed_undo_context_rx.try_recv() {
self.undo_chain.truncate(self.current_undo_index);
self.undo_chain.push(diff);
self.current_undo_index += 1;
}
}
pub fn undo(
&mut self,
edit_contexts: &mut DenseSlotMap<EditContextKey, EditContext>,
) -> DataSetResult<()> {
for (_, edit_context) in edit_contexts.iter_mut() {
edit_context.commit_pending_undo_context();
}
self.drain_rx();
if self.current_undo_index > 0 {
if let Some(current_step) = self.undo_chain.get(self.current_undo_index - 1) {
let edit_context = edit_contexts
.get_mut(current_step.edit_context_key)
.unwrap();
let result = edit_context.apply_diff(¤t_step.diff_set.revert_diff);
self.current_undo_index -= 1;
return result;
}
}
Ok(())
}
pub fn redo(
&mut self,
edit_contexts: &mut DenseSlotMap<EditContextKey, EditContext>,
) -> DataSetResult<()> {
self.drain_rx();
if let Some(current_step) = self.undo_chain.get(self.current_undo_index) {
let edit_context = edit_contexts
.get_mut(current_step.edit_context_key)
.unwrap();
edit_context.cancel_pending_undo_context()?;
let result = edit_context.apply_diff(¤t_step.diff_set.apply_diff);
self.current_undo_index += 1;
return result;
}
Ok(())
}
}
pub struct UndoContext {
edit_context_key: EditContextKey,
before_state: DataSet,
tracked_assets: HashSet<AssetId>,
context_name: Option<&'static str>,
completed_undo_context_tx: Sender<CompletedUndoContextMessage>,
}
impl UndoContext {
pub(crate) fn new(
undo_stack: &UndoStack,
edit_context_key: EditContextKey,
) -> Self {
UndoContext {
edit_context_key,
before_state: Default::default(),
tracked_assets: Default::default(),
context_name: Default::default(),
completed_undo_context_tx: undo_stack.completed_undo_context_tx.clone(),
}
}
pub(crate) fn track_new_asset(
&mut self,
asset_id: AssetId,
) {
if self.context_name.is_some() {
self.tracked_assets.insert(asset_id);
}
}
pub(crate) fn track_existing_asset(
&mut self,
before_state: &DataSet,
asset_id: AssetId,
) -> DataSetResult<()> {
if self.context_name.is_some() {
if !self.tracked_assets.contains(&asset_id) {
self.tracked_assets.insert(asset_id);
self.before_state.copy_from(&before_state, asset_id)?;
}
}
Ok(())
}
pub(crate) fn has_open_context(&self) -> bool {
self.context_name.is_some()
}
pub(crate) fn begin_context(
&mut self,
after_state: &DataSet,
name: &'static str,
) {
if self.context_name == Some(name) {
} else {
if self.context_name.is_some() {
self.commit_context(after_state);
}
self.context_name = Some(name);
}
}
pub(crate) fn end_context(
&mut self,
after_state: &DataSet,
end_context_behavior: EndContextBehavior,
) {
if end_context_behavior != EndContextBehavior::AllowResume {
self.commit_context(after_state);
}
}
pub(crate) fn cancel_context(
&mut self,
after_state: &mut DataSet,
) -> DataSetResult<()> {
let mut first_error = None;
if !self.tracked_assets.is_empty() {
let keys_to_delete: Vec<_> = after_state
.assets()
.keys()
.filter(|x| {
self.tracked_assets.contains(x) && !self.before_state.assets().contains_key(x)
})
.copied()
.collect();
for key_to_delete in keys_to_delete {
if let Err(e) = after_state.delete_asset(key_to_delete) {
if first_error.is_none() {
first_error = Some(Err(e));
}
}
}
for (asset_id, _asset) in self.before_state.assets() {
if let Err(e) = after_state.copy_from(&self.before_state, *asset_id) {
if first_error.is_none() {
first_error = Some(Err(e));
}
}
}
self.tracked_assets.clear();
}
self.before_state = Default::default();
self.context_name = None;
first_error.unwrap_or(Ok(()))
}
pub(crate) fn commit_context(
&mut self,
after_state: &DataSet,
) {
if !self.tracked_assets.is_empty() {
let diff_set = DataSetDiffSet::diff_data_set(
&self.before_state,
&after_state,
&self.tracked_assets,
);
if diff_set.has_changes() {
self.completed_undo_context_tx
.send(CompletedUndoContextMessage {
edit_context_key: self.edit_context_key,
diff_set,
})
.unwrap();
}
self.tracked_assets.clear();
}
self.before_state = Default::default();
self.context_name = None;
}
}