-- |
-- Module: M.IO.Tick
-- Description: Timing control for periodic operations
-- Copyright: (c) axionbuster, 2025
-- License: BSD-3-Clause
--
-- This module provides functionality for running actions at controlled intervals
-- with adaptive timing adjustments.
module M.IO.Tick
  ( -- * Timing Control
    tick,
  )
where

import Control.Monad
import Data.Functor
import Effectful
import Effectful.Concurrent
import Effectful.Concurrent.STM
import Effectful.State.Static.Local
import GHC.Clock

-- | Executes an action periodically with adaptive timing control.
--
-- The function ensures the action runs at a target frequency by adjusting
-- delays between executions based on execution time.
--
-- @
-- tick ratevar action    -- Runs \'action\' at frequency specified by rateVar (in Hz)
-- @
tick ::
  (IOE :> es, Concurrent :> es) =>
  -- | Target frequency in Hz (stored in 'TVar')
  TVar Double ->
  -- | Action to execute periodically
  Eff es () ->
  Eff es b
tick :: forall (es :: [Effect]) b.
(IOE :> es, Concurrent :> es) =>
TVar Double -> Eff es () -> Eff es b
tick TVar Double
tr Eff es ()
f = do
  Double
a <- IO Double -> Eff es Double
forall a. IO a -> Eff es a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO Double
getMonotonicTime -- s
  Double -> Eff (State Double : es) b -> Eff es b
forall s (es :: [Effect]) a.
HasCallStack =>
s -> Eff (State s : es) a -> Eff es a
evalState Double
a do
    Eff (State Double : es) () -> Eff (State Double : es) b
forall (f :: * -> *) a b. Applicative f => f a -> f b
forever do
      Eff es () -> Eff (State Double : es) ()
forall (es :: [Effect]) a (e :: Effect). Eff es a -> Eff (e : es) a
raise Eff es ()
f
      Double
t0 <- Eff (State Double : es) Double
forall s (es :: [Effect]).
(HasCallStack, State s :> es) =>
Eff es s
get
      Double
t1 <- IO Double -> Eff (State Double : es) Double
forall a. IO a -> Eff (State Double : es) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO Double
getMonotonicTime
      Double
wa <- TVar Double -> Eff (State Double : es) Double
forall (es :: [Effect]) a. (Concurrent :> es) => TVar a -> Eff es a
readTVarIO TVar Double
tr Eff (State Double : es) Double
-> (Double -> Double) -> Eff (State Double : es) Double
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \Double
t -> Double -> Double -> Double
forall a. Ord a => a -> a -> a
max Double
0 (Double -> Double
forall a. Fractional a => a -> a
recip Double
t Double -> Double -> Double
forall a. Num a => a -> a -> a
- (Double
t1 Double -> Double -> Double
forall a. Num a => a -> a -> a
- Double
t0))
      Int -> Eff (State Double : es) ()
forall (es :: [Effect]). (Concurrent :> es) => Int -> Eff es ()
threadDelay (Double -> Int
forall b. Integral b => Double -> b
forall a b. (RealFrac a, Integral b) => a -> b
ceiling (Double
1e6 Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
wa)) -- 1e6 us = (1e6 us/s) * s = s