hsc3-server-0.4.0: Sound/SC3/Server/Monad.hs
{-# LANGUAGE FlexibleContexts
, FlexibleInstances
, GeneralizedNewtypeDeriving
, MultiParamTypeClasses
, TypeFamilies
, UndecidableInstances #-}
module Sound.SC3.Server.Monad
( -- * Server Monad
ServerT
, runServerT
, capture
, Server
, runServer
-- * Server options
, MonadServer(..)
, serverOption
-- * Allocation
, Allocator
, BufferId
, BufferIdAllocator
, bufferIdAllocator
, BusId
, BusIdAllocator
, audioBusIdAllocator
, controlBusIdAllocator
, NodeId
, NodeIdAllocator
, nodeIdAllocator
, Range
, MonadIdAllocator(..)
-- * Communication and synchronization
, MonadSendOSC(..)
, MonadRecvOSC(..)
, SyncId
, SyncIdAllocator
, syncIdAllocator
, sync
, unsafeSync
-- * Concurrency
, fork
) where
import Control.Applicative
import Control.Concurrent.Lifted (ThreadId)
import qualified Control.Concurrent.Lifted as CL
import Control.Concurrent.MVar.Strict
import Control.DeepSeq (NFData)
import Control.Monad (MonadPlus, liftM)
import Control.Monad.Base (MonadBase(..), liftBaseDefault)
import Control.Monad.Fix (MonadFix)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Trans.Reader (ReaderT(..))
import qualified Control.Monad.Trans.Reader as R
import Control.Monad.Trans.Resource (MonadResource, MonadThrow)
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.Trans.Control
import Sound.OpenSoundControl (Datum(..), OSC(..), immediately)
import Sound.SC3.Server.Allocator (Id, IdAllocator, RangeAllocator, Range)
import qualified Sound.SC3.Server.Allocator as A
import Sound.SC3.Server.Command (notify)
import Sound.SC3.Server.Connection (Connection)
import qualified Sound.SC3.Server.Connection as C
import Sound.SC3.Server.Notification (Notification, synced)
import Sound.SC3.Server.Process.Options (ServerOptions)
import Sound.SC3.Server.State ( BufferId, BufferIdAllocator
, BusId, BusIdAllocator
, NodeId, NodeIdAllocator
, SyncId, SyncIdAllocator
)
import qualified Sound.SC3.Server.State as State
data State = State {
_serverOptions :: ServerOptions
, _connection :: Connection
, _syncIdAllocator :: MVar SyncIdAllocator
, _nodeIdAllocator :: MVar NodeIdAllocator
, _bufferIdAllocator :: MVar BufferIdAllocator
, _controlBusIdAllocator :: MVar BusIdAllocator
, _audioBusIdAllocator :: MVar BusIdAllocator
}
newtype ServerT m a = ServerT { unServerT :: ReaderT State m a }
deriving (Alternative, Applicative, Functor, Monad, MonadFix, MonadIO, MonadPlus, MonadResource, MonadThrow, MonadTrans)
type Server = ServerT IO
instance MonadBase b m => MonadBase b (ServerT m) where
{-# INLINE liftBase #-}
liftBase = liftBaseDefault
instance MonadTransControl ServerT where
newtype StT ServerT a = StServerT {unStServerT::a}
{-# INLINE liftWith #-}
liftWith f = ServerT $ ReaderT $ \r -> f $ \t -> liftM StServerT $ runReaderT (unServerT t) r
{-# INLINE restoreT #-}
restoreT = ServerT . ReaderT . const . liftM unStServerT
instance MonadBaseControl b m => MonadBaseControl b (ServerT m) where
newtype StM (ServerT m) a = StMT { unStMT :: ComposeSt ServerT m a }
{-# INLINE liftBaseWith #-}
liftBaseWith = defaultLiftBaseWith StMT
{-# INLINE restoreM #-}
restoreM = defaultRestoreM unStMT
newtype Allocator a = Allocator (State -> MVar a)
syncIdAllocator :: Allocator SyncIdAllocator
syncIdAllocator = Allocator _syncIdAllocator
nodeIdAllocator :: Allocator NodeIdAllocator
nodeIdAllocator = Allocator _nodeIdAllocator
bufferIdAllocator :: Allocator BufferIdAllocator
bufferIdAllocator = Allocator _bufferIdAllocator
controlBusIdAllocator :: Allocator BusIdAllocator
controlBusIdAllocator = Allocator _controlBusIdAllocator
audioBusIdAllocator :: Allocator BusIdAllocator
audioBusIdAllocator = Allocator _audioBusIdAllocator
-- | Run a 'ServerT' computation given a connection and return the result.
runServerT :: MonadIO m => ServerT m a -> ServerOptions -> Connection -> m a
runServerT (ServerT r) so c = do
sa <- new State.syncIdAllocator
na <- new State.nodeIdAllocator
ba <- new State.bufferIdAllocator
ca <- new State.controlBusIdAllocator
aa <- new State.audioBusIdAllocator
let s = State so c sa na ba ca aa
runReaderT (init >> r) s
where
as = State.mkAllocators so
new :: (IdAllocator a, NFData a, MonadIO m) => (State.Allocators -> a) -> m (MVar a)
new f = liftIO $ newMVar (f as)
-- Register with server to receive notifications.
(ServerT init) = sync (Bundle immediately [notify True])
-- | Run a 'Server' computation given a connection and return the result in the IO monad.
runServer :: Server a -> ServerOptions -> Connection -> IO a
runServer = runServerT
-- | Capture server state for later execution.
capture :: Monad m => ServerT m (ServerT m a -> m a)
capture = ServerT $ do
s <- R.ask
return $ \(ServerT m) -> R.runReaderT m s
class Monad m => MonadServer m where
-- | Return the server options.
serverOptions :: m ServerOptions
-- | Return a server option.
serverOption :: MonadServer m => (ServerOptions -> a) -> m a
serverOption = flip liftM serverOptions
-- serverOptions :: MonadIO m => ServerT m ServerOptions
instance Monad m => MonadServer (ServerT m) where
serverOptions = ServerT $ R.asks _serverOptions
-- | Monadic resource id management interface.
class Monad m => MonadIdAllocator m where
-- | Return the root node id.
rootNodeId :: m NodeId
-- | Allocate an id using the given allocator.
alloc :: (IdAllocator a, NFData a) => Allocator a -> m (Id a)
-- | Free an id using the given allocator.
free :: (IdAllocator a, NFData a) => Allocator a -> Id a -> m ()
-- | Allocate a number of ids using the given allocator.
allocMany :: (IdAllocator a, NFData a) => Allocator a -> Int -> m [Id a]
-- | Free a number of ids using the given allocator.
freeMany :: (IdAllocator a, NFData a) => Allocator a -> [Id a] -> m ()
-- | Allocate a contiguous range of ids using the given allocator.
allocRange :: (RangeAllocator a, NFData a) => Allocator a -> Int -> m (Range (Id a))
-- | Free a contiguous range of ids using the given allocator.
freeRange :: (RangeAllocator a, NFData a) => Allocator a -> Range (Id a) -> m ()
withAllocator :: (IdAllocator a, NFData a, MonadIO m) => (a -> IO (b, a)) -> Allocator a -> ServerT m b
withAllocator f (Allocator a) = ServerT $ do
mv <- R.asks a
liftIO $ modifyMVar mv $ \s -> do
(i, s') <- f s
return $! (s', i)
withAllocator_ :: (IdAllocator a, NFData a, MonadIO m) => (a -> IO a) -> Allocator a -> ServerT m ()
withAllocator_ f = withAllocator (liftM ((,)()) . f)
instance (MonadIO m) => MonadIdAllocator (ServerT m) where
rootNodeId = return (fromIntegral 0)
alloc a = withAllocator A.alloc a
free a i = withAllocator_ (A.free i) a
allocMany a n = withAllocator (A.allocMany n) a
freeMany a is = withAllocator_ (A.freeMany is) a
allocRange a n = withAllocator (A.allocRange n) a
freeRange a r = withAllocator_ (A.freeRange r) a
class Monad m => MonadSendOSC m where
send :: OSC -> m ()
withConnection :: MonadIO m => (Connection -> IO a) -> ServerT m a
withConnection f = ServerT $ R.asks _connection >>= \c -> liftIO (f c)
instance MonadIO m => MonadSendOSC (ServerT m) where
send osc = withConnection $ \c -> C.send c osc
class Monad m => MonadRecvOSC m where
-- | Wait for a notification and return the result.
waitFor :: OSC -> Notification a -> m a
-- | Wait for a notification and ignore the result.
waitFor_ :: OSC -> Notification a -> m ()
-- | Wait for a set of notifications and return their results in unspecified order.
waitForAll :: OSC -> [Notification a] -> m [a]
-- | Wait for a set of notifications and ignore their results.
waitForAll_ :: OSC -> [Notification a] -> m ()
instance MonadIO m => MonadRecvOSC (ServerT m) where
waitFor osc n = withConnection $ \c -> C.waitFor c osc n
waitFor_ osc n = withConnection $ \c -> C.waitFor_ c osc n
waitForAll osc ns = withConnection $ \c -> C.waitForAll c osc ns
waitForAll_ osc ns = withConnection $ \c -> C.waitForAll_ c osc ns
-- | Append a @\/sync@ message to an OSC packet.
appendSync :: OSC -> SyncId -> OSC
appendSync p i =
case p of
m@(Message _ _) -> Bundle immediately [m, s]
(Bundle t xs) -> Bundle t (xs ++ [s])
where s = Message "/sync" [Int (fromIntegral i)]
-- | Send an OSC packet and wait for the synchronization barrier.
sync :: (MonadIO m) => OSC -> ServerT m ()
sync osc = do
i <- alloc syncIdAllocator
waitFor_ (osc `appendSync` i) (synced i)
free syncIdAllocator i
-- NOTE: This is only guaranteed to work with a transport that preserves
-- packet order. NOTE 2: And not even then ;)
unsafeSync :: (MonadIO m) => ServerT m ()
unsafeSync = sync (Bundle immediately [])
-- | Fork a computation in a new thread and return the thread id.
fork :: (MonadBaseControl IO m) => ServerT m () -> ServerT m ThreadId
fork = CL.fork