#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TaskStopReason {
Completed,
Cancelled,
Replaced,
}
pub trait Task<M = ()>: Send + Sync {
#[allow(unused_variables)]
fn is_locked(&self, memory: &M) -> bool {
false
}
#[allow(unused_variables)]
fn on_enter(&mut self, memory: &mut M) {}
#[allow(unused_variables)]
fn on_exit(&mut self, memory: &mut M) {}
#[allow(unused_variables)]
fn on_stop(&mut self, memory: &mut M, reason: TaskStopReason) {
self.on_exit(memory);
}
#[allow(unused_variables)]
fn on_update(&mut self, memory: &mut M) {}
#[allow(unused_variables)]
fn on_process(&mut self, memory: &mut M) -> bool {
false
}
}
#[derive(Debug, Default, Copy, Clone)]
pub struct NoTask;
impl<M> Task<M> for NoTask {}
#[allow(clippy::type_complexity)]
pub struct ClosureTask<M = ()> {
locked: Option<Box<dyn Fn(&M) -> bool + Send + Sync>>,
enter: Option<Box<dyn FnMut(&mut M) + Send + Sync>>,
exit: Option<Box<dyn FnMut(&mut M) + Send + Sync>>,
stop: Option<Box<dyn FnMut(&mut M, TaskStopReason) + Send + Sync>>,
update: Option<Box<dyn FnMut(&mut M) + Send + Sync>>,
process: Option<Box<dyn FnMut(&mut M) -> bool + Send + Sync>>,
}
impl<M> Default for ClosureTask<M> {
fn default() -> Self {
Self {
locked: None,
enter: None,
exit: None,
stop: None,
update: None,
process: None,
}
}
}
impl<M> ClosureTask<M> {
pub fn locked<F>(mut self, f: F) -> Self
where
F: Fn(&M) -> bool + 'static + Send + Sync,
{
self.locked = Some(Box::new(f));
self
}
pub fn enter<F>(mut self, f: F) -> Self
where
F: FnMut(&mut M) + 'static + Send + Sync,
{
self.enter = Some(Box::new(f));
self
}
pub fn exit<F>(mut self, f: F) -> Self
where
F: FnMut(&mut M) + 'static + Send + Sync,
{
self.exit = Some(Box::new(f));
self
}
pub fn stop<F>(mut self, f: F) -> Self
where
F: FnMut(&mut M, TaskStopReason) + 'static + Send + Sync,
{
self.stop = Some(Box::new(f));
self
}
pub fn update<F>(mut self, f: F) -> Self
where
F: FnMut(&mut M) + 'static + Send + Sync,
{
self.update = Some(Box::new(f));
self
}
pub fn process<F>(mut self, f: F) -> Self
where
F: FnMut(&mut M) -> bool + 'static + Send + Sync,
{
self.process = Some(Box::new(f));
self
}
}
impl<M> Task<M> for ClosureTask<M> {
fn is_locked(&self, memory: &M) -> bool {
self.locked.as_ref().map(|f| f(memory)).unwrap_or_default()
}
fn on_enter(&mut self, memory: &mut M) {
if let Some(f) = &mut self.enter {
f(memory)
}
}
fn on_exit(&mut self, memory: &mut M) {
if let Some(f) = &mut self.exit {
f(memory)
}
}
fn on_stop(&mut self, memory: &mut M, reason: TaskStopReason) {
if let Some(f) = &mut self.stop {
f(memory, reason);
} else {
self.on_exit(memory);
}
}
fn on_update(&mut self, memory: &mut M) {
if let Some(f) = &mut self.update {
f(memory)
}
}
fn on_process(&mut self, memory: &mut M) -> bool {
self.process.as_mut().map(|f| f(memory)).unwrap_or_default()
}
}
impl<M> std::fmt::Debug for ClosureTask<M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClosureTask").finish()
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub enum TransactionCommitPolicy {
#[default]
Isolated,
MergeIntoParent,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransactionJournal<U> {
frames: Vec<Vec<U>>,
}
impl<U> Default for TransactionJournal<U> {
fn default() -> Self {
Self { frames: Vec::new() }
}
}
impl<U> TransactionJournal<U> {
pub fn begin(&mut self) {
self.frames.push(Vec::new());
}
pub fn record(&mut self, undo: U) -> bool {
let Some(frame) = self.frames.last_mut() else {
return false;
};
frame.push(undo);
true
}
pub fn commit(&mut self, policy: TransactionCommitPolicy) {
let Some(frame) = self.frames.pop() else {
return;
};
if policy == TransactionCommitPolicy::MergeIntoParent
&& let Some(parent) = self.frames.last_mut()
{
parent.extend(frame);
}
}
pub fn rollback(&mut self) -> impl Iterator<Item = U> {
self.frames.pop().unwrap_or_default().into_iter().rev()
}
pub fn is_active(&self) -> bool {
!self.frames.is_empty()
}
pub fn depth(&self) -> usize {
self.frames.len()
}
}
pub trait TransactionalMemory {
type Undo;
fn begin_transaction(&mut self);
fn commit_transaction(&mut self, policy: TransactionCommitPolicy);
fn rollback_transaction(&mut self);
fn record_undo(&mut self, undo: Self::Undo);
}
pub struct JournaledTransactionTask<M = ()> {
task: Box<dyn Task<M>>,
active: bool,
commit_policy: TransactionCommitPolicy,
}
impl<M> JournaledTransactionTask<M> {
pub fn new<T>(task: T) -> Self
where
T: Task<M> + 'static,
{
Self::new_raw(Box::new(task))
}
pub fn new_raw(task: Box<dyn Task<M>>) -> Self {
Self {
task,
active: false,
commit_policy: TransactionCommitPolicy::default(),
}
}
pub fn commit_policy(mut self, policy: TransactionCommitPolicy) -> Self {
self.commit_policy = policy;
self
}
pub fn get_commit_policy(&self) -> TransactionCommitPolicy {
self.commit_policy
}
pub fn task(&self) -> &dyn Task<M> {
self.task.as_ref()
}
pub fn task_mut(&mut self) -> &mut dyn Task<M> {
self.task.as_mut()
}
}
impl<M> Task<M> for JournaledTransactionTask<M>
where
M: TransactionalMemory,
{
fn is_locked(&self, memory: &M) -> bool {
self.task.is_locked(memory)
}
fn on_enter(&mut self, memory: &mut M) {
memory.begin_transaction();
self.task.on_enter(memory);
self.active = true;
}
fn on_exit(&mut self, memory: &mut M) {
self.on_stop(memory, TaskStopReason::Cancelled);
}
fn on_stop(&mut self, memory: &mut M, reason: TaskStopReason) {
self.task.on_stop(memory, reason);
if !self.active {
return;
}
match reason {
TaskStopReason::Completed => {
memory.commit_transaction(self.commit_policy);
}
TaskStopReason::Cancelled | TaskStopReason::Replaced => {
memory.rollback_transaction();
}
}
self.active = false;
}
fn on_update(&mut self, memory: &mut M) {
self.task.on_update(memory);
}
fn on_process(&mut self, memory: &mut M) -> bool {
self.task.on_process(memory)
}
}
impl<M> std::fmt::Debug for JournaledTransactionTask<M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JournaledTransactionTask")
.field("active", &self.active)
.field("commit_policy", &self.commit_policy)
.finish()
}
}
#[allow(clippy::type_complexity)]
pub struct TransactionScopeTask<M = ()> {
task: Box<dyn Task<M>>,
active: bool,
begin: Option<Box<dyn FnMut(&mut M) + Send + Sync>>,
commit: Option<Box<dyn FnMut(&mut M) + Send + Sync>>,
rollback: Option<Box<dyn FnMut(&mut M) + Send + Sync>>,
}
impl<M> TransactionScopeTask<M> {
pub fn new<T>(task: T) -> Self
where
T: Task<M> + 'static,
{
Self::new_raw(Box::new(task))
}
pub fn new_raw(task: Box<dyn Task<M>>) -> Self {
Self {
task,
active: false,
begin: None,
commit: None,
rollback: None,
}
}
pub fn begin<F>(mut self, f: F) -> Self
where
F: FnMut(&mut M) + 'static + Send + Sync,
{
self.begin = Some(Box::new(f));
self
}
pub fn commit<F>(mut self, f: F) -> Self
where
F: FnMut(&mut M) + 'static + Send + Sync,
{
self.commit = Some(Box::new(f));
self
}
pub fn rollback<F>(mut self, f: F) -> Self
where
F: FnMut(&mut M) + 'static + Send + Sync,
{
self.rollback = Some(Box::new(f));
self
}
pub fn task(&self) -> &dyn Task<M> {
self.task.as_ref()
}
pub fn task_mut(&mut self) -> &mut dyn Task<M> {
self.task.as_mut()
}
}
impl<M> Task<M> for TransactionScopeTask<M> {
fn is_locked(&self, memory: &M) -> bool {
self.task.is_locked(memory)
}
fn on_enter(&mut self, memory: &mut M) {
if let Some(f) = &mut self.begin {
f(memory);
}
self.task.on_enter(memory);
self.active = true;
}
fn on_exit(&mut self, memory: &mut M) {
self.on_stop(memory, TaskStopReason::Cancelled);
}
fn on_stop(&mut self, memory: &mut M, reason: TaskStopReason) {
self.task.on_stop(memory, reason);
if !self.active {
return;
}
match reason {
TaskStopReason::Completed => {
if let Some(f) = &mut self.commit {
f(memory);
}
}
TaskStopReason::Cancelled | TaskStopReason::Replaced => {
if let Some(f) = &mut self.rollback {
f(memory);
}
}
}
self.active = false;
}
fn on_update(&mut self, memory: &mut M) {
self.task.on_update(memory);
}
fn on_process(&mut self, memory: &mut M) -> bool {
self.task.on_process(memory)
}
}
impl<M> std::fmt::Debug for TransactionScopeTask<M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TransactionScopeTask")
.field("active", &self.active)
.finish()
}
}