use std::{sync::Arc};
use mf_model::{node_pool::NodePool, schema::Schema, tree::Tree};
use crate::TransformResult;
use super::step::{Step, StepResult};
#[derive(Debug, Clone)]
enum LazyDoc {
Original(Arc<NodePool>),
Pending { base: Arc<NodePool>, steps: im::Vector<Arc<dyn Step>> },
Computed(Arc<NodePool>),
}
#[derive(Debug, Clone)]
pub struct Transform {
pub base_doc: Arc<NodePool>,
lazy_doc: LazyDoc,
draft: Option<Tree>,
pub steps: im::Vector<Arc<dyn Step>>,
pub invert_steps: im::Vector<Arc<dyn Step>>,
pub schema: Arc<Schema>,
needs_recompute: bool,
}
impl Transform {
pub fn new(
doc: Arc<NodePool>,
schema: Arc<Schema>,
) -> Transform {
Transform {
base_doc: doc.clone(),
lazy_doc: LazyDoc::Original(doc),
draft: None,
steps: im::Vector::new(),
invert_steps: im::Vector::new(),
schema,
needs_recompute: false,
}
}
pub fn doc(&self) -> Arc<NodePool> {
match &self.lazy_doc {
LazyDoc::Original(doc) => doc.clone(),
LazyDoc::Computed(doc) => doc.clone(),
LazyDoc::Pending { base, steps } => {
self.compute_doc_state(base.clone(), steps.clone())
},
}
}
fn get_draft(&mut self) -> &mut Tree {
if self.draft.is_none() {
self.draft = Some(self.base_doc.get_inner().as_ref().clone());
}
self.draft.as_mut().unwrap()
}
pub fn step(
&mut self,
step: Arc<dyn Step>,
) -> TransformResult<()> {
let schema = self.schema.clone();
let draft = self.get_draft();
let result: StepResult = step.apply(draft, schema)?;
match result.failed {
Some(message) => Err(anyhow::anyhow!(message)),
None => {
self.add_step(step);
Ok(())
},
}
}
pub fn doc_changed(&self) -> bool {
!self.steps.is_empty()
}
fn add_step(
&mut self,
step: Arc<dyn Step>,
) {
if let Some(invert_step) = step.invert(&self.base_doc.get_inner()) {
self.invert_steps.push_back(invert_step);
}
self.steps.push_back(step.clone());
self.lazy_doc = LazyDoc::Pending {
base: self.base_doc.clone(),
steps: self.steps.clone(),
};
self.needs_recompute = true;
}
fn compute_doc_state(
&self,
base: Arc<NodePool>,
steps: im::Vector<Arc<dyn Step>>,
) -> Arc<NodePool> {
if steps.is_empty() {
return base;
}
if let Some(ref draft) = self.draft {
NodePool::new(Arc::new(draft.clone()))
} else {
base
}
}
pub fn apply_steps_batch(
&mut self,
steps: Vec<Arc<dyn Step>>,
) -> TransformResult<()> {
let schema = self.schema.clone();
let base_doc_inner = self.base_doc.get_inner().clone();
let mut new_invert_steps = Vec::new();
for step in &steps {
if let Some(invert_step) = step.invert(&base_doc_inner) {
new_invert_steps.push(invert_step);
}
}
let draft = self.get_draft();
for step in &steps {
let result = step.apply(draft, schema.clone())?;
if let Some(message) = result.failed {
return Err(anyhow::anyhow!(message));
}
}
for step in steps {
self.steps.push_back(step);
}
for invert_step in new_invert_steps {
self.invert_steps.push_back(invert_step);
}
self.lazy_doc = LazyDoc::Pending {
base: self.base_doc.clone(),
steps: self.steps.clone(),
};
self.needs_recompute = true;
Ok(())
}
pub fn commit(&mut self) {
if self.needs_recompute && self.draft.is_some() {
let new_doc =
NodePool::new(Arc::new(self.draft.as_ref().unwrap().clone()));
self.base_doc = new_doc.clone();
self.lazy_doc = LazyDoc::Computed(new_doc);
self.draft = None;
self.needs_recompute = false;
}
}
pub fn rollback(&mut self) {
self.lazy_doc = LazyDoc::Original(self.base_doc.clone());
self.draft = None;
self.steps = im::Vector::new();
self.invert_steps = im::Vector::new();
self.needs_recompute = false;
}
pub fn rollback_steps(
&mut self,
count: usize,
) -> TransformResult<()> {
if count > self.invert_steps.len() {
return Err(anyhow::anyhow!("回滚步骤数量超出历史记录"));
}
let schema = self.schema.clone();
let mut invert_steps_to_apply = Vec::new();
for _ in 0..count {
if let Some(invert_step) = self.invert_steps.pop_back() {
invert_steps_to_apply.push(invert_step);
self.steps.pop_back();
}
}
let draft = self.get_draft();
for invert_step in invert_steps_to_apply {
invert_step.apply(draft, schema.clone())?;
}
self.needs_recompute = true;
Ok(())
}
pub fn clear_history(&mut self) {
self.steps = im::Vector::new();
self.invert_steps = im::Vector::new();
}
pub fn history_size(&self) -> usize {
self.steps.len()
}
}