playwright/src/Control/Concurrent/BeeZero.hs

408 lines
12 KiB
Haskell

{- This file is part of playwright.
-
- Written in 2024 by Pere Lev <[email protected]>
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
-- | A live actor system, built on top of "Control.Concurrent.Exchange".
--
-- It can be used as is, and\/or further features such as disk persistence and
-- networking can be built on top.
module Control.Concurrent.BeeZero
( -- * Intro
-- * Declaring an actor's interface
Signature (..)
, Method (..)
, LookupSig ()
, Bee (..)
-- * Defining an actor implementation
, HandleMethod ()
, handleMethod
, Script ()
, makeScript
, Implementor (..)
-- * Using actors in the current thread
, Near ()
, near
, callNear
, callNear_
-- * Launching and calling actors running in a separate thread
, Far ()
, Next (..)
, launchFar
, matchMethodClient
, matchMethodClient'
, callFar
, callFar_
)
where
import Control.Concurrent
import Control.Monad
import Control.Monad.IO.Unlift
import Data.Kind
import Data.Proxy
import Data.Type.Bool
import Data.Type.Equality
import GHC.TypeLits
import UnliftIO.Exception
import qualified Data.Vinyl as V
import Control.Concurrent.Exchange
import qualified Data.Vinyl.Local as V
-- / -------------------------------------------------------------------------
data Signature = Signature Type Type
type SignatureParam :: Signature -> Type
type family SignatureParam s where
SignatureParam ('Signature p _) = p
type SignatureReturn :: Signature -> Type
type family SignatureReturn s where
SignatureReturn ('Signature _ r) = r
data Method = Method Symbol Signature
type MethodSignature :: Method -> Signature
type family MethodSignature method where
MethodSignature ('Method _ sig) = sig
{-
type family MethodToPair (m :: Method) =
(p :: (Symbol, Signature)) | p -> m where
MethodToPair (Method sym sig) = '(sym, sig)
data MethodToPair :: Method -> Exp (Symbol, Signature)
type instance Eval (MethodToPair (Method sym sig)) = '(sym, sig)
data LookupSig :: Symbol -> [Method] -> Exp (Maybe Signature)
type instance Eval (LookupSig sym ms) =
Eval (Lookup sym (Eval (Map MethodToPair ms)))
type Lookup :: a -> [(a, b)] -> Maybe b
type family Lookup item list where
Lookup _ '[] = 'Nothing
Lookup x ((y, z) : yzs) = If (x == y) (Just z) (Lookup x yzs)
-}
type LookupSig :: Symbol -> [Method] -> Maybe Signature
type family LookupSig symbol methods where
LookupSig _ '[] = Nothing
LookupSig x ('Method y s ': ms) = If (x == y) (Just s) (LookupSig x ms)
-- / -------------------------------------------------------------------------
-- | Actor type, defines methods it offers.
class Bee (b :: Type) where
type BeeInterface b :: [Method]
newtype HandleMethod
(monad :: Type -> Type) (side :: Type) (method :: Method) = HandleMethod
{ _unHandleMethod
:: SignatureParam (MethodSignature method)
-> monad (side, SignatureReturn (MethodSignature method))
}
handleMethod
:: forall
(sym :: Symbol)
(sig :: Signature)
(monad :: Type -> Type)
(side :: Type) .
(SignatureParam sig -> monad (side, SignatureReturn sig))
-> HandleMethod monad side ('Method sym sig)
handleMethod = HandleMethod
-- Actor behavior implementation (there can be more than one for a given actor
-- type)
type HandlerSet m side ms = V.Rec (HandleMethod m side) ms
newtype Script (m :: Type -> Type) (side :: Type) (b :: Type) = Script
{ unScript :: HandlerSet m side (BeeInterface b)
}
makeScript :: forall b m s. HandlerSet m s (BeeInterface b) -> Script m s b
makeScript = Script
-- | A type that provides method handler implementations for a given actor
-- interface.
class (Bee (ImplementorBee i), Monad (ImplementorMonad i)) => Implementor i where
-- | The actor interface being implemented.
type ImplementorBee i :: Type
-- | The monad in which method handlers run
type ImplementorMonad i :: Type -> Type
-- | The side value returned by method handlers
type ImplementorSide i :: Type
-- | Set of handlers for the actor's methods.
beeScript
:: Script (ImplementorMonad i) (ImplementorSide i) (ImplementorBee i)
newtype Near (monad :: Type -> Type) (impl :: Type) = Near
{ _unNear :: forall a. (ImplementorMonad impl) a -> monad a
}
near
:: forall (me :: Type) (them :: Type) .
(Implementor me, Implementor them)
=> (forall a. (ImplementorMonad them) a -> (ImplementorMonad me) a)
-> Near (ImplementorMonad me) them
near = Near
type family MethodExchange (m :: Method) =
(m' :: (Type, Type)) | m' -> m where
MethodExchange ('Method sym ('Signature arg ret)) =
'((Proxy sym, arg), ret)
type family MethodExchanges (ms :: [Method]) =
(ms' :: [(Type, Type)]) | ms' -> ms where
MethodExchanges '[] = '[]
MethodExchanges (x ': xs) = MethodExchange x ': MethodExchanges xs
adaptHandler
:: forall
(monad :: Type -> Type)
(method :: Method)
(sym :: Symbol)
(sig :: Signature)
(side :: Type) .
( method ~ 'Method sym sig
, MethodExchange method
~
'((Proxy sym, SignatureParam sig), SignatureReturn sig)
)
=> HandleMethod monad side method
-> MethodHandler monad side (MethodExchange method)
adaptHandler (HandleMethod f) = methodHandler (f . snd)
class AdaptHandlers (ms :: [Method]) where
adaptHandlerSet
:: forall (monad :: Type -> Type) (side :: Type) .
V.Rec (HandleMethod monad side) ms
-> V.Rec (MethodHandler monad side) (MethodExchanges ms)
instance AdaptHandlers ('[] :: [Method]) where
adaptHandlerSet V.RNil = V.RNil
instance
forall (m :: Method) (sym :: Symbol) (sig :: Signature) (ms :: [Method]) .
( m ~ 'Method sym sig
, MethodExchange m
~
'((Proxy sym, SignatureParam sig), SignatureReturn sig)
, AdaptHandlers ms
) =>
AdaptHandlers (m : ms) where
adaptHandlerSet (h V.:& hs) =
adaptHandler h V.:& adaptHandlerSet hs
callNear
:: forall
(sym :: Symbol)
(i :: Type)
(m :: Type -> Type)
(sig :: Signature) .
( Implementor i
, 'Method sym sig V. BeeInterface (ImplementorBee i)
, LookupSig sym (BeeInterface (ImplementorBee i)) ~ Just sig
, '((Proxy sym, SignatureParam sig), SignatureReturn sig)
V.
MethodExchanges (BeeInterface (ImplementorBee i))
, AdaptHandlers (BeeInterface (ImplementorBee i))
)
=> Near m i
-> SignatureParam sig
-> m (ImplementorSide i, SignatureReturn sig)
callNear (Near run) arg =
run $ runMethodHandler (Proxy @sym, arg) $ adaptHandlerSet $ unScript $ beeScript @i
callNear_
:: forall
(sym :: Symbol)
(i :: Type)
(m :: Type -> Type)
(sig :: Signature) .
( Implementor i
, Functor m
, 'Method sym sig V. BeeInterface (ImplementorBee i)
, LookupSig sym (BeeInterface (ImplementorBee i)) ~ Just sig
, '((Proxy sym, SignatureParam sig), SignatureReturn sig)
V.
MethodExchanges (BeeInterface (ImplementorBee i))
, AdaptHandlers (BeeInterface (ImplementorBee i))
)
=> Near m i
-> SignatureParam sig
-> m ()
callNear_ bee arg = void $ callNear @sym bee arg
type ClientSet (ms :: [Method]) = V.Rec AsyncExchangeClientWrapper (MethodExchanges ms)
newtype Caller (b :: Type) = Caller
{ _unCaller :: ClientSet (BeeInterface b)
}
newtype Far (b :: Type) = Far
{ _unFar :: Caller b
}
data Next = Stop | Continue
launchFar
:: forall (impl :: Type) (util :: Type) (pairs :: [(Type, Type)]) .
( pairs ~ MethodExchanges (BeeInterface (ImplementorBee impl))
, Implementor impl
, V.RMapCM pairs pairs
, V.RecApplicative pairs
, AdaptHandlers (BeeInterface (ImplementorBee impl))
, MonadUnliftIO (ImplementorMonad impl)
)
=> IO util
-> ImplementorMonad impl ()
-> (forall a. util -> ImplementorMonad impl a -> IO a)
-> (ImplementorSide impl -> (ImplementorMonad impl (), Next))
-> (SomeException -> ImplementorMonad impl ())
-> IO (Far (ImplementorBee impl))
launchFar prepareUtil selfInit runBee makeSide useException = do
mchan <- newMethodServerChan @pairs
let server = methodServerFromChan mchan
handlers = adaptHandlerSet $ unScript $ beeScript @impl
_ <- forkIO $ do
u <- prepareUtil
runBee u $ do
selfInit
loop server handlers
let clients = asyncExchangeClientsForMethods mchan
return $ Far $ Caller clients
where
loop server handlers = do
(become, next) <- do
result <- try $ handleMethodCallSync server handlers
case result of
Left e -> do
useException (e :: SomeException)
return (pure (), Continue)
Right side -> pure $ makeSide side
become
case next of
Stop -> pure ()
Continue -> loop server handlers
matchMethodClient'
:: forall
(sym :: Symbol)
(b :: Type)
(sig :: Signature) .
( Bee b
, 'Method sym sig V. BeeInterface b
, LookupSig sym (BeeInterface b) ~ Just sig
, '((Proxy sym, SignatureParam sig), SignatureReturn sig)
V.
MethodExchanges (BeeInterface b)
)
=> Far b
-> AsyncExchangeClient
(Proxy sym, SignatureParam sig)
(SignatureReturn sig)
matchMethodClient' (Far (Caller clients)) = matchAsyncExchangeClient clients
matchMethodClient
:: forall
(sym :: Symbol)
(b :: Type)
(sig :: Signature) .
( Bee b
, 'Method sym sig V. BeeInterface b
, LookupSig sym (BeeInterface b) ~ Just sig
, '((Proxy sym, SignatureParam sig), SignatureReturn sig)
V.
MethodExchanges (BeeInterface b)
)
=> Far b
-> AsyncExchangeClient
(SignatureParam sig)
(SignatureReturn sig)
matchMethodClient = adaptAsyncExchangeClient (Proxy @sym,) . matchMethodClient'
callFar
:: forall
(sym :: Symbol)
(b :: Type)
(sig :: Signature) .
( Bee b
, 'Method sym sig V. BeeInterface b
, LookupSig sym (BeeInterface b) ~ Just sig
, '((Proxy sym, SignatureParam sig), SignatureReturn sig)
V.
MethodExchanges (BeeInterface b)
)
=> Far b
-> SignatureParam sig
-> AsyncExchangeClient (SignatureReturn sig) ()
-> IO ()
callFar far arg clientReturn =
let client =
matchMethodClient @sym far ::
AsyncExchangeClient
(SignatureParam sig)
(SignatureReturn sig)
in exchangeAsyncClient client arg clientReturn
callFar_
:: forall
(sym :: Symbol)
(b :: Type)
(sig :: Signature) .
( Bee b
, 'Method sym sig V. BeeInterface b
, LookupSig sym (BeeInterface b) ~ Just sig
, '((Proxy sym, SignatureParam sig), SignatureReturn sig)
V.
MethodExchanges (BeeInterface b)
)
=> Far b
-> SignatureParam sig
-> IO ()
callFar_ far arg = do
let client =
matchMethodClient @sym far ::
AsyncExchangeClient
(SignatureParam sig)
(SignatureReturn sig)
(_, clientReturn) <- newAsyncExchange
exchangeAsyncClient client arg clientReturn
-- Swarm: Vat
-- Hive: Whole local instance