hbs2/hbs2-core/test/TestUniqProtoId.hs

210 lines
5.5 KiB
Haskell

{-# Language TypeFamilyDependencies #-}
{-# Language FunctionalDependencies #-}
{-# Language AllowAmbiguousTypes #-}
module TestUniqProtoId where
import HBS2.Clock
import HasProtocol
import FakeMessaging
import Data.Kind
import GHC.TypeLits
import Data.Proxy
import Data.Map qualified as Map
import Data.Map (Map)
import Control.Monad.Reader
import Data.ByteString (ByteString)
import Control.Concurrent.Async
import Control.Concurrent.STM
import Control.Concurrent.STM.TChan as Chan
import Data.Cache (Cache)
import Data.Cache qualified as Cache
import Data.Foldable
import Data.Hashable
import Data.List qualified as List
import Data.Maybe
import Safe
import Control.Logger.Simple qualified as Log
import Prettyprinter hiding (pipe)
data AnyMessage e = AnyMessage Integer (Encoded e)
data EngineEnv e = forall bus . (Messaging bus e (AnyMessage e)) =>
EngineEnv
{ peer :: Maybe (Peer e)
, self :: Peer e
, bus :: bus
}
-- makeLenses 'EngineEnv
data AnyProtocol e m = forall p . ( HasProtocol e p
, KnownNat (ProtocolId p)
, Response e p m
) =>
AnyProtocol
{ myProtoId :: Integer
, protoDecode :: Encoded e -> Maybe p
, protoEncode :: p -> Encoded e
, handle :: p -> m ()
}
class Response e p (m :: Type -> Type) where
response :: p -> m ()
class Request e p (m :: Type -> Type) where
request :: Peer e -> p -> m ()
makeResponse :: forall e p m . ( MonadIO m
, Response e p m
, HasProtocol e p
)
=> (p -> m ()) -> AnyProtocol e m
makeResponse h = AnyProtocol { myProtoId = natVal (Proxy @(ProtocolId p))
, protoDecode = decode
, protoEncode = encode
, handle = h
}
newtype EngineM e m a = EngineM { fromEngine :: ReaderT (EngineEnv e) m a }
deriving ( Functor
, Applicative
, Monad
, MonadTrans
, MonadIO
, MonadReader (EngineEnv e)
)
runEngineM :: EngineEnv e -> EngineM e m a -> m a
runEngineM e f = runReaderT (fromEngine f) e
instance (MonadIO m, HasProtocol e p) => Request e p (EngineM e m) where
request p msg = do
let proto = protoId @e @p (Proxy @p)
ask >>= \case
EngineEnv { self = s, bus = b} -> do
liftIO $ sendTo b (To p) (From s) (AnyMessage proto (encode msg))
instance (MonadIO m, HasProtocol e p) => Response e p (EngineM e m) where
response resp = do
env <- ask
let proto = protoId @e @p (Proxy @p)
case env of
(EngineEnv { peer = Just p
, bus = b
, self = s
} ) -> do
liftIO $ sendTo b (To p) (From s) (AnyMessage proto (encode resp))
_ -> pure ()
newEnv :: forall e bus m . (Monad m, Messaging bus e (AnyMessage e)) => Peer e -> bus -> m (EngineEnv e)
newEnv p pipe = pure $ EngineEnv Nothing p pipe
runPeer :: MonadIO m => EngineEnv e -> [AnyProtocol e (EngineM e m)] -> m a
runPeer env@(EngineEnv {self = me, bus = pipe}) hh = do
let resp = [ (pid, a) | a@AnyProtocol { myProtoId = pid } <- hh ]
let disp = Map.fromList resp
runEngineM env $ do
forever $ do
messages <- receive pipe (To me)
for_ messages $ \(From pip, AnyMessage n msg) -> do
local (\e -> e { peer = Just pip } ) $ do
case Map.lookup n disp of
Just (AnyProtocol {protoDecode = decoder, handle = h}) -> maybe (pure ()) h (decoder msg)
Nothing -> pure ()
data PingPong = Ping Int
| Pong Int
deriving stock (Show,Read)
data PeekPoke = Peek Int
| Poke Int
| Nop
deriving stock (Show,Read)
instance HasProtocol Fake PingPong where
type instance ProtocolId PingPong = 1
type instance Encoded Fake = String
decode = readMay
encode = show
instance HasProtocol Fake PeekPoke where
type instance ProtocolId PeekPoke = 2
type instance Encoded Fake = String
decode = readMay
encode = show
pingPongHandler :: forall a m . (MonadIO m, Response a PingPong m, HasProtocol a PingPong) => PingPong -> m ()
pingPongHandler =
\case
Ping c -> liftIO (print $ "effect: PING" <+> pretty c) >> response @a (Pong c)
Pong c -> liftIO (print $ "effect: PONG" <+> pretty c) >> response @a (Ping (succ c))
peekPokeHandler :: forall a m . (MonadIO m, Response a PeekPoke m, HasProtocol a PeekPoke) => PeekPoke -> m ()
peekPokeHandler =
\case
Peek c -> liftIO (print $ "effect: Peek" <+> pretty c) >> response @a (Poke c)
Poke c -> liftIO (print $ "effect: Poke" <+> pretty c) >> response @a Nop
Nop -> liftIO (print $ pretty "effect: Nop") >> response @a (Peek 1)
testUniqiProtoId :: IO ()
testUniqiProtoId = do
-- setLogLevel
fake <- newFakeP2P True
let peer0 = FakePeer 0
let peer1 = FakePeer 1
env0 <- newEnv peer0 fake
env1 <- newEnv peer1 fake
runEngineM env0 $ do
request peer1 (Ping 0)
runEngineM env1 $ do
request peer0 (Peek 0)
pip1 <- async $
runPeer env0
[ makeResponse pingPongHandler
, makeResponse peekPokeHandler
]
pip2 <- async $
runPeer env1
[ makeResponse pingPongHandler
, makeResponse peekPokeHandler
]
void $ waitAnyCatchCancel [pip1, pip2]