{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} -- | -- Module : Control.TimeWarp.Timed.TimedIO -- Copyright : (c) Serokell, 2016 -- License : GPL-3 (see the file LICENSE) -- Maintainer : Serokell -- Stability : experimental -- Portability : POSIX, GHC -- -- Real-mode implementation of `MonadTimed`. -- Each function in inplementation refers to plain `IO`. module Control.TimeWarp.Timed.TimedIO ( TimedIO , runTimedIO ) where import qualified Control.Concurrent as C import Control.Monad.Base (MonadBase) import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow, throwM) import Control.Monad.Reader (ReaderT (..), ask, runReaderT) import Control.Monad.Trans (MonadIO, lift, liftIO) import Control.Monad.Trans.Control (MonadBaseControl, StM, liftBaseWith, restoreM) import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Time.Units (toMicroseconds) import SlaveThread as ST import qualified System.Timeout as T import System.Wlog (CanLog) import Control.TimeWarp.Timed.MonadTimed (Microsecond, MonadTimed (..), MonadTimedError (MTTimeoutError), ThreadId) -- | Default implementation for `IO`, i.e. real mode. -- `wait` refers to `Control.Concurrent.threadDelay`, -- `fork` refers to `Control.Concurrent.forkIO`, and so on. newtype TimedIO a = TimedIO { getTimedIO :: ReaderT Microsecond IO a } deriving (Functor, Applicative, Monad, MonadIO, MonadThrow, MonadCatch, MonadBase IO, MonadMask, CanLog) instance MonadBaseControl IO TimedIO where type StM TimedIO a = a liftBaseWith f = TimedIO $ liftBaseWith $ \g -> f $ g . getTimedIO restoreM = TimedIO . restoreM type instance ThreadId TimedIO = C.ThreadId instance MonadTimed TimedIO where virtualTime = TimedIO $ (-) <$> lift curTime <*> ask currentTime = TimedIO $ lift curTime wait relativeToNow = do cur <- virtualTime liftIO $ C.threadDelay $ fromIntegral $ relativeToNow cur - cur fork (TimedIO a) = TimedIO $ lift . C.forkIO . runReaderT a =<< ask myThreadId = TimedIO $ lift $ C.myThreadId throwTo tid e = TimedIO $ lift $ C.throwTo tid e timeout (toMicroseconds -> t) (TimedIO action) = TimedIO $ do res <- liftIO . T.timeout (fromIntegral t) . runReaderT action =<< ask maybe (throwM $ MTTimeoutError "Timeout has exceeded") return res forkSlave (TimedIO a) = TimedIO $ lift . ST.fork . runReaderT a =<< ask -- | Launches scenario using real time and threads. runTimedIO :: TimedIO a -> IO a runTimedIO = (curTime >>= ) . runReaderT . getTimedIO curTime :: IO Microsecond curTime = round . ( * 1000000) <$> getPOSIXTime