use std::error::Error;
use either::Either;
pub trait UndoLog<Root:?Sized> {
fn revert(self, root:&mut Root);
}
pub trait RevertableOp<Root:?Sized> {
type Err: Error;
type Log: UndoLog<Root>;
fn apply(self, root: &mut Root)->Result<Self::Log, Self::Err>;
}
pub trait Accessor<Src:?Sized> {
type Dest: ?Sized;
fn access<F,O>(&self, src: &mut Src, op:F)->O
where F: FnOnce(&mut Self::Dest)->O;
}
impl<Src, Dest, G, Ptr> Accessor<Src> for G
where Src: ?Sized,
Dest:?Sized,
G: Fn(&mut Src)->Ptr,
Ptr: std::ops::DerefMut<Target = Dest> {
type Dest = Dest;
#[inline(always)]
fn access<F,O>(&self, src: &mut Src, op:F)->O
where F: FnOnce(&mut Self::Dest)->O
{
let mut ptr = self(src);
op(&mut *ptr)
}
}
impl<Root:?Sized> UndoLog<Root> for () {
fn revert(self, _:&mut Root) {}
}
impl<Root:?Sized, A: UndoLog<Root>, B: UndoLog<Root>> UndoLog<Root> for (A,B) {
fn revert(self, root:&mut Root) {
self.0.revert(root);
self.1.revert(root);
}
}
impl<Root:?Sized, T: UndoLog<Root>> UndoLog<Root> for Option<T> {
fn revert(self, root:&mut Root) {
if let Some(undo) = self {
undo.revert(root);
}
}
}
impl<Root:?Sized, A: UndoLog<Root>, B:UndoLog<Root>> UndoLog<Root> for Either<A,B> {
fn revert(self, root:&mut Root) {
match self {
Either::Left(left) => left.revert(root),
Either::Right(right) => right.revert(root)
};
}
}
impl<Root:?Sized, T: UndoLog<Root>> UndoLog<Root> for Vec<T> {
fn revert(self, root:&mut Root) {
self.into_iter().rev().for_each(|log| log.revert(root));
}
}
pub struct SubUndoLog<AccessFn, Log, SubRoot:?Sized>(AccessFn, Log, std::marker::PhantomData<SubRoot>);
impl<Root:?Sized, AccessFn, Log, SubRoot> UndoLog<Root> for SubUndoLog<AccessFn, Log, SubRoot>
where AccessFn: Accessor<Root, Dest=SubRoot>, Log: UndoLog<SubRoot>, SubRoot:?Sized {
fn revert(self, root: &mut Root) {
let SubUndoLog(access, log, _) = self;
access.access(root, |subroot| log.revert(subroot));
}
}
#[must_use]
pub struct Transaction<'a, Root:'a+?Sized, Undo=(), E=std::convert::Infallible> {
root: &'a mut Root,
status: Status<Undo, E>,
}
pub enum Status<Undo, E> {
Poisoned(E),
Active(Undo),
}
impl<'a, T:'a+?Sized> Transaction<'a, T> {
pub fn start(root:&'a mut T)->Self {
Transaction { root, status: Status::Active(()) }
}
}
impl<'a, Root:?Sized+'a, Undo: UndoLog<Root>, E> Transaction<'a, Root, Undo, E> {
pub fn commit(self)->Result<(), E> {
match self.status {
Status::Poisoned(err) => Err(err),
Status::Active(_) => Ok(())
}
}
pub fn revert(mut self) {
if let Status::Active(undo) = self.status { undo.revert(&mut self.root) }
}
pub fn subtransaction<SubRoot, SubUndo, AccessFn, BodyFn, SubErr>(self, access: AccessFn, body: BodyFn)->
Transaction<'a, Root, (SubUndoLog<AccessFn, SubUndo, SubRoot>, Undo), Either<SubErr, E>>
where
AccessFn: Accessor<Root, Dest=SubRoot>,
SubRoot: ?Sized,
BodyFn: FnOnce(Transaction<'_, SubRoot, ()>)->Transaction<'_, SubRoot, SubUndo, SubErr>,
SubUndo: UndoLog<SubRoot>
{
match self.status {
Status::Poisoned(e) => Transaction { root: self.root, status: Status::Poisoned(Either::Right(e)) },
Status::Active(undo) => {
let status = access.access(self.root, |subroot| {
body(Transaction { root: subroot, status: Status::Active(())}).status
});
match status {
Status::Poisoned(e) => Transaction { root: self.root, status: Status::Poisoned(Either::Left(e)) },
Status::Active(subundo) => Transaction {
root: self.root,
status: Status::Active( (SubUndoLog(access, subundo, std::marker::PhantomData), undo) )
}
}
}
}
}
pub fn apply<Op: RevertableOp<Root>>(self, op:Op)->Transaction<'a, Root, (Op::Log, Undo), Either<Op::Err, E>> {
match self.status {
Status::Poisoned(e) => Transaction { root: self.root, status: Status::Poisoned(Either::Right(e)) },
Status::Active(undo) => match op.apply(self.root) {
Ok(log) => Transaction { root: self.root, status: Status::Active((log, undo)) },
Err(e) => {
undo.revert(self.root);
Transaction { root: self.root, status: Status::Poisoned(Either::Left(e)) }
}
}
}
}
pub fn apply_multi<Op: RevertableOp<Root>>(self, ops:impl IntoIterator<Item=Op>)
->Transaction<'a, Root, (Vec<Op::Log>, Undo), Either<Op::Err, E>> {
match self.status {
Status::Poisoned(e) => Transaction { root: self.root, status: Status::Poisoned(Either::Right(e)) },
Status::Active(undo) => {
let mut logs: Vec<Op::Log> = vec![];
for op in ops {
match op.apply(self.root) {
Ok(log) => logs.push(log),
Err(e) => {
logs.revert(self.root);
undo.revert(self.root);
return Transaction { root: self.root, status: Status::Poisoned(Either::Left(e)) };
}
}
}
Transaction { root: self.root, status: Status::Active((logs, undo)) }
}
}
}
pub fn into_undo_log(self)->Result<Undo, E> {
match self.status {
Status::Active(undo) => Ok(undo),
Status::Poisoned(e) => Err(e)
}
}
}