content: {-# LANGUAGE RankNTypes #-}
  {-# OPTIONS_HADDOCK hide #-}
  
  {-| Handles timing of animation.
       The main point is that we want to restrict the framerate to something
       sensible, instead of just displaying at the machines maximum possible
       rate and soaking up 100% cpu.
  
       We also keep track of the elapsed time since the start of the program,
       so we can pass this to the user's animation function.
  -}
  module Brillo.Internals.Interface.Animate.Timing (
    animateBegin,
    animateEnd,
  )
  where
  
  import Brillo.Internals.Interface.Animate.State
  import Brillo.Internals.Interface.Backend
  import Control.Monad
  import Data.IORef
  
  
  {-| Handles animation timing details.
       Call this function at the start of each frame.
  -}
  animateBegin :: IORef State -> DisplayCallback
  animateBegin stateRef backendRef =
    do
      -- write the current time into the display state
      displayTime <- elapsedTime backendRef
      displayTimeLast <- stateRef `getsIORef` stateDisplayTime
      let displayTimeElapsed = displayTime - displayTimeLast
  
      modifyIORef' stateRef $ \s ->
        s
          { stateDisplayTime = displayTime
          , stateDisplayTimeLast = displayTimeLast
          }
  
      -- increment the animation time
      animate <- stateRef `getsIORef` stateAnimate
      animateCount <- stateRef `getsIORef` stateAnimateCount
      animateTime <- stateRef `getsIORef` stateAnimateTime
      animateStart <- stateRef `getsIORef` stateAnimateStart
  
      {-      when (animateCount `mod` 5 == 0)
               $  putStr  $  "  displayTime        = " ++ show displayTime                ++ "\n"
                          ++ "  displayTimeLast    = " ++ show displayTimeLast            ++ "\n"
                          ++ "  displayTimeElapsed = " ++ show displayTimeElapsed         ++ "\n"
                          ++ "  fps                = " ++ show (truncate $ 1 / displayTimeElapsed)   ++ "\n"
      -}
      when (animate && not animateStart) $
        modifyIORef' stateRef $ \s ->
          s
            { stateAnimateTime = animateTime + displayTimeElapsed
            }
  
      when animate $
        modifyIORef' stateRef $ \s ->
          s
            { stateAnimateCount = animateCount + 1
            , stateAnimateStart = False
            }
  
  
  {-| Handles animation timing details.
       Call this function at the end of each frame.
  -}
  animateEnd :: IORef State -> DisplayCallback
  animateEnd stateRef backendRef =
    do
      -- timing gate, limits the maximum frame frequency (FPS)
      timeClamp <- stateRef `getsIORef` stateDisplayTimeClamp
  
      -- the start of this gate
      gateTimeStart <- elapsedTime backendRef
  
      -- end of the previous gate
      gateTimeEnd <- stateRef `getsIORef` stateGateTimeEnd
      let gateTimeElapsed = gateTimeStart - gateTimeEnd
  
      when (gateTimeElapsed < timeClamp) $
        do sleep backendRef (timeClamp - gateTimeElapsed)
  
      gateTimeFinal <- elapsedTime backendRef
  
      modifyIORef' stateRef $ \s ->
        s
          { stateGateTimeEnd = gateTimeFinal
          , stateGateTimeElapsed = gateTimeElapsed
          }
  
  
  getsIORef :: IORef a -> (a -> r) -> IO r
  getsIORef ref fun =
    liftM fun $ readIORef ref
  