use crate::SheetId;
use crate::engine::addr::GridAddr;
use crate::engine::named_range::{NameScope, NamedDefinition};
use crate::engine::row_visibility::RowVisibilitySource;
use crate::engine::vertex::VertexId;
use crate::reference::CellRef;
use formualizer_common::LiteralValue;
use formualizer_parse::parser::ASTNode;
#[derive(Debug, Clone, PartialEq)]
pub struct SpillSnapshot {
pub target_cells: Vec<CellRef>,
pub values: Vec<Vec<LiteralValue>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ChangeEventMeta {
pub actor_id: Option<String>,
pub correlation_id: Option<String>,
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ChangeEvent {
SetValue {
addr: CellRef,
old_value: Option<LiteralValue>,
old_formula: Option<ASTNode>,
new: LiteralValue,
},
SetFormula {
addr: CellRef,
old_value: Option<LiteralValue>,
old_formula: Option<ASTNode>,
new: ASTNode,
},
SetRowVisibility {
sheet_id: SheetId,
row0: u32,
source: RowVisibilitySource,
old_hidden: bool,
new_hidden: bool,
},
AddVertex {
id: VertexId,
coord: GridAddr,
sheet_id: SheetId,
value: Option<LiteralValue>,
formula: Option<ASTNode>,
kind: Option<crate::engine::vertex::VertexKind>,
flags: Option<u8>,
},
RemoveVertex {
id: VertexId,
old_value: Option<LiteralValue>,
old_formula: Option<ASTNode>,
old_dependencies: Vec<VertexId>, old_dependents: Vec<VertexId>, coord: Option<GridAddr>,
sheet_id: Option<SheetId>,
kind: Option<crate::engine::vertex::VertexKind>,
flags: Option<u8>,
},
CompoundStart {
description: String, depth: usize,
},
CompoundEnd {
depth: usize,
},
VertexMoved {
id: VertexId,
sheet_id: SheetId,
old_coord: GridAddr,
new_coord: GridAddr,
},
FormulaAdjusted {
id: VertexId,
addr: Option<CellRef>,
old_ast: ASTNode,
new_ast: ASTNode,
},
NamedRangeAdjusted {
name: String,
scope: NameScope,
old_definition: NamedDefinition,
new_definition: NamedDefinition,
},
EdgeAdded {
from: VertexId,
to: VertexId,
},
EdgeRemoved {
from: VertexId,
to: VertexId,
},
DefineName {
name: String,
scope: NameScope,
definition: NamedDefinition,
},
UpdateName {
name: String,
scope: NameScope,
old_definition: NamedDefinition,
new_definition: NamedDefinition,
},
DeleteName {
name: String,
scope: NameScope,
old_definition: Option<NamedDefinition>,
},
SpillCommitted {
anchor: VertexId,
old: Option<SpillSnapshot>,
new: SpillSnapshot,
},
SpillCleared {
anchor: VertexId,
old: SpillSnapshot,
},
StagedFormulaCellChanged {
sheet: String,
row: u32,
col: u32,
old: Option<String>,
new: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct FormulaRunAdjusted {
pub(crate) first: VertexId,
pub(crate) len: u32,
pub(crate) sheet_id: SheetId,
pub(crate) col: u32,
pub(crate) row0: u32,
pub(crate) old_first: ASTNode,
pub(crate) new_first: ASTNode,
}
impl FormulaRunAdjusted {
#[inline]
pub(crate) fn len(&self) -> usize {
self.len as usize
}
pub(crate) fn event(&self, i: u32) -> ChangeEvent {
let relocate = |ast: &ASTNode| {
if i == 0 {
return ast.clone();
}
crate::engine::template::relocate::instantiate_member_ast(ast, i64::from(i), 0)
.expect("a run member's formula relocates")
};
ChangeEvent::FormulaAdjusted {
id: VertexId(self.first.0 + i),
addr: Some(CellRef::new(
self.sheet_id,
crate::reference::Coord::new(self.row0 + i, self.col, true, true),
)),
old_ast: relocate(&self.old_first),
new_ast: relocate(&self.new_first),
}
}
pub(crate) fn events(&self) -> impl Iterator<Item = ChangeEvent> + '_ {
(0..self.len).map(|i| self.event(i))
}
}
#[derive(Debug)]
struct LazyEntry {
pos: usize,
seq0: u64,
group: Option<u64>,
meta: ChangeEventMeta,
run: std::sync::Arc<FormulaRunAdjusted>,
}
#[derive(Debug, Default)]
struct Retained {
events: Vec<ChangeEvent>,
metas: Vec<ChangeEventMeta>,
seqs: Vec<u64>,
groups: Vec<Option<u64>>,
lazy: Vec<LazyEntry>,
}
impl Retained {
fn expand(&mut self) -> usize {
let Some(first) = self.lazy.first() else {
return 0;
};
let start = first.pos;
let tail_events = self.events.split_off(start);
let tail_metas = self.metas.split_off(start);
let tail_seqs = self.seqs.split_off(start);
let tail_groups = self.groups.split_off(start);
let tail_len = tail_events.len();
let added: usize = self.lazy.iter().map(|e| e.run.len()).sum();
self.events.reserve(tail_len + added);
self.metas.reserve(tail_len + added);
self.seqs.reserve(tail_len + added);
self.groups.reserve(tail_len + added);
let mut tail = tail_events
.into_iter()
.zip(tail_metas)
.zip(tail_seqs.into_iter().zip(tail_groups));
let mut p = start;
for entry in std::mem::take(&mut self.lazy) {
while p < entry.pos {
let ((event, meta), (seq, group)) = tail.next().expect("record position in range");
self.events.push(event);
self.metas.push(meta);
self.seqs.push(seq);
self.groups.push(group);
p += 1;
}
for (i, event) in entry.run.events().enumerate() {
self.events.push(event);
self.metas.push(entry.meta.clone());
self.seqs.push(entry.seq0 + i as u64);
self.groups.push(entry.group);
}
}
for ((event, meta), (seq, group)) in tail {
self.events.push(event);
self.metas.push(meta);
self.seqs.push(seq);
self.groups.push(group);
}
tail_len + added
}
}
struct RetainedCell {
inner: std::cell::UnsafeCell<Retained>,
expanded: std::sync::OnceLock<()>,
len: usize,
pending_records: usize,
#[cfg(test)]
work: std::sync::atomic::AtomicUsize,
}
unsafe impl Sync for RetainedCell {}
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Retained>();
};
impl Default for RetainedCell {
fn default() -> Self {
Self {
inner: std::cell::UnsafeCell::new(Retained::default()),
expanded: std::sync::OnceLock::from(()),
len: 0,
pending_records: 0,
#[cfg(test)]
work: std::sync::atomic::AtomicUsize::new(0),
}
}
}
impl std::fmt::Debug for RetainedCell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RetainedCell")
.field("len", &self.len)
.field("expanded", &self.expanded.get().is_some())
.finish()
}
}
impl RetainedCell {
#[inline]
fn note_work(&self, _work: usize) {
#[cfg(test)]
self.work
.fetch_add(_work, std::sync::atomic::Ordering::Relaxed);
}
fn get(&self) -> &Retained {
self.expanded.get_or_init(|| {
let inner = unsafe { &mut *self.inner.get() };
self.note_work(inner.expand());
});
unsafe { &*self.inner.get() }
}
fn settle(&mut self) -> &mut Retained {
if self.expanded.get().is_none() {
let work = self.inner.get_mut().expand();
self.note_work(work);
self.expanded = std::sync::OnceLock::from(());
self.pending_records = 0;
}
self.inner.get_mut()
}
fn plain_len(&mut self) -> usize {
self.inner.get_mut().events.len()
}
fn push(&mut self, event: ChangeEvent, meta: ChangeEventMeta, seq: u64, group: Option<u64>) {
let r = self.inner.get_mut();
r.events.push(event);
r.metas.push(meta);
r.seqs.push(seq);
r.groups.push(group);
self.len += 1;
}
fn push_run(&mut self, entry: LazyEntry) {
self.len += entry.run.len();
self.inner.get_mut().lazy.push(entry);
self.pending_records += 1;
self.expanded = std::sync::OnceLock::new();
}
fn drain_front(&mut self, n: usize) {
let r = self.settle();
r.events.drain(0..n);
r.metas.drain(0..n);
r.seqs.drain(0..n);
r.groups.drain(0..n);
self.len = r.events.len();
}
fn truncate(&mut self, len: usize) {
let r = self.settle();
r.events.truncate(len);
r.metas.truncate(len);
r.seqs.truncate(len);
r.groups.truncate(len);
self.len = r.events.len();
}
fn split_off(&mut self, index: usize) -> Vec<ChangeEvent> {
let r = self.settle();
let events = r.events.split_off(index);
let _ = r.metas.split_off(index);
let _ = r.seqs.split_off(index);
let _ = r.groups.split_off(index);
self.len = r.events.len();
events
}
fn clear(&mut self) {
*self = Self {
#[cfg(test)]
work: std::sync::atomic::AtomicUsize::new(
self.work.load(std::sync::atomic::Ordering::Relaxed),
),
..Self::default()
};
}
}
#[derive(Debug, Default)]
pub struct ChangeLog {
enabled: bool,
max_changelog_events: Option<usize>,
compound_depth: usize,
next_seq: u64,
group_stack: Vec<u64>,
next_group_id: u64,
current_meta: ChangeEventMeta,
retained: RetainedCell,
}
#[derive(Debug)]
pub(crate) struct MutationCapture {
events: Vec<ChangeEvent>,
lazy: Vec<(usize, std::sync::Arc<FormulaRunAdjusted>)>,
compound_depth: usize,
current_meta: ChangeEventMeta,
}
impl MutationCapture {
pub(crate) fn new(current_meta: ChangeEventMeta) -> Self {
Self {
events: Vec::new(),
lazy: Vec::new(),
compound_depth: 0,
current_meta,
}
}
pub(crate) fn len(&self) -> usize {
self.events.len()
}
pub(crate) fn events(&self) -> &[ChangeEvent] {
&self.events
}
pub(crate) fn lazy_len(&self) -> usize {
self.lazy.len()
}
pub(crate) fn record_lazy(&mut self, run: FormulaRunAdjusted) {
if run.len == 0 {
return;
}
self.lazy
.push((self.events.len(), std::sync::Arc::new(run)));
}
pub(crate) fn expanded_events_from(&self, start: usize, lazy_start: usize) -> Vec<ChangeEvent> {
let lazy = &self.lazy[lazy_start.min(self.lazy.len())..];
let mut out = Vec::with_capacity(
self.events.len().saturating_sub(start)
+ lazy.iter().map(|(_, r)| r.len()).sum::<usize>(),
);
let mut k = 0;
for p in start..=self.events.len() {
while k < lazy.len() && lazy[k].0 <= p {
out.extend(lazy[k].1.events());
k += 1;
}
if let Some(e) = self.events.get(p) {
out.push(e.clone());
}
}
out
}
pub(crate) fn close_compounds(&mut self) {
while self.compound_depth > 0 {
self.end_compound();
}
}
fn push(&mut self, event: ChangeEvent) {
self.events.push(event);
}
}
pub(crate) fn compound_start_description<'a>(
end: usize,
event: impl Fn(usize) -> &'a ChangeEvent,
) -> Option<&'a str> {
if !matches!(event(end), ChangeEvent::CompoundEnd { .. }) {
return None;
}
let mut open = 1usize;
for i in (0..end).rev() {
match event(i) {
ChangeEvent::CompoundEnd { .. } => open += 1,
ChangeEvent::CompoundStart { description, .. } => {
open -= 1;
if open == 0 {
return Some(description.as_str());
}
}
_ => {}
}
}
None
}
impl ChangeLogger for MutationCapture {
fn record(&mut self, event: ChangeEvent) {
self.push(event);
}
fn set_enabled(&mut self, _: bool) {}
fn begin_compound(&mut self, description: String) {
self.compound_depth += 1;
self.push(ChangeEvent::CompoundStart {
description,
depth: self.compound_depth,
});
}
fn end_compound(&mut self) {
if self.compound_depth == 0 {
return;
}
self.push(ChangeEvent::CompoundEnd {
depth: self.compound_depth,
});
self.compound_depth -= 1;
}
}
impl ChangeLog {
pub fn new() -> Self {
Self {
enabled: true,
max_changelog_events: None,
compound_depth: 0,
next_seq: 0,
group_stack: Vec::new(),
next_group_id: 1,
current_meta: ChangeEventMeta::default(),
retained: RetainedCell::default(),
}
}
pub fn with_max_changelog_events(max: usize) -> Self {
let mut out = Self::new();
out.max_changelog_events = Some(max);
out
}
pub fn set_max_changelog_events(&mut self, max: Option<usize>) {
self.max_changelog_events = max;
self.enforce_cap();
}
fn enforce_cap(&mut self) {
let Some(max) = self.max_changelog_events else {
return;
};
if max == 0 {
self.retained.clear();
return;
}
if self.len() <= max {
return;
}
let drop_n = self.len() - max;
self.retained.drain_front(drop_n);
}
fn replay_lazy(
&mut self,
run: std::sync::Arc<FormulaRunAdjusted>,
meta: &ChangeEventMeta,
retain: bool,
) {
if !self.enabled {
return;
}
let seq0 = self.next_seq;
self.next_seq += run.len as u64;
if retain {
let entry = LazyEntry {
pos: self.retained.plain_len(),
seq0,
group: self.group_stack.last().copied(),
meta: meta.clone(),
run,
};
self.retained.push_run(entry);
}
}
fn replay_record(&mut self, event: ChangeEvent, meta: &ChangeEventMeta, retain: bool) {
if !self.enabled {
return;
}
let seq = self.next_seq;
self.next_seq += 1;
if retain {
let group = self.group_stack.last().copied();
self.retained.push(event, meta.clone(), seq, group);
}
}
fn replay_begin_compound(&mut self, description: String, meta: &ChangeEventMeta, retain: bool) {
self.compound_depth += 1;
if self.compound_depth == 1 {
let gid = self.next_group_id;
self.next_group_id += 1;
self.group_stack.push(gid);
} else if let Some(&gid) = self.group_stack.last() {
self.group_stack.push(gid);
}
self.replay_record(
ChangeEvent::CompoundStart {
description,
depth: self.compound_depth,
},
meta,
retain,
);
}
fn replay_end_compound(&mut self, meta: &ChangeEventMeta, retain: bool) {
if self.compound_depth == 0 {
return;
}
self.replay_record(
ChangeEvent::CompoundEnd {
depth: self.compound_depth,
},
meta,
retain,
);
self.compound_depth -= 1;
self.group_stack.pop();
}
fn replay_capture(&mut self, capture: MutationCapture, retain: bool) {
let mut lazy = capture.lazy.into_iter().peekable();
for (p, event) in capture.events.into_iter().enumerate() {
while let Some((_, run)) = lazy.next_if(|(pos, _)| *pos <= p) {
self.replay_lazy(run, &capture.current_meta, retain);
}
match event {
ChangeEvent::CompoundStart { description, .. } => {
self.replay_begin_compound(description, &capture.current_meta, retain);
}
ChangeEvent::CompoundEnd { .. } => {
self.replay_end_compound(&capture.current_meta, retain);
}
event => self.replay_record(event, &capture.current_meta, retain),
}
}
for (_, run) in lazy {
self.replay_lazy(run, &capture.current_meta, retain);
}
if retain {
self.enforce_cap();
}
}
pub(crate) fn current_meta(&self) -> ChangeEventMeta {
self.current_meta.clone()
}
pub(crate) fn publish_capture(&mut self, capture: MutationCapture) {
self.replay_capture(capture, true);
}
pub(crate) fn discard_capture(&mut self, capture: MutationCapture) {
self.replay_capture(capture, false);
}
pub fn record(&mut self, event: ChangeEvent) {
if self.enabled {
let seq = self.next_seq;
self.next_seq += 1;
let current_group = self.group_stack.last().copied();
self.retained
.push(event, self.current_meta.clone(), seq, current_group);
self.enforce_cap();
}
}
pub fn record_with_meta(&mut self, event: ChangeEvent, meta: ChangeEventMeta) {
if self.enabled {
let seq = self.next_seq;
self.next_seq += 1;
let current_group = self.group_stack.last().copied();
self.retained.push(event, meta, seq, current_group);
self.enforce_cap();
}
}
pub fn begin_compound(&mut self, description: String) {
self.compound_depth += 1;
if self.compound_depth == 1 {
let gid = self.next_group_id;
self.next_group_id += 1;
self.group_stack.push(gid);
} else {
if let Some(&gid) = self.group_stack.last() {
self.group_stack.push(gid);
}
}
if self.enabled {
self.record(ChangeEvent::CompoundStart {
description,
depth: self.compound_depth,
});
}
}
pub fn end_compound(&mut self) {
if self.compound_depth > 0 {
if self.enabled {
self.record(ChangeEvent::CompoundEnd {
depth: self.compound_depth,
});
}
self.compound_depth -= 1;
self.group_stack.pop();
}
}
#[cfg(test)]
pub(crate) fn unexpanded_run_records(&self) -> usize {
if self.retained.expanded.get().is_some() {
0
} else {
self.retained.pending_records
}
}
#[cfg(test)]
pub(crate) fn expansion_work(&self) -> usize {
self.retained
.work
.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn events(&self) -> &[ChangeEvent] {
&self.retained.get().events
}
pub fn event_meta(&self, index: usize) -> Option<&ChangeEventMeta> {
self.retained.get().metas.get(index)
}
pub fn set_actor_id(&mut self, actor_id: Option<String>) {
self.current_meta.actor_id = actor_id;
}
pub fn set_correlation_id(&mut self, correlation_id: Option<String>) {
self.current_meta.correlation_id = correlation_id;
}
pub fn set_reason(&mut self, reason: Option<String>) {
self.current_meta.reason = reason;
}
pub fn truncate(&mut self, len: usize) {
self.retained.truncate(len);
}
pub fn clear(&mut self) {
self.retained.clear();
self.compound_depth = 0;
self.group_stack.clear();
}
pub fn len(&self) -> usize {
self.retained.len
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn take_from(&mut self, index: usize) -> Vec<ChangeEvent> {
self.retained.split_off(index)
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn compound_depth(&self) -> usize {
self.compound_depth
}
pub fn meta(&self, index: usize) -> Option<(u64, Option<u64>)> {
let r = self.retained.get();
r.seqs.get(index).copied().zip(r.groups.get(index).copied())
}
pub fn last_group_indices(&self) -> Vec<usize> {
let groups = &self.retained.get().groups;
if let Some(&last_gid) = groups.iter().rev().flatten().next() {
let idxs: Vec<usize> = groups
.iter()
.enumerate()
.filter_map(|(i, g)| if *g == Some(last_gid) { Some(i) } else { None })
.collect();
if !idxs.is_empty() {
return idxs;
}
}
self.len().checked_sub(1).into_iter().collect()
}
}
pub trait ChangeLogger {
fn record(&mut self, event: ChangeEvent);
fn set_enabled(&mut self, enabled: bool);
fn begin_compound(&mut self, description: String);
fn end_compound(&mut self);
}
impl ChangeLogger for ChangeLog {
fn record(&mut self, event: ChangeEvent) {
ChangeLog::record(self, event);
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn begin_compound(&mut self, description: String) {
ChangeLog::begin_compound(self, description);
}
fn end_compound(&mut self) {
ChangeLog::end_compound(self);
}
}
pub struct NullChangeLogger;
impl ChangeLogger for NullChangeLogger {
fn record(&mut self, _: ChangeEvent) {}
fn set_enabled(&mut self, _: bool) {}
fn begin_compound(&mut self, _: String) {}
fn end_compound(&mut self) {}
}