packages feed

brillo-2.0.0: Brillo/Internals/Interface/Simulate/Idle.hs

{-# LANGUAGE RankNTypes #-}
{-# OPTIONS_HADDOCK hide #-}

module Brillo.Internals.Interface.Simulate.Idle (callback_simulate_idle)
where

import Brillo.Data.ViewPort
import Brillo.Internals.Interface.Animate.State qualified as AN
import Brillo.Internals.Interface.Backend qualified as Backend
import Brillo.Internals.Interface.Callback
import Brillo.Internals.Interface.Simulate.State qualified as SM
import Control.Monad
import Data.IORef
import GHC.Float (double2Float)


{-| The graphics library calls back on this function when it's finished drawing
     and it's time to do some computation.
-}
callback_simulate_idle ::
  -- | the simulation state
  IORef SM.State ->
  -- | the animation statea
  IORef AN.State ->
  {-| action to get the 'ViewPort'.  We don't use an 'IORef'
  directly because sometimes we hold a ref to a 'ViewPort' (in
  Game) and sometimes a ref to a 'ViewState'.
  -}
  IO ViewPort ->
  -- | the current world
  IORef world ->
  -- | fn to advance the world
  (ViewPort -> Float -> world -> IO world) ->
  {-| how much time to advance world by
     in single step mode
  -}
  Float ->
  IdleCallback
callback_simulate_idle simSR animateSR viewSA worldSR worldAdvance _singleStepTime backendRef =
  {-# SCC "callbackIdle" #-}
  do simulate_run simSR animateSR viewSA worldSR worldAdvance backendRef


-- take the number of steps specified by controlWarp
simulate_run ::
  IORef SM.State ->
  IORef AN.State ->
  IO ViewPort ->
  IORef world ->
  (ViewPort -> Float -> world -> IO world) ->
  IdleCallback
simulate_run simSR _ viewSA worldSR worldAdvance backendRef =
  do
    viewS <- viewSA
    simS <- readIORef simSR
    worldS <- readIORef worldSR

    -- get the elapsed time since the start simulation (wall clock)
    elapsedTime <- fmap double2Float $ Backend.elapsedTime backendRef

    -- get how far along the simulation is
    simTime <- simSR `getsIORef` SM.stateSimTime

    -- we want to simulate this much extra time to bring the simulation
    --      up to the wall clock.
    let thisTime = elapsedTime - simTime

    -- work out how many steps of simulation this equals
    resolution <- simSR `getsIORef` SM.stateResolution
    let timePerStep = 1 / fromIntegral resolution
    let thisSteps_ = truncate $ fromIntegral resolution * thisTime
    let thisSteps = if thisSteps_ < 0 then 0 else thisSteps_

    let newSimTime = simTime + fromIntegral thisSteps * timePerStep

    {-      putStr  $  "elapsed time    = " ++ show elapsedTime     ++ "\n"
                    ++ "sim time        = " ++ show simTime         ++ "\n"
                    ++ "this time       = " ++ show thisTime        ++ "\n"
                    ++ "this steps      = " ++ show thisSteps       ++ "\n"
                    ++ "new sim time    = " ++ show newSimTime      ++ "\n"
                    ++ "taking          = " ++ show thisSteps       ++ "\n\n"
    -}
    -- work out the final step number for this display cycle
    let nStart = SM.stateIteration simS
    let nFinal = nStart + thisSteps

    -- keep advancing the world until we get to the final iteration number
    (_, world') <-
      untilM
        (\(n, _) -> n >= nFinal)
        (\(n, w) -> liftM (\w' -> (n + 1, w')) (worldAdvance viewS timePerStep w))
        (nStart, worldS)

    -- write the world back into its IORef
    -- We need to seq on the world to avoid space leaks when the window is not showing.
    world' `seq` writeIORef worldSR world'

    -- update the control state
    modifyIORef' simSR $ \c ->
      c
        { SM.stateIteration = nFinal
        , SM.stateSimTime = newSimTime
        }

    -- tell backend we want to draw the window after returning
    Backend.postRedisplay backendRef


getsIORef :: IORef a -> (a -> r) -> IO r
getsIORef ref fun =
  liftM fun $ readIORef ref


untilM :: (Monad m) => (a -> Bool) -> (a -> m a) -> a -> m a
untilM test op i = go i
  where
    go x
      | test x = return x
      | otherwise = op x >>= go