eternal-0.0.9: src/Control/Eternal/Reactive.hs
{-# LANGUAGE RankNTypes, GADTs, ScopedTypeVariables, LambdaCase #-}
module Control.Eternal.Reactive
( Action
, Request
, reactiveObjectIO
, Sink
, pauseIO
, reactiveIO
) where
-- |
-- Module: Control.Eternal.Reactive
-- Copyright: Andy Gill (??-2008), Heather Cynede (2014)
-- License: BSD3
-- |
import Control.Concurrent.Chan
import Control.Concurrent
import Control.Exception as Ex
-- An action is an IO-based change to an explicit state
type Action s = s -> IO s -- only state change
type Request s a = s -> IO (s,a) -- state change + reply to be passed back to caller
-- Choices:
-- * do the Requests see the failure
-- * Actions do not see anything
-- *
data Msg s = Act (Action s)
| forall a . Req (Request s a) (MVar a)
| Done (MVar ())
reactiveObjectIO
:: forall state object. state
-> ( ThreadId
-> (forall r. Request state r -> IO r) -- requests
-> (Action state -> IO ()) -- actions
-> IO () -- done
-> object
) -> IO object
reactiveObjectIO state mkObject = do
chan <- newChan
let dispatch st =
readChan chan >>= \case Act act -> do state1 <- act st
dispatch $! state1
Req req box -> do (state1,ret) <- req st
putMVar box ret
dispatch $! state1
Done box -> do putMVar box ()
return ()
requestit :: forall r. Request state r -> IO r
requestit fun = do ret <- newEmptyMVar
writeChan chan $ Req fun ret
takeMVar ret -- wait
actionit act = writeChan chan $ Act act
doneit = do ret <- newEmptyMVar
writeChan chan $ Done ret
takeMVar ret -- wait
pid <- forkIO $ dispatch state
return (mkObject pid requestit actionit doneit)
type Sink a = a -> IO ()
-- This turns a reactive style call into a pausing IO call.
pauseIO :: (a -> Sink b -> IO ()) -> a -> IO b
pauseIO fn a = do var <- newEmptyMVar
forkIO $ do fn a (\ b -> putMVar var b)
takeMVar var
-- This turns a pausing IO call into a reactive style call.
reactiveIO :: (a -> IO b) -> a -> Sink b -> IO ()
reactiveIO fn a sinkB = do forkIO $ sinkB =<< fn a
return ()