{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -fno-warn-missing-methods #-} -- | 'JSMVar' provides the same functionality and -- concurrency abstraction in Javascript computations -- as 'Control.Concurrent.MVar' in Haskell. module Language.Sunroof.JS.MVar ( JSMVar , newMVar, newEmptyMVar , putMVar, takeMVar ) where import Data.Boolean ( IfB(..), EqB(..) ) import Language.Sunroof.Classes ( Sunroof(..), SunroofArgument(..) ) import Language.Sunroof.Types import Language.Sunroof.Concurrent ( forkJS ) import Language.Sunroof.Selector ( (!) ) import Language.Sunroof.TH import Language.Sunroof.JS.Object ( JSObject ) import Language.Sunroof.JS.Array ( JSArray , newArray, length' , push, shift ) -- ------------------------------------------------------------- -- JSMVar Type -- ------------------------------------------------------------- -- | 'JSMVar' abstraction. The type parameter gives -- the type of values held in a 'JSMVar'. newtype JSMVar a = JSMVar JSObject deriveJSTuple [d| instance (SunroofArgument o) => JSTuple (JSMVar o) where type Internals (JSMVar o) = ( (JSArray (JSContinuation (JSContinuation o))) -- callbacks of written data , (JSArray (JSContinuation o)) -- callbacks of waiting readers ) |] -- | Reference equality, not value equality. instance (SunroofArgument o) => EqB (JSMVar o) where (JSMVar a) ==* (JSMVar b) = a ==* b -- ------------------------------------------------------------- -- JSMVar Combinators -- ------------------------------------------------------------- -- | Create a new 'JSMVar' with the given value inside. -- See 'newEmptyMVar'. newMVar :: forall a t . (SunroofArgument a) => a -> JS t (JSMVar a) newMVar a = do written <- newArray () waiting <- newArray () -- mvar must be empty, with no one waiting, so just push and continue f <- continuation $ \ (k :: JSContinuation a) -> goto k a :: JSB () written # push (f :: JSContinuation (JSContinuation a)) tuple (written, waiting) -- | Create a new empty 'JSMVar'. -- See 'newMVar'. newEmptyMVar :: (SunroofArgument a) => JS t (JSMVar a) newEmptyMVar = do written <- newArray () waiting <- newArray () tuple (written, waiting) -- TODO: Not quite right; pauses until someone bites -- | Put the value into the 'JSMVar'. If there already is a -- value inside, this will block until it is taken out. putMVar :: forall a . (SunroofArgument a) => a -> JSMVar a -> JS B () putMVar a (match -> (written,waiting)) = do ifB ((waiting ! length') ==* 0) (-- no-one is waiting, so check for fullness ifB ((written ! length') ==* 0) (-- mvar empty, so just push and continue do f <- continuation $ \ (k :: JSContinuation a) -> goto k a :: JSB () written # push (f :: JSContinuation (JSContinuation a)) ) (-- mvar full, so block callcc $ \ (k :: JSContinuation ()) -> do f <- continuation $ \ (kr :: JSContinuation a) -> do -- we've got a request for the contents -- so we can continue forkJS $ (goto k () :: JSB ()) -- and send the boxed value goto kr a :: JSB () written # push (f :: JSContinuation (JSContinuation a)) done ) ) -- If someone is already waiting, then just pass the value (and continue without pausing) (do f <- shift waiting forkJS (goto f a :: JSB ()) return () ) -- | Take the value out of the 'JSMVar'. If there is no value -- inside, this will block until one is available. takeMVar :: forall a . (Sunroof a, SunroofArgument a) => JSMVar a -> JS B a takeMVar (match -> (written,waiting)) = do ifB ((written ! length') ==* 0) (do -- Add yourself to the 'waiting for writer' Q. callcc $ \ k -> do waiting # push (k :: JSContinuation a) done ) (do f <- shift written -- Here, we add our continuation into the written Q. callcc $ \ k -> goto f k )