packages feed

antikythera-0.1.0.0: src/Control/Antikythera/Scheduling.hs

{-# LANGUAGE NumericUnderscores #-}

-- |
-- Module        : Control.Antikythera
-- Copyright     : Gautier DI FOLCO
-- License       : ISC
--
-- Maintainer    : Gautier DI FOLCO <gautier.difolco@gmail.com>
-- Stability     : Stable
-- Portability   : Portable
--
-- Run an action given a 'Periodicity'
--
-- > import Control.Antikythera
-- >
-- > runPeriodicityZonedTime (inclusiveRange (Min 8) (Max 23) hour .&& every 30 minute) $
-- >   putStrLn "Don't forget to hydrate"
module Control.Antikythera.Scheduling
  ( -- * 'Periodicity' runners
    runPeriodicityUTCTime,
    runPeriodicityZonedTime,
    runPeriodicityZonedTime',
    runPeriodicity,
    runPeriodicityWithHooks,

    -- * Position in time
    PositionInTime (..),
    utcTime,
    zonedTime,
    zonedTime',
  )
where

import Control.Antikythera.Periodicity
import Control.Antikythera.Unit.Time
import Control.Concurrent.Thread.Delay (delay)
import Control.Monad (forM_)
import Data.Time

-- * 'Periodicity' runners

-- | Run an action given a 'Periodicity' using system's 'UTCTime'
--
-- Note: the action is run in the loop, consider using a dedicated thread as any exception would break it
runPeriodicityUTCTime :: Periodicity UTCTime -> IO () -> IO ()
runPeriodicityUTCTime = runPeriodicity utcTime

-- | Run an action given a 'Periodicity' using system's 'ZonedTime' (wrapped to accomodate combination operators)
--
-- Note: the action is run in the loop, consider using a dedicated thread as any exception would break it
runPeriodicityZonedTime :: Periodicity ZonedTimeWrapped -> IO () -> IO ()
runPeriodicityZonedTime = runPeriodicity zonedTime

-- | Run an action given a 'Periodicity' using system's 'ZonedTime'
--
-- Note: the action is run in the loop, consider using a dedicated thread as any exception would break it
runPeriodicityZonedTime' :: Periodicity ZonedTime -> IO () -> IO ()
runPeriodicityZonedTime' = runPeriodicity zonedTime'

-- | Run an action given a 'Periodicity'
--
-- Note: the action is run in the loop, consider using a dedicated thread as any exception would break it
runPeriodicity ::
  -- | Fetch the time and compute the delay time
  PositionInTime t ->
  Periodicity t ->
  IO () ->
  IO ()
runPeriodicity =
  runPeriodicityWithHooks
    (const $ return ())
    (return ())
    (const $ return ())

-- | Run an action given a 'Periodicity' with hooks
--
-- Note: the action is run in the loop, consider using a dedicated thread as any exception would break it
runPeriodicityWithHooks ::
  -- | Hooks planned
  (t -> IO ()) ->
  -- | Hooks at time (before running the action)
  IO () ->
  -- | Hooks done
  (a -> IO ()) ->
  -- | Fetch the time and compute the delay time
  PositionInTime t ->
  Periodicity t ->
  IO a ->
  IO ()
runPeriodicityWithHooks hookPlanned hookBefore hookAfter pit p f = do
  now <- pit.getTime
  forM_ (p.nextPeriod now) $ \next -> do
    hookPlanned next
    delay $ pit.delayMicroSeconds now next
    hookBefore
    f >>= hookAfter
    runPeriodicityWithHooks hookPlanned hookBefore hookAfter pit p f

-- * Position in time

-- | Fetch the time and compute the delay time
data PositionInTime t = PositionInTime
  { getTime :: IO t,
    -- | now -> nextPeriod -> µs
    delayMicroSeconds :: t -> t -> Integer
  }

-- | System's 'UTCTime'
utcTime :: PositionInTime UTCTime
utcTime =
  PositionInTime
    { getTime = getCurrentTime,
      delayMicroSeconds = \now next ->
        ceiling $ 1_000_000 * nominalDiffTimeToSeconds (diffUTCTime next now)
    }

-- | System's 'ZonedTime' (wrapped to accomodate combination operators)
zonedTime :: PositionInTime ZonedTimeWrapped
zonedTime =
  PositionInTime
    { getTime = ZonedTimeWrapped <$> getZonedTime,
      delayMicroSeconds = \(ZonedTimeWrapped now) (ZonedTimeWrapped next) ->
        ceiling $ 1_000_000 * nominalDiffTimeToSeconds (diffUTCTime (zonedTimeToUTC next) (zonedTimeToUTC now))
    }

-- | System's 'ZonedTime'
zonedTime' :: PositionInTime ZonedTime
zonedTime' =
  PositionInTime
    { getTime = getZonedTime,
      delayMicroSeconds = \now next ->
        ceiling $ 1_000_000 * nominalDiffTimeToSeconds (diffUTCTime (zonedTimeToUTC next) (zonedTimeToUTC now))
    }