hactor-1.1.0.0: Control/Concurrent/Actor/Internal.hs
-- |
-- Module : Control.Concurrent.Actor.Internal
-- Copyright : (c) 2014 Forkk
-- License : MIT
-- Maintainer : forkk@forkk.net
-- Stability : experimental
-- Portability : GHC only (requires throwTo)
--
-- Module exposing more of hactor's internals. Use with caution.
--
module Control.Concurrent.Actor.Internal
(
-- * Types
ActorHandle (..)
, ActorMessage
, ActorM
-- * Sending Messages
, send
, sendIO
-- * Receiving Messages
, receive
, receiveMaybe
, receiveSTM
-- * Spawning Actors
, spawnActor
, runActor
-- * Getting Information
, self
, actorThread
-- * Internals
, ActorContext (..)
, MailBox
, getContext
, getMailBox
) where
import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad.Reader
-- {{{ Types
-- {{{ Message
-- | The @ActorMessage@ class must be implemented by any type that will be sent
-- as a message to actors.
-- Any given type of actor will have one @ActorMessage@ type that is sent to
-- that actor. This ensures type safety.
-- Currently this is simply a dummy class with nothing in it, but things may be
-- added in the future.
class ActorMessage msg
-- Allow actors that don't take messages.
instance ActorMessage ()
-- }}}
-- {{{ Handle and context
-- | An @ActorHandle@ acts as a reference to a specific actor.
data ActorMessage msg => ActorHandle msg = ActorHandle
{ ahContext :: ActorContext msg -- Context for this handle's actor.
, ahThread :: ThreadId -- The actor's thread ID.
}
-- | The @ActorContext@ holds shared information about a given actor.
-- This is information such as the actor's mail box, the list of actors it's
-- linked to, etc.
data ActorMessage msg => ActorContext msg = ActorContext
{ acMailBox :: MailBox msg -- Channel for the actor's messages.
}
-- | The type for the actor's mail box.
type MailBox msg = TChan msg
-- }}}
-- }}}
-- | The base actor monad.
type ActorM msg = ReaderT (ActorContext msg) IO
-- {{{ Get info
-- | Gets a handle to the current actor.
self :: ActorMessage msg => ActorM msg (ActorHandle msg)
self = do
context <- ask
thread <- liftIO $ myThreadId
return $ ActorHandle context thread
-- | Retrieves the mail box for the current actor.
-- This is an internal function and may be dangerous. Use with caution.
getMailBox :: ActorMessage msg => ActorM msg (MailBox msg)
getMailBox = asks acMailBox
-- | Gets the internal context object for the current actor.
-- This is an internal function and may be dangerous. Use with caution.
getContext :: ActorMessage msg => ActorM msg (ActorContext msg)
getContext = ask
-- }}}
-- {{{ Receiving
-- | Reads a message from the actor's mail box.
-- If there are no messages, blocks until one is received. If you don't want
-- this, use @receiveMaybe@ instead.
receive :: ActorMessage msg => ActorM msg (msg)
receive = do
chan <- getMailBox
-- Read from the channel, retrying if there is nothing to read.
liftIO $ atomically $ readTChan chan
-- | Reads a message from the actor's mail box.
-- If there are no messages, returns @Nothing@.
receiveMaybe :: ActorMessage msg => ActorM msg (Maybe msg)
receiveMaybe = do
chan <- getMailBox
liftIO $ atomically $ tryReadTChan chan
-- | An @ActorM@ action which returns an @STM@ action to receive a message.
receiveSTM :: ActorMessage msg => ActorM msg (STM msg)
receiveSTM = do
chan <- getMailBox
return $ readTChan chan
-- }}}
-- {{{ Sending
-- | Sends a message to the given actor handle.
-- This is secretly just @sendIO@ lifted into an actor monad.
send :: (ActorMessage msg, ActorMessage msg') =>
ActorHandle msg -> msg -> ActorM msg' ()
send hand msg = liftIO $ sendIO hand msg
-- | Sends a message to the given actor handle from within the IO monad.
sendIO :: ActorMessage msg => ActorHandle msg -> msg -> IO ()
sendIO hand msg =
atomically $ writeTChan mailBox $ msg
where
mailBox = handleMailBox hand
-- }}}
-- {{{ Spawning
-- | Internal function for starting actors.
-- This takes an @ActorM@ action, makes a channel for it, wraps it in exception
-- handling stuff, and turns it into an IO monad. The function returns a tuple
-- containing the actor's context and the IO action to execute the actor.
wrapActor :: ActorMessage msg => ActorM msg () -> IO (IO (), ActorContext msg)
wrapActor actorAction = do
-- TODO: Exception handling.
-- First, create a channel for the actor.
chan <- atomically newTChan
-- Next, create the context and run the ReaderT action.
let context = ActorContext chan
ioAction = runReaderT actorAction context
-- Return the information.
return (ioAction, context)
-- | Spawns the given actor on another thread and returns a handle to it.
spawnActor :: ActorMessage msg => ActorM msg () -> IO (ActorHandle msg)
spawnActor actorAction = do
-- Wrap the actor action.
(ioAction, context) <- wrapActor actorAction
-- Fork the actor's IO action to another thread.
thread <- forkIO ioAction
-- Return the handle.
return $ ActorHandle context thread
-- | Runs the given actor on the current thread.
-- This function effectively turns the current thread into the actor's thread.
-- Obviously, this means that this function will block until the actor exits.
-- You probably want to use this for your "main" actor.
runActor :: ActorMessage msg => ActorM msg () -> IO ()
runActor actorAction = do
-- Wrap the actor action. We discard the context, because we won't be
-- returning a handle to this actor.
(ioAction, _) <- wrapActor actorAction
-- Execute the IO action on the current thread.
ioAction
-- }}}
-- {{{ Utility functions
-- | Gets the mail box for the given handle.
handleMailBox :: ActorMessage msg => ActorHandle msg -> MailBox msg
handleMailBox = acMailBox . ahContext
-- | Gets the thread ID for the given actor handle.
actorThread :: ActorMessage msg => ActorHandle msg -> ThreadId
actorThread = ahThread
-- }}}