use std::any::Any;
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(test)]
use crate::param::{AccountId, Pnl};
#[cfg(test)]
use crate::pretrade::AccountBlock;
static NEXT_MUTATION_OWNER_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg(test)]
pub(crate) struct AccountPnlReconciliation {
pub(crate) account_id: AccountId,
pub(crate) discarded_delta: Option<Pnl>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg(test)]
pub(crate) struct AccountAdjustmentRollbackReport {
pub(crate) account_blocks: Vec<AccountBlock>,
pub(crate) invalidated_account_blocks: Vec<AccountBlock>,
pub(crate) reconciliations: Vec<AccountPnlReconciliation>,
}
#[derive(Default)]
pub(crate) struct MutationRollbackResult {
#[cfg(test)]
pub(crate) report: AccountAdjustmentRollbackReport,
}
impl MutationRollbackResult {
#[cfg(test)]
fn append(&mut self, mut other: Self) {
self.report
.account_blocks
.append(&mut other.report.account_blocks);
self.report
.invalidated_account_blocks
.append(&mut other.report.invalidated_account_blocks);
self.report
.reconciliations
.append(&mut other.report.reconciliations);
}
#[cfg(not(test))]
fn append(&mut self, _other: Self) {}
}
pub(crate) fn next_mutation_owner_id() -> u64 {
loop {
let id = NEXT_MUTATION_OWNER_ID.fetch_add(1, Ordering::Relaxed);
if id != 0 {
return id;
}
}
}
pub struct Mutation {
commit: Box<dyn FnOnce()>,
rollback: Box<dyn FnOnce() -> MutationRollbackResult>,
lifetime_guard: Option<Box<dyn Any>>,
}
impl Mutation {
pub fn new(commit: impl FnOnce() + 'static, rollback: impl FnOnce() + 'static) -> Self {
Self::new_reporting(commit, move || {
rollback();
MutationRollbackResult::default()
})
}
pub(crate) fn new_reporting(
commit: impl FnOnce() + 'static,
rollback: impl FnOnce() -> MutationRollbackResult + 'static,
) -> Self {
Self {
commit: Box::new(commit),
rollback: Box::new(rollback),
lifetime_guard: None,
}
}
pub(crate) fn new_reporting_with_guard<Guard>(
commit: impl FnOnce() + 'static,
rollback: impl FnOnce() -> MutationRollbackResult + 'static,
guard: Guard,
) -> Self
where
Guard: 'static,
{
let mut mutation = Self::new_reporting(commit, rollback);
mutation.lifetime_guard = Some(Box::new(guard));
mutation
}
fn commit(self) {
let Self {
commit,
rollback,
lifetime_guard,
} = self;
drop(rollback);
commit();
drop(lifetime_guard);
}
fn rollback(self) -> MutationRollbackResult {
let Self {
commit,
rollback,
lifetime_guard,
} = self;
drop(commit);
let result = rollback();
drop(lifetime_guard);
result
}
}
pub struct Mutations {
mutations: Vec<Mutation>,
owner_id: u64,
}
impl Default for Mutations {
fn default() -> Self {
Self::new()
}
}
impl Mutations {
pub fn new() -> Self {
Self {
mutations: Vec::new(),
owner_id: next_mutation_owner_id(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
mutations: Vec::with_capacity(capacity),
owner_id: next_mutation_owner_id(),
}
}
pub fn push(&mut self, mutation: Mutation) {
self.mutations.push(mutation);
}
pub(crate) fn owner_id(&self) -> u64 {
self.owner_id
}
pub(crate) fn commit_all(self) {
for mutation in self.mutations {
mutation.commit();
}
}
pub(crate) fn rollback_all(self) -> MutationRollbackResult {
let mut result = MutationRollbackResult::default();
for mutation in self.mutations.into_iter().rev() {
result.append(mutation.rollback());
}
result
}
#[cfg(test)]
pub(crate) fn is_empty(&self) -> bool {
self.mutations.is_empty()
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::rc::Rc;
use super::{Mutation, Mutations};
fn noop_action() {}
#[test]
fn commit_all_applies_in_registration_order() {
let calls = Rc::new(RefCell::new(Vec::new()));
let mut mutations = Mutations::with_capacity(3);
for id in ["a", "b", "c"] {
let c = Rc::clone(&calls);
mutations.push(Mutation::new(
move || {
c.borrow_mut().push(id);
},
noop_action,
));
}
mutations.commit_all();
assert_eq!(&*calls.borrow(), &["a", "b", "c"]);
}
#[test]
fn rollback_all_applies_in_reverse_order() {
let calls = Rc::new(RefCell::new(Vec::new()));
let mut mutations = Mutations::with_capacity(3);
for id in ["a", "b", "c"] {
let r = Rc::clone(&calls);
mutations.push(Mutation::new(noop_action, move || {
r.borrow_mut().push(id);
}));
}
let _ = mutations.rollback_all();
assert_eq!(&*calls.borrow(), &["c", "b", "a"]);
}
#[test]
fn default_creates_empty_mutations() {
let mutations = Mutations::default();
assert!(mutations.is_empty());
}
#[test]
fn new_creates_empty_mutations() {
let mutations = Mutations::new();
assert!(mutations.is_empty());
}
#[test]
fn commit_all_on_empty_is_noop() {
noop_action();
Mutations::new().commit_all();
}
#[test]
fn rollback_all_on_empty_is_noop() {
let _ = Mutations::new().rollback_all();
}
}