use serde_json::Value;
const MAX_UNDO: usize = 100;
#[derive(Debug, Clone)]
struct Snapshot {
request: Value,
rollback: usize,
parts_library: std::rc::Rc<Value>,
}
#[derive(Debug, Clone)]
pub struct History {
request: Value,
rollback: usize,
undo_stack: Vec<Snapshot>,
redo_stack: Vec<Snapshot>,
last_edit_key: Option<String>,
feature_counter: u64,
parts_library: std::rc::Rc<Value>,
parts_library_mirrors_store: bool,
}
impl Default for History {
fn default() -> Self {
Self {
request: empty_request(),
rollback: 0,
undo_stack: Vec::new(),
redo_stack: Vec::new(),
last_edit_key: None,
feature_counter: 0,
parts_library: std::rc::Rc::new(Value::Null),
parts_library_mirrors_store: false,
}
}
}
fn empty_request() -> Value {
serde_json::json!({ "expressions": "", "configurator": {}, "features": [] })
}
fn trailing_number(id: &str) -> u64 {
let digit_bytes = id
.bytes()
.rev()
.take_while(u8::is_ascii_digit)
.count();
id[id.len() - digit_bytes..].parse().unwrap_or(0)
}
impl History {
pub fn from_request_json(json: &str) -> Result<Self, String> {
let mut request: Value =
serde_json::from_str(json).map_err(|e| format!("history parse: {e}"))?;
if !request.get("features").map(Value::is_array).unwrap_or(false) {
if let Some(obj) = request.as_object_mut() {
obj.insert("features".into(), Value::Array(Vec::new()));
} else {
request = empty_request();
}
}
let stored = request
.as_object_mut()
.and_then(|obj| obj.remove("featureCounter"))
.and_then(|value| value.as_u64());
let parts_library = request
.as_object_mut()
.and_then(|obj| obj.remove("partsLibrary"))
.unwrap_or(Value::Null);
let mut history = Self {
request,
rollback: 0,
parts_library: std::rc::Rc::new(parts_library),
..Self::default()
};
history.rollback = history.len().saturating_sub(1);
history.feature_counter = stored.unwrap_or_else(|| history.max_id_suffix());
Ok(history)
}
fn max_id_suffix(&self) -> u64 {
self.features()
.iter()
.filter_map(|f| {
f.get("inputParams")
.and_then(|p| p.get("id"))
.and_then(Value::as_str)
})
.map(trailing_number)
.max()
.unwrap_or(0)
}
pub fn features(&self) -> &[Value] {
self.request
.get("features")
.and_then(Value::as_array)
.map(Vec::as_slice)
.unwrap_or(&[])
}
fn features_mut(&mut self) -> &mut Vec<Value> {
let obj = self
.request
.as_object_mut()
.expect("history request is a JSON object");
obj.entry("features")
.or_insert_with(|| Value::Array(Vec::new()));
obj.get_mut("features")
.and_then(Value::as_array_mut)
.expect("features is a JSON array")
}
pub fn len(&self) -> usize {
self.features().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn rollback(&self) -> usize {
self.rollback.min(self.len().saturating_sub(1))
}
pub fn set_rollback(&mut self, index: usize) {
self.rollback = if self.is_empty() {
0
} else {
index.min(self.len() - 1)
};
self.last_edit_key = None;
}
pub fn feature_type(&self, index: usize) -> Option<String> {
self.features()
.get(index)?
.get("type")
.and_then(Value::as_str)
.map(String::from)
}
pub fn feature_id(&self, index: usize) -> Option<String> {
self.features()
.get(index)?
.get("inputParams")
.and_then(|p| p.get("id"))
.and_then(Value::as_str)
.map(String::from)
}
pub fn index_of(&self, id: &str) -> Option<usize> {
self.features().iter().position(|f| {
f.get("inputParams")
.and_then(|p| p.get("id"))
.and_then(Value::as_str)
== Some(id)
})
}
pub fn feature_params(&self, index: usize) -> Option<Value> {
self.features().get(index)?.get("inputParams").cloned()
}
pub fn fold_param_no_undo(&mut self, id: &str, key: &str, value: Value) -> bool {
let Some(index) = self.index_of(id) else {
return false;
};
if let Some(feature) = self.features_mut().get_mut(index) {
if let Some(params) = feature
.get_mut("inputParams")
.and_then(Value::as_object_mut)
{
params.insert(key.to_string(), value);
return true;
}
}
false
}
pub fn set_feature_params(&mut self, index: usize, params: Value) {
if index >= self.len() {
return;
}
self.checkpoint(Some(&format!("param:{index}")));
if let Some(feat) = self.features_mut().get_mut(index) {
if let Some(obj) = feat.as_object_mut() {
obj.insert("inputParams".into(), params);
}
}
}
pub fn set_many_feature_params(&mut self, edits: &[(usize, Value)]) {
let len = self.len();
let mut in_range: Vec<&(usize, Value)> =
edits.iter().filter(|(index, _)| *index < len).collect();
if in_range.is_empty() {
return;
}
in_range.sort_by_key(|(index, _)| *index);
let key: Vec<String> = in_range
.iter()
.map(|(index, _)| index.to_string())
.collect();
self.checkpoint(Some(&format!("params:{}", key.join(","))));
for (index, params) in in_range {
if let Some(feature) = self.features_mut().get_mut(*index) {
if let Some(object) = feature.as_object_mut() {
object.insert("inputParams".into(), params.clone());
}
}
}
}
pub fn push_feature(&mut self, feature: Value) {
self.checkpoint(None);
self.features_mut().push(feature);
}
pub fn push_features(&mut self, features: Vec<Value>) {
if features.is_empty() {
return;
}
self.checkpoint(None);
self.features_mut().extend(features);
}
pub fn remove_feature(&mut self, index: usize) {
if index >= self.len() {
return;
}
self.checkpoint(None);
self.features_mut().remove(index);
}
pub fn swap(&mut self, a: usize, b: usize) {
let len = self.len();
if a < len && b < len && a != b {
self.checkpoint(None);
self.features_mut().swap(a, b);
}
}
pub fn next_feature_id(&mut self, base: &str) -> String {
self.feature_counter += 1;
format!("{base}{}", self.feature_counter)
}
pub fn prefix_request(&self) -> Value {
let mut request = self.request.clone();
if let Some(id) = self.feature_id(self.rollback()) {
if let Some(obj) = request.as_object_mut() {
obj.insert("stopAtId".into(), Value::String(id));
}
}
request
}
pub fn listing_json(&self) -> String {
let features: Vec<Value> = self
.features()
.iter()
.enumerate()
.map(|(index, _)| {
serde_json::json!({
"index": index,
"type": self.feature_type(index).unwrap_or_else(|| "?".into()),
"id": self.feature_id(index).unwrap_or_else(|| "(no id)".into()),
})
})
.collect();
serde_json::json!({ "step": self.rollback(), "features": features }).to_string()
}
pub fn request_json(&self) -> String {
let has_library = self
.parts_library
.as_object()
.map(|map| !map.is_empty())
.unwrap_or(false);
if self.feature_counter == 0 && !has_library {
return self.request.to_string();
}
let mut document = self.request.clone();
if let Some(obj) = document.as_object_mut() {
if self.feature_counter != 0 {
obj.insert("featureCounter".into(), Value::from(self.feature_counter));
}
if has_library {
obj.insert("partsLibrary".into(), (*self.parts_library).clone());
}
}
document.to_string()
}
pub fn request_json_without_parts_library(&self) -> String {
if self.feature_counter == 0 {
return self.request.to_string();
}
let mut document = self.request.clone();
if let Some(obj) = document.as_object_mut() {
obj.insert("featureCounter".into(), Value::from(self.feature_counter));
}
document.to_string()
}
pub fn adopt_folded_request(&mut self, json: &str) -> Result<(), String> {
let mut value: Value = serde_json::from_str(json)
.map_err(|error| format!("folded document parse: {error}"))?;
let Some(object) = value.as_object_mut() else {
return Err("folded document must be an object".to_string());
};
object.remove("featureCounter");
let adopted_library = object.remove("partsLibrary");
if !object.contains_key("features") {
object.insert("features".into(), Value::Array(Vec::new()));
}
self.request = value;
if let Some(library) = adopted_library {
self.set_parts_library(library);
self.parts_library_mirrors_store = false;
}
let last = self.len().saturating_sub(1);
self.rollback = self.rollback.min(last);
Ok(())
}
fn snapshot(&self) -> Snapshot {
Snapshot {
request: self.request.clone(),
rollback: self.rollback,
parts_library: self.parts_library.clone(),
}
}
fn restore(&mut self, snap: Snapshot) {
self.request = snap.request;
self.parts_library = snap.parts_library;
self.parts_library_mirrors_store = false;
let last = self.len().saturating_sub(1);
self.rollback = snap.rollback.min(last);
}
fn checkpoint(&mut self, coalesce_key: Option<&str>) {
if coalesce_key.is_some() && coalesce_key == self.last_edit_key.as_deref() {
return;
}
self.undo_stack.push(self.snapshot());
if self.undo_stack.len() > MAX_UNDO {
self.undo_stack.remove(0);
}
self.redo_stack.clear();
self.last_edit_key = coalesce_key.map(str::to_string);
}
pub fn can_undo(&self) -> bool {
!self.undo_stack.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo_stack.is_empty()
}
pub fn undo(&mut self) -> bool {
let Some(prev) = self.undo_stack.pop() else {
return false;
};
self.redo_stack.push(self.snapshot());
self.restore(prev);
self.last_edit_key = None;
true
}
pub fn redo(&mut self) -> bool {
let Some(next) = self.redo_stack.pop() else {
return false;
};
self.undo_stack.push(self.snapshot());
self.restore(next);
self.last_edit_key = None;
true
}
}
impl History {
pub fn adopt_document(&mut self, document_json: &str) -> Result<(), String> {
let mut document: Value = serde_json::from_str(document_json)
.map_err(|error| format!("adopt document parse: {error}"))?;
if !document.is_object() {
return Err("adopt document: not a JSON object".into());
}
let mut adopted_library = None;
if let Some(obj) = document.as_object_mut() {
obj.remove("featureCounter");
adopted_library = obj.remove("partsLibrary");
if !obj.get("features").map(Value::is_array).unwrap_or(false) {
obj.insert("features".into(), Value::Array(Vec::new()));
}
}
self.request = document;
if let Some(library) = adopted_library {
self.set_parts_library(library);
self.parts_library_mirrors_store = false;
}
Ok(())
}
pub fn adopt_document_checkpointed(&mut self, document_json: &str) -> Result<(), String> {
let probe: Value = serde_json::from_str(document_json)
.map_err(|error| format!("adopt document parse: {error}"))?;
if !probe.is_object() {
return Err("adopt document: not a JSON object".into());
}
self.checkpoint(None);
self.adopt_document(document_json)
}
pub fn set_parts_library(&mut self, library: Value) {
let empty = library.as_object().map(|m| m.is_empty()).unwrap_or(true);
self.parts_library = std::rc::Rc::new(if empty { Value::Null } else { library });
self.parts_library_mirrors_store = true;
}
pub fn set_parts_library_edited(&mut self, library: Value, coalesce_key: Option<&str>) {
self.checkpoint(coalesce_key);
let empty = library.as_object().map(|m| m.is_empty()).unwrap_or(true);
self.parts_library = std::rc::Rc::new(if empty { Value::Null } else { library });
self.parts_library_mirrors_store = false;
}
pub fn parts_library_mirrors_store(&self) -> bool {
self.parts_library_mirrors_store
}
pub fn parts_library(&self) -> &Value {
&self.parts_library
}
pub fn assembly_block(&self) -> Option<&Value> {
self.request.get("assembly")
}
pub fn wire_harness_block(&self) -> Option<&Value> {
self.request.get("wireHarness")
}
pub fn set_wire_harness_block(&mut self, block: Option<Value>) {
self.checkpoint(None);
if let Some(object) = self.request.as_object_mut() {
match block {
Some(block) => {
object.insert("wireHarness".into(), block);
}
None => {
object.remove("wireHarness");
}
}
}
}
}
impl History {
pub fn pmi_block(&self) -> Option<&Value> {
self.request.get("pmi")
}
pub fn set_pmi_block(&mut self, block: Option<Value>, coalesce_key: Option<&str>) {
self.checkpoint(coalesce_key);
self.put_pmi_block(block);
}
pub fn set_pmi_block_no_undo(&mut self, block: Option<Value>) {
self.put_pmi_block(block);
}
fn put_pmi_block(&mut self, block: Option<Value>) {
if let Some(object) = self.request.as_object_mut() {
match block {
Some(block) => {
object.insert("pmi".into(), block);
}
None => {
object.remove("pmi");
}
}
}
}
pub fn break_coalescing(&mut self) {
self.last_edit_key = None;
}
}
impl History {
pub fn part_attributes_block(&self) -> Option<&Value> {
self.request.get(crate::engine_state::PART_ATTRIBUTES)
}
pub fn set_part_attributes_block(&mut self, block: Option<Value>, coalesce_key: Option<&str>) {
self.checkpoint(coalesce_key);
if let Some(object) = self.request.as_object_mut() {
match block {
Some(block) => {
object.insert(crate::engine_state::PART_ATTRIBUTES.into(), block);
}
None => {
object.remove(crate::engine_state::PART_ATTRIBUTES);
}
}
}
}
}
impl History {
pub fn expressions(&self) -> String {
self.request
.get("expressions")
.and_then(Value::as_str)
.unwrap_or("")
.to_string()
}
pub fn set_expressions(&mut self, expressions: &str) {
self.checkpoint(Some("expressions"));
if let Some(obj) = self.request.as_object_mut() {
obj.insert(
"expressions".into(),
Value::String(expressions.to_string()),
);
}
}
pub fn configurator(&self) -> Value {
self.request
.get("configurator")
.cloned()
.unwrap_or_else(|| Value::Object(serde_json::Map::new()))
}
}
impl History {
pub fn feature_persistent_data(&self, index: usize) -> Option<Value> {
self.features().get(index)?.get("persistentData").cloned()
}
pub fn set_feature_persistent_field(&mut self, index: usize, key: &str, value: Value) {
self.set_feature_persistent_field_coalesced(index, key, value, None);
}
pub fn set_feature_persistent_field_coalesced(
&mut self,
index: usize,
key: &str,
value: Value,
coalesce_key: Option<&str>,
) {
if index >= self.len() {
return;
}
self.checkpoint(coalesce_key);
if let Some(feat) = self.features_mut().get_mut(index).and_then(Value::as_object_mut) {
let entry = feat
.entry("persistentData")
.or_insert_with(|| Value::Object(serde_json::Map::new()));
if !entry.is_object() {
*entry = Value::Object(serde_json::Map::new());
}
if let Some(obj) = entry.as_object_mut() {
obj.insert(key.to_string(), value);
}
}
}
}