use core::marker::PhantomData;
use soroban_sdk::{contracttype, Env, IntoVal, TryFromVal, Val};
pub trait TransitionHandler<K, V> {
fn on_guard(&self, env: &Env, state_machine: &StateMachine<K, V>)
where
K: Clone + IntoVal<Env, Val> + TryFromVal<Env, Val>,
V: Clone + IntoVal<Env, Val> + TryFromVal<Env, Val>;
fn on_effect(&self, env: &Env, state_machine: &StateMachine<K, V>)
where
K: Clone + IntoVal<Env, Val> + TryFromVal<Env, Val>,
V: Clone + IntoVal<Env, Val> + TryFromVal<Env, Val>;
}
pub struct StateMachine<'a, K, V>
where
K: 'a + IntoVal<Env, Val> + TryFromVal<Env, Val>,
V: IntoVal<Env, Val> + TryFromVal<Env, Val>,
{
region: &'a K,
storage_type: StorageType,
_data: PhantomData<*const V>,
}
impl<'a, K, V> StateMachine<'a, K, V>
where
K: Clone + IntoVal<Env, Val> + TryFromVal<Env, Val>,
V: Clone + IntoVal<Env, Val> + TryFromVal<Env, Val>,
{
pub fn new(region: &'a K, storage_type: StorageType) -> Self {
StateMachine {
region,
storage_type,
_data: PhantomData,
}
}
pub fn get_region(&self) -> &'a K {
self.region
}
pub fn get_storage_type(&self) -> &StorageType {
&self.storage_type
}
pub fn set_state(&self, env: &Env, value: V) {
match self.storage_type {
StorageType::Instance => env
.storage()
.instance()
.set(&self.region.into_val(env), &value),
StorageType::Persistent => env
.storage()
.persistent()
.set(&self.region.into_val(env), &value),
StorageType::Temporary => env
.storage()
.temporary()
.set(&self.region.into_val(env), &value),
}
}
pub fn get_state(&self, env: &Env) -> Option<V> {
match self.storage_type {
StorageType::Instance => env.storage().instance().get(&self.region.into_val(env)),
StorageType::Persistent => env.storage().persistent().get(&self.region.into_val(env)),
StorageType::Temporary => env.storage().temporary().get(&self.region.into_val(env)),
}
}
}
#[contracttype]
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum StorageType {
Instance,
Persistent,
Temporary,
}
#[contracttype]
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum StateMachineRegion {
Default,
}
#[macro_export]
macro_rules! impl_state_machine {
($instance:expr, $env:expr, $storage_type:expr, $handler:expr, $state_enum:ident, $state_variant:ident) => {
let state_key = $state_enum::$state_variant;
let region_key = $crate::fsm::StateMachineRegion::Default;
$crate::impl_state_machine!(@internal $instance, $env, $storage_type, $handler, state_key, region_key, $state_enum, $crate::fsm::StateMachineRegion);
};
($instance:expr, $env:expr, $storage_type:expr, $handler:expr, $state_enum:ident, $state_variant:ident, (),
$region_enum:ident, $region_variant:ident, ()) => {
let state_key = $state_enum::$state_variant;
let region_key = $region_enum::$region_variant;
$crate::impl_state_machine!(@internal $instance, $env, $storage_type, $handler, state_key, region_key, $state_enum, $region_enum);
};
($instance:expr, $env:expr, $storage_type:expr, $handler:expr, $state_enum:ident, $state_variant:ident,
(), $region_enum:ident, $region_variant:ident, ($($region_tuple_value:expr),+)) => {
let state_key = $state_enum::$state_variant;
let region_key = $region_enum::$region_variant($($region_tuple_value),*);
$crate::impl_state_machine!(@internal $instance, $env, $storage_type, $handler, state_key, region_key, $state_enum, $region_enum);
};
($instance:expr, $env:expr, $storage_type:expr, $handler:expr, $state_enum:ident, $state_variant:ident, ($($state_tuple_value:expr),+)) => {
let state_key = $state_enum::$state_variant($($state_tuple_value),*);
let region_key = $crate::fsm::StateMachineRegion::Default;
$crate::impl_state_machine!(@internal $instance, $env, $storage_type, $handler, state_key, region_key, $state_enum, $crate::fsm::StateMachineRegion);
};
($instance:expr, $env:expr, $storage_type:expr, $handler:expr, $state_enum:ident, $state_variant:ident, ($($state_tuple_value:expr),+),
$region_enum:ident, $region_variant:ident, ()) => {
let state_key = $state_enum::$state_variant($($state_tuple_value),*);
let region_key = $region_enum::$region_variant;
$crate::impl_state_machine!(@internal $instance, $env, $storage_type, $handler, state_key, region_key, $state_enum, $region_enum);
};
($instance:expr, $env:expr, $storage_type:expr, $handler:expr, $state_enum:ident, $state_variant:ident,
($($state_tuple_value:expr),+),$region_enum:ident, $region_variant:ident, ($($region_tuple_value:expr),+)) => {
let state_key = $state_enum::$state_variant($($state_tuple_value),*);
let region_key = $region_enum::$region_variant($($region_tuple_value),*);
$crate::impl_state_machine!(@internal $instance, $env, $storage_type, $handler, state_key, region_key, $state_enum, $region_enum);
};
(@internal $instance:expr, $env:expr, $storage_type:expr, $handler:expr, $state_key:expr, $region_key:expr, $state_enum:ty, $region_enum:ty) => {
let sm = $crate::fsm::StateMachine::<$region_enum, $state_enum>::new(&$region_key, $storage_type);
if $handler {
$instance.on_guard($env, &sm);
}
match sm.get_state(&$env) {
Some(current_state) if current_state != $state_key =>
panic!("Expected state {:?} but got {:?}", current_state, $state_key),
None =>
panic!("Expected state set in state-machine"),
_ => {}
}
if $handler {
$instance.on_effect($env, &sm);
}
};
}