@@ -49,7 +49,7 @@ import Control.Concurrent.STM.TChan
49import Control.Concurrent.STM.TVar
49import Control.Concurrent.STM.TVar
50 (writeTVar, readTVar, readTVarIO, modifyTVar', newTVarIO)
50 (writeTVar, readTVar, readTVarIO, modifyTVar', newTVarIO)
51import Control.Concurrent.MVar
51import Control.Concurrent.MVar
52− (MVar, MVar, putMVar, takeMVar, newEmptyMVar)
52+ (tryTakeMVar, MVar, putMVar, takeMVar, newEmptyMVar)
54import System.IO.Unsafe (unsafeInterleaveIO)
54import System.IO.Unsafe (unsafeInterleaveIO)
55import System.Mem.Weak (addFinalizer)
55import System.Mem.Weak (addFinalizer)
@@ -67,6 +67,8 @@ import Language.Javascript.JSaddle.Exception (JSException(..))
67-- import Language.Javascript.JSaddle.Native.Internal (wrapJSVal)
67-- import Language.Javascript.JSaddle.Native.Internal (wrapJSVal)
68import Control.DeepSeq (deepseq)
68import Control.DeepSeq (deepseq)
69import GHC.Stats (getGCStatsEnabled, getGCStats, GCStats(..))
69import GHC.Stats (getGCStatsEnabled, getGCStats, GCStats(..))
70+import Data.Maybe (isJust)
71+import Data.Foldable (forM_)
72-- | Enable (or disable) JSaddle logging
74-- | Enable (or disable) JSaddle logging
@@ -146,10 +148,12 @@ sendAsyncCommand cmd = do
146 s <- doSendAsyncCommand <$> JSM ask
148 s <- doSendAsyncCommand <$> JSM ask
147 liftIO $ s cmd
149 liftIO $ s cmd
149−runJavaScript :: (Batch -> IO ()) -> JSM () -> IO (Results -> IO (), IO ())
151+runJavaScript :: (Batch -> IO ()) -> JSM () -> IO (Results -> IO (), Results -> IO Batch, IO ())
150runJavaScript sendBatch entryPoint = do
152runJavaScript sendBatch entryPoint = do
151 startTime' <- getCurrentTime
153 startTime' <- getCurrentTime
152 recvMVar <- newEmptyMVar
154 recvMVar <- newEmptyMVar
155+ syncSendBatch <- newEmptyMVar
156+ lastAsyncBatch <- newEmptyMVar
153 commandChan <- newTChanIO
157 commandChan <- newTChanIO
154 callbacks <- newTVarIO M.empty
158 callbacks <- newTVarIO M.empty
155 nextRef' <- newTVarIO 0
159 nextRef' <- newTVarIO 0
@@ -169,17 +173,39 @@ runJavaScript sendBatch entryPoint = do
169 , nextRef = nextRef'
173 , nextRef = nextRef'
170 , doEnableLogging = atomicWriteIORef loggingEnabled
174 , doEnableLogging = atomicWriteIORef loggingEnabled
172− let processResults = \case
176+ let processResults :: Bool -> Results -> IO ()
177+ processResults syncCallbacks = \case
173 (ProtocolError err) -> error $ "Protocol error : " <> T.unpack err
178 (ProtocolError err) -> error $ "Protocol error : " <> T.unpack err
174 (Callback f this a) -> do
179 (Callback f this a) -> do
175− logInfo ("Call " <>)
176 f'@(JSVal fNumber) <- runReaderT (unJSM $ wrapJSVal f) ctx
180 f'@(JSVal fNumber) <- runReaderT (unJSM $ wrapJSVal f) ctx
177 this' <- runReaderT (unJSM $ wrapJSVal this) ctx
181 this' <- runReaderT (unJSM $ wrapJSVal this) ctx
178 args <- runReaderT (unJSM $ mapM wrapJSVal a) ctx
182 args <- runReaderT (unJSM $ mapM wrapJSVal a) ctx
183+ logInfo (("Call " <> show fNumber <> " ") <>)
179 (M.lookup fNumber <$> liftIO (readTVarIO callbacks)) >>= \case
184 (M.lookup fNumber <$> liftIO (readTVarIO callbacks)) >>= \case
180 Nothing -> liftIO $ putStrLn "Callback called after it was freed"
185 Nothing -> liftIO $ putStrLn "Callback called after it was freed"
186+ Just cb -> void . forkIO $ do
181− Just cb -> void . forkIO $ runReaderT (unJSM $ cb f' this' args) ctx
187+ runReaderT (unJSM $ cb f' this' args) ctx
188+ when syncCallbacks $ do
189+ doSendAsyncCommand ctx EndSyncBlock
190+ void $ doSendCommand ctx Sync
191+ Duplicate -> error "Error : Unexpected Duplicate"
182 m -> putMVar recvMVar m
192 m -> putMVar recvMVar m
193+ asyncResults :: Results -> IO ()
194+ asyncResults = processResults False
195+ syncResults :: Results -> IO Batch
196+ syncResults results = do
197+ existing <- tryTakeMVar syncSendBatch
198+ when (isJust existing) $ error "Error : Unexpected Sync Results"
199+ tryTakeMVar lastAsyncBatch >>= \case
200+ Just lastAsync -> processResults True results >> return lastAsync
202+ nextBatch <- newEmptyMVar
203+ putMVar syncSendBatch (\batch -> do
204+ existing <- tryTakeMVar nextBatch
205+ when (isJust existing) $ error "Error : Unexpected Sync Results"
206+ putMVar nextBatch batch)
207+ processResults True results
183 logInfo s =
209 logInfo s =
184 readIORef loggingEnabled >>= \case
210 readIORef loggingEnabled >>= \case
185 True -> do
211 True -> do
@@ -189,23 +215,30 @@ runJavaScript sendBatch entryPoint = do
189 cbCount <- M.size <$> readTVarIO callbacks
215 cbCount <- M.size <$> readTVarIO callbacks
190 putStrLn . s $ "M " <> currentBytesUsedStr <> "CB " <> show cbCount
216 putStrLn . s $ "M " <> currentBytesUsedStr <> "CB " <> show cbCount
191 False -> return ()
217 False -> return ()
218+ _ <- forkIO . forM_ [1..] $ \nBatch ->
192− _ <- forkIO . forever $ readBatch commandChan >>= \case
219+ readBatch nBatch commandChan >>= \case
193− (batch@(Batch cmds _), resultMVars) -> do
220+ (batch@(Batch cmds _ _), resultMVars) -> do
194 logInfo (\x -> "Sync " <> x <> show (length cmds, last cmds))
221 logInfo (\x -> "Sync " <> x <> show (length cmds, last cmds))
222+ tryTakeMVar syncSendBatch >>= \case
223+ Just send -> send batch
225+ putMVar lastAsyncBatch batch
195− sendBatch batch
226+ sendBatch batch
196− takeMVar recvMVar >>= \case
227+ takeMVar recvMVar >>= \r -> do
228+ _ <- tryTakeMVar lastAsyncBatch
197− Success results | length results /= length resultMVars -> error "Unexpected number of jsaddle results"
230+ Success results | length results /= length resultMVars -> error "Unexpected number of jsaddle results"
198− | otherwise -> zipWithM_ putMVar resultMVars results
231+ | otherwise -> zipWithM_ putMVar resultMVars results
199− Failure results exception -> do
232+ Failure results exception -> do
200− -- The exception will only be rethrown in Haskell if/when one of the
233+ -- The exception will only be rethrown in Haskell if/when one of the
201− -- missing results (if any) is evaluated.
234+ -- missing results (if any) is evaluated.
202− putStrLn "A JavaScript exception was thrown! (may not reach Haskell code)"
235+ putStrLn "A JavaScript exception was thrown! (may not reach Haskell code)"
203− zipWithM_ putMVar resultMVars $ results <> repeat (ThrowJSValue exception)
236+ zipWithM_ putMVar resultMVars $ results <> repeat (ThrowJSValue exception)
204− _ -> error "Unexpected jsaddle results"
237+ _ -> error "Unexpected jsaddle results"
205− return (processResults, runReaderT (unJSM entryPoint) ctx)
238+ return (asyncResults, syncResults, runReaderT (unJSM entryPoint) ctx)
207− readBatch :: TChan (Either AsyncCommand (Command, MVar Result)) -> IO (Batch, [MVar Result])
240+ readBatch :: Int -> TChan (Either AsyncCommand (Command, MVar Result)) -> IO (Batch, [MVar Result])
208− readBatch chan = do
241+ readBatch nBatch chan = do
209 first <- atomically $ readTChan chan -- We want at least one command to send
242 first <- atomically $ readTChan chan -- We want at least one command to send
210 loop first ([], [])
243 loop first ([], [])
@@ -216,17 +249,17 @@ runJavaScript sendBatch entryPoint = do
216 let cmds = Right syncCmd:cmds'
249 let cmds = Right syncCmd:cmds'
217 resultMVars = resultMVar:resultMVars'
250 resultMVars = resultMVar:resultMVars'
218 atomically (tryReadTChan chan) >>= \case
251 atomically (tryReadTChan chan) >>= \case
219− Nothing -> return (Batch (reverse cmds) False, reverse resultMVars)
252+ Nothing -> return (Batch (reverse cmds) False nBatch, reverse resultMVars)
220 Just cmd -> loop cmd (cmds, resultMVars)
253 Just cmd -> loop cmd (cmds, resultMVars)
221 loop (Left asyncCmd) (cmds', resultMVars) = do
254 loop (Left asyncCmd) (cmds', resultMVars) = do
222 let cmds = Left asyncCmd:cmds'
255 let cmds = Left asyncCmd:cmds'
223 atomically (tryReadTChan chan) >>= \case
256 atomically (tryReadTChan chan) >>= \case
224− Nothing -> return (Batch (reverse cmds) False, reverse resultMVars)
257+ Nothing -> return (Batch (reverse cmds) False nBatch, reverse resultMVars)
225 Just cmd -> loop cmd (cmds, resultMVars)
258 Just cmd -> loop cmd (cmds, resultMVars)
226 -- When we have seen a SyncWithAnimationFrame command only a synchronous command should end the batch
259 -- When we have seen a SyncWithAnimationFrame command only a synchronous command should end the batch
227 loopAnimation :: Either AsyncCommand (Command, MVar Result) -> ([Either AsyncCommand Command], [MVar Result]) -> IO (Batch, [MVar Result])
260 loopAnimation :: Either AsyncCommand (Command, MVar Result) -> ([Either AsyncCommand Command], [MVar Result]) -> IO (Batch, [MVar Result])
228 loopAnimation (Right (Sync, resultMVar)) (cmds, resultMVars) =
261 loopAnimation (Right (Sync, resultMVar)) (cmds, resultMVars) =
229− return (Batch (reverse (Right Sync:cmds)) True, reverse (resultMVar:resultMVars))
262+ return (Batch (reverse (Right Sync:cmds)) True nBatch, reverse (resultMVar:resultMVars))
230 loopAnimation (Right (syncCmd, resultMVar)) (cmds, resultMVars) =
263 loopAnimation (Right (syncCmd, resultMVar)) (cmds, resultMVars) =
231 atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Right syncCmd:cmds, resultMVar:resultMVars)
264 atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Right syncCmd:cmds, resultMVar:resultMVars)
232 loopAnimation (Left asyncCmd) (cmds, resultMVars) =
265 loopAnimation (Left asyncCmd) (cmds, resultMVars) =