distributed-process-execution-0.1.0: src/Control/Distributed/Process/Execution/EventManager.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ImpredicativeTypes #-}
-----------------------------------------------------------------------------
-- |
-- Module : Control.Distributed.Process.Execution.EventManager
-- Copyright : (c) Well-Typed / Tim Watson
-- License : BSD3 (see the file LICENSE)
--
-- Maintainer : Tim Watson <watson.timothy@gmail.com>
-- Stability : experimental
-- Portability : non-portable (requires concurrency)
--
-- [Overview]
--
-- The /EventManager/ is a parallel/concurrent event handling tool, built on
-- top of the /Exchange API/. Arbitrary events are published to the event
-- manager using 'notify', and are broadcast simulataneously to a set of
-- registered /event handlers/.
--
-- [Defining and Registering Event Handlers]
--
-- Event handlers are defined as @Serializable m => s -> m -> Process s@,
-- i.e., an expression taking an initial state, an arbitrary @Serializable@
-- event/message and performing an action in the @Process@ monad that evaluates
-- to a new state.
--
-- See "Control.Distributed.Process.Execution.Exchange".
--
-----------------------------------------------------------------------------
module Control.Distributed.Process.Execution.EventManager
( EventManager
, start
, startSupervised
, startSupervisedRef
, notify
, addHandler
, addMessageHandler
) where
import Control.Distributed.Process hiding (Message, link)
import qualified Control.Distributed.Process as P (Message)
import Control.Distributed.Process.Execution.Exchange
( Exchange
, Message(..)
, post
, broadcastExchange
, broadcastExchangeT
, broadcastClient
)
import qualified Control.Distributed.Process.Execution.Exchange as Exchange
( startSupervised
)
import Control.Distributed.Process.Extras.Internal.Primitives
import Control.Distributed.Process.Extras.Internal.Unsafe
( InputStream
, matchInputStream
)
import Control.Distributed.Process.Supervisor (SupervisorPid)
import Control.Distributed.Process.Serializable hiding (SerializableDict)
import Data.Binary
import Data.Typeable (Typeable)
import GHC.Generics
{- notes
Event manager is implemented over a simple BroadcastExchange. We eschew the
complexities of identifying handlers and allowing them to be removed/deleted
or monitored, since we avoid running them in the exchange process. Instead,
each handler runs as an independent process, leaving handler management up
to the user and allowing all the usual process managemnet techniques (e.g.,
registration, supervision, etc) to be utilised instead.
-}
-- | Opaque handle to an Event Manager.
--
newtype EventManager = EventManager { ex :: Exchange }
deriving (Typeable, Generic)
instance Binary EventManager where
instance Resolvable EventManager where
resolve = resolve . ex
-- | Start a new /Event Manager/ process and return an opaque handle
-- to it.
start :: Process EventManager
start = broadcastExchange >>= return . EventManager
startSupervised :: SupervisorPid -> Process EventManager
startSupervised sPid = do
ex <- broadcastExchangeT >>= \t -> Exchange.startSupervised t sPid
return $ EventManager ex
startSupervisedRef :: SupervisorPid -> Process (ProcessId, P.Message)
startSupervisedRef sPid = do
ex <- startSupervised sPid
Just pid <- resolve ex
return (pid, unsafeWrapMessage ex)
-- | Broadcast an event to all registered handlers.
notify :: Serializable a => EventManager -> a -> Process ()
notify em msg = post (ex em) msg
-- | Add a new event handler. The handler runs in its own process,
-- which is spawned locally on behalf of the caller.
addHandler :: forall s a. Serializable a
=> EventManager
-> (s -> a -> Process s)
-> Process s
-> Process ProcessId
addHandler m h s =
spawnLocal $ newHandler (ex m) (\s' m' -> handleMessage m' (h s')) s
-- | As 'addHandler', but operates over a raw @Control.Distributed.Process.Message@.
addMessageHandler :: forall s.
EventManager
-> (s -> P.Message -> Process (Maybe s))
-> Process s
-> Process ProcessId
addMessageHandler m h s = spawnLocal $ newHandler (ex m) h s
newHandler :: forall s .
Exchange
-> (s -> P.Message -> Process (Maybe s))
-> Process s
-> Process ()
newHandler ex handler initState = do
linkTo ex
is <- broadcastClient ex
listen is handler =<< initState
listen :: forall s . InputStream Message
-> (s -> P.Message -> Process (Maybe s))
-> s
-> Process ()
listen inStream handler state = do
receiveWait [ matchInputStream inStream ] >>= handleEvent inStream handler state
where
handleEvent is h s p = do
r <- h s (payload p)
let s2 = case r of
Nothing -> s
Just s' -> s'
listen is h s2