module Foreign.JavaScript.EventLoop (
eventLoop,
runEval, callEval, debug, onDisconnect,
newHandler, fromJSStablePtr,
) where
import Control.Applicative
import Control.Concurrent
import Control.Concurrent.Async
import Control.Concurrent.STM as STM
import Control.Exception as E (finally)
import Control.Monad
import qualified Data.Aeson as JSON
import Data.IORef
import qualified Data.Map as Map
import qualified Data.Text as T
import qualified System.Mem
import Foreign.RemotePtr as Foreign
import Foreign.JavaScript.Types
rebug :: IO ()
#ifdef REBUG
rebug = System.Mem.performGC
#else
rebug = return ()
#endif
handleEvent w@(Window{..}) (name, args, consistency) = do
mhandler <- Foreign.lookup name wEventHandlers
case mhandler of
Nothing -> return ()
Just f -> withRemotePtr f (\_ f -> f args)
eventLoop :: (Window -> IO void) -> (Comm -> IO ())
eventLoop init comm = do
events <- newTQueueIO
results <- newTQueueIO :: IO (TQueue JSON.Value)
calls <- newTQueueIO :: IO (TQueue (Maybe (TMVar JSON.Value), ServerMsg))
handling <- newTVarIO False
calling <- newTVarIO False
w0 <- newPartialWindow
let run msg = do
atomically $ writeTQueue calls (Nothing , msg)
call msg = do
ref <- newEmptyTMVarIO
atomically $ writeTQueue calls (Just ref, msg)
atomically $ takeTMVar ref
debug s = do
atomically $ writeServer comm $ Debug s
disconnect <- newTVarIO $ return ()
let onDisconnect m = atomically $ writeTVar disconnect m
let w = w0 { runEval = run . RunEval
, callEval = call . CallEval
, debug = debug
, timestamp = run Timestamp
, onDisconnect = onDisconnect
}
let multiplexer = do
m <- untilJustM $ atomically $ do
msg <- readClient comm
case msg of
Event x y -> do
b <- (||) <$> readTVar handling <*> readTVar calling
let c = if b then Inconsistent else Consistent
writeTQueue events (x,y,c)
return Nothing
Result x -> do
writeTQueue results x
return Nothing
Quit -> Just <$> readTVar disconnect
m
let handleCalls = forever $ do
ref <- atomically $ do
(ref, msg) <- readTQueue calls
writeTVar calling True
writeServer comm msg
return ref
atomically $ do
writeTVar calling False
case ref of
Just ref -> do
result <- readTQueue results
putTMVar ref result
Nothing -> return ()
let handleEvents = do
init w
forever $ do
e <- atomically $ do
writeTVar handling True
readTQueue events
handleEvent w e
rebug
atomically $ writeTVar handling False
Foreign.withRemotePtr (wRoot w) $ \_ _ -> do
E.finally
(foldr1 race_ [multiplexer, handleEvents, handleCalls])
(commClose comm)
return ()
untilJustM :: Monad m => m (Maybe a) -> m a
untilJustM m = m >>= \x -> case x of
Nothing -> untilJustM m
Just a -> return a
newHandler :: Window -> ([JSON.Value] -> IO ()) -> IO HsEvent
newHandler w@(Window{..}) handler = do
coupon <- newCoupon wEventHandlers
newRemotePtr coupon (handler . parseArgs) wEventHandlers
where
fromSuccess (JSON.Success x) = x
parseArgs x = fromSuccess (JSON.fromJSON x) :: [JSON.Value]
fromJSStablePtr :: JSON.Value -> Window -> IO JSObject
fromJSStablePtr js w@(Window{..}) = do
let JSON.Success coupon = JSON.fromJSON js
mhs <- Foreign.lookup coupon wJSObjects
case mhs of
Just hs -> return hs
Nothing -> do
ptr <- newRemotePtr coupon (JSPtr coupon) wJSObjects
addFinalizer ptr $
runEval ("Haskell.freeStablePtr('" ++ T.unpack coupon ++ "')")
return ptr