content: module Ema.Dynamic (
    Dynamic (Dynamic),
  ) where
  
  import Control.Monad.Logger (MonadLogger, logDebugNS)
  import UnliftIO (MonadUnliftIO, race_)
  import UnliftIO.Concurrent (threadDelay)
  
  {- | A time-varying value of type `a`, changing under monad `m`.
  
    To create a `Dynamic`, supply the initial value along with a function that
    forever updates it using the given monadic update function.
  
   `Dynamic`'s can be composed using `Applicative`.
  -}
  newtype Dynamic m a
    = Dynamic
        ( -- Initial value
          a
        , -- Set a new value
          (a -> m ()) -> m ()
        )
  
  instance Functor (Dynamic m) where
    fmap f (Dynamic (x0, xf)) =
      Dynamic
        ( f x0
        , \send -> xf $ send . f
        )
  
  instance (MonadUnliftIO m, MonadLogger m) => Applicative (Dynamic m) where
    pure x = Dynamic (x, const pass)
    liftA2 f (Dynamic (x0, xf)) (Dynamic (y0, yf)) =
      Dynamic
        ( f x0 y0
        , \send -> do
            var <- newTVarIO (x0, y0)
            sendLock :: TMVar () <- newEmptyTMVarIO
            race_
              ( do
                  xf $ \x -> do
                    atomically $ putTMVar sendLock ()
                    logDebugNS "ema.dyn.app" "left update"
                    send <=< atomically $ do
                      modifyTVar' var $ first (const x)
                      f x . snd <$> readTVar var
                    atomically $ takeTMVar sendLock
                  logDebugNS "ema.dyn.app" "updater exited; keeping thread alive"
                  threadDelay maxBound
              )
              ( do
                  yf $ \y -> do
                    atomically $ putTMVar sendLock ()
                    logDebugNS "ema.dyn.app" "right update"
                    send <=< atomically $ do
                      modifyTVar' var $ second (const y)
                      (`f` y) . fst <$> readTVar var
                    atomically $ takeTMVar sendLock
                  logDebugNS "ema.dyn.app" "updater exited; keeping thread alive"
                  threadDelay maxBound
              )
        )
  