aivika-0.2: Simulation/Aivika/Dynamics/Internal/Process.hs
-- |
-- Module : Simulation.Aivika.Dynamics.Internal.Process
-- Copyright : Copyright (c) 2009-2011, David Sorokin <david.sorokin@gmail.com>
-- License : BSD3
-- Maintainer : David Sorokin <david.sorokin@gmail.com>
-- Stability : experimental
-- Tested with: GHC 7.0.3
--
-- A value in the 'Process' monad represents a discontinuous process that
-- can suspend and resume at any time. It behaves like a dynamic process too.
-- Any value in the 'Dynamics' monad can be lifted to the Process monad.
-- Moreover, a value in the Process monad can be run in the Dynamics monad.
--
-- A value of the 'ProcessID' type is just an identifier of such a process.
--
module Simulation.Aivika.Dynamics.Internal.Process
(ProcessID,
Process(..),
processQueue,
newProcessID,
holdProcess,
passivateProcess,
processPassive,
reactivateProcess,
processID,
runProcess) where
import Data.IORef
import Control.Monad
import Control.Monad.Trans
import Simulation.Aivika.Dynamics.Internal.Dynamics
import Simulation.Aivika.Dynamics.Internal.Cont
import Simulation.Aivika.Dynamics.Lift
import Simulation.Aivika.Dynamics.EventQueue
-- | Represents a process identificator.
data ProcessID =
ProcessID { processQueue :: EventQueue, -- ^ Return the event queue.
processStarted :: IORef Bool,
processCont :: IORef (Maybe (Dynamics (() -> IO ()))) }
-- | Specifies a discontinuous process that can suspend at any time
-- and then resume later.
newtype Process a = Process (ProcessID -> Cont a)
-- | Hold the process for the specified time period.
holdProcess :: Double -> Process ()
holdProcess dt =
Process $ \pid ->
Cont $ \c ->
Dynamics $ \ps ->
do let Dynamics m = enqueueCont (processQueue pid) (pointTime ps + dt) c
m ps
-- | Passivate the process.
passivateProcess :: Process ()
passivateProcess =
Process $ \pid ->
Cont $ \c ->
Dynamics $ \p ->
do let x = processCont pid
a <- readIORef x
case a of
Nothing -> writeIORef x $ Just c
Just _ -> error "Cannot passivate the process twice: passivate"
-- | Test whether the process with the specified ID is passivated.
processPassive :: ProcessID -> Process Bool
processPassive pid =
Process $ \_ ->
Cont $ \(Dynamics c) ->
Dynamics $ \p ->
do cont' <- c p
let x = processCont pid
a <- readIORef x
case a of
Nothing -> cont' False
Just _ -> cont' True
-- | Reactivate a process with the specified ID.
reactivateProcess :: ProcessID -> Process ()
reactivateProcess pid =
Process $ \pid' ->
Cont $ \c@(Dynamics cont) ->
Dynamics $ \p ->
do let x = processCont pid
a <- readIORef x
case a of
Nothing ->
do cont' <- cont p
cont' ()
Just (Dynamics cont2) ->
do writeIORef x Nothing
let Dynamics m = enqueueCont (processQueue pid') (pointTime p) c
m p
cont2' <- cont2 p
cont2' ()
-- | Start the process with the specified ID at the desired time.
runProcess :: Process () -> ProcessID -> Double -> Dynamics ()
runProcess (Process p) pid t =
runCont m r
where m = do y <- liftIO $ readIORef (processStarted pid)
if y
then error $
"A process with such ID " ++
"has been started already: runProc"
else liftIO $ writeIORef (processStarted pid) True
Cont $ \c -> enqueueCont (processQueue pid) t c
p pid
r = let f () = return () in return f
-- | Return the current process ID.
processID :: Process ProcessID
processID = Process $ \pid -> return pid
-- | Create a new process ID.
newProcessID :: EventQueue -> Dynamics ProcessID
newProcessID q =
do x <- liftIO $ newIORef Nothing
y <- liftIO $ newIORef False
return ProcessID { processQueue = q,
processStarted = y,
processCont = x }
instance Eq ProcessID where
x == y = processCont x == processCont y -- for the references are unique
instance Monad Process where
return = returnP
m >>= k = bindP m k
instance Functor Process where
fmap = liftM
instance Lift Process where
liftD = liftP
instance MonadIO Process where
liftIO = liftIOP
returnP :: a -> Process a
{-# INLINE returnP #-}
returnP a = Process (\pid -> return a)
bindP :: Process a -> (a -> Process b) -> Process b
{-# INLINE bindP #-}
bindP (Process m) k =
Process $ \pid ->
do a <- m pid
let Process m' = k a
m' pid
liftP :: Dynamics a -> Process a
{-# INLINE liftP #-}
liftP m = Process $ \pid -> liftD m
liftIOP :: IO a -> Process a
{-# INLINE liftIOP #-}
liftIOP m = Process $ \pid -> liftIO m