module Control.Eff.Concurrent.Protocol.Server
( Server(..)
, StartArgument(..)
, ToServerEffects
, ModelState
, modifyModel
, getAndModifyModel
, modifyAndGetModel
, getModel
, putModel
, getAndPutModel
, useModel
, zoomModel
, SettingsReader
, askSettings
, viewSettings
, Event(..)
, start
, startLink
, protocolServerLoop
, GenServer(..)
, ToGenServerEffects
, GenServerId(..)
, GenServerProtocol
, GenServerModel
, GenServerSettings
, genServer
, Stateless
, ToStatelessEffects
, statelessGenServer
, Request(..)
, sendReply
, RequestOrigin(..)
)
where
import Control.Applicative
import Control.DeepSeq
import Control.Eff
import Control.Eff.Extend ()
import Control.Eff.Concurrent.Process
import Control.Eff.Concurrent.Process.Timer
import Control.Eff.Concurrent.Protocol
import Control.Eff.Concurrent.Protocol.Request
import Control.Eff.Log
import Control.Eff.Reader.Strict
import Control.Eff.State.Strict
import Control.Lens
import Data.Coerce
import Data.Default
import Data.Kind
import Data.String
import Data.Typeable
import Data.Type.Pretty
import qualified Data.Text as T
import GHC.Stack (HasCallStack)
import GHC.Generics
class
(Typeable (Protocol a)) =>
Server (a :: Type) e
where
data StartArgument a e
type Protocol a :: Type
type Protocol a = a
type Model a :: Type
type Model a = ()
type Settings a :: Type
type Settings a = ()
setup ::
StartArgument a e
-> Eff e (Model a, Settings a)
default setup ::
(Default (Model a), Default (Settings a))
=> StartArgument a e
-> Eff e (Model a, Settings a)
setup _ = pure (def, def)
update ::
StartArgument a e
-> Event (Protocol a)
-> Eff (ToServerEffects a e) ()
type ToServerEffects a e =
ModelState a ': SettingsReader a ': e
type ModelState a = State (Model a)
modifyModel :: forall a e . Member (ModelState a) e => (Model a -> Model a) -> Eff e ()
modifyModel f = getModel @a >>= putModel @a . f
getAndModifyModel :: forall a e . Member (ModelState a) e => (Model a -> Model a) -> Eff e (Model a)
getAndModifyModel f = getModel @a <* modify f
modifyAndGetModel :: forall a e . Member (ModelState a) e => (Model a -> Model a) -> Eff e (Model a)
modifyAndGetModel f = modifyModel @a f *> getModel @a
getModel :: forall a e . Member (ModelState a) e => Eff e (Model a)
getModel = get
useModel :: forall a b e . Member (ModelState a) e => Getting b (Model a) b -> Eff e b
useModel l = view l <$> getModel @a
putModel :: forall a e . Member (ModelState a) e => Model a -> Eff e ()
putModel = put
getAndPutModel :: forall a e . Member (ModelState a) e => Model a -> Eff e (Model a)
getAndPutModel m = getModel @a <* putModel @a m
zoomModel :: forall a b c e. Member (ModelState a) e => Lens' (Model a) b -> Eff (State b ': e) c -> Eff e c
zoomModel l a = do
m0 <- getModel @a
(c, m1) <- runState (view l m0) a
modifyModel @a (l .~ m1)
return c
type SettingsReader a = Reader (Settings a)
askSettings :: forall a e . Member (SettingsReader a) e => Eff e (Settings a)
askSettings = ask
viewSettings :: forall a b e . Member (SettingsReader a) e => Getting b (Settings a) b -> Eff e b
viewSettings l = view l <$> askSettings @a
start
:: forall a q h
. ( Server a (InterruptableProcess q)
, Typeable a
, LogsTo h (InterruptableProcess q)
, HasCallStack)
=> StartArgument a (InterruptableProcess q) -> Eff (InterruptableProcess q) (Endpoint (Protocol a))
start a = asEndpoint <$> spawn (protocolServerLoop a)
startLink
:: forall a q h . (Typeable a, Server a (InterruptableProcess q), LogsTo h (InterruptableProcess q), HasCallStack)
=> StartArgument a (InterruptableProcess q) -> Eff (InterruptableProcess q) (Endpoint (Protocol a))
startLink a = asEndpoint <$> spawnLink (protocolServerLoop a)
protocolServerLoop
:: forall q e h a
. ( Server a e
, SetMember Process (Process q) e
, Member Interrupts e
, LogsTo h e
, Typeable a
)
=> StartArgument a e -> Eff e ()
protocolServerLoop a = do
(st, env) <- setup a
_ <- runReader env (runState st (receiveSelectedLoop sel mainLoop))
return ()
where
sel :: MessageSelector (Event (Protocol a))
sel =
OnRequest <$> selectMessage @(Request (Protocol a))
<|> OnDown <$> selectMessage @ProcessDown
<|> OnTimeOut <$> selectMessage @TimerElapsed
<|> OnMessage <$> selectAnyMessage
handleInt i = update a (OnInterrupt i) *> pure Nothing
mainLoop ::
(Typeable a)
=> Either (Interrupt 'Recoverable) (Event (Protocol a))
-> Eff (ToServerEffects a e) (Maybe ())
mainLoop (Left i) = handleInt i
mainLoop (Right i) = update a i *> pure Nothing
data Event a =
OnRequest (Request a)
| OnInterrupt (Interrupt 'Recoverable)
| OnDown ProcessDown
| OnTimeOut TimerElapsed
| OnMessage StrictDynamic
deriving (Show,Generic,Typeable)
instance NFData a => NFData (Event a) where
rnf = \case
OnRequest r -> rnf r
OnInterrupt r -> rnf r
OnDown r -> rnf r
OnTimeOut r -> rnf r
OnMessage r -> r `seq` ()
type instance ToPretty (Event a) = ToPretty a <+> PutStr "event"
data GenServer tag e where
MkGenServer :: LogIo e =>
{ _setupCallback :: Eff (InterruptableProcess e) (Model (GenServer tag e), Settings (GenServer tag e))
, _updateCallback
:: Event (GenServerProtocol tag)
-> Eff (ToGenServerEffects tag e) ()
} -> GenServer tag e
type ToGenServerEffects tag e = ToServerEffects (GenServer tag e) (InterruptableProcess e)
newtype GenServerId tag =
MkGenServerId { _fromGenServerId :: T.Text }
deriving (Typeable, NFData, Ord, Eq, IsString)
instance Show (GenServerId tag) where
showsPrec _d (MkGenServerId x) = showString (T.unpack x)
type family GenServerProtocol tag
type family GenServerModel tag
type family GenServerSettings tag
instance ( Typeable (GenServerProtocol tag)
, LogIo e )
=> Server (GenServer (tag :: Type) e) (InterruptableProcess e) where
type Protocol (GenServer tag e) = GenServerProtocol tag
type Model (GenServer tag e) = GenServerModel tag
type Settings (GenServer tag e) = GenServerSettings tag
data instance StartArgument (GenServer tag e) (InterruptableProcess e) =
MkGenStartArgument
{ _genServerId :: GenServerId tag
, _genServerCallbacks :: GenServer tag e
} deriving Typeable
setup (MkGenStartArgument _ cb) = _setupCallback cb
update (MkGenStartArgument _ cb) req = _updateCallback cb req
instance NFData (StartArgument (GenServer tag e) (InterruptableProcess e)) where
rnf (MkGenStartArgument x _) = rnf x
instance Typeable tag => Show (StartArgument (GenServer tag e) (InterruptableProcess e)) where
showsPrec d (MkGenStartArgument x _) =
showParen (d>=10)
( showsPrec 11 x
. showChar ' ' . showsTypeRep (typeRep (Proxy @tag))
. showString " gen-server"
)
genServer
:: forall tag e .
( Typeable tag
, HasCallStack
, LogIo e
, Server (GenServer tag e) (InterruptableProcess e)
)
=> (GenServerId tag -> Eff (InterruptableProcess e) (GenServerModel tag, GenServerSettings tag))
-> (GenServerId tag -> Event (GenServerProtocol tag) -> Eff (ToGenServerEffects tag e) ())
-> GenServerId tag
-> StartArgument (GenServer tag e) (InterruptableProcess e)
genServer initCb stepCb i =
MkGenStartArgument
{ _genServerId = i
, _genServerCallbacks =
MkGenServer { _setupCallback = initCb i
, _updateCallback = stepCb i . coerce
}
}
data Stateless tag deriving Typeable
type ToStatelessEffects e = State () ': Reader () ': e
type instance GenServerModel (Stateless tag) = ()
type instance GenServerSettings (Stateless tag) = ()
type instance GenServerProtocol (Stateless tag) = GenServerProtocol tag
type instance ToPretty (Stateless t) = ToPretty t
statelessGenServer
:: forall tag e . ( Typeable tag
, HasCallStack
, LogIo e
, Typeable tag
, Server (GenServer (Stateless tag) e) (InterruptableProcess e)
)
=> (GenServerId tag -> Event (GenServerProtocol tag) -> Eff (ToStatelessEffects (InterruptableProcess e)) ())
-> GenServerId tag
-> StartArgument (GenServer (Stateless tag) e) (InterruptableProcess e)
statelessGenServer stepCb (MkGenServerId i) =
genServer (const (pure ((), ()))) runStep (MkGenServerId i)
where
runStep :: GenServerId (Stateless tag) -> Event (GenServerProtocol (Stateless tag)) -> Eff (ToStatelessEffects (InterruptableProcess e)) ()
runStep (MkGenServerId i') loopEvent = stepCb (MkGenServerId i') (coerce loopEvent)