packages feed

GPipe-GLFW4-2.0.0: src/Graphics/GPipe/Context/GLFW/RPC.hs

module Graphics.GPipe.Context.GLFW.RPC where

-- stdlib
import           Control.Concurrent            (ThreadId, myThreadId)
import           Control.Concurrent.MVar       (newEmptyMVar, putMVar, takeMVar)
import           Control.Concurrent.STM        (STM, atomically)
import           Control.Concurrent.STM.TQueue (TQueue, newTQueue, peekTQueue,
                                                tryReadTQueue, writeTQueue)
import           Data.Sequence                 (Seq, empty, (|>))
-- local
--import qualified Graphics.GPipe.Context.GLFW.Calls as Call

data Handle = Handle ThreadId (TQueue RPC)
    deriving
    ( Eq
    )

-- TODO: change RPC to a chan of `IO ()` and collapse `runActions`
data RPC
    = Execute (IO ())
    | Noop

-- | Create an RPC handle bound to the current thread. Actions sent from the
-- bound thread will just be run w/o doing an RPC.
newBound :: IO Handle
newBound = do
    tid <- myThreadId
    comm <- atomically $ newTQueue
    return $ Handle tid comm

-- XXX: consider pushing thread-check to all callsites of sendEffect, fetchResult
-- TODO: dry-up thread id check
sendEffect :: Handle -> IO () -> IO ()
sendEffect (Handle boundTid comm) action = do
    tid <- myThreadId
    if boundTid == tid
        then action
        else atomically $ writeTQueue comm (Execute action)

fetchResult :: Handle -> IO a -> IO a
fetchResult (Handle boundTid comm) action = do
    tid <- myThreadId
    if boundTid == tid
        then action
        else do
            reply <- newEmptyMVar
            -- XXX: Make sure the value put in the MVar is evaluated first
            atomically$ writeTQueue comm (Execute $ action >>= putMVar reply)
            takeMVar reply

drainComm :: TQueue a -> STM (Seq a)
drainComm queue = go empty
    where
        go rpcs = do
            result <- tryReadTQueue queue
            case result of
                Just rpc -> go $ rpcs |> rpc
                Nothing  -> return rpcs

runActions :: Foldable t => t RPC -> IO ()
runActions = mapM_ go
    where
        go Noop             = print "noop"
        go (Execute action) = action

awaitActions :: Handle -> IO RPC
awaitActions (Handle _ comm) = atomically . peekTQueue $ comm

processActions :: Handle -> IO ()
processActions (Handle _ comm) = (atomically . drainComm $ comm) >>= runActions