hbro-1.4.0.0: library/Hbro/Event.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Hbro.Event (
-- * Types
Event(..)
, Signal
, Handler
-- * Signal manipulation
, newSignal
, emit
, emit'
, closeSignal
, closeSignal'
, listenTo
-- * Handlers
, addHandler
, addRecursiveHandler
, deleteHandlers
) where
-- {{{ Imports
import Hbro.Prelude
import Control.Concurrent.Async.Lifted
import Control.Concurrent.STM.TMChan
import Control.Monad.Logger.Extended
import Control.Monad.Trans.Resource
import Data.Function (fix)
-- }}}
-- | An event type is defined by its input type.
class (Show e) => Event e where
type Input e :: *
type Input e = ()
describeInput :: e -> Input e -> Maybe Text
-- | A signal notifies the occurrence of an event.
data (Event e) => Signal e = Signal e (TMChan (Input e)) (TVar [ReleaseKey])
-- | Event handler.
type Handler m a = Input a -> m ()
instance (Event e) => Describable (Signal e) where
describe (Signal e _ _) = tshow e
-- | 'Signal' exports no constructor, use this function instead.
newSignal :: (BaseIO m, Event e) => e -> m (Signal e)
newSignal e = Signal e <$> io newBroadcastTMChanIO <*> io (newTVarIO [])
-- | Blocks until signal is received.
waitFor :: (MonadIO m) => TMChan a -> m (Maybe a)
waitFor = atomically . readTMChan
-- | Trigger an event.
emit :: (Event e, MonadIO m, MonadLogger m) => Signal e -> Input e -> m ()
emit signal@(Signal e _ h) input = do
forM_ (describeInput e input) $ debug . ("Event triggered: " ++)
handlers <- atomically $ readTVar h
when (null handlers) . forM_ (describeInput e input) $ debug . (++) "No handler for event: "
emit' signal input
-- | Like 'emit', but doesn't log anything.
emit' :: (Event e, MonadIO m) => Signal e -> Input e -> m ()
emit' (Signal _ s _) input = atomically $ writeTMChan s input
-- | Close a signal and all its attached hooks.
closeSignal :: (Event e, MonadIO m, MonadLogger m, MonadResource m) => Signal e -> m ()
closeSignal signal = debug "Closing signal." >> closeSignal' signal
-- | Like 'close', but doesn't log anything.
closeSignal' :: (Event e, MonadIO m, MonadResource m) => Signal e -> m ()
closeSignal' signal@(Signal _ s _) = atomically (closeTMChan s) >> deleteHandlers signal
-- | Asynchronously wait for the next event.
listenTo :: (Event a, MonadIO m) => Signal a -> m (Async (Maybe (Input a)))
listenTo (Signal _ signal _) = io . async . waitFor =<< atomically (dupTMChan signal)
-- | Execute a function each time an event occurs.
addHandler :: (Event a, ControlIO m, MonadResource m) => Signal a -> Handler m a -> m ReleaseKey
addHandler (Signal _ s handlers) f = do
signal <- atomically $ dupTMChan s
result <- liftBaseWith $ \runInIO -> do
runInIO . flip allocate cancel . async . fix $ \recurse ->
waitFor signal >>= mapM_ (\x -> runInIO (f x) >> recurse)
(releaseKey, _ :: Async ()) <- restoreM result
atomically $ modifyTVar handlers (releaseKey:)
return releaseKey
-- | Generalized version of 'addHandler' where the callback function may recurse.
addRecursiveHandler :: (Event a, ControlIO m, MonadResource m) => Signal a -> b -> (b -> Input a -> m b) -> m ReleaseKey
addRecursiveHandler (Signal _ s handlers) init f = do
signal <- atomically $ dupTMChan s
result <- liftBaseWith $ \runInIO ->
runInIO . flip allocate cancel . async . void . runInIO . flip fix init $ \recurse acc ->
waitFor signal >>= mapM_ (f acc >=> recurse)
(releaseKey, _ :: Async ()) <- restoreM result
atomically $ modifyTVar handlers (releaseKey:)
return releaseKey
-- | Stop all handlers associated to the given signal.
deleteHandlers :: (Event e, MonadIO m, MonadResource m) => Signal e -> m ()
deleteHandlers (Signal _ _ handlers) = void . mapM release =<< atomically (readTVar handlers)