@@ -37,10 +37,10 @@ import qualified JavaScript.Web.AnimationFrame as GHCJS
37 (waitForAnimationFrame)
37 (waitForAnimationFrame)
39import Control.Exception (throwIO)
39import Control.Exception (throwIO)
40−import Control.Monad (void, forever)
40+import Control.Monad (void, forever, zipWithM_)
41import Control.Monad.IO.Class (MonadIO(..))
41import Control.Monad.IO.Class (MonadIO(..))
42import Control.Monad.Trans.Reader (ask, runReaderT)
42import Control.Monad.Trans.Reader (ask, runReaderT)
43−import Control.Monad.STM (STM, atomically)
43+import Control.Monad.STM (atomically)
44import Control.Concurrent (forkIO)
44import Control.Concurrent (forkIO)
45import Control.Concurrent.STM.TChan
45import Control.Concurrent.STM.TChan
46 (tryReadTChan, TChan, readTChan, writeTChan, newTChanIO)
46 (tryReadTChan, TChan, readTChan, writeTChan, newTChanIO)
@@ -49,16 +49,19 @@ import Control.Concurrent.STM.TVar
49import Control.Concurrent.MVar
49import Control.Concurrent.MVar
50 (MVar, MVar, putMVar, takeMVar, newEmptyMVar)
50 (MVar, MVar, putMVar, takeMVar, newEmptyMVar)
52+import System.IO.Unsafe (unsafeInterleaveIO)
52import Data.Monoid ((<>))
54import Data.Monoid ((<>))
53import qualified Data.Text as T (unpack)
55import qualified Data.Text as T (unpack)
54import qualified Data.Map as M (lookup, delete, insert, empty)
56import qualified Data.Map as M (lookup, delete, insert, empty)
55import Data.Time.Clock (getCurrentTime,diffUTCTime)
57import Data.Time.Clock (getCurrentTime,diffUTCTime)
57import Language.Javascript.JSaddle.Types
59import Language.Javascript.JSaddle.Types
58− (Command(..), AsyncCommand(..), Result(..), JSContextRef(..), JSVal(..),
60+ (Command(..), AsyncCommand(..), Result(..), Results(..), JSContextRef(..), JSVal(..),
59 Object(..), JSValueReceived(..), JSM(..), Batch(..), JSValueForSend(..))
61 Object(..), JSValueReceived(..), JSM(..), Batch(..), JSValueForSend(..))
60import Language.Javascript.JSaddle.Exception (JSException(..))
62import Language.Javascript.JSaddle.Exception (JSException(..))
61import Language.Javascript.JSaddle.Native.Internal (wrapJSVal)
63import Language.Javascript.JSaddle.Native.Internal (wrapJSVal)
64+import Control.DeepSeq (deepseq)
64-- | Forces execution of pending asyncronous code
67-- | Forces execution of pending asyncronous code
@@ -66,7 +69,9 @@ syncPoint :: JSM ()
66#ifdef ghcjs_HOST_OS
69#ifdef ghcjs_HOST_OS
67syncPoint = return ()
70syncPoint = return ()
69−syncPoint = void $ sendCommand Sync
73+ SyncResult <- sendCommand Sync
72-- | Forces execution of pending asyncronous code after performing `f`
77-- | Forces execution of pending asyncronous code after performing `f`
@@ -126,7 +131,7 @@ sendAsyncCommand cmd = do
126 s <- doSendAsyncCommand <$> JSM ask
131 s <- doSendAsyncCommand <$> JSM ask
127 liftIO $ s cmd
132 liftIO $ s cmd
129−runJavaScript :: (Batch -> IO ()) -> JSM () -> IO (Result -> IO (), IO ())
134+runJavaScript :: (Batch -> IO ()) -> JSM () -> IO (Results -> IO (), IO ())
130runJavaScript sendBatch entryPoint = do
135runJavaScript sendBatch entryPoint = do
131 startTime' <- getCurrentTime
136 startTime' <- getCurrentTime
132 recvChan <- newTChanIO
137 recvChan <- newTChanIO
@@ -135,18 +140,19 @@ runJavaScript sendBatch entryPoint = do
135 nextRef' <- newTVarIO 0
140 nextRef' <- newTVarIO 0
136 let ctx = JSContextRef {
141 let ctx = JSContextRef {
137 startTime = startTime'
142 startTime = startTime'
138− , doSendCommand = \cmd -> do
143+ , doSendCommand = \cmd -> cmd `deepseq` do
139 result <- newEmptyMVar
144 result <- newEmptyMVar
140 atomically $ writeTChan commandChan (Right (cmd, result))
145 atomically $ writeTChan commandChan (Right (cmd, result))
146+ unsafeInterleaveIO $
141− takeMVar result >>= \case
147+ takeMVar result >>= \case
142− (ThrowJSValue (JSValueReceived v)) -> throwIO $ JSException (JSVal v)
148+ (ThrowJSValue (JSValueReceived v)) -> throwIO $ JSException (JSVal v)
143− r -> return r
149+ r -> return r
144− , doSendAsyncCommand = atomically . writeTChan commandChan . Left
150+ , doSendAsyncCommand = \cmd -> cmd `deepseq` atomically (writeTChan commandChan $ Left cmd)
145 , addCallback = \(Object (JSVal val)) cb -> atomically $ modifyTVar callbacks (M.insert val cb)
151 , addCallback = \(Object (JSVal val)) cb -> atomically $ modifyTVar callbacks (M.insert val cb)
146 , freeCallback = \(Object (JSVal val)) -> atomically $ modifyTVar callbacks (M.delete val)
152 , freeCallback = \(Object (JSVal val)) -> atomically $ modifyTVar callbacks (M.delete val)
147 , nextRef = nextRef'
153 , nextRef = nextRef'
149− let processResult = \case
155+ let processResults = \case
150 (ProtocolError err) -> error $ "Protocol error : " <> T.unpack err
156 (ProtocolError err) -> error $ "Protocol error : " <> T.unpack err
151 (Callback f this a) -> do
157 (Callback f this a) -> do
152 f'@(JSVal fNumber) <- runReaderT (unJSM $ wrapJSVal f) ctx
158 f'@(JSVal fNumber) <- runReaderT (unJSM $ wrapJSVal f) ctx
@@ -157,40 +163,39 @@ runJavaScript sendBatch entryPoint = do
157 Just cb -> void . forkIO $ runReaderT (unJSM $ cb f' this' args) ctx
163 Just cb -> void . forkIO $ runReaderT (unJSM $ cb f' this' args) ctx
158 m -> atomically $ writeTChan recvChan m
164 m -> atomically $ writeTChan recvChan m
159 _ <- forkIO . forever $ readBatch commandChan >>= \case
165 _ <- forkIO . forever $ readBatch commandChan >>= \case
160− (batch, Just resultMVar) -> do
166+ (batch, resultMVars) -> do
162− atomically (readTChan recvChan) >>= putMVar resultMVar
163− (batch, Nothing) -> do
164 sendBatch batch
167 sendBatch batch
165 atomically (readTChan recvChan) >>= \case
168 atomically (readTChan recvChan) >>= \case
166− SyncResult -> return ()
169+ Success results -> zipWithM_ putMVar resultMVars results
167− ThrowJSValue e -> atomically (discardToSyncPoint commandChan) >>= (`putMVar` ThrowJSValue e)
170+ Failure results exception -> zipWithM_ putMVar resultMVars $ results <> repeat (ThrowJSValue exception)
168− _ -> error "Unexpected result processing batch"
171+ _ -> error "Unexpected jsaddle results"
170− return (processResult, runReaderT (unJSM entryPoint) ctx)
172+ return (processResults, runReaderT (unJSM entryPoint) ctx)
172− readBatch :: TChan (Either AsyncCommand (Command, MVar Result)) -> IO (Batch, Maybe (MVar Result))
174+ readBatch :: TChan (Either AsyncCommand (Command, MVar Result)) -> IO (Batch, [MVar Result])
173 readBatch chan = do
175 readBatch chan = do
174 first <- atomically $ readTChan chan -- We want at least one command to send
176 first <- atomically $ readTChan chan -- We want at least one command to send
175− loop first []
177+ loop first ([], [])
179+ loop :: Either AsyncCommand (Command, MVar Result) -> ([Either AsyncCommand Command], [MVar Result]) -> IO (Batch, [MVar Result])
177− loop (Left asyncCmd@(SyncWithAnimationFrame _)) asyncCmds =
180+ loop (Left asyncCmd@(SyncWithAnimationFrame _)) (cmds, resultMVars) =
178− atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (asyncCmd:asyncCmds)
181+ atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Left asyncCmd:cmds, resultMVars)
179− loop (Right (cmd, resultMVar)) asyncCmds =
182+ loop (Right (syncCmd, resultMVar)) (cmds', resultMVars') = do
183+ let cmds = Right syncCmd:cmds'
184+ resultMVars = resultMVar:resultMVars'
185+ atomically (tryReadTChan chan) >>= \case
180− return (Batch (reverse asyncCmds) cmd False, Just resultMVar)
186+ Nothing -> return (Batch (reverse cmds) False, reverse resultMVars)
187+ Just cmd -> loop cmd (cmds, resultMVars)
181− loop (Left asyncCmd) asyncCmds' = do
188+ loop (Left asyncCmd) (cmds', resultMVars) = do
182− let asyncCmds = asyncCmd:asyncCmds'
189+ let cmds = Left asyncCmd:cmds'
183 atomically (tryReadTChan chan) >>= \case
190 atomically (tryReadTChan chan) >>= \case
184− Nothing -> return (Batch (reverse asyncCmds) Sync False, Nothing)
191+ Nothing -> return (Batch (reverse cmds) False, reverse resultMVars)
185− Just cmd -> loop cmd asyncCmds
192+ Just cmd -> loop cmd (cmds, resultMVars)
186 -- When we have seen a SyncWithAnimationFrame command only a synchronous command should end the batch
193 -- When we have seen a SyncWithAnimationFrame command only a synchronous command should end the batch
187− loopAnimation (Right (cmd, resultMVar)) asyncCmds =
188− return (Batch (reverse asyncCmds) cmd True, Just resultMVar)
189− loopAnimation (Left asyncCmd) asyncCmds =
190− atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (asyncCmd:asyncCmds)
191− discardToSyncPoint :: TChan (Either AsyncCommand (Command, MVar Result)) -> STM (MVar Result)
194+ loopAnimation :: Either AsyncCommand (Command, MVar Result) -> ([Either AsyncCommand Command], [MVar Result]) -> IO (Batch, [MVar Result])
192− discardToSyncPoint chan =
195+ loopAnimation (Right (Sync, resultMVar)) (cmds, resultMVars) =
193− readTChan chan >>= \case
196+ return (Batch (reverse (Right Sync:cmds)) True, reverse (resultMVar:resultMVars))
194− Right (_, resultMVar) -> return resultMVar
197+ loopAnimation (Right (syncCmd, resultMVar)) (cmds, resultMVars) =
195− _ -> discardToSyncPoint chan
198+ atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Right syncCmd:cmds, resultMVar:resultMVars)
199+ loopAnimation (Left asyncCmd) (cmds, resultMVars) =
200+ atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Left asyncCmd:cmds, resultMVars)