{- Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. -} -- | A value that may change over time, and that we can observe efficiently. module Glean.Util.Observed ( ObserveMonad(..) , Observed , changingValue , fixedValue , doOnUpdate ) where import Control.Monad import Data.IORef import Util.STM -- | A value that may change over time, and that we can observe efficiently. data Observed a = Observed { observedGet :: STM a , observedUpdated :: Maybe (IORef (IO ())) -- invoked when the value is updated } -- | Create an 'Observed' that tracks an underlying value. -- -- The caller should invoke the returned 'IO' action when the -- underlying value changes. changingValue :: a -> IO (Observed a, (a -> a) -> IO ()) changingValue initial = do tvar <- newTVarIO initial updateActionRef <- newIORef (return ()) let onUpdate fn = do atomically (modifyTVar tvar fn) join $ readIORef updateActionRef return (Observed (readTVar tvar) (Just updateActionRef), onUpdate) fixedValue :: a -> Observed a fixedValue a = Observed (return a) Nothing instance Functor Observed where fmap f (Observed stm upd) = Observed (fmap f stm) upd -- | Run the given IO action whenever the value in the Observed is -- updated. Guarantees (or lack thereof): -- -- * When the action runs, the Observed has already been updated and -- 'get' will return the new value. -- * Multiple instances of the action may run concurrently -- doOnUpdate :: Observed a -> IO () -> IO () doOnUpdate Observed{..} action = forM_ observedUpdated $ \ref -> atomicModifyIORef' ref $ \io -> (io >> action, ()) -- | Monads which can 'get' the current value of an 'Observed' class ObserveMonad m where -- | Obtain the current value of an 'Observed'. This is efficient -- enough to be called every time you need the value. get :: Observed a -> m a instance ObserveMonad STM where get (Observed f _) = f instance ObserveMonad IO where get = atomically . get