fixed-timestep 0.1.0.0 → 0.2.0.0
raw patch · 6 files changed
+140/−95 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Time.Flick: secnd :: Flicks
- TimeStep: Boottime :: Clock
- TimeStep: Monotonic :: Clock
- TimeStep: MonotonicCoarse :: Clock
- TimeStep: MonotonicRaw :: Clock
- TimeStep: ProcessCPUTime :: Clock
- TimeStep: Realtime :: Clock
- TimeStep: RealtimeCoarse :: Clock
- TimeStep: ThreadCPUTime :: Clock
- TimeStep: asyncRepeatedly :: Rational -> IO a -> IO (Async void)
- TimeStep: asyncRepeatedly' :: Rational -> Clock -> IO a -> IO (Async void)
- TimeStep: data Clock
- TimeStep: data TimeStepClock
- TimeStep: instance GHC.Classes.Eq TimeStep.TimeStepClock
- TimeStep: loopUsing :: TimeStepClock -> IO a -> IO void
- TimeStep: newClock :: Rational -> Clock -> IO TimeStepClock
- TimeStep: repeatedly :: Rational -> IO a -> IO void
- TimeStep: repeatedly' :: Rational -> Clock -> IO a -> IO void
+ Time.Flick: oneSecond :: Flicks
+ Time.Repeatedly: Boottime :: Clock
+ Time.Repeatedly: Monotonic :: Clock
+ Time.Repeatedly: MonotonicCoarse :: Clock
+ Time.Repeatedly: MonotonicRaw :: Clock
+ Time.Repeatedly: ProcessCPUTime :: Clock
+ Time.Repeatedly: Realtime :: Clock
+ Time.Repeatedly: RealtimeCoarse :: Clock
+ Time.Repeatedly: ThreadCPUTime :: Clock
+ Time.Repeatedly: asyncRepeatedly :: Rational -> IO a -> IO (Async void)
+ Time.Repeatedly: asyncRepeatedly' :: Rational -> Clock -> IO a -> IO (Async void)
+ Time.Repeatedly: data Clock
+ Time.Repeatedly: data TimeStepClock
+ Time.Repeatedly: instance GHC.Classes.Eq Time.Repeatedly.TimeStepClock
+ Time.Repeatedly: loopUsing :: TimeStepClock -> IO a -> IO void
+ Time.Repeatedly: newClock :: Rational -> Clock -> IO TimeStepClock
+ Time.Repeatedly: repeatedly :: Rational -> IO a -> IO void
+ Time.Repeatedly: repeatedly' :: Rational -> Clock -> IO a -> IO void
Files
- CHANGELOG.md +10/−2
- README.md +38/−0
- fixed-timestep.cabal +3/−3
- lib/Time/Flick.hs +5/−6
- lib/Time/Repeatedly.hs +84/−0
- lib/TimeStep.hs +0/−84
CHANGELOG.md view
@@ -1,5 +1,13 @@ # Revision history for fixed-timestep -## 0.1.0.0 -- YYYY-mm-dd +## 0.2.0.0 -- 2019-03-25 -* First version. Released on an unsuspecting world. +* Rename `Time.Flick.secnd` to `Time.Flick.oneSecond` + * This pleases the Orthography Gods. +* Move `TimeStep` module to `Time.Repeatedly` + * It makes more sense to keep all modules under one namespace. + +## 0.1.0.0 -- 2019-03-13 + +* Released. Basic timekeeping in units of flicks, and + functions for repeatedly running actions.
+ README.md view
@@ -0,0 +1,38 @@+# fixed-timestep + +Pure Haskell library to run an action repeatedly, a specific amount of times per second. + +Internal timekeeping is done in [flicks](https://github.com/OculusVR/Flicks#README), a +unit of time equal to precisely 1/705600000 of a second. + +Basic usage: + +```haskell +λ> import Data.Time.Clock +λ> import Time.Repeatedly +λ> repeatedly 1 (print =<< getCurrentTime) +2019-03-13 21:14:36.826248 UTC +2019-03-13 21:14:37.8282926 UTC +2019-03-13 21:14:38.8327692 UTC +2019-03-13 21:14:39.8359494 UTC +2019-03-13 21:14:40.8374415 UTC +^CInterrupted. +``` + +Using `async`, you can repeat multiple actions independently, and also cancel them: + +```haskell +λ> import Data.Time.Clock +λ> import Control.Concurrent.Asyncx +λ> import Time.Repeatedly +λ> printer <- asyncRepeatedly 1 (print =<< getCurrentTime) +2019-03-13 21:21:27.5228834 UTC +2019-03-13 21:21:28.523892 UTC +2019-03-13 21:21:29.5283634 UTC +2019-03-13 21:21:30.5313565 UTC +2019-03-13 21:21:31.535904 UTC +2019-03-13 21:21:32.5373828 UTC +2019-03-13 21:21:33.5393834 UTC +2019-03-13 21:21:34.5408811 UTC +cancel printer +```
fixed-timestep.cabal view
@@ -4,7 +4,7 @@ -- http://haskell.org/cabal/users-guide/ name: fixed-timestep -version: 0.1.0.0 +version: 0.2.0.0 synopsis: Pure Haskell library to repeat an action at a specific frequency. description: Repeat IO actions at a specific frequency, using flicks (1/705600000 of a second) for timekeeping. @@ -17,7 +17,7 @@ maintainer: Nicolas Stamm <nstamm@gmx.de> copyright: 2019 Nicolas Stamm (Solonarv) category: Time -extra-source-files: CHANGELOG.md +extra-source-files: CHANGELOG.md, README.md source-repository head type: git @@ -25,7 +25,7 @@ library exposed-modules: Time.Flick - , TimeStep + , Time.Repeatedly -- other-modules: other-extensions: DerivingStrategies GeneralizedNewtypeDeriving NamedFieldPuns ScopedTypeVariables
lib/Time/Flick.hs view
@@ -4,11 +4,10 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} module Time.Flick ( -- * The Flicks data type Flicks(..) - , flicksPerSecond, secnd + , flicksPerSecond, oneSecond -- * Conversions , approxFlicks , periodForFreq @@ -34,13 +33,13 @@ newtype Flicks = Flicks { unFlicks :: Int64 } deriving newtype (Eq, Ord, Show, Num, Enum, Integral, Real) --- | How many flicks are in a second. Precisely 705600000. +-- | How many flicks are in a second: precisely 705600000. flicksPerSecond :: Num a => a flicksPerSecond = 705600000 -- | One second in flicks. -secnd :: Flicks -secnd = flicksPerSecond +oneSecond :: Flicks +oneSecond = flicksPerSecond -- | Convert a number of seconds into flicks, rounding towards zero. approxFlicks :: Rational -> Flicks @@ -62,7 +61,7 @@ flicksToFixed :: forall r. HasResolution r => Flicks -> Fixed r flicksToFixed (Flicks t) = MkFixed val where - val = fromIntegral t * resolution (Proxy @r) `quot` flicksPerSecond + val = fromIntegral t * resolution (Proxy :: Proxy r) `quot` flicksPerSecond -- | Convert flicks into @'DiffTime'@ flicksToDiffTime :: Flicks -> DiffTime
+ lib/Time/Repeatedly.hs view
@@ -0,0 +1,84 @@+-- | Utilities for repeatedly running @'IO'@ actions +-- at a specific frequency. +module Time.Repeatedly + ( -- * Repeating actions + repeatedly + , repeatedly' + -- * Repeating actions in another thread + , asyncRepeatedly + , asyncRepeatedly' + -- * Implementation details + , TimeStepClock + , newClock + , loopUsing + -- * Reexports (timekeeping) + , module Time.Flick + , Clock(..) + ) where + +import Control.Concurrent +import Control.Monad +import Data.Bifunctor +import Data.Fixed (divMod') +import Data.Int +import Data.IORef +import Data.Ratio + +import Control.Concurrent.Async +import System.Clock + +import Time.Flick + +-- | Internal state of the stepper. Created using @'newClock'@. +data TimeStepClock = TimeStepClock + { tscLastTick :: IORef Flicks + , tscDesiredTickTime :: Flicks + , tscClockType :: Clock + } deriving Eq + +-- | Run an action repeatedly at the specified frequency. +-- Uses the 'Monotonic' clock for timing. +repeatedly :: Rational -> IO a -> IO void +repeatedly freq = repeatedly' freq Monotonic + +-- | Run an action repeatedly at the specified frequency, +-- using the given clock type. +repeatedly' :: Rational -> Clock -> IO a -> IO void +repeatedly' freq clockTy act = do + clock <- newClock freq clockTy + loopUsing clock act + +-- | Run an action repeatedly at the specified frequency, in a separate thread. +-- Uses the 'Monotonic' clock for timing. +asyncRepeatedly :: Rational -> IO a -> IO (Async void) +asyncRepeatedly freq = asyncRepeatedly' freq Monotonic + +-- | Run an action repeatedly at the specified frequency, in a separate thread, +-- using the given clock type. +asyncRepeatedly' :: Rational -> Clock -> IO a -> IO (Async void) +asyncRepeatedly' freq clockTy act = do + clock <- newClock freq clockTy + async (loopUsing clock act) + +-- | Initialise a new clock structure for the given frequency and using +-- the given clock type. +newClock :: Rational -> Clock -> IO TimeStepClock +newClock freq clockTy = do + leftoverRef <- newIORef =<< flicksNow clockTy + pure TimeStepClock + { tscLastTick = leftoverRef + , tscDesiredTickTime = periodForFreq freq + , tscClockType = clockTy + } + +-- | Repeat the given action using the information in the clock structure. +loopUsing :: TimeStepClock -> IO a -> IO void +loopUsing (TimeStepClock lastTickRef tickTime clockTy) act = forever $ do + now <- flicksNow clockTy + lastTick <- readIORef lastTickRef + let elapsed = now - lastTick + (ticksToRun, leftover) = elapsed `divMod` tickTime + replicateM_ (fromIntegral ticksToRun) act + writeIORef lastTickRef now + now' <- flicksNow clockTy + threadDelayFlicks (tickTime + now - now')
− lib/TimeStep.hs
@@ -1,84 +0,0 @@--- | Utilities for repeatedly running @'IO'@ actions --- at a specific frequency. -module TimeStep - ( -- * Repeating actions - repeatedly - , repeatedly' - -- * Repeating actions in another thread - , asyncRepeatedly - , asyncRepeatedly' - -- * Implementation details - , TimeStepClock - , newClock - , loopUsing - -- * Reexports (timekeeping) - , module Time.Flick - , Clock(..) - ) where - -import Control.Concurrent -import Control.Monad -import Data.Bifunctor -import Data.Fixed (divMod') -import Data.Int -import Data.IORef -import Data.Ratio - -import Control.Concurrent.Async -import System.Clock - -import Time.Flick - --- | Internal state of the stepper. Created using @'newClock'@. -data TimeStepClock = TimeStepClock - { tscLastTick :: IORef Flicks - , tscDesiredTickTime :: Flicks - , tscClockType :: Clock - } deriving Eq - --- | Run an action repeatedly at the specified frequency. --- Uses the 'Monotonic' clock for timing. -repeatedly :: Rational -> IO a -> IO void -repeatedly freq = repeatedly' freq Monotonic - --- | Run an action repeatedly at the specified frequency, --- using the given clock type. -repeatedly' :: Rational -> Clock -> IO a -> IO void -repeatedly' freq clockTy act = do - clock <- newClock freq clockTy - loopUsing clock act - --- | Run an action repeatedly at the specified frequency, in a separate thread. --- Uses the 'Monotonic' clock for timing. -asyncRepeatedly :: Rational -> IO a -> IO (Async void) -asyncRepeatedly freq = asyncRepeatedly' freq Monotonic - --- | Run an action repeatedly at the specified frequency, in a separate thread, --- using the given clock type. -asyncRepeatedly' :: Rational -> Clock -> IO a -> IO (Async void) -asyncRepeatedly' freq clockTy act = do - clock <- newClock freq clockTy - async (loopUsing clock act) - --- | Initialise a new clock structure for the given frequency and using --- the given clock type. -newClock :: Rational -> Clock -> IO TimeStepClock -newClock freq clockTy = do - leftoverRef <- newIORef =<< flicksNow clockTy - pure TimeStepClock - { tscLastTick = leftoverRef - , tscDesiredTickTime = periodForFreq freq - , tscClockType = clockTy - } - --- | Repeat the given action using the information in the clock structure. -loopUsing :: TimeStepClock -> IO a -> IO void -loopUsing (TimeStepClock lastTickRef tickTime clockTy) act = forever $ do - now <- flicksNow clockTy - lastTick <- readIORef lastTickRef - let elapsed = now - lastTick - (ticksToRun, leftover) = elapsed `divMod` tickTime - replicateM_ (fromIntegral ticksToRun) act - writeIORef lastTickRef now - now' <- flicksNow clockTy - threadDelayFlicks (tickTime + now - now')