Skip to content

Commit 79b4fa1

Browse files
committed
TodoMVC
1 parent fa87752 commit 79b4fa1

21 files changed

+1177
-337
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ bin/**
44
shell.nix
55
doc
66
dist-newstyle
7-
node_modules
7+
node_modules
8+
build

9.6.5.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ let
33
nixpkgs = builtins.fetchTarball {
44
url = "https://github.com/NixOS/nixpkgs/archive/refs/tags/24.05.tar.gz";
55
};
6-
ghc-wasm-meta = "gitlab:ghc/ghc-wasm-meta?host=gitlab.haskell.org&ref=108e4693cd147777e8d93683e58c8a5e1da74c96";
6+
ghc-wasm-meta = "gitlab:ghc/ghc-wasm-meta?host=gitlab.haskell.org&ref=455a759195e71c572e73b56d868e544176d32897";
77
};
88

99
pkgs = import sources.nixpkgs {};

Clickable/DOM.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
{-# OPTIONS_GHC -Wall #-}
1212
module Clickable.DOM where
1313

14-
import Data.Text (Text)
15-
import GHC.Generics (Generic)
14+
import Clickable.Internal
1615
import Clickable.Types
17-
import Data.Kind
1816
import Data.Int
19-
import Clickable.Internal
17+
import Data.Kind
18+
import Data.Text (Text)
19+
import GHC.Generics (Generic)
2020
import Unsafe.Coerce
2121

2222

Clickable/HTML.hs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,15 @@ toggleClass className dynEnable = HTML \s e -> do
9595
pure ((), s')
9696
{-# INLINE toggleClass #-}
9797

98-
attachTo :: JSExp -> HTML a -> JSM a
99-
attachTo rootEl contents = JSM \e -> do
100-
e.ien_command $ PushStack rootEl
101-
(r, _) <- contents.unHTML Nothing e
98+
execHTML :: JSExp -> HTML a -> JSM a
99+
execHTML elm action = JSM \e -> do
100+
e.ien_command $ PushStack elm
101+
(r, _) <- action.unHTML Nothing e
102102
e.ien_command PopStack
103103
pure r
104104

105-
attach :: HTML a -> JSM a
106-
attach = attachTo $ Id "document" `Dot` "body"
105+
execHTMLBody :: HTML a -> JSM a
106+
execHTMLBody = execHTML $ Id "document" `Dot` "body"
107107

108108
saveStackHead :: HTML RefId
109109
saveStackHead = HTML \s e ->
@@ -181,8 +181,8 @@ simpleList listDyn h = do
181181
pure InternalElem {elem_scope = scope, elem_state, placeholder = place'}
182182
dropElem :: InternalElem a -> JSM ()
183183
dropElem ie = do
184-
destroyScope ie.elem_scope
185184
detachPlaceholder ie.placeholder
185+
destroyScope ie.elem_scope
186186
updateList :: IORef [InternalElem a] -> [a] -> JSM ()
187187
updateList ref new = do
188188
ies <- liftIO $ readIORef ref
@@ -200,10 +200,3 @@ clearPlaceholder rid = jsCmd $ ClearPlaceholder $ Ref rid
200200

201201
detachPlaceholder :: RefId -> JSM ()
202202
detachPlaceholder rid = jsCmd $ DetachPlaceholder $ Ref rid
203-
204-
execHTML :: JSExp -> HTML a -> JSM a
205-
execHTML elm action = JSM \e -> do
206-
e.ien_command $ PushStack elm
207-
(r, _) <- action.unHTML Nothing e
208-
e.ien_command PopStack
209-
pure r

Clickable/Internal.hs

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,21 @@ import Control.Monad.State.Strict
1313
import Data.Binary qualified as Binary
1414
import Data.Binary.Put (execPut)
1515
import Data.ByteString.Builder.Extra (runBuilder, Next (..), BufferWriter)
16+
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
1617
import Data.Functor.Const
1718
import Data.IORef
1819
import Data.List qualified as List
1920
import Data.Map qualified as Map
21+
import Data.Maybe
2022
import Data.Text (Text)
2123
import Data.Tuple (swap)
2224
import Foreign.C.String (CStringLen)
25+
import Foreign.Marshal (copyBytes)
2326
import Foreign.Marshal.Alloc (mallocBytes)
2427
import Foreign.Ptr
2528
import GHC.Exts
29+
import GHC.Stack
2630
import Unsafe.Coerce
27-
import Data.Maybe
2831

2932
newEvent :: JSM (Event a)
3033
newEvent = state \s ->
@@ -359,11 +362,30 @@ jsCmd cmd = JSM \e ->
359362
jsEval :: JSExp -> JSM JSVal
360363
jsEval cmd = JSM \e -> do
361364
e.ien_command cmd
362-
e.ien_flush
365+
eid <- atomicModifyIORef' e.ien_state nextId
366+
control e.ien_prompt_tag \c -> do
367+
let jsc :: IO JSVal -> JSM ()
368+
jsc a = JSM \_ -> c a
369+
sub = Subscription e.ien_scope (unsafeCoerce jsc)
370+
modifyIORef' e.ien_state \s -> s {
371+
ist_subscriptions = Map.insert eid [sub] s.ist_subscriptions
372+
}
373+
e.ien_command $ Resume eid Out
374+
e.ien_flush
375+
where
376+
nextId s = (s {ist_id_supply = s.ist_id_supply + 1}, EventId s.ist_id_supply)
363377
{-# INLINE jsEval #-}
364378

365379
jsFlush :: JSM ()
366380
jsFlush = JSM \e -> void $ e.ien_flush
381+
{-# INLINE jsFlush #-}
382+
383+
jsUnsafe :: (HasCallStack, FromJSVal a) => UnsafeJavaScript -> JSM a
384+
jsUnsafe ujs = do
385+
j <- jsEval (Eval ujs)
386+
case fromJSVal j of
387+
Just a -> pure a
388+
Nothing -> error "jsUnsafe: fromJSVal failed"
367389

368390
commandBuffer :: CStringLen -> (CStringLen -> IO ()) -> IO (JSExp -> IO (), IO ())
369391
commandBuffer (buf, bufSize) consume = do
@@ -380,31 +402,17 @@ commandBuffer (buf, bufSize) consume = do
380402
writeCommand :: BufferWriter -> Int -> IO Int
381403
writeCommand bufWrite off = do
382404
(written, next) <- bufWrite (buf `plusPtr` off) (bufSize - off)
383-
let off' = off + written
384-
case next of
385-
Done -> pure off'
386-
More minSize _moreWrite
387-
| off == 0 ->
388-
error $ "Buffer too small, encountered command that requires at \
389-
\least " <> show minSize <> " bytes"
390-
| otherwise -> do
391-
consume (castPtr buf, off)
392-
writeRemains bufWrite 0
393-
Chunk chunk moreWrite -> do
394-
off1 <- writeRemains (runBuilder $ execPut $ Binary.put chunk) off
395-
writeRemains moreWrite off1
396-
397-
writeRemains :: BufferWriter -> Int -> IO Int
398-
writeRemains bufWrite off = do
399-
(written, next) <- bufWrite (buf `plusPtr` off) (bufSize - off)
400-
let off' = off + written
401-
case next of
402-
Done -> pure off'
403-
More _minSize _moreWrite ->
404-
error $ "Buffer too small, inscrease the buffer size"
405-
Chunk chunk moreWrite -> do
406-
off1 <- writeRemains (runBuilder $ execPut $ Binary.put chunk) off
407-
writeRemains moreWrite off1
405+
writeNext next $ off + written
406+
407+
writeNext :: Next -> Int -> IO Int
408+
writeNext Done off = pure off
409+
writeNext (More minSize _) _off =
410+
error $ "Buffer too small, encountered command that requires at \
411+
\least " <> show minSize <> " bytes"
412+
writeNext (Chunk chunk more) off =
413+
unsafeUseAsCStringLen chunk \(zs, len) -> do
414+
copyBytes (buf `plusPtr` off) zs len
415+
writeCommand more (off + len)
408416

409417
flush :: IORef Int -> IO ()
410418
flush ref = do
@@ -414,24 +422,18 @@ commandBuffer (buf, bufSize) consume = do
414422
newInternalEnv :: Int -> (CStringLen -> IO ()) -> IO (InternalEnv, CStringLen)
415423
newInternalEnv bufSize consume = do
416424
buf <- mallocBytes bufSize
417-
ien_state <- newIORef emptyState
418-
(write, flush) <- commandBuffer (buf, bufSize) consume
419-
ien_prompt_tag <- newPromptTag
420-
ien_continuations <- newIORef Map.empty
421-
let bufResult = (castPtr buf, bufSize)
422-
pure (
423-
InternalEnv {
424-
ien_command = write,
425-
ien_flush = do
426-
tid <- atomicModifyIORef' ien_state \s ->
427-
(s {ist_id_supply = s.ist_id_supply + 1}, ContId s.ist_id_supply)
428-
write $ Resume tid
429-
flush
430-
control ien_prompt_tag \c ->
431-
modifyIORef' ien_continuations $ Map.insert tid c,
432-
ien_state,
433-
ien_scope = ScopeId 0,
434-
ien_prompt_tag,
435-
ien_continuations
436-
}, bufResult
437-
)
425+
let strLen = (castPtr buf, bufSize)
426+
ienv <- mkEnv buf
427+
pure (ienv, strLen)
428+
where
429+
mkEnv buf = do
430+
ien_prompt_tag <- newPromptTag
431+
ien_state <- newIORef emptyState
432+
(write, flush) <- commandBuffer (buf, bufSize) consume
433+
pure InternalEnv {
434+
ien_command = write,
435+
ien_flush = flush,
436+
ien_state,
437+
ien_scope = ScopeId 0,
438+
ien_prompt_tag
439+
}

Clickable/Types.hs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@ import GHC.Generics qualified as G
3232
import GHC.List qualified as List
3333
import GHC.Types
3434
import GHC.Generics
35+
import Control.Monad.Fix
3536

3637
newtype JSM a = JSM {unJSM :: InternalEnv -> IO a}
3738
deriving (
3839
Functor,
3940
Applicative,
4041
Monad,
4142
MonadIO,
42-
MonadReader InternalEnv
43+
MonadReader InternalEnv,
44+
MonadFix
4345
) via ReaderT InternalEnv IO
4446

4547
instance MonadState InternalState JSM where
@@ -55,11 +57,10 @@ class MonadJSM m where
5557

5658
data InternalEnv = InternalEnv {
5759
ien_command :: JSExp -> IO (),
58-
ien_flush :: IO JSVal,
60+
ien_flush :: IO (),
5961
ien_state :: IORef InternalState,
6062
ien_scope :: ScopeId,
61-
ien_prompt_tag :: PromptTag (),
62-
ien_continuations :: IORef (Map ContId (IO JSVal -> IO ()))
63+
ien_prompt_tag :: PromptTag ()
6364
}
6465

6566
data InternalState = InternalState {
@@ -85,7 +86,8 @@ newtype HTML a = HTML {unHTML :: Maybe RefId -> InternalEnv -> IO (a, Maybe RefI
8586
Functor,
8687
Applicative,
8788
Monad,
88-
MonadIO
89+
MonadIO,
90+
MonadFix
8991
) via StateT (Maybe RefId) JSM
9092

9193
instance MonadJSM HTML where
@@ -152,16 +154,17 @@ data JSExp where
152154

153155
Eval :: UnsafeJavaScript -> JSExp
154156
TriggerEvent :: EventId -> JSExp -> JSExp
155-
Resume :: ContId -> JSExp
157+
Resume :: EventId -> JSExp -> JSExp
158+
Out :: JSExp
156159

157160
deriving stock (Generic, Show)
158161
deriving anyclass Binary
159162

160163
data ClientMsg where
161164
StartMsg :: StartFlags -> ClientMsg
162165
EventMsg :: EventId -> JSExp -> ClientMsg
163-
ResumeMsg :: ContId -> JSExp -> ClientMsg
164-
deriving stock Generic
166+
ResumeMsg :: EventId -> JSExp -> ClientMsg
167+
deriving stock (Generic, Show)
165168
deriving anyclass Binary
166169

167170
-- | JavaScript value, result of evaluating an 'JSExp'. Should only
@@ -170,7 +173,7 @@ data ClientMsg where
170173
type JSVal = JSExp
171174

172175
newtype StartFlags = StartFlags {unStartFlags :: JSVal}
173-
deriving newtype Binary
176+
deriving newtype (Binary, Show)
174177

175178
newtype ScopeId = ScopeId {unScopeId :: Word32}
176179
deriving newtype (Binary, Eq, Ord, Show)
@@ -181,9 +184,6 @@ newtype RefId = RefId {unRefId :: Word32}
181184
newtype EventId = EventId {unEventId :: Word32}
182185
deriving newtype (Show, Ord, Eq, Binary)
183186

184-
newtype ContId = ContId {unContId :: Word32}
185-
deriving newtype (Show, Ord, Eq, Binary)
186-
187187
newtype UnsafeJavaScript = UnsafeJavaScript {unUnsafeJavaScript :: Text}
188188
deriving newtype (IsString, Show, Semigroup, Monoid, Binary)
189189

Clickable/Wasm.hs renamed to Clickable/WASM.hs

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{-# LANGUAGE GHC2024 #-}
22
{-# LANGUAGE StrictData #-}
3-
{-# LANGUAGE CPP #-}
43
{-# LANGUAGE BlockArguments #-}
54
{-# LANGUAGE RecursiveDo #-}
65
{-# LANGUAGE LambdaCase #-}
@@ -9,59 +8,62 @@
98
{-# LANGUAGE MagicHash #-}
109
{-# LANGUAGE UnboxedTuples #-}
1110
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
12-
module Clickable.Wasm where
11+
{-# OPTIONS_GHC -Wall #-}
12+
module Clickable.WASM where
1313

1414
import Clickable.Internal
1515
import Clickable.Types
16-
import Control.Monad
1716
import Data.Binary (Binary)
17+
import Data.Binary qualified as Binary
1818
import Data.ByteString (ByteString)
19-
import Data.IORef
19+
import Data.ByteString.Lazy qualified as BSL
20+
import Data.ByteString.Unsafe qualified as BSU
2021
import Data.Word
22+
import Foreign.C.String (CStringLen)
2123
import Foreign.Ptr
2224
import System.IO.Unsafe (unsafePerformIO)
23-
import qualified Data.Binary as Binary
24-
import qualified Data.ByteString.Lazy as BSL
25-
import qualified Data.ByteString.Unsafe as BSU
26-
import qualified Data.Map as Map
27-
import Data.Tuple (swap)
28-
import Data.Map (Map)
29-
import Foreign.C.String (CStringLen)
25+
import GHC.Exts
26+
import Data.Map qualified as Map
27+
import Data.Maybe (fromMaybe)
28+
import Data.IORef
29+
import Control.Monad
30+
import Unsafe.Coerce
31+
import System.IO
3032

3133
foreign import ccall safe
3234
"clickable_eval_buffer" clickable_eval_buffer :: Ptr Word8 -> Int -> IO ()
3335

3436
env :: InternalEnv
3537
{-# NOINLINE env #-}
3638

37-
continuations :: IORef (Map Word32 (IO JSVal -> IO ()))
38-
{-# NOINLINE continuations #-}
39-
4039
buf :: CStringLen
4140
{-# NOINLINE buf #-}
4241

43-
(env, continuations, buf) = unsafePerformIO $
42+
(env, buf) = unsafePerformIO $
4443
newInternalEnv (100 * 1024) \(ptr, len) ->
4544
clickable_eval_buffer (castPtr ptr) len
4645

47-
mkWasmApp :: JSM () -> Ptr Word8 -> IO (Ptr Word8)
48-
mkWasmApp app p | p == nullPtr = do
49-
runTransition env app
46+
mkWasmApp :: (StartFlags -> JSM ()) -> Ptr Word8 -> IO (Ptr Word8)
47+
mkWasmApp _app p | p == nullPtr = do
48+
hSetBuffering stdout LineBuffering
49+
hSetBuffering stderr LineBuffering
5050
return $ castPtr $ fst buf
5151
mkWasmApp app inmsg = do
5252
msg <- loadMessage inmsg $ snd buf
5353
case msg of
54-
Just (StartMsg _flags) ->
55-
runTransition env app
54+
Just (StartMsg flags) ->
55+
runJSM env $ app flags
5656
Just (EventMsg eventId pload) ->
57-
runTransition env $
58-
triggerEvent (unsafeFromEventId eventId) pload
57+
runJSM env $ triggerEvent (unsafeFromEventId eventId) pload
5958
Just (ResumeMsg contId pload) -> do
60-
awatingThread <- atomicModifyIORef' continuations $
61-
swap . Map.alterF (,Nothing) contId
62-
forM_ awatingThread \cont -> cont $ pure pload
59+
cont <- atomicModifyIORef' env.ien_state $ lookupCont $ coerce contId
60+
forM_ cont \c -> runJSM env $ c.sub_callback $ unsafeCoerce $ ((pure pload) :: IO JSVal)
6361
_ -> error "mkWasmApp: Failed to parse incomming command"
64-
return $ castPtr $ fst buf
62+
pure $ castPtr $ fst buf
63+
where
64+
lookupCont :: EventId -> InternalState -> (InternalState, [Subscription Any])
65+
lookupCont eventId s = (s {ist_subscriptions = subs}, fromMaybe [] cont) where
66+
(cont, subs) = Map.alterF (,Nothing) eventId $ s.ist_subscriptions
6567

6668
loadMessage :: Binary msg => Ptr a -> Int -> IO (Maybe msg)
6769
loadMessage p len

0 commit comments

Comments
 (0)