-- | A very minimal library for broadcasting events to listeners. module FP.API.Signal where import Control.Applicative import Control.Concurrent.MVar import Control.Monad.IO.Class import Data.Foldable import Data.IORef import qualified Data.IntMap as IM import Prelude import System.Timeout data Signal m a = Signal (IORef (IM.IntMap (a -> m ()))) newSignal :: IO (Signal m a) newSignal = Signal <$> newIORef IM.empty sendSignal :: MonadIO m => Signal m a -> a -> m () sendSignal (Signal ref) x = do m <- liftIO $ readIORef ref forM_ m $ \f -> f x subscribeSignal :: Signal m a -> (IO () -> a -> m ()) -> IO (IO ()) subscribeSignal (Signal ref) f = atomicModifyIORef ref $ \m -> let m' = IM.insert key (f unsub) m unsub = unsubscriber key key = if IM.null m then 0 else 1 + fst (IM.findMax m) in (m', unsub) where unsubscriber key = atomicModifyIORef ref $ \m -> (IM.delete key m, ()) -- | Block, watching for a particular value (indicated by 'Just'). -- Once a 'Just' value is seen, stop subscribing, and return the -- value. The @Maybe Int@ argument specifies an optional timeout in -- microseconds. If the function instead times out, then 'Nothing' is -- yielded. blockOnSignal :: MonadIO m => Maybe Int -> Signal m a -> (a -> m (Maybe b)) -> IO (Maybe b) blockOnSignal mmicros sig f = do resultVar <- newEmptyMVar unsub <- subscribeSignal sig $ \_ x -> do mres <- f x liftIO $ forM_ mres $ putMVar resultVar result <- maybe (fmap Just) timeout mmicros $ takeMVar resultVar unsub return result