packages feed

reanimate-0.5.0.0: test/TimeVarTests.hs

{-# LANGUAGE RankNTypes #-}
module TimeVarTests
  ( timeVarTests
  ) where

import           Control.Exception
import           Control.Monad.ST.Unsafe
import           Data.IORef
import           System.IO.Unsafe
import           Test.Tasty
import           Test.Tasty.ExpectedFailure
import           Test.Tasty.HUnit

import           Reanimate
import           Reanimate.Scene

timeVarTests :: TestTree
timeVarTests =
  testGroup "Time variables"
  [ expectFail $ tc "Error check1" $ do
      v <- newVar (0::Double)
      checkAt v
        [ (0,0), (0.25, 0.25), (0.5, 0.5), (1,0) ]
  , expectFail $ tc "Error check2" $ do
      io $ True @?= False
  , tc "Default value" $ do
      v <- newVar True
      checkAt v
        [ (-1, True), (0, True), (1, True) ]
  , tc "Modified value" $ do
      v <- newVar False
      writeVar v True
      checkAt v
        [(-1, False), (0, True), (1, True)]
  , tc "Modified value" $ do
      v <- newVar False
      writeVar v True
      writeVar v False
      checkAt v
        [(-1, False), (0, False), (1, False)]
  , tc "Overwrite future" $ do
      v <- newVar "def"
      fork $ do
        wait 1
        writeVar v "written in the future"
      writeVar v "overwrite"
      checkAt v
        [ (-1, "def"), (0, "overwrite"), (1, "overwrite") ]

  , tc "Interrupt tween" $ do
      v <- newVar 0
      fork $ tweenVar v 1 $ \prev t -> fromToS prev 1 t
      checkAt v
        [ (0,0), (0.25, 0.25), (0.5, 0.5), (1,1) ]
      fork $ do
        wait 0.5
        writeVar v 0.5
      checkAt v
        [ (0,0), (0.25, 0.25), (0.5, 0.5), (1,0.5) ]
  , tc "Double tween" $ do
      v <- newVar 0
      fork $ tweenVar v 1 $ \prev t -> fromToS prev 1 t
      fork $ tweenVar v 1 $ \prev t -> fromToS prev 0 t
      checkAt v
        [ (0,0), (0.25, 0.25*0.75), (0.5, 0.5*0.5), (1,1*0) ]
  , expectFail $ tc "Negative tween duration" $ do
      v <- newVar 0
      tweenVar v (-1) $ \prev t -> fromToS prev 1 t
  , tc "Tween 1" $ do
      v <- newVar 0
      wait 1
      tweenVar v 1 $ \prev t -> fromToS prev 1 t
      checkAt v
        [ (0,0), (1,0), (1.5,0.5), (2,1), (3,1) ]
  , tc "Tween 2" $ do
      v <- newVar (0::Int)
      wait 1
      tweenVar v 1 $ \_prev _t -> 1
      checkAt v
        [ (0,0), (1,1), (1.5,1), (2,1), (3,1) ]
  , tc "Tween 3" $ do
      v <- newVar (0::Int)
      wait 1
      tweenVar v 0 $ \_prev _t -> 1
      checkAt v
        [ (0,0), (1,1), (2,1)]
  , tc "Tween 4" $ do
      v <- newVar (0::Double)
      tweenVar v 1 $ \prev t -> fromToS prev 1 t
      tweenVar v 1 $ \prev t -> fromToS prev 0 t
      checkAt v
        [ (0,0), (0.5,0.5), (1,1), (1.5,0.5), (2,0)]
  , tc "Tween 5" $ do
      v <- newVar (0::Double)
      tweenVar v 1 $ \prev t -> fromToS prev 1 t
      tweenVar v 1 $ \prev t -> fromToS prev 0 t
      wait (-1)
      writeVar v 2
      checkAt v
        [ (0,0), (0.5,0.5), (1,2), (1.5,2), (2,2)]
  , tc "Tween zero duration" $ do
      v <- newVar 0
      wait 1
      tweenVar v 0 $ \_prev t -> t
      checkAt v
        [ (0,0), (1,1), (2,1)]
  , tc "Performance/writeVar" $ do
      v <- newVar (0::Int)
      ref <- io $ newIORef (0::Int)
      modifyVar v $ \old -> unsafePerformIO (modifyIORef ref (+1) >> return 1) + old
      at0 <- readVar v
      wait 1
      at1 <- readVar v
      io $ at0+at1 @?= 2
      counter <- io $ readIORef ref
      io $ counter @?= 1
  , tc "Performance/tweenVar" $ do
      v <- newVar (0::Double)
      ref <- io $ newIORef (0::Int)
      let expensive prev t = unsafePerformIO $ do
            modifyIORef ref (+1)
            evaluate (fromToS prev 1 t)
      tweenVar v 1 expensive
      -- Reading at 0.5 and 1 should call the expensive function.
      -- Reading at 2 and 3 should reuse the result from 1.
      -- The count should therefore be 2 and not 4.
      checkAt v
        [ (0.5,0.5), (1,1), (2,1), (3,1) ]
      counter <- io $ readIORef ref
      io $ counter @?= 2
  , tc "Performance/tweenVar 2" $ do
      v <- newVar (0::Double)
      fork $ wait 10 >> writeVar v 1
      ref <- io $ newIORef (0::Int)
      let expensive prev t = unsafePerformIO $ do
            modifyIORef ref (+1)
            evaluate (fromToS prev 1 t)
      tweenVar v 1 expensive
      wait 2
      writeVar v 2
      -- Reading at 0.5 and 1 should call the expensive function.
      -- Reading at 2 should reuse the result from 1.
      -- The count should therefore be 2 and not 4.
      checkAt v
        [ (0.5,0.5), (1,1), (2,1), (3,2), (4,2) ]
      counter <- io $ readIORef ref
      io $ counter @?= 2
  ]

checkAt :: (Eq a, Show a) => Var s a -> [(Time, a)] -> Scene s ()
checkAt _ [] = pure ()
checkAt v ((d,expected):xs) = do
  fork $ do
    now <- queryNow
    wait (d-now)
    val <- readVar v
    io $ val @?= expected
  checkAt v xs

tc :: TestName -> (forall s. Scene s ()) -> TestTree
tc name action = testCase name $
  (evalScene action @?= ())
  `catch` \(ErrorCall err) -> assertFailure err

io :: IO a -> Scene s a
io action = liftST (unsafeIOToST action)