on master help sign in
Lazy evaluation and batching of commands.

This should help keep the number of round trips down at the expense of making JavaScript exceptions less predictable. We can include some functions to help manage exceptions if we need more control.

commit56022c3217db1d31d730ff7ab18331d94a22981a authorHamish Mackenzie <Hamish.K.Mackenzie@googlemail.com> dateThu 29 Sep 2016, 04:01 (10 years ago) parent58fa55d
7 files changed+138−154
data/jsaddle.js+82−88 src/Language/Javascript/JSaddle/Evaluate.hs+3−5 src/Language/Javascript/JSaddle/Object.hs+10−20 src/Language/Javascript/JSaddle/Properties.hs+3−7 src/Language/Javascript/JSaddle/Types.hs+19−25 src/Language/Javascript/JSaddle/Value.hs+5−7 src/Language/Javascript/JSaddle/WebSockets.hs+16−2
data/jsaddle.js+82−88
@@ -36,102 +36,96 @@ var connect = function() {
3636 case "SetPropertyAtIndex":
3737 jsaddle_values.get(d.contents[0])[d.contents[1]]=jsaddle_values.get(d.contents[2]);
3838 break;
39+ case "EvaluateScript":
40+ var n = d.contents[1];
41+ jsaddle_values.set(n, eval(d.contents[0]));
42+ break;
43+ case "StringToValue":
44+ var n = d.contents[1];
45+ jsaddle_values.set(n, d.contents[0]);
46+ break;
47+ case "GetPropertyByName":
48+ var n = d.contents[2];
49+ jsaddle_values.set(n, jsaddle_values.get(d.contents[0])[d.contents[1]]);
50+ break;
51+ case "GetPropertyAtIndex":
52+ var n = d.contents[2];
53+ jsaddle_values.set(n, jsaddle_values.get(d.contents[0])[d.contents[1]]);
54+ break;
55+ case "NumberToValue":
56+ var n = d.contents[1];
57+ jsaddle_values.set(n, d.contents[0]);
58+ break;
59+ case "NewEmptyObject":
60+ var n = d.contents;
61+ jsaddle_values.set(n, {});
62+ break;
63+ case "NewCallback":
64+ var nFunction = d.contents;
65+ jsaddle_values.set(nFunction, function() {
66+ var nThis = ++jsaddle_index;
67+ jsaddle_values.set(nThis, this);
68+ var args = [];
69+ for (var i = 0; i != arguments.length; i++) {
70+ var nArg = ++jsaddle_index;
71+ jsaddle_values.set(nArg, arguments[i]);
72+ args[i] = nArg;
73+ }
74+ ws.send(JSON.stringify({"tag": "Callback", "contents": [nFunction, nThis, args]}));
75+ });
76+ break;
77+ case "CallAsFunction":
78+ var n = d.contents[3];
79+ jsaddle_values.set(n,
80+ jsaddle_values.get(d.contents[0]).apply(jsaddle_values.get(d.contents[1]),
81+ d.contents[2].map(function(arg){return jsaddle_values.get(arg);})));
82+ break;
83+ case "CallAsConstructor":
84+ var n = d.contents[2];
85+ var r;
86+ var f = jsaddle_values.get(d.contents[0]);
87+ var a = d.contents[1].map(function(arg){return jsaddle_values.get(arg);});
88+ switch(a.length) {
89+ case 0 : r = new f(); break;
90+ case 1 : r = new f(a[0]); break;
91+ case 2 : r = new f(a[0],a[1]); break;
92+ case 3 : r = new f(a[0],a[1],a[2]); break;
93+ case 4 : r = new f(a[0],a[1],a[2],a[3]); break;
94+ case 5 : r = new f(a[0],a[1],a[2],a[3],a[4]); break;
95+ case 6 : r = new f(a[0],a[1],a[2],a[3],a[4],a[5]); break;
96+ case 7 : r = new f(a[0],a[1],a[2],a[3],a[4],a[5],a[6]); break;
97+ default:
98+ var ret;
99+ var temp = function() {
100+ ret = f.apply(this, a);
101+ };
102+ temp.prototype = f.prototype;
103+ var i = new temp();
104+ if(ret instanceof Object)
105+ r = ret;
106+ else {
107+ i.constructor = f;
108+ r = i;
109+ }
110+ }
111+ jsaddle_values.set(n, r);
112+ break;
113+ case "NewArray":
114+ var n = d.contents[1];
115+ jsaddle_values.set(n, d.contents[0].map(function(v){return jsaddle_values.get(v);}));
116+ break;
117+ default:
118+ ws.send(JSON.stringify({"tag": "ProtocolError", "contents": e.data}));
119+ return;
39120 }
40121 }
41122 var d = batch[1];
42123 switch (d.tag) {
43− case "EvaluateScript":
44− var n = ++jsaddle_index;
45− jsaddle_values.set(n, eval(d.contents));
46− ws.send(JSON.stringify({"tag": "EvaluateScriptResult", "contents": n}));
47− break;
48124 case "ValueToString":
49125 var val = jsaddle_values.get(d.contents);
50126 var s = val === null ? "null" : val === undefined ? "undefined" : val.toString();
51127 ws.send(JSON.stringify({"tag": "ValueToStringResult", "contents": s}));
52128 break;
53− case "StringToValue":
54− var n = ++jsaddle_index;
55− jsaddle_values.set(n, d.contents);
56− ws.send(JSON.stringify({"tag": "StringToValueResult", "contents": n}));
57− break;
58− case "GetPropertyByName":
59− var n = ++jsaddle_index;
60− jsaddle_values.set(n, jsaddle_values.get(d.contents[0])[d.contents[1]]);
61− ws.send(JSON.stringify({"tag": "GetPropertyByNameResult", "contents": n}));
62− break;
63− case "GetPropertyAtIndex":
64− var n = ++jsaddle_index;
65− jsaddle_values.set(n, jsaddle_values.get(d.contents[0])[d.contents[1]]);
66− ws.send(JSON.stringify({"tag": "GetPropertyAtIndexResult", "contents": n}));
67− break;
68− case "NumberToValue":
69− var n = ++jsaddle_index;
70− jsaddle_values.set(n, d.contents);
71− ws.send(JSON.stringify({"tag": "NumberToValueResult", "contents": n}));
72− break;
73− case "NewEmptyObject":
74− var n = ++jsaddle_index;
75− jsaddle_values.set(n, {});
76− ws.send(JSON.stringify({"tag": "NewEmptyObjectResult", "contents": n}));
77− break;
78− case "NewCallback":
79− var nFunction = ++jsaddle_index;
80− jsaddle_values.set(nFunction, function() {
81− var nThis = ++jsaddle_index;
82− jsaddle_values.set(nThis, this);
83− var args = [];
84− for (var i = 0; i != arguments.length; i++) {
85− var nArg = ++jsaddle_index;
86− jsaddle_values.set(nArg, arguments[i]);
87− args[i] = nArg;
88− }
89− ws.send(JSON.stringify({"tag": "Callback", "contents": [nFunction, nThis, args]})); });
90− ws.send(JSON.stringify({"tag": "NewCallbackResult", "contents": nFunction}));
91− break;
92− case "CallAsFunction":
93− var n = ++jsaddle_index;
94− jsaddle_values.set(n,
95− jsaddle_values.get(d.contents[0]).apply(jsaddle_values.get(d.contents[1]),
96− d.contents[2].map(function(arg){return jsaddle_values.get(arg);})));
97− ws.send(JSON.stringify({"tag": "CallAsFunctionResult", "contents": n}));
98− break;
99− case "CallAsConstructor":
100− var n = ++jsaddle_index;
101− var r;
102− var f = jsaddle_values.get(d.contents[0]);
103− var a = d.contents[1].map(function(arg){return jsaddle_values.get(arg);});
104− switch(a.length) {
105− case 0 : r = new f(); break;
106− case 1 : r = new f(a[0]); break;
107− case 2 : r = new f(a[0],a[1]); break;
108− case 3 : r = new f(a[0],a[1],a[2]); break;
109− case 4 : r = new f(a[0],a[1],a[2],a[3]); break;
110− case 5 : r = new f(a[0],a[1],a[2],a[3],a[4]); break;
111− case 6 : r = new f(a[0],a[1],a[2],a[3],a[4],a[5]); break;
112− case 7 : r = new f(a[0],a[1],a[2],a[3],a[4],a[5],a[6]); break;
113− default:
114− var ret;
115− var temp = function() {
116− ret = f.apply(this, a);
117− };
118− temp.prototype = f.prototype;
119− var i = new temp();
120− if(ret instanceof Object)
121− r = ret;
122− else {
123− i.constructor = f;
124− r = i;
125− }
126− }
127− jsaddle_values.set(n, r);
128− ws.send(JSON.stringify({"tag": "CallAsConstructorResult", "contents": n}));
129− break;
130− case "NewArray":
131− var n = ++jsaddle_index;
132− jsaddle_values.set(n, d.contents.map(function(v){return jsaddle_values.get(v);}));
133− ws.send(JSON.stringify({"tag": "NewArrayResult", "contents": n}));
134− break;
135129 case "ValueToBool":
136130 ws.send(JSON.stringify({"tag": "ValueToBoolResult", "contents": jsaddle_values.get(d.contents) ? true : false}));
137131 break;
@@ -172,7 +166,7 @@ var connect = function() {
172166 ws.send(JSON.stringify({"tag": "PropertyNamesResult", "contents": result}));
173167 break;
174168 case "Sync":
175− ws.send(JSON.stringify({"tag": "SyncResult"}));
169+ ws.send(JSON.stringify({"tag": "SyncResult", "contents": []}));
176170 break;
177171 default:
178172 ws.send(JSON.stringify({"tag": "ProtocolError", "contents": e.data}));
src/Language/Javascript/JSaddle/Evaluate.hs+3−5
@@ -20,7 +20,7 @@ module Language.Javascript.JSaddle.Evaluate (
2020 eval
2121) where
2222
23−import Language.Javascript.JSaddle.Types (JSVal)
23+import Language.Javascript.JSaddle.Types (Result(..), JSVal)
2424#ifdef ghcjs_HOST_OS
2525import GHCJS.Types (nullRef)
2626import GHCJS.Marshal.Pure (pFromJSVal)
@@ -29,7 +29,7 @@ import Language.Javascript.JSaddle.Types (JSString)
2929import Language.Javascript.JSaddle.Native
3030 (wrapJSVal, withJSString)
3131import Language.Javascript.JSaddle.WebSockets
32− (Command(..), Result(..), sendCommand)
32+ (AsyncCommand(..), Result(..), sendLazyCommand)
3333#endif
3434import Language.Javascript.JSaddle.Classes
3535 (ToJSString(..))
@@ -58,7 +58,5 @@ foreign import javascript unsafe "$r = eval($1);"
5858#else
5959eval script = do
6060 let rscript = toJSString script
61− withJSString rscript $ \script' -> do
62− EvaluateScriptResult result <- sendCommand $ EvaluateScript script'
63− wrapJSVal result
61+ withJSString rscript $ sendLazyCommand . EvaluateScript
6462#endif
src/Language/Javascript/JSaddle/Object.hs+10−20
@@ -108,10 +108,9 @@ import Control.Monad (liftM)
108108import Data.Coerce (coerce)
109109#else
110110import Language.Javascript.JSaddle.Native
111− (wrapJSVal, wrapJSString, withJSVals,
112− withObject)
111+ (wrapJSString, withJSVals, withObject)
113112import Language.Javascript.JSaddle.WebSockets
114− (Command(..), Result(..), sendCommand)
113+ (Command(..), AsyncCommand(..), Result(..), sendCommand, sendLazyCommand)
115114#endif
116115import Language.Javascript.JSaddle.Value (valToObject)
117116import Language.Javascript.JSaddle.Classes
@@ -128,7 +127,7 @@ import Data.Text (Text)
128127-- $setup
129128-- >>> import Language.Javascript.JSaddle.Test (testJSaddle)
130129-- >>> import Language.Javascript.JSaddle.Evaluate (eval)
131−-- >>> import Language.Javascript.JSaddle.Value (val, valToText, JSNull(..))
130+-- >>> import Language.Javascript.JSaddle.Value (val, valToText, JSNull(..), deRefVal)
132131-- >>> import Language.Javascript.JSaddle.String (strToText)
133132-- >>> import Control.Lens.Operators ((^.))
134133-- >>> import qualified Data.Text as T (unpack)
@@ -269,7 +268,7 @@ jsgf name = global # name
269268--
270269-- > jsg0 name = jsgf name ()
271270--
272−-- >>> testJSaddle $ jsg0 "globalFunc"
271+-- >>> testJSaddle $ jsg0 "globalFunc" >>= valToText
273272-- TypeError:...undefine...
274273jsg0 :: (ToJSString name) => name -> JSM JSVal
275274jsg0 name = jsgf name ()
@@ -404,9 +403,7 @@ obj :: JSM Object
404403#ifdef ghcjs_HOST_OS
405404obj = liftIO Object.create
406405#else
407−obj = do
408− NewEmptyObjectResult result <- sendCommand NewEmptyObject
409− Object <$> wrapJSVal result
406+obj = Object <$> sendLazyCommand NewEmptyObject
410407#endif
411408
412409-- | Short hand @::JSCallAsFunction@ so a haskell function can be passed to
@@ -414,7 +411,7 @@ obj = do
414411--
415412-- >>> testJSaddle $ eval "(function(f) {f('Hello');})(function (a) {console.log(a)})"
416413-- undefined
417−-- >>> testJSaddle $ call (eval "(function(f) {f('Hello');})") global [fun $ \ _ _ args -> valToText (head args) >>= (liftIO . putStrLn . T.unpack) ]
414+-- >>> testJSaddle . deRefVal $ call (eval "(function(f) {f('Hello');})") global [fun $ \ _ _ args -> valToText (head args) >>= (liftIO . putStrLn . T.unpack) ]
418415-- Hello
419416-- undefined
420417fun :: JSCallAsFunction -> JSCallAsFunction
@@ -442,8 +439,7 @@ foreign import javascript unsafe "$r = function () { $1(this, arguments); }"
442439 makeFunctionWithCallback :: Callback (JSVal -> JSVal -> IO ()) -> IO Object
443440#else
444441function f = do
445− NewCallbackResult result <- sendCommand NewCallback
446− obj <- Object <$> wrapJSVal result
442+ obj <- Object <$> sendLazyCommand NewCallback
447443 asks addCallback >>= \add -> liftIO $ add obj f
448444 return $ Function () obj
449445#endif
@@ -488,9 +484,7 @@ array args = do
488484#else
489485array args = do
490486 rargs <- makeArgs args
491− withJSVals rargs $ \rargs' -> do
492− NewArrayResult result <- sendCommand $ NewArray rargs'
493− Object <$> wrapJSVal result
487+ withJSVals rargs $ \rargs' -> Object <$> sendLazyCommand (NewArray rargs')
494488#endif
495489
496490-- Make an array out of various lists
@@ -570,9 +564,7 @@ objCallAsFunction f this args = do
570564 rargs <- makeArgs args
571565 withObject f $ \rfunction ->
572566 withObject this $ \rthis ->
573− withJSVals rargs $ \rargs' -> do
574− CallAsFunctionResult result <- sendCommand $ CallAsFunction rfunction rthis rargs'
575− wrapJSVal result
567+ withJSVals rargs $ sendLazyCommand . CallAsFunction rfunction rthis
576568#endif
577569
578570-- | Call a JavaScript object as a constructor. Consider using 'new'.
@@ -620,9 +612,7 @@ foreign import javascript unsafe "\
620612objCallAsConstructor f args = do
621613 rargs <- makeArgs args
622614 withObject f $ \rfunction ->
623− withJSVals rargs $ \rargs' -> do
624− CallAsConstructorResult result <- sendCommand $ CallAsConstructor rfunction rargs'
625− wrapJSVal result
615+ withJSVals rargs $ sendLazyCommand . CallAsConstructor rfunction
626616#endif
627617
628618-- >>> testJSaddle $ strictEqual nullObject (eval "null")
src/Language/Javascript/JSaddle/Properties.hs+3−7
@@ -39,7 +39,7 @@ import Language.Javascript.JSaddle.Types (JSString)
3939import Language.Javascript.JSaddle.Native
4040 (wrapJSVal, withObject, withJSString, withToJSVal)
4141import Language.Javascript.JSaddle.WebSockets
42− (Command(..), AsyncCommand(..), Result(..), sendCommand, sendAsyncCommand)
42+ (Command(..), AsyncCommand(..), Result(..), sendLazyCommand, sendAsyncCommand)
4343#endif
4444import Control.Monad.Trans.Reader (ask)
4545import Language.Javascript.JSaddle.Value (JSVal)
@@ -59,9 +59,7 @@ foreign import javascript unsafe "try { $r=$2[$1] } catch(e) { $3[0] = e }"
5959objGetPropertyByName this name = do
6060 let name' = toJSString name
6161 withObject this $ \rthis ->
62− withJSString name' $ \rname -> do
63− GetPropertyByNameResult result <- sendCommand $ GetPropertyByName rthis rname
64− wrapJSVal result
62+ withJSString name' $ sendLazyCommand . GetPropertyByName rthis
6563#endif
6664
6765-- | Get a property value given the object and the index of the property.
@@ -74,9 +72,7 @@ foreign import javascript unsafe "try { $r=$2[$1] } catch(e) { $3[0] = e }"
7472 js_tryIndex :: Index -> Object -> MutableJSArray -> IO JSVal
7573#else
7674objGetPropertyAtIndex this index =
77− withObject this $ \rthis -> do
78− GetPropertyAtIndexResult result <- sendCommand $ GetPropertyAtIndex rthis index
79− wrapJSVal result
75+ withObject this $ \rthis -> sendLazyCommand $ GetPropertyAtIndex rthis index
8076#endif
8177
8278-- | Set a property value given the object and the name of the property.
src/Language/Javascript/JSaddle/Types.hs+19−25
@@ -45,20 +45,23 @@ import JavaScript.Array (MutableJSArray)
4545import Data.Word (Word(..))
4646import GHCJS.Nullable (Nullable(..))
4747#else
48+import Data.Int (Int64)
4849import Data.Text (Text)
4950import Data.Aeson
5051 (defaultOptions, genericToEncoding, ToJSON(..), FromJSON(..))
5152import GHC.Generics (Generic)
53+import Control.Concurrent.STM.TVar (TVar)
5254#endif
5355
5456#ifdef ghcjs_HOST_OS
5557type JSContextRef = ()
5658type Index = Int
5759#else
58−newtype JSValueReceived = JSValueReceived Int deriving(Show, ToJSON, FromJSON)
59−newtype JSValueForSend = JSValueForSend Int deriving(Show, ToJSON, FromJSON)
60−newtype JSVal = JSVal Int deriving(Show, ToJSON, FromJSON)
61−newtype MutableJSArray = MutableJSArray Int deriving(Show, ToJSON, FromJSON)
60+type JSRef = Int64
61+newtype JSValueReceived = JSValueReceived JSRef deriving(Show, ToJSON, FromJSON)
62+newtype JSValueForSend = JSValueForSend JSRef deriving(Show, ToJSON, FromJSON)
63+newtype JSVal = JSVal JSRef deriving(Show, ToJSON, FromJSON)
64+newtype MutableJSArray = MutableJSArray JSRef deriving(Show, ToJSON, FromJSON)
6265type Index = Int
6366newtype JSObjectForSend = JSObjectForSend JSValueForSend deriving(Show, ToJSON, FromJSON)
6467newtype Object = Object JSVal deriving(Show, ToJSON, FromJSON)
@@ -70,6 +73,16 @@ newtype Nullable a = Nullable a
7073data AsyncCommand = FreeRef JSValueForSend
7174 | SetPropertyByName JSObjectForSend JSStringForSend JSValueForSend
7275 | SetPropertyAtIndex JSObjectForSend Index JSValueForSend
76+ | StringToValue JSStringForSend JSValueForSend
77+ | NumberToValue Double JSValueForSend
78+ | GetPropertyByName JSObjectForSend JSStringForSend JSValueForSend
79+ | GetPropertyAtIndex JSObjectForSend Index JSValueForSend
80+ | CallAsFunction JSObjectForSend JSObjectForSend [JSValueForSend] JSValueForSend
81+ | CallAsConstructor JSObjectForSend [JSValueForSend] JSValueForSend
82+ | NewEmptyObject JSValueForSend
83+ | NewCallback JSValueForSend
84+ | NewArray [JSValueForSend] JSValueForSend
85+ | EvaluateScript JSStringForSend JSValueForSend
7386 deriving (Show, Generic)
7487
7588instance ToJSON AsyncCommand where
@@ -84,19 +97,9 @@ data Command = DeRefVal JSValueForSend
8497 | ValueToJSON JSValueForSend
8598 | IsNull JSValueForSend
8699 | IsUndefined JSValueForSend
87− | NumberToValue Double
88− | StringToValue JSStringForSend
89100 | StrictEqual JSValueForSend JSValueForSend
90101 | InstanceOf JSValueForSend JSObjectForSend
91− | GetPropertyByName JSObjectForSend JSStringForSend
92− | GetPropertyAtIndex JSObjectForSend Index
93− | CallAsFunction JSObjectForSend JSObjectForSend [JSValueForSend]
94− | CallAsConstructor JSObjectForSend [JSValueForSend]
95− | NewEmptyObject
96− | NewCallback
97− | NewArray [JSValueForSend]
98102 | PropertyNames JSObjectForSend
99− | EvaluateScript JSStringForSend
100103 | Sync
101104 deriving (Show, Generic)
102105
@@ -113,27 +116,17 @@ instance ToJSON Batch where
113116
114117instance FromJSON Batch
115118
116−data Result = DeRefValResult Int Text
119+data Result = DeRefValResult JSRef Text
117120 | ValueToBoolResult Bool
118121 | ValueToNumberResult Double
119122 | ValueToStringResult JSStringReceived
120123 | ValueToJSONResult JSStringReceived
121124 | IsNullResult Bool
122125 | IsUndefinedResult Bool
123− | NumberToValueResult JSValueReceived
124− | StringToValueResult JSValueReceived
125126 | StrictEqualResult Bool
126127 | InstanceOfResult Bool
127− | GetPropertyByNameResult JSValueReceived
128− | GetPropertyAtIndexResult JSValueReceived
129− | CallAsFunctionResult JSValueReceived
130− | CallAsConstructorResult JSValueReceived
131− | NewEmptyObjectResult JSValueReceived
132− | NewCallbackResult JSValueReceived
133128 | Callback JSValueReceived JSValueReceived [JSValueReceived]
134− | NewArrayResult JSValueReceived
135129 | PropertyNamesResult [JSStringReceived]
136− | EvaluateScriptResult JSValueReceived
137130 | ThrowJSValue JSValueReceived
138131 | ProtocolError Text
139132 | SyncResult
@@ -149,6 +142,7 @@ data JSContextRef = JSContextRef {
149142 , doSendAsyncCommand :: AsyncCommand -> IO ()
150143 , addCallback :: Object -> JSCallAsFunction -> IO ()
151144 , freeCallback :: Object -> IO ()
145+ , nextRef :: TVar JSRef
152146}
153147
154148#endif
src/Language/Javascript/JSaddle/Value.hs+5−7
@@ -87,7 +87,9 @@ import Language.Javascript.JSaddle.Exception (rethrow)
8787import Language.Javascript.JSaddle.Native
8888 (wrapJSString, withJSVal, withObject, withJSString,
8989 withToJSVal, wrapJSVal)
90−import Language.Javascript.JSaddle.WebSockets (Command(..), Result(..), sendCommand)
90+import Language.Javascript.JSaddle.WebSockets
91+ (Command(..), AsyncCommand(..), Result(..), sendCommand,
92+ sendLazyCommand)
9193#endif
9294import Language.Javascript.JSaddle.Monad (JSM)
9395import Data.Text (Text)
@@ -442,9 +444,7 @@ valMakeNumber :: Double -> JSM JSVal
442444#ifdef ghcjs_HOST_OS
443445valMakeNumber n = liftIO $ GHCJS.toJSVal n
444446#else
445−valMakeNumber n = do
446− NumberToValueResult result <- sendCommand (NumberToValue n)
447− wrapJSVal result
447+valMakeNumber = sendLazyCommand . NumberToValue
448448#endif
449449
450450-- | Makes a JavaScript number
@@ -490,9 +490,7 @@ valMakeString :: JSString -> JSM JSVal
490490valMakeString = return . pToJSVal
491491#else
492492valMakeString s =
493− withJSString s $ \s' -> do
494− StringToValueResult result <- sendCommand $ StringToValue s'
495− wrapJSVal result
493+ withJSString s $ sendLazyCommand . StringToValue
496494#endif
497495
498496-- | Makes a JavaScript string
src/Language/Javascript/JSaddle/WebSockets.hs+16−2
@@ -20,6 +20,7 @@ module Language.Javascript.JSaddle.WebSockets (
2020 , Command(..)
2121 , Result(..)
2222 , sendCommand
23+ , sendLazyCommand
2324 , sendAsyncCommand
2425) where
2526
@@ -32,7 +33,7 @@ import Control.Concurrent (forkIO)
3233import Control.Concurrent.STM.TChan
3334 (tryReadTChan, TChan, readTChan, writeTChan, newTChanIO)
3435import Control.Concurrent.STM.TVar
35− (readTVarIO, modifyTVar, newTVarIO)
36+ (writeTVar, readTVar, readTVarIO, modifyTVar, newTVarIO)
3637import Control.Concurrent.MVar
3738 (MVar, MVar, putMVar, takeMVar, newEmptyMVar)
3839
@@ -50,7 +51,7 @@ import Network.Wai.Handler.WebSockets (websocketsOr)
5051
5152import Language.Javascript.JSaddle.Types
5253 (Command(..), AsyncCommand(..), Result(..), JSContextRef(..), JSVal(..),
53− Object(..), JSValueReceived(..), JSM, Batch(..))
54+ Object(..), JSValueReceived(..), JSM, Batch(..), JSValueForSend(..))
5455import Language.Javascript.JSaddle.Exception (JSException(..))
5556import Language.Javascript.JSaddle.Monad (runJSaddle)
5657import Language.Javascript.JSaddle.Native (wrapJSVal)
@@ -67,6 +68,17 @@ sendCommand cmd = do
6768 s <- asks doSendCommand
6869 liftIO $ s cmd
6970
71+sendLazyCommand :: (JSValueForSend -> AsyncCommand) -> JSM JSVal
72+sendLazyCommand cmd = do
73+ nextRefTVar <- asks nextRef
74+ n <- liftIO . atomically $ do
75+ n <- subtract 1 <$> readTVar nextRefTVar
76+ writeTVar nextRefTVar n
77+ return n
78+ s <- asks doSendAsyncCommand
79+ liftIO $ s (cmd $ JSValueForSend n)
80+ wrapJSVal (JSValueReceived n)
81+
7082sendAsyncCommand :: AsyncCommand -> JSM ()
7183sendAsyncCommand cmd = do
7284 s <- asks doSendAsyncCommand
@@ -81,6 +93,7 @@ jsaddleOr opts entryPoint = websocketsOr opts wsApp
8193 recvChan <- newTChanIO
8294 commandChan <- newTChanIO
8395 callbacks <- newTVarIO M.empty
96+ nextRef <- newTVarIO 0
8497 let ctx = JSContextRef {
8598 doSendCommand = \cmd -> do
8699 result <- newEmptyMVar
@@ -91,6 +104,7 @@ jsaddleOr opts entryPoint = websocketsOr opts wsApp
91104 , doSendAsyncCommand = atomically . writeTChan commandChan . Left
92105 , addCallback = \(Object (JSVal val)) cb -> atomically $ modifyTVar callbacks (M.insert val cb)
93106 , freeCallback = \(Object (JSVal val)) -> atomically $ modifyTVar callbacks (M.delete val)
107+ , nextRef = nextRef
94108 }
95109 forkIO . forever $
96110 receiveDataMessage conn >>= \case