module Simulation.Aivika.Trans.Internal.Process
(
ProcessId,
Process(..),
ProcessLift(..),
invokeProcess,
runProcess,
runProcessUsingId,
runProcessInStartTime,
runProcessInStartTimeUsingId,
runProcessInStopTime,
runProcessInStopTimeUsingId,
spawnProcess,
spawnProcessUsingId,
enqueueProcess,
enqueueProcessUsingId,
newProcessId,
processId,
processUsingId,
holdProcess,
interruptProcess,
processInterrupted,
passivateProcess,
processPassive,
reactivateProcess,
cancelProcessWithId,
cancelProcess,
processCancelled,
processCancelling,
whenCancellingProcess,
processAwait,
processYield,
timeoutProcess,
timeoutProcessUsingId,
processParallel,
processParallelUsingIds,
processParallel_,
processParallelUsingIds_,
catchProcess,
finallyProcess,
throwProcess,
zipProcessParallel,
zip3ProcessParallel,
unzipProcess,
memoProcess,
neverProcess) where
import Data.Maybe
import Control.Exception
import Control.Monad
import Control.Monad.Trans
import Control.Applicative
import Simulation.Aivika.Trans.Session
import Simulation.Aivika.Trans.ProtoRef
import Simulation.Aivika.Trans.Comp
import Simulation.Aivika.Trans.Internal.Specs
import Simulation.Aivika.Trans.Internal.Parameter
import Simulation.Aivika.Trans.Internal.Simulation
import Simulation.Aivika.Trans.Internal.Dynamics
import Simulation.Aivika.Trans.Internal.Event
import Simulation.Aivika.Trans.Internal.Cont
import Simulation.Aivika.Trans.Internal.Signal
data ProcessId m =
ProcessId { processStarted :: ProtoRef m Bool,
processMarker :: SessionMarker m,
processReactCont :: ProtoRef m (Maybe (ContParams m ())),
processCancelSource :: ContCancellationSource m,
processInterruptRef :: ProtoRef m Bool,
processInterruptCont :: ProtoRef m (Maybe (ContParams m ())),
processInterruptVersion :: ProtoRef m Int }
newtype Process m a = Process (ProcessId m -> Cont m a)
class ProcessLift t where
liftProcess :: MonadComp m => Process m a -> t m a
invokeProcess :: ProcessId m -> Process m a -> Cont m a
invokeProcess pid (Process m) = m pid
holdProcess :: MonadComp m => Double -> Process m ()
holdProcess dt =
Process $ \pid ->
Cont $ \c ->
Event $ \p ->
do let x = processInterruptCont pid
writeProtoRef x $ Just c
writeProtoRef (processInterruptRef pid) False
v <- readProtoRef (processInterruptVersion pid)
invokeEvent p $
enqueueEvent (pointTime p + dt) $
Event $ \p ->
do v' <- readProtoRef (processInterruptVersion pid)
when (v == v') $
do writeProtoRef x Nothing
invokeEvent p $ resumeCont c ()
interruptProcess :: MonadComp m => ProcessId m -> Event m ()
interruptProcess pid =
Event $ \p ->
do let x = processInterruptCont pid
a <- readProtoRef x
case a of
Nothing -> return ()
Just c ->
do writeProtoRef x Nothing
writeProtoRef (processInterruptRef pid) True
modifyProtoRef (processInterruptVersion pid) $ (+) 1
invokeEvent p $ enqueueEvent (pointTime p) $ resumeCont c ()
processInterrupted :: MonadComp m => ProcessId m -> Event m Bool
processInterrupted pid =
Event $ \p ->
readProtoRef (processInterruptRef pid)
passivateProcess :: MonadComp m => Process m ()
passivateProcess =
Process $ \pid ->
Cont $ \c ->
Event $ \p ->
do let x = processReactCont pid
a <- readProtoRef x
case a of
Nothing -> writeProtoRef x $ Just c
Just _ -> error "Cannot passivate the process twice: passivateProcess"
processPassive :: MonadComp m => ProcessId m -> Event m Bool
processPassive pid =
Event $ \p ->
do let x = processReactCont pid
a <- readProtoRef x
return $ isJust a
reactivateProcess :: MonadComp m => ProcessId m -> Event m ()
reactivateProcess pid =
Event $ \p ->
do let x = processReactCont pid
a <- readProtoRef x
case a of
Nothing ->
return ()
Just c ->
do writeProtoRef x Nothing
invokeEvent p $ enqueueEvent (pointTime p) $ resumeCont c ()
processIdPrepare :: MonadComp m => ProcessId m -> Event m ()
processIdPrepare pid =
Event $ \p ->
do y <- readProtoRef (processStarted pid)
if y
then error $
"Another process with the specified identifier " ++
"has been started already: processIdPrepare"
else writeProtoRef (processStarted pid) True
let signal = processCancelling pid
invokeEvent p $
handleSignal_ signal $ \_ ->
do interruptProcess pid
reactivateProcess pid
runProcess :: MonadComp m => Process m () -> Event m ()
runProcess p =
do pid <- liftSimulation newProcessId
runProcessUsingId pid p
runProcessUsingId :: MonadComp m => ProcessId m -> Process m () -> Event m ()
runProcessUsingId pid p =
do processIdPrepare pid
runCont m cont econt ccont (processCancelSource pid) False
where cont = return
econt = throwEvent
ccont = return
m = invokeProcess pid p
runProcessInStartTime :: MonadComp m => Process m () -> Simulation m ()
runProcessInStartTime = runEventInStartTime . runProcess
runProcessInStartTimeUsingId :: MonadComp m => ProcessId m -> Process m () -> Simulation m ()
runProcessInStartTimeUsingId pid p =
runEventInStartTime $ runProcessUsingId pid p
runProcessInStopTime :: MonadComp m => Process m () -> Simulation m ()
runProcessInStopTime = runEventInStopTime . runProcess
runProcessInStopTimeUsingId :: MonadComp m => ProcessId m -> Process m () -> Simulation m ()
runProcessInStopTimeUsingId pid p =
runEventInStopTime $ runProcessUsingId pid p
enqueueProcess :: MonadComp m => Double -> Process m () -> Event m ()
enqueueProcess t p =
enqueueEvent t $ runProcess p
enqueueProcessUsingId :: MonadComp m => Double -> ProcessId m -> Process m () -> Event m ()
enqueueProcessUsingId t pid p =
enqueueEvent t $ runProcessUsingId pid p
processId :: MonadComp m => Process m (ProcessId m)
processId = Process return
newProcessId :: MonadComp m => Simulation m (ProcessId m)
newProcessId =
Simulation $ \r ->
do let s = runSession r
m <- newSessionMarker s
x <- newProtoRef s Nothing
y <- newProtoRef s False
c <- invokeSimulation r newContCancellationSource
i <- newProtoRef s False
z <- newProtoRef s Nothing
v <- newProtoRef s 0
return ProcessId { processStarted = y,
processMarker = m,
processReactCont = x,
processCancelSource = c,
processInterruptRef = i,
processInterruptCont = z,
processInterruptVersion = v }
cancelProcessWithId :: MonadComp m => ProcessId m -> Event m ()
cancelProcessWithId pid = contCancellationInitiate (processCancelSource pid)
cancelProcess :: (MonadComp m, MonadIO m) => Process m a
cancelProcess =
do pid <- processId
liftEvent $ cancelProcessWithId pid
throwProcess $
(error "The process must be cancelled already: cancelProcess." :: SomeException)
processCancelled :: MonadComp m => ProcessId m -> Event m Bool
processCancelled pid = contCancellationInitiated (processCancelSource pid)
processCancelling :: ProcessId m -> Signal m ()
processCancelling pid = contCancellationInitiating (processCancelSource pid)
whenCancellingProcess :: MonadComp m => Event m () -> Process m ()
whenCancellingProcess h =
Process $ \pid ->
liftEvent $
handleSignal_ (processCancelling pid) $ \() -> h
instance MonadComp m => Eq (ProcessId m) where
x == y = processMarker x == processMarker y
instance MonadComp m => Monad (Process m) where
return a = Process $ \pid -> return a
(Process m) >>= k =
Process $ \pid ->
do a <- m pid
let Process m' = k a
m' pid
instance MonadCompTrans Process where
liftComp = Process . const . liftComp
instance MonadComp m => Functor (Process m) where
fmap f (Process x) = Process $ \pid -> fmap f $ x pid
instance MonadComp m => Applicative (Process m) where
pure = Process . const . pure
(Process x) <*> (Process y) = Process $ \pid -> x pid <*> y pid
instance (MonadComp m, MonadIO m) => MonadIO (Process m) where
liftIO = Process . const . liftIO
instance ParameterLift Process where
liftParameter = Process . const . liftParameter
instance SimulationLift Process where
liftSimulation = Process . const . liftSimulation
instance DynamicsLift Process where
liftDynamics = Process . const . liftDynamics
instance EventLift Process where
liftEvent = Process . const . liftEvent
instance ProcessLift Process where
liftProcess = id
catchProcess :: (MonadComp m, Exception e) => Process m a -> (e -> Process m a) -> Process m a
catchProcess (Process m) h =
Process $ \pid ->
catchCont (m pid) $ \e ->
let Process m' = h e in m' pid
finallyProcess :: MonadComp m => Process m a -> Process m b -> Process m a
finallyProcess (Process m) (Process m') =
Process $ \pid ->
finallyCont (m pid) (m' pid)
throwProcess :: (MonadComp m, Exception e) => e -> Process m a
throwProcess = liftIO . throw
processParallel :: MonadComp m => [Process m a] -> Process m [a]
processParallel xs =
liftSimulation (processParallelCreateIds xs) >>= processParallelUsingIds
processParallelUsingIds :: MonadComp m => [(ProcessId m, Process m a)] -> Process m [a]
processParallelUsingIds xs =
Process $ \pid ->
do liftEvent $ processParallelPrepare xs
contParallel $
flip map xs $ \(pid, m) ->
(invokeProcess pid m, processCancelSource pid)
processParallel_ :: MonadComp m => [Process m a] -> Process m ()
processParallel_ xs =
liftSimulation (processParallelCreateIds xs) >>= processParallelUsingIds_
processParallelUsingIds_ :: MonadComp m => [(ProcessId m, Process m a)] -> Process m ()
processParallelUsingIds_ xs =
Process $ \pid ->
do liftEvent $ processParallelPrepare xs
contParallel_ $
flip map xs $ \(pid, m) ->
(invokeProcess pid m, processCancelSource pid)
processParallelCreateIds :: MonadComp m => [Process m a] -> Simulation m [(ProcessId m, Process m a)]
processParallelCreateIds xs =
do pids <- liftSimulation $ forM xs $ const newProcessId
return $ zip pids xs
processParallelPrepare :: MonadComp m => [(ProcessId m, Process m a)] -> Event m ()
processParallelPrepare xs =
Event $ \p ->
forM_ xs $ invokeEvent p . processIdPrepare . fst
processUsingId :: MonadComp m => ProcessId m -> Process m a -> Process m a
processUsingId pid x =
Process $ \pid' ->
do liftEvent $ processIdPrepare pid
rerunCont (invokeProcess pid x) (processCancelSource pid)
spawnProcess :: MonadComp m => ContCancellation -> Process m () -> Process m ()
spawnProcess cancellation x =
do pid <- liftSimulation newProcessId
spawnProcessUsingId cancellation pid x
spawnProcessUsingId :: MonadComp m => ContCancellation -> ProcessId m -> Process m () -> Process m ()
spawnProcessUsingId cancellation pid x =
Process $ \pid' ->
do liftEvent $ processIdPrepare pid
spawnCont cancellation (invokeProcess pid x) (processCancelSource pid)
processAwait :: MonadComp m => Signal m a -> Process m a
processAwait signal =
Process $ \pid -> contAwait signal
data MemoResult a = MemoComputed a
| MemoError IOException
| MemoCancelled
memoProcess :: MonadComp m => Process m a -> Simulation m (Process m a)
memoProcess x =
Simulation $ \r ->
do let s = runSession r
started <- newProtoRef s False
computed <- invokeSimulation r newSignalSource
value <- newProtoRef s Nothing
let result =
do Just x <- liftComp $ readProtoRef value
case x of
MemoComputed a -> return a
MemoError e -> throwProcess e
MemoCancelled -> cancelProcess
return $
do v <- liftComp $ readProtoRef value
case v of
Just _ -> result
Nothing ->
do f <- liftComp $ readProtoRef started
case f of
True ->
do processAwait $ publishSignal computed
result
False ->
do liftComp $ writeProtoRef started True
r <- liftComp $ newProtoRef s MemoCancelled
finallyProcess
(catchProcess
(do a <- x
liftComp $ writeProtoRef r (MemoComputed a))
(\e ->
liftComp $ writeProtoRef r (MemoError e)))
(liftEvent $
do liftComp $
do x <- readProtoRef r
writeProtoRef value (Just x)
triggerSignal computed ())
result
zipProcessParallel :: MonadComp m => Process m a -> Process m b -> Process m (a, b)
zipProcessParallel x y =
do [Left a, Right b] <- processParallel [fmap Left x, fmap Right y]
return (a, b)
zip3ProcessParallel :: MonadComp m => Process m a -> Process m b -> Process m c -> Process m (a, b, c)
zip3ProcessParallel x y z =
do [Left a,
Right (Left b),
Right (Right c)] <-
processParallel [fmap Left x,
fmap (Right . Left) y,
fmap (Right . Right) z]
return (a, b, c)
unzipProcess :: (MonadComp m, MonadIO m) => Process m (a, b) -> Simulation m (Process m a, Process m b)
unzipProcess xy =
do xy' <- memoProcess xy
return (fmap fst xy', fmap snd xy')
timeoutProcess :: (MonadComp m, MonadIO m) => Double -> Process m a -> Process m (Maybe a)
timeoutProcess timeout p =
do pid <- liftSimulation newProcessId
timeoutProcessUsingId timeout pid p
timeoutProcessUsingId :: (MonadComp m, MonadIO m) => Double -> ProcessId m -> Process m a -> Process m (Maybe a)
timeoutProcessUsingId timeout pid p =
do s <- liftSimulation newSignalSource
timeoutPid <- liftSimulation newProcessId
spawnProcessUsingId CancelChildAfterParent timeoutPid $
finallyProcess
(holdProcess timeout)
(liftEvent $
cancelProcessWithId pid)
spawnProcessUsingId CancelChildAfterParent pid $
do sn <- liftParameter simulationSession
r <- liftComp $ newProtoRef sn Nothing
finallyProcess
(catchProcess
(do a <- p
liftComp $ writeProtoRef r $ Just (Right a))
(\e ->
liftComp $ writeProtoRef r $ Just (Left e)))
(liftEvent $
do x <- liftComp $ readProtoRef r
triggerSignal s x)
x <- processAwait $ publishSignal s
case x of
Nothing -> return Nothing
Just (Right a) -> return (Just a)
Just (Left (SomeException e)) -> throwProcess e
processYield :: MonadComp m => Process m ()
processYield =
Process $ \pid ->
Cont $ \c ->
Event $ \p ->
invokeEvent p $
enqueueEvent (pointTime p) $
resumeCont c ()
neverProcess :: MonadComp m => Process m a
neverProcess =
Process $ \pid ->
Cont $ \c ->
let signal = processCancelling pid
in handleSignal_ signal $ \_ ->
resumeCont c $ error "It must never be computed: neverProcess"