Preserve exchange allocations during atomic replacement and reserve generated aliases

This commit is contained in:
2026-09-05 18:51:11 +03:00
parent 757ab88f53
commit e41b8de992
4 changed files with 404 additions and 37 deletions
+24 -7
View File
@@ -1,5 +1,6 @@
use crate::*;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use thiserror::Error;
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
@@ -135,7 +136,9 @@ impl Session {
}
pub fn execute(&mut self, command: Command) -> Result<Option<Id>, ModelError> {
let mut candidate = self.project.clone();
let selected = apply_command(&mut candidate, &command, 0)?;
let mut pending_deletions = BTreeSet::new();
let selected = apply_command(&mut candidate, &command, 0, &mut pending_deletions)?;
crate::references::finish_deletions(&mut candidate, &pending_deletions);
validate_candidate(&candidate)?;
if candidate != self.project {
self.past
@@ -161,9 +164,11 @@ pub fn validate_candidate(p: &Project) -> Result<(), ModelError> {
}
pub fn execute_batch(project: &Project, commands: &[Command]) -> Result<Project, ModelError> {
let mut candidate = project.clone();
let mut pending_deletions = BTreeSet::new();
for command in commands {
apply_command(&mut candidate, command, 0)?;
apply_command(&mut candidate, command, 0, &mut pending_deletions)?;
}
crate::references::finish_deletions(&mut candidate, &pending_deletions);
validate_candidate(&candidate)?;
Ok(candidate)
}
@@ -224,7 +229,12 @@ fn attach(p: &mut Project, id: Id) {
}
}
}
fn apply_command(p: &mut Project, c: &Command, depth: usize) -> Result<Option<Id>, ModelError> {
fn apply_command(
p: &mut Project,
c: &Command,
depth: usize,
pending_deletions: &mut BTreeSet<Id>,
) -> Result<Option<Id>, ModelError> {
if depth > 64 {
return Err(ModelError(
"Command batch nesting exceeds 64 levels.".into(),
@@ -233,12 +243,13 @@ fn apply_command(p: &mut Project, c: &Command, depth: usize) -> Result<Option<Id
let selected = match c {
Command::Replace(value) => {
*p = value.clone();
pending_deletions.clear();
None
}
Command::Batch(commands) => {
let mut selected = None;
for command in commands {
let id = apply_command(p, command, depth + 1)?;
let id = apply_command(p, command, depth + 1, pending_deletions)?;
if id.is_some() {
selected = id;
}
@@ -318,6 +329,9 @@ fn apply_command(p: &mut Project, c: &Command, depth: usize) -> Result<Option<Id
v.id = *old;
p.relations.insert(*old, v);
reidentify(p, *old, value.id)?;
if pending_deletions.remove(old) {
pending_deletions.insert(value.id);
}
Some(value.id)
}
Command::CreateDiagram(value) => {
@@ -438,6 +452,9 @@ fn apply_command(p: &mut Project, c: &Command, depth: usize) -> Result<Option<Id
}
Command::Reidentify(old, new) => {
reidentify(p, *old, *new)?;
if pending_deletions.remove(old) {
pending_deletions.insert(*new);
}
Some(*new)
}
Command::Rename(id, value) => {
@@ -469,7 +486,7 @@ fn apply_command(p: &mut Project, c: &Command, depth: usize) -> Result<Option<Id
items: vec![],
},
)?;
return apply_command(p, &Command::Batch(commands), depth + 1);
return apply_command(p, &Command::Batch(commands), depth + 1, pending_deletions);
}
Some(p.relate(*kind, name, *source, *target))
}
@@ -480,7 +497,7 @@ fn apply_command(p: &mut Project, c: &Command, depth: usize) -> Result<Option<Id
if !p.contains(*id) {
return Err(missing());
}
crate::references::delete_cascade(p, *id);
crate::references::delete_cascade_deferred(p, *id, pending_deletions);
None
}
Command::AddStep(owner, exchange) => {
@@ -519,7 +536,7 @@ fn apply_command(p: &mut Project, c: &Command, depth: usize) -> Result<Option<Id
.and_then(Element::steps)
.and_then(|s| s.get(*index))
.ok_or_else(|| ModelError("Step index is out of range.".into()))?;
crate::references::delete_cascade(p, id);
crate::references::delete_cascade_deferred(p, id, pending_deletions);
Some(*owner)
}
Command::MoveStep(owner, from, to) => {