ron-0.12: lib/RON/Event/Simulation.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
-- | Lamport clock network simulation.
-- 'ReplicaSim' provides 'Replica' and 'Clock' instances,
-- replicas may interchange data while they are connected in a 'NetworkSim'.
module RON.Event.Simulation (
NetworkSimT,
ReplicaSimT,
runNetworkSimT,
runReplicaSimT,
) where
import RON.Prelude
import Control.Monad.State.Strict (state)
import qualified Data.HashMap.Strict as HM
import RON.Event (Event (..), Replica (Replica), ReplicaClock,
TimeVariety (Logical), advance, getEvents, getPid,
mkTime)
import RON.Util.Word (Word60, ls60)
-- | Lamport clock simulation. Key is 'Replica'.
-- Non-present value is equivalent to (0, initial).
newtype NetworkSimT m a = NetworkSim (StateT (HashMap Replica Word60) m a)
deriving (Applicative, Functor, Monad, MonadError e)
instance MonadTrans NetworkSimT where
lift = NetworkSim . lift
-- | ReplicaSim inside Lamport clock simulation.
newtype ReplicaSimT m a = ReplicaSim (ReaderT Replica (NetworkSimT m) a)
deriving (Applicative, Functor, Monad, MonadError e)
instance MonadTrans ReplicaSimT where
lift = ReplicaSim . lift . lift
instance Monad m => ReplicaClock (ReplicaSimT m) where
getPid = ReplicaSim ask
getEvents n' = ReplicaSim $ do
replica <- ask
(t0, t1) <-
lift $ NetworkSim $ state $ \replicaStates -> let
t0orig = HM.lookupDefault (ls60 0) replica replicaStates
Replica r = replica
randomLeap =
ls60 . fromIntegral $ hash (t0orig, n, r) `mod` 0x10000
t0 = t0orig + randomLeap
t1 = t0 + n
in ((t0, t1), HM.insert replica t1 replicaStates)
pure [Event{time = mkTime Logical t, replica} | t <- [succ t0 .. t1]]
where
n = max n' (ls60 1)
advance time = ReplicaSim $ do
rid <- ask
lift . NetworkSim . modify' $ HM.alter (Just . advancePS) rid
where
advancePS = \case
Nothing -> time
Just current -> max time current
instance MonadFail m => MonadFail (ReplicaSimT m) where
fail = lift . fail
-- | Execute network simulation
--
-- Usage:
--
-- @
-- 'runExceptT' . runNetworkSimT $ do
-- 'runReplicaSimT' r1 $ do
-- actions...
-- 'runReplicaSimT' r2 $ do
-- actions...
-- 'runReplicaSimT' r1 $ ...
-- @
--
-- Each 'runNetworkSimT' starts its own networks.
-- One shouldn't use in one network events generated in another.
runNetworkSimT :: Monad m => NetworkSimT m a -> m a
runNetworkSimT (NetworkSim action) = evalStateT action mempty
runReplicaSimT :: Replica -> ReplicaSimT m a -> NetworkSimT m a
runReplicaSimT rid (ReplicaSim action) = runReaderT action rid