<commit_before>{-|
Module      : Game.GoreAndAsh.Time
Description : Simple API for timer events
Copyright   : (c) <NAME>, 2016
License     : BSD3
Maintainer  : <EMAIL>
Stability   : experimental
Portability : POSIX
-}
{-# LANGUAGE RecursiveDo #-}
module Game.GoreAndAsh.Time(
    TimerMonad(..)
  , AlignWithFps(..)
  , tickEveryN
  , TimerT
  ) where

import Control.Concurrent.Thread.Delay as TD
import Control.Monad.Base
import Control.Monad.Catch
import Control.Monad.Error.Class
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Reader
import Control.Monad.Trans.Control
import Control.Monad.Trans.Identity
import Data.IORef
import Data.Proxy
import Data.Time
import Game.GoreAndAsh.Core.ExternalRef
import Game.GoreAndAsh.Core.Monad
import GHC.Event hiding (Event)

-- | API of logging module that is used by game logic code
class (Reflex t, MonadFix m) => TimerMonad t m | m -> t where
  -- | Get event that fires every n seconds.
  --
  -- Note: the event will tick even there are no subscriders to it. Use
  -- 'tickEveryUntil' if you want to free underlying thread.
  tickEvery :: NominalDiffTime -> m (Event t ())
  -- | Generate an event after given time once
  tickOnce :: NominalDiffTime -> m (Event t ())
  -- | Get event that fires every n seconds. Second event cease ticking.
  tickEveryUntil :: NominalDiffTime -> Event t a -> m (Event t ())
  -- | Get event that fires only after given event with specified delay
  delayBy :: NominalDiffTime -> Event t a -> m (Event t a)

class AlignWithFps r where
  -- | Fire event not frequently as given frame per second ratio. Starts global
  -- counter, so first occurence of resulted event can be delayed by 1/fps seconds.
  -- The resulted event fires with last occurence of original event during 1/fps
  -- interval.
  alignWithFps :: (TimerMonad t m, MonadAppHost t m)
    => Int -- ^ FPS (frames per second)
    -> r t a -- ^ Event/Dynamic that frequently changes
    -> m (r t a) -- ^ Event/Dynamic that changes are aligned with FPS

  -- | Fire event not frequently as given frame per second ratio. Doesn't delay
  -- first occurence, simply throws away every occurences within 1/fps time interval
  -- after not filtered fire.
  limitRate :: (TimerMonad t m, MonadAppHost t m)
    => Int -- ^ FPS or rate
    -> r t a -- ^ Event/Dynamic which occurences you want to limit
    -> m (r t a) -- ^ Event/Dynamic that changes are filtered with given rate

instance AlignWithFps Event where
  alignWithFps fps ea = do
    fpsE <- tickEvery . realToFrac $ 1 / (fromIntegral fps :: Double)
    ref <- liftIO $ newIORef Nothing
    performEvent_ $ ffor ea $ liftIO . atomicWriteIORef ref . Just
    alignedE <- performEvent $ ffor fpsE $ const $
      liftIO $ atomicModifyIORef' ref $ \v -> (Nothing, v)
    return $ fmapMaybe id alignedE

  limitRate fps ea = do
    let
      dt = realToFrac $ 1 / (fromIntegral fps :: Double)
      step = do
        tickE <- tickOnce dt
        switchPromptly never (const ea <$> tickE)
    switchPromptlyDyn <$> holdAppHost (return ea) (const step <$> ea)

instance AlignWithFps Dynamic where<commit_msg>Rewrite limitRate function<commit_after>{-|
Module      : Game.GoreAndAsh.Time
Description : Simple API for timer events
Copyright   : (c) <NAME>, 2016
License     : BSD3
Maintainer  : <EMAIL>
Stability   : experimental
Portability : POSIX
-}
{-# LANGUAGE RecursiveDo #-}
module Game.GoreAndAsh.Time(
    TimerMonad(..)
  , AlignWithFps(..)
  , tickEveryN
  , TimerT
  ) where

import Control.Concurrent.Thread.Delay as TD
import Control.Monad.Base
import Control.Monad.Catch
import Control.Monad.Error.Class
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Reader
import Control.Monad.Trans.Control
import Control.Monad.Trans.Identity
import Data.IORef
import Data.Proxy
import Data.Time
import Game.GoreAndAsh.Core.ExternalRef
import Game.GoreAndAsh.Core.Monad
import GHC.Event hiding (Event)

-- | API of logging module that is used by game logic code
class (Reflex t, MonadFix m) => TimerMonad t m | m -> t where
  -- | Get event that fires every n seconds.
  --
  -- Note: the event will tick even there are no subscriders to it. Use
  -- 'tickEveryUntil' if you want to free underlying thread.
  tickEvery :: NominalDiffTime -> m (Event t ())
  -- | Generate an event after given time once
  tickOnce :: NominalDiffTime -> m (Event t ())
  -- | Get event that fires every n seconds. Second event cease ticking.
  tickEveryUntil :: NominalDiffTime -> Event t a -> m (Event t ())
  -- | Get event that fires only after given event with specified delay
  delayBy :: NominalDiffTime -> Event t a -> m (Event t a)

class AlignWithFps r where
  -- | Fire event not frequently as given frame per second ratio. Starts global
  -- counter, so first occurence of resulted event can be delayed by 1/fps seconds.
  -- The resulted event fires with last occurence of original event during 1/fps
  -- interval.
  alignWithFps :: (TimerMonad t m, MonadAppHost t m)
    => Int -- ^ FPS (frames per second)
    -> r t a -- ^ Event/Dynamic that frequently changes
    -> m (r t a) -- ^ Event/Dynamic that changes are aligned with FPS

  -- | Fire event not frequently as given frame per second ratio. Doesn't delay
  -- first occurence, simply throws away every occurences within 1/fps time interval
  -- after not filtered fire.
  limitRate :: (TimerMonad t m, MonadAppHost t m)
    => Int -- ^ FPS or rate
    -> r t a -- ^ Event/Dynamic which occurences you want to limit
    -> m (r t a) -- ^ Event/Dynamic that changes are filtered with given rate

instance AlignWithFps Event where
  alignWithFps fps ea = do
    fpsE <- tickEvery . realToFrac $ 1 / (fromIntegral fps :: Double)
    ref <- liftIO $ newIORef Nothing
    performEvent_ $ ffor ea $ liftIO . atomicWriteIORef ref . Just
    alignedE <- performEvent $ ffor fpsE $ const $
      liftIO $ atomicModifyIORef' ref $ \v -> (Nothing, v)
    return $ fmapMaybe id alignedE

  limitRate fps ea = do
    -- Create variable with gate flag
    ref <- liftIO $ newIORef False
    -- Pass values when gate is open
    msilentedE <- performEvent $ ffor ea $ \v -> do
      silented <- liftIO $ readIORef ref
      return $ if silented then Nothing else Just v
    let silentedE = fmapMaybe id msilentedE -- remove Nothing from occurences
    -- Close gate for dt after each final occurence
    _ <- performAppHost $ ffor silentedE $ const $ do
      liftIO $ writeIORef ref True
      let dt = realToFrac $ 1 / (fromIntegral fps :: Double)
      releaseE <- tickOnce dt
      performEvent_ $ ffor releaseE $ const $ liftIO $ writeIORef ref False
    return silentedE

instance AlignWithFps Dynamic where
{'fraction_non_alphanumeric': 0.08840688107703815, 'fraction_numerical': 0.0029917726252804786, 'mean_word_length': 3.0521212121212122, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '896213', 'n_tokens_mistral': 2114, 'n_tokens_neox': 2039, 'n_words': 806}