{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | A 'Connection' encapsulates the communication with the synthesis server. -- This module provides functions for opening and closing connections, as well -- as communication and synchronisation primitives. module Sound.SC3.Server.Connection ( Connection -- * Creation and termination , open , close -- * Sending packets , send -- * Receiving packets , withListener ) where import Control.Concurrent (ThreadId, forkIO, myThreadId) import Control.Monad (forever) import Control.Concurrent.MVar import qualified Control.Exception as E import Control.Monad (void) import qualified Data.HashTable.IO as H import Sound.OSC.FD (OSC(..), Packet, Transport) import qualified Sound.OSC.FD as OSC type Listener = Packet -> IO () type ListenerMap = H.CuckooHashTable ThreadId Listener data Connection = forall t . Transport t => Connection t (MVar ListenerMap) listeners :: Connection -> MVar ListenerMap listeners (Connection _ l) = l recvLoop :: Connection -> IO () recvLoop (Connection t ls) = forever $ OSC.recvPacket t >>= \osc -> withMVar ls $ H.mapM_ (\(_, l) -> l osc) -- | Create a new connection given an OSC transport. open :: Transport t => t -> IO Connection open t = do ls <- newMVar =<< H.new let c = Connection t ls void $ forkIO $ recvLoop c return c -- | Close the connection. -- -- The behavior of sending messages after closing the connection is undefined. close :: Connection -> IO () close (Connection t _) = OSC.close t -- | Send an OSC packet asynchronously. send :: OSC o => Connection -> o -> IO () send (Connection t _) = OSC.sendOSC t -- ==================================================================== -- Listeners -- | Add a listener to the listener map. addListener :: Connection -> Listener -> IO (ThreadId, Maybe Listener) addListener c l = do uid <- myThreadId withMVar (listeners c) $ \lm -> do l' <- H.lookup lm uid H.insert lm uid l return (uid, l') -- | Remove a listener from the listener map. removeListener :: Connection -> ThreadId -> Maybe Listener -> IO () removeListener c uid Nothing = withMVar (listeners c) $ \ls -> H.delete ls uid removeListener c uid (Just l) = withMVar (listeners c) $ \ls -> H.insert ls uid l -- | Perform an IO action with a registered listener that is automatically removed. withListener :: Connection -> (Packet -> IO ()) -> IO a -> IO a withListener c l = do E.bracket (addListener c l) (uncurry (removeListener c)) . const