use super::size;
use super::splice::{self, Resolved};
use super::writes::{At, Lookup, Mark, Writes};
use crate::base::Change;
use crate::expr::arena;
use crate::expr::path;
use crate::expr::{Filled, Path, Seg};
use bumpalo::Bump;
use datavalue::{DataValue, OwnedDataValue};
use self_cell::self_cell;
use std::borrow::Cow;
use std::marker::PhantomData;
pub(crate) struct Owner<'p> {
pub(crate) ctx: OwnedDataValue,
pub(crate) arena: Bump,
_program: PhantomData<&'p ()>,
}
pub(crate) type Root<'q> = &'q DataValue<'q>;
self_cell!(
pub(crate) struct RunView<'p> {
owner: Owner<'p>,
#[covariant]
dependent: Root,
}
);
#[derive(Clone, Copy, Debug)]
pub(crate) struct Tuning {
pub(crate) invalidate: bool,
pub(crate) invalidate_floor: usize,
pub(crate) garbage_floor: usize,
}
impl Default for Tuning {
fn default() -> Self {
Self {
invalidate: true,
invalidate_floor: 64 << 10,
garbage_floor: 64 << 10,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct Stats {
pub(crate) views: u32,
pub(crate) checkpoints: u32,
pub(crate) invalidations: u32,
pub(crate) converted: u32,
pub(crate) live_max: usize,
pub(crate) peak_arena: usize,
}
enum State<'p> {
Owned(Owner<'p>),
Viewed(RunView<'p>),
Taken,
}
#[derive(Clone, Copy)]
pub(crate) struct Reader<'r> {
pub(crate) root: &'r DataValue<'r>,
pub(crate) owned: &'r OwnedDataValue,
pub(crate) log: &'r Writes,
}
impl<'r> Reader<'r> {
pub(crate) fn get(self, path: &Path) -> Option<Cow<'r, OwnedDataValue>> {
self.find(path).map(Filled::into_cow)
}
pub(crate) fn get_resolved(self, path: &Path, r: Resolved) -> Option<Cow<'r, OwnedDataValue>> {
self.find_resolved(path, r).map(Filled::into_cow)
}
pub(crate) fn find(self, path: &Path) -> Option<Filled<'r>> {
if self.log.is_empty() {
return path.get(self.owned).map(Filled::lent);
}
let r = splice::resolve(*self.root, path.segs())?;
self.find_resolved(path, r)
}
fn find_resolved(self, path: &Path, r: Resolved) -> Option<Filled<'r>> {
match self.log.lookup(At::new(path.segs(), r.kinds)) {
Lookup::Clean => path.get(self.owned).map(Filled::lent),
Lookup::Owned(v) => v.map(Filled::lent),
Lookup::Dirty => splice::get(*self.root, path.segs()).map(Filled::View),
}
}
}
pub(crate) struct RunContext<'m, 'p> {
slot: &'m mut OwnedDataValue,
state: State<'p>,
pub(super) log: Writes,
live: usize,
tuning: Tuning,
stats: Stats,
}
pub(crate) struct Edit<'e, 'q, 'p> {
owner: &'q Owner<'p>,
root: &'e mut Root<'q>,
log: &'e mut Writes,
}
impl<'q, 'p> Edit<'_, 'q, 'p> {
fn mark_new(
&mut self,
before: DataValue<'q>,
at: At<'_>,
created: Option<usize>,
leaf: Option<OwnedDataValue>,
) {
let Some(i) = created else {
return self.log.mark(at, Mark::Set(leaf));
};
let below = |v| path::chain(&at.segs()[i + 1..], v);
if let Some(j) = at.index(i)
&& let Some(DataValue::Array(items)) = splice::get(before, &at.segs()[..i])
&& j >= items.len()
{
let from = items.len();
let owned = leaf.map(|v| {
let mut tail = vec![OwnedDataValue::Null; j - from];
tail.push(below(v));
tail
});
return self.log.mark(at.prefix(i), Mark::Appended { from, owned });
}
self.log.mark(at.prefix(i + 1), Mark::Set(leaf.map(below)));
}
pub(crate) fn owner(&self) -> &'q Owner<'p> {
self.owner
}
pub(crate) fn arena(&self) -> &'q Bump {
&self.owner.arena
}
pub(crate) fn root(&self) -> DataValue<'q> {
**self.root
}
pub(crate) fn root_ref(&self) -> &'q DataValue<'q> {
self.root
}
pub(crate) fn get(&self, path: &Path) -> Option<DataValue<'q>> {
splice::get(self.root(), path.segs())
}
pub(crate) fn set(
&mut self,
path: &Path,
value: DataValue<'q>,
owned: Option<OwnedDataValue>,
) -> bool {
self.set_segs(path.segs(), value, owned).0
}
pub(crate) fn set_segs(
&mut self,
segs: &[Seg],
value: DataValue<'q>,
owned: Option<OwnedDataValue>,
) -> (bool, Option<DataValue<'q>>) {
let before = self.root();
let Some((after, r, old)) = splice::set_resolved(self.arena(), before, segs, value) else {
return (false, None);
};
*self.root = self.arena().alloc(after);
self.mark_new(before, At::new(segs, r.kinds), r.created, owned);
(true, old)
}
pub(crate) fn overwrite<'v, const N: usize>(
&mut self,
object: &Path,
values: impl FnOnce(
&'q [(&'q str, DataValue<'q>)],
) -> Option<[(&'v Path, Option<DataValue<'q>>); N]>,
) -> bool {
let before = self.root();
let arena = self.arena();
let Some((after, kinds, mark_at)) =
splice::update_object(arena, before, object.segs(), |arena, pairs| {
let values = values(pairs)?;
debug_assert!(
pairs.len() == N
&& pairs.iter().zip(&values).all(|((k, _), (leaf, _))| {
matches!(leaf.segs().split_last(),
Some((last, parent)) if parent == object.segs() && *last.key == **k)
}),
"`values` names the object's keys, in its order"
);
if values.iter().all(|(_, value)| value.is_none()) {
return Some((pairs, None));
}
let updated = arena
.alloc_slice_fill_with(N, |i| (pairs[i].0, values[i].1.unwrap_or(pairs[i].1)));
let mut changed = values.iter().filter(|(_, value)| value.is_some());
let leaf = match (changed.next(), changed.next()) {
(Some((leaf, _)), None) => Some(*leaf),
_ => None,
};
let each_once = || (1..N).all(|i| pairs[..i].iter().all(|(k, _)| *k != pairs[i].0));
let at = match leaf {
Some(leaf) if each_once() => leaf,
_ => object,
};
Some((updated, Some(at)))
})
else {
return false;
};
if let Some(at) = mark_at {
*self.root = arena.alloc(after);
self.log.mark(At::new(at.segs(), kinds), Mark::Set(None));
}
true
}
pub(crate) fn remove(&mut self, path: &Path) -> Option<DataValue<'q>> {
self.remove_segs(path.segs())
}
pub(crate) fn remove_segs(&mut self, segs: &[Seg]) -> Option<DataValue<'q>> {
let before = self.root();
let (after, kinds, removed) = splice::remove_resolved(self.arena(), before, segs)?;
let at = At::new(segs, kinds);
*self.root = self.arena().alloc(after);
if !at.is_index(at.len() - 1) {
self.log.reorder(at.prefix(at.len() - 1));
}
self.log.mark(at, Mark::Removed);
Some(removed)
}
pub(crate) fn append(
&mut self,
path: &Path,
items: &[DataValue<'q>],
owned: Option<Vec<OwnedDataValue>>,
) -> Result<bool, &'static str> {
let before = self.root();
let existing = splice::get(before, path.segs());
let Some((after, r)) = splice::append(self.arena(), before, path.segs(), existing, items)?
else {
return Ok(false);
};
let at = At::new(path.segs(), r.kinds);
*self.root = self.arena().alloc(after);
match existing {
Some(DataValue::Array(old)) => self.log.mark(
at,
Mark::Appended {
from: old.len(),
owned,
},
),
_ => self.mark_new(before, at, r.created, owned.map(OwnedDataValue::Array)),
}
Ok(true)
}
pub(crate) fn merge_root(
&mut self,
key: &str,
value: DataValue<'q>,
owned: Option<OwnedDataValue>,
) {
let before = self.root();
let slot = before
.as_object()
.and_then(|slots| slots.iter().find(|(k, _)| *k == key))
.map(|(_, v)| *v);
*self.root = self
.arena()
.alloc(splice::merge_root(self.arena(), before, key, value));
match (slot, value) {
(Some(DataValue::Object(_)), DataValue::Object(new_pairs)) => {
let mut owned = match owned {
Some(OwnedDataValue::Object(pairs)) => Some(pairs.into_iter().map(|(_, v)| v)),
_ => None,
};
for (k, _) in new_pairs {
let v = owned.as_mut().and_then(Iterator::next);
self.log.mark_root_key(key, Some(k), Mark::Set(v));
}
}
_ => self.log.mark_root_key(key, None, Mark::Set(owned)),
}
}
}
impl<'m, 'p> RunContext<'m, 'p> {
pub(crate) fn new(slot: &'m mut OwnedDataValue, tuning: Tuning) -> Self {
let ctx = std::mem::take(slot);
Self {
slot,
state: State::Owned(Owner {
ctx,
arena: arena::take(),
_program: PhantomData,
}),
log: Writes::default(),
live: 0,
tuning,
stats: Stats::default(),
}
}
#[cfg(test)]
pub(crate) fn stats(&self) -> Stats {
self.stats
}
pub(crate) fn has_view(&self) -> bool {
matches!(self.state, State::Viewed(_))
}
fn ensure_view(&mut self) {
if let State::Owned(_) = self.state {
let State::Owned(owner) = std::mem::replace(&mut self.state, State::Taken) else {
unreachable!()
};
let view = RunView::new(owner, |o| o.arena.alloc(o.ctx.view_in(&o.arena)));
self.live = arena::used(&view.borrow_owner().arena);
self.stats.live_max = self.stats.live_max.max(self.live);
self.stats.views += 1;
self.state = State::Viewed(view);
}
}
pub(crate) fn read<R>(&mut self, f: impl for<'q> FnOnce(&'q Owner<'p>, Root<'q>) -> R) -> R {
self.ensure_view();
let State::Viewed(view) = &self.state else {
unreachable!("a run context is not used after its context went back")
};
view.with_dependent(|owner, root| f(owner, root))
}
pub(crate) fn edit<R>(&mut self, f: impl for<'e, 'q> FnOnce(&mut Edit<'e, 'q, 'p>) -> R) -> R {
self.ensure_view();
let State::Viewed(view) = &mut self.state else {
unreachable!("a run context is not used after its context went back")
};
let log = &mut self.log;
view.with_dependent_mut(|owner, root| f(&mut Edit { owner, root, log }))
}
pub(crate) fn set(
&mut self,
path: &Path,
value: OwnedDataValue,
size_hint: Option<usize>,
) -> bool {
self.set_segs(path.segs(), value, size_hint)
}
pub(crate) fn set_recorded(
&mut self,
path: &Path,
value: OwnedDataValue,
size_hint: Option<usize>,
changes: Option<&mut Vec<Change>>,
) -> bool {
if let Some(changes) = changes {
changes.push(Change::assign(path, self.get_or_null(path), value.clone()));
}
self.set(path, value, size_hint)
}
pub(crate) fn set_segs(
&mut self,
segs: &[Seg],
value: OwnedDataValue,
size_hint: Option<usize>,
) -> bool {
if self.has_view() {
let limit = self.live.max(self.tuning.invalidate_floor);
let large = match size_hint {
Some(n) => n > limit,
None => size::exceeds(&value, limit),
};
if !(self.tuning.invalidate && large) {
return self.edit(|e| {
let av = value.to_arena(e.arena());
e.set_segs(segs, av, Some(value)).0
});
}
self.checkpoint();
self.stats.invalidations += 1;
}
match &mut self.state {
State::Owned(owner) => path::set(&mut owner.ctx, segs, value),
_ => unreachable!(),
}
}
pub(crate) fn engine_set(
&mut self,
path: &Path,
owned: impl FnOnce() -> OwnedDataValue,
view: impl for<'q> FnOnce(&'q Owner<'p>) -> DataValue<'q>,
) -> bool {
match &mut self.state {
State::Owned(owner) => path.assign(&mut owner.ctx, owned()),
_ => self.edit(|e| {
let value = view(e.owner());
e.set(path, value, None)
}),
}
}
pub(crate) fn engine_update(
&mut self,
path: &Path,
update: impl FnOnce(&mut OwnedDataValue) -> bool,
) -> bool {
match &mut self.state {
State::Owned(owner) => path::get_mut(&mut owner.ctx, path.segs()).is_some_and(update),
_ => false,
}
}
pub(crate) fn remove_segs(&mut self, segs: &[Seg]) -> bool {
match &mut self.state {
State::Owned(owner) => path::remove(&mut owner.ctx, segs).is_some(),
_ => self.edit(|e| e.remove_segs(segs).is_some()),
}
}
pub(crate) fn is_object_at(&self, segs: &[Seg]) -> bool {
match &self.state {
State::Owned(owner) => {
path::get(&owner.ctx, segs).is_some_and(OwnedDataValue::is_object)
}
State::Viewed(view) => {
splice::get(**view.borrow_dependent(), segs).is_some_and(|v| v.is_object())
}
State::Taken => unreachable!("a run context is not used after its context went back"),
}
}
pub(crate) fn get(&self, path: &Path) -> Option<Cow<'_, OwnedDataValue>> {
match &self.state {
State::Owned(owner) => path.get(&owner.ctx).map(Cow::Borrowed),
State::Viewed(view) => self.reader_of(view).get(path),
State::Taken => unreachable!("a run context is not used after its context went back"),
}
}
pub(crate) fn get_or_null(&self, path: &Path) -> OwnedDataValue {
self.get(path).map_or(OwnedDataValue::Null, Cow::into_owned)
}
pub(crate) fn reader(&mut self) -> Reader<'_> {
self.ensure_view();
let State::Viewed(view) = &self.state else {
unreachable!("a run context is not used after its context went back")
};
self.reader_of(view)
}
fn reader_of<'r>(&'r self, view: &'r RunView<'p>) -> Reader<'r> {
Reader {
root: view.borrow_dependent(),
owned: &view.borrow_owner().ctx,
log: &self.log,
}
}
pub(crate) fn tick(&mut self) {
if let State::Viewed(view) = &self.state {
let used = arena::used(&view.borrow_owner().arena);
self.stats.peak_arena = self.stats.peak_arena.max(used);
let garbage = used.saturating_sub(self.live);
if garbage > self.live.max(self.tuning.garbage_floor) {
self.checkpoint();
}
}
}
pub(crate) fn checkpoint(&mut self) {
let State::Viewed(view) = &self.state else {
return;
};
self.stats.peak_arena = self
.stats
.peak_arena
.max(arena::used(&view.borrow_owner().arena));
let log = &mut self.log;
let converted = view.with_dependent(|_, root| log.settle(**root));
let State::Viewed(view) = std::mem::replace(&mut self.state, State::Taken) else {
unreachable!("the view was in place a moment ago")
};
let mut owner = view.into_owner();
owner.arena.reset();
self.state = State::Owned(owner);
let State::Owned(owner) = &mut self.state else {
unreachable!("the owner was just put back")
};
self.log.apply(&mut owner.ctx);
self.live = 0;
self.stats.checkpoints += 1;
self.stats.converted += converted;
}
pub(crate) fn finish(mut self) -> Stats {
self.checkpoint();
self.give_back();
self.stats
}
fn give_back(&mut self) {
let owner = match std::mem::replace(&mut self.state, State::Taken) {
State::Owned(owner) => owner,
State::Viewed(view) => view.into_owner(),
State::Taken => return,
};
*self.slot = owner.ctx;
arena::give(owner.arena);
}
}
impl Drop for RunContext<'_, '_> {
fn drop(&mut self) {
if !std::thread::panicking() {
self.checkpoint();
}
self.give_back();
}
}