on develop help sign in
Add notReady and notReadyUntil
commit1e930c296f7a9b9195bf6ed9e4d57102a2bc4f67 authorRyan Trinkle <ryan.trinkle@gmail.com> dateSun 19 Mar 2017, 21:59 (10 years ago) parentb39c5c9
6 files changed+140−47
src/Reflex/Dom/Builder/Class.hs+14 src/Reflex/Dom/Builder/Immediate.hs+62−16 src/Reflex/Dom/Builder/Static.hs+7−1 src/Reflex/Dom/Internal.hs+6 src/Reflex/Dom/Widget/Basic.hs+9−3 test/prebuild.hs+42−27
src/Reflex/Dom/Builder/Class.hs+14
@@ -141,6 +141,20 @@ class (Monad m, Reflex t, DomSpace (DomBuilderSpace m), MonadAdjust t m) => DomB
141141 { _rawElementConfig_eventSpec = _rawElementConfig_eventSpec cfg
142142 }
143143 {-# INLINABLE wrapRawElement #-}
144+ notReadyUntil :: Event t a -> m ()
145+ default notReadyUntil :: ( MonadTrans f
146+ , m ~ f m'
147+ , DomBuilder t m'
148+ )
149+ => Event t a -> m ()
150+ notReadyUntil = lift . notReadyUntil
151+ notReady :: m ()
152+ default notReady :: ( MonadTrans f
153+ , m ~ f m'
154+ , DomBuilder t m'
155+ )
156+ => m ()
157+ notReady = lift notReady
144158
145159class DomBuilder t m => MountableDomBuilder t m where
146160 type DomFragment m :: *
src/Reflex/Dom/Builder/Immediate.hs+62−16
@@ -83,6 +83,8 @@ data ImmediateDomBuilderEnv t
8383 = ImmediateDomBuilderEnv { _immediateDomBuilderEnv_document :: Document
8484 , _immediateDomBuilderEnv_parent :: Node
8585 , _immediateDomBuilderEnv_events :: Chan [DSum (TriggerRef t) TriggerInvocation]
86+ , _immediateDomBuilderEnv_unreadyChildren :: IORef Word -- Number of children who still aren't fully rendered
87+ , _immediateDomBuilderEnv_commitAction :: IO () -- Action to take when all children are ready --TODO: we should probably get rid of this once we invoke it
8688 }
8789
8890newtype ImmediateDomBuilderT t m a = ImmediateDomBuilderT { unImmediateDomBuilderT :: ReaderT (ImmediateDomBuilderEnv t) m a } deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException, MonadAsyncException)
@@ -198,9 +200,9 @@ newtype EventFilterTriggerRef t er (en :: EventTag) = EventFilterTriggerRef (IOR
198200wrap :: forall m er t. SupportsImmediateDomBuilder t m => RawElement GhcjsDomSpace -> RawElementConfig er t (ImmediateDomBuilderT t m) -> ImmediateDomBuilderT t m (Element er GhcjsDomSpace t)
199201wrap e cfg = do
200202 events <- askEvents
201− mapM_ (performEvent_ . fmap (imapM_ $ \(AttributeName mAttrNamespace n) mv -> case mAttrNamespace of
203+ forM_ (_rawElementConfig_modifyAttributes cfg) $ \modifyAttrs -> performEvent $ ffor modifyAttrs $ imapM_ $ \(AttributeName mAttrNamespace n) mv -> case mAttrNamespace of
202204 Nothing -> maybe (removeAttribute e n) (setAttribute e n) mv
203− Just ns -> maybe (removeAttributeNS e (Just ns) n) (setAttributeNS e (Just ns) n) mv)) (_rawElementConfig_modifyAttributes cfg)
205+ Just ns -> maybe (removeAttributeNS e (Just ns) n) (setAttributeNS e (Just ns) n) mv
204206 eventTriggerRefs :: DMap EventName (EventFilterTriggerRef t er) <- liftIO $ fmap DMap.fromList $ forM (DMap.toList $ _ghcjsEventSpec_filters $ _rawElementConfig_eventSpec cfg) $ \(en :=> GhcjsEventFilter f) -> do
205207 triggerRef <- newIORef Nothing
206208 _ <- elementOnEventName en e $ do
@@ -230,23 +232,24 @@ wrap e cfg = do
230232 --TODO: I don't think this is quite right: if a new trigger is created between when this is enqueued and when it fires, this may not work quite right
231233 ref <- newIORef $ Just t
232234 writeChan events [TriggerRef ref :=> TriggerInvocation v (return ())]
233− return $ Element es e
235+ return $ Element
236+ { _element_events = es
237+ , _element_raw = e
238+ }
234239
235240{-# INLINABLE makeElement #-}
236241makeElement :: forall er t m a. SupportsImmediateDomBuilder t m => Text -> ElementConfig er t (ImmediateDomBuilderT t m) -> ImmediateDomBuilderT t m a -> ImmediateDomBuilderT t m ((Element er GhcjsDomSpace t, a), DOM.Element)
237242makeElement elementTag cfg child = do
238− doc <- askDocument
239− events <- askEvents
243+ env <- ImmediateDomBuilderT ask
244+ let doc = _immediateDomBuilderEnv_document env
240245 Just e <- ImmediateDomBuilderT $ fmap DOM.castToElement <$> case cfg ^. namespace of
241246 Nothing -> createElement doc (Just elementTag)
242247 Just ens -> createElementNS doc (Just ens) (Just elementTag)
243248 ImmediateDomBuilderT $ iforM_ (cfg ^. initialAttributes) $ \(AttributeName mAttrNamespace n) v -> case mAttrNamespace of
244249 Nothing -> setAttribute e n v
245250 Just ans -> setAttributeNS e (Just ans) n v
246− result <- lift $ runImmediateDomBuilderT child $ ImmediateDomBuilderEnv
251+ result <- lift $ runImmediateDomBuilderT child $ env
247252 { _immediateDomBuilderEnv_parent = toNode e
248− , _immediateDomBuilderEnv_document = doc
249− , _immediateDomBuilderEnv_events = events
250253 }
251254 append e
252255 wrapped <- wrap e $ extractRawElementConfig cfg
@@ -414,6 +417,21 @@ instance SupportsImmediateDomBuilder t m => DomBuilder t (ImmediateDomBuilderT t
414417 return (wrapped, result)
415418 placeRawElement = append
416419 wrapRawElement = wrap
420+ notReadyUntil e = do
421+ eOnce <- headE e
422+ env <- ImmediateDomBuilderT ask
423+ let unreadyChildren = _immediateDomBuilderEnv_unreadyChildren env
424+ liftIO $ modifyIORef' unreadyChildren succ
425+ let ready = liftIO $ do
426+ old <- readIORef unreadyChildren
427+ let new = pred old
428+ writeIORef unreadyChildren $! new
429+ when (new == 0) $ _immediateDomBuilderEnv_commitAction env
430+ performEvent_ $ ready <$ eOnce
431+ notReady = do
432+ env <- ImmediateDomBuilderT ask
433+ let unreadyChildren = _immediateDomBuilderEnv_unreadyChildren env
434+ liftIO $ modifyIORef' unreadyChildren succ
417435
418436data FragmentState
419437 = FragmentState_Unmounted
@@ -474,19 +492,48 @@ instance (Reflex t, MonadAdjust t m, MonadIO m, MonadHold t m, PerformEvent t m,
474492 runWithReplace a0 a' = do
475493 initialEnv <- ImmediateDomBuilderT ask
476494 before <- textNodeInternal ("" :: Text)
495+ let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
496+ haveEverBeenReady <- liftIO $ newIORef False
497+ let myCommitAction = liftIO $ do --TODO: When we change out a child, we need to cancel it's ability to declare us ready
498+ readIORef haveEverBeenReady >>= \case
499+ True -> return ()
500+ False -> do
501+ writeIORef haveEverBeenReady True
502+ old <- readIORef parentUnreadyChildren
503+ let new = pred old
504+ writeIORef parentUnreadyChildren $! new
505+ when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
477506 -- We draw 'after' in this roundabout way to avoid using MonadFix
478507 Just after <- createTextNode (_immediateDomBuilderEnv_document initialEnv) ("" :: Text)
479508 let drawInitialChild = do
480− result <- a0
481− append after
509+ unreadyChildren <- liftIO $ newIORef 0
510+ let f = do
511+ result <- a0
512+ append after
513+ return result
514+ result <- runImmediateDomBuilderT f $ initialEnv
515+ { _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
516+ , _immediateDomBuilderEnv_commitAction = myCommitAction
517+ }
518+ liftIO $ readIORef unreadyChildren >>= \case
519+ 0 -> writeIORef haveEverBeenReady True
520+ _ -> modifyIORef' parentUnreadyChildren succ
482521 return result
483− (result0, result') <- lift $ runWithReplace (runImmediateDomBuilderT drawInitialChild initialEnv) $ ffor a' $ \child -> do
522+ (result0, result') <- lift $ runWithReplace drawInitialChild $ ffor a' $ \child -> do
484523 Just df <- createDocumentFragment $ _immediateDomBuilderEnv_document initialEnv
524+ unreadyChildren <- liftIO $ newIORef 0
525+ let commitAction = do
526+ deleteBetweenExclusive before after
527+ insertBefore df after
528+ myCommitAction
485529 result <- runImmediateDomBuilderT child $ initialEnv
486530 { _immediateDomBuilderEnv_parent = toNode df
531+ , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
532+ , _immediateDomBuilderEnv_commitAction = commitAction
487533 }
488− deleteBetweenExclusive before after
489− insertBefore df after
534+ liftIO $ readIORef unreadyChildren >>= \case
535+ 0 -> commitAction
536+ _ -> return () -- Whoever decrements it to 0 will handle it
490537 return result
491538 return (result0, result')
492539 traverseDMapWithKeyWithAdjust (f :: forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a)) (dm0 :: DMap k v) dm' = do
@@ -567,14 +614,13 @@ mkHasFocus e = do
567614{-# INLINABLE insertImmediateAbove #-}
568615insertImmediateAbove :: (IsNode placeholder, PerformEvent t m, MonadIO (Performable m)) => placeholder -> Event t (ImmediateDomBuilderT t (Performable m) a) -> ImmediateDomBuilderT t m (Event t a)
569616insertImmediateAbove n toInsertAbove = do
570− events <- askEvents
617+ env <- ImmediateDomBuilderT ask
571618 lift $ performEvent $ ffor toInsertAbove $ \new -> do
572619 Just doc <- getOwnerDocument n
573620 Just df <- createDocumentFragment doc
574− result <- runImmediateDomBuilderT new $ ImmediateDomBuilderEnv
621+ result <- runImmediateDomBuilderT new $ env
575622 { _immediateDomBuilderEnv_parent = toNode df
576623 , _immediateDomBuilderEnv_document = doc
577− , _immediateDomBuilderEnv_events = events
578624 }
579625 df `insertBefore` n
580626 return result
src/Reflex/Dom/Builder/Static.hs+7−1
@@ -204,7 +204,11 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
204204 let open = mconcat [constant ("<" <> byteString tagBS), attrs2, constant (byteString ">")]
205205 let close = constant $ byteString $ "</" <> tagBS <> ">"
206206 modify $ (:) $ mconcat [open, innerHtml, close]
207− return (Element es (), result)
207+ let e = Element
208+ { _element_events = es
209+ , _element_raw = ()
210+ }
211+ return (e, result)
208212 {-# INLINABLE inputElement #-}
209213 inputElement cfg = do
210214 (e, _result) <- element "input" (cfg ^. inputElementConfig_elementConfig) $ return ()
@@ -250,6 +254,8 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
250254 return (wrapped, result)
251255 placeRawElement () = return ()
252256 wrapRawElement () _ = return $ Element (EventSelector $ const never) ()
257+ notReadyUntil _ = return () --TODO: Do we need to support this somehow?
258+ notReady = return () --TODO: Do we need to support this somehow?
253259
254260--TODO: Make this more abstract --TODO: Put the WithWebView underneath PerformEventT - I think this would perform better
255261type StaticWidget x = PostBuildT Spider (StaticDomBuilderT Spider (PerformEventT Spider (SpiderHost Global)))
src/Reflex/Dom/Internal.hs+6
@@ -88,10 +88,13 @@ mainWidgetWithHead' widgets = runWebGUI $ \webView -> withWebViewSingletonMono w
8888 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
8989 let go :: forall c. Widget () c -> DOM.DocumentFragment -> PerformEventT Spider (SpiderHost Global) c
9090 go w df = do
91+ unreadyChildren <- liftIO $ newIORef 0
9192 let builderEnv = ImmediateDomBuilderEnv
9293 { _immediateDomBuilderEnv_document = toDocument doc
9394 , _immediateDomBuilderEnv_parent = toNode df
9495 , _immediateDomBuilderEnv_events = events
96+ , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
97+ , _immediateDomBuilderEnv_commitAction = return () --TODO
9598 }
9699 runWithWebView (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv) wv
97100 rec b <- go (headWidget a) headFragment
@@ -114,10 +117,13 @@ attachWidget' rootElement wv w = do
114117 Just df <- createDocumentFragment doc
115118 ((a, events), fc) <- attachWidget'' $ \events -> do
116119 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
120+ unreadyChildren <- liftIO $ newIORef 0
117121 let builderEnv = ImmediateDomBuilderEnv
118122 { _immediateDomBuilderEnv_document = toDocument doc
119123 , _immediateDomBuilderEnv_parent = toNode df
120124 , _immediateDomBuilderEnv_events = events
125+ , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
126+ , _immediateDomBuilderEnv_commitAction = return () --TODO
121127 }
122128 a <- runWithWebView (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv) wv
123129 return ((a, events), postBuildTriggerRef)
src/Reflex/Dom/Widget/Basic.hs+9−3
@@ -85,6 +85,7 @@ import Reflex.Dom.Builder.Class
8585import Reflex.Dom.Class
8686import Reflex.Dom.Internal.Foreign ()
8787import Reflex.Dynamic
88+import Reflex.PerformEvent.Class
8889import Reflex.PostBuild.Class
8990
9091import Control.Arrow
@@ -145,6 +146,7 @@ dynText t = do
145146 [ updated t
146147 , tag (current t) postBuild
147148 ]
149+ notReadyUntil postBuild
148150
149151display :: (PostBuild t m, DomBuilder t m, Show a) => Dynamic t a -> m ()
150152display = dynText . fmap (T.pack . show)
@@ -162,7 +164,8 @@ dyn :: (DomBuilder t m, PostBuild t m) => Dynamic t (m a) -> m (Event t a)
162164dyn child = do
163165 postBuild <- getPostBuild
164166 let newChild = leftmost [updated child, tagCheap (current child) postBuild]
165− snd <$> widgetHoldInternal (return ()) newChild
167+ result <- snd <$> widgetHoldInternal notReady newChild
168+ return result
166169
167170-- | Given an initial widget and an Event of widget-creating actions, create a widget that is recreated whenever the Event fires.
168171-- The returned Dynamic of widget results occurs when the Event does.
@@ -342,8 +345,11 @@ elDynAttrNS' mns elementTag attrs child = do
342345 modifyAttrs <- dynamicAttributesToModifyAttributes attrs
343346 let cfg = def
344347 & elementConfig_namespace .~ mns
345− & modifyAttributes .~ fmap mapKeysToAttributeName modifyAttrs
346− element elementTag cfg child
348+ & modifyAttributes .~ fmapCheap mapKeysToAttributeName modifyAttrs
349+ result <- element elementTag cfg child
350+ postBuild <- getPostBuild
351+ notReadyUntil postBuild
352+ return result
347353
348354dynamicAttributesToModifyAttributes :: (Ord k, PostBuild t m) => Dynamic t (Map k Text) -> m (Event t (Map k (Maybe Text)))
349355dynamicAttributesToModifyAttributes = dynamicAttributesToModifyAttributesWithInitial mempty
test/prebuild.hs+42−27
@@ -11,30 +11,12 @@ import Control.Monad.State.Strict
1111import Data.Monoid
1212import Data.Word
1313
14−type Paused t = DynamicWriterT t All
15−
16−runPaused :: (Reflex t, MonadFix m, MonadHold t m) => Paused t m a -> m (Event t (), a)
17−runPaused a = do
18− (result, runnable) <- runDynamicWriterT a
19− let done = void $ ffilter getAll $ updated $ uniqDyn runnable
20− return (done, result)
21−
22−--TODO: Perhaps we could use guaranteed-single-shot events here
23−pausedUntil :: (Reflex t, MonadFix m, MonadHold t m) => Event t a -> Paused t m ()
24−pausedUntil e = do
25− tellDyn =<< holdDyn (All False) (All True <$ e) --TODO: Disconnect after one firing
26−
27−dyn' :: (DomBuilder t m, PostBuild t m, m ~ Paused t m', MonadFix m', MonadHold t m') => Dynamic t (m a) -> m (Event t a)
28−dyn' child = do
29− postBuild <- getPostBuild
30− let newChild = leftmost [updated child, tagCheap (current child) postBuild]
31− newChildDone <- snd <$> runWithReplace (return ()) newChild
32− pausedUntil newChildDone
33− return newChildDone
34−
3514main :: IO ()
36−main = mainWidget $ do
37− let slow = dynTree
15+main = mainWidget w
16+
17+w :: forall t m. (MonadWidget t m, MountableDomBuilder t m) => m ()
18+w = do
19+ let slow :: forall m'. MonadWidget t m' => m' ()
3820 {-
3921 performEventChain = do
4022 postBuild <- delay 0 =<< getPostBuild
@@ -45,26 +27,59 @@ main = mainWidget $ do
4527 _ <- widgetHold (text "Starting") $ text . T.pack . show <$> n
4628 return ()
4729 -}
48− dynTree = elAttr "div" ("style" =: "position:relative;width:256px;height:256px") $ go maxDepth
30+ {-
31+ slow = elAttr "div" ("style" =: "position:relative;width:256px;height:256px") $ go maxDepth
4932 where maxDepth = 6 :: Int
5033 go 0 = blank
51− go n = void $ dyn' $ pure $ do
34+ go n = void $ dyn $ pure $ do
5235 let bgcolor = "rgba(0,0,0," <> T.pack (show (1 - (fromIntegral n / fromIntegral maxDepth) :: Double)) <> ")"
5336 s pos = pos <> ";position:absolute;border:1px solid white;background-color:" <> bgcolor
5437 elAttr "div" ("style" =: s "left:0;right:50%;top:0;bottom:50%") $ go $ pred n
5538 elAttr "div" ("style" =: s "left:50%;right:0;top:0;bottom:50%") $ go $ pred n
5639 elAttr "div" ("style" =: s "left:50%;right:0;top:50%;bottom:0") $ go $ pred n
5740 elAttr "div" ("style" =: s "left:0;right:50%;top:50%;bottom:0") $ go $ pred n
41+ -}
42+ {-
43+ slow = do
44+ let size = 64
45+ replicateM_ size $ elAttr "div" ("style" =: ("height:4px;width:" <> T.pack (show (size*4)) <> "px;line-height:0;background-color:gray")) $ do
46+ replicateM_ size $ elDynAttr "div" (pure $ "style" =: "display:inline-block;width:4px;height:4px;background-color:black") blank
47+ -}
48+ slow = el "table" $ do
49+ let size = 64
50+ replicateM_ size $ el "tr" $ do
51+ replicateM_ size $ el "td" $ do
52+ dynText $ pure "."
53+ {-
54+ slow = do
55+ postBuild <- getPostBuild
56+ replicateM_ ((2 :: Int) ^ (14 :: Int)) $ performEvent_ $ return () <$ postBuild
57+ done <- performEvent $ return () <$ postBuild
58+ _ <- widgetHold (text "Doing performEvent") $ text "Done" <$ done
59+ return ()
60+ -}
5861 el "h1" $ text "Bad"
5962 el "div" $ do
6063 draw <- button "Draw"
61− widgetHold blank $ ffor draw $ \_ -> void $ runPaused slow
64+ widgetHold blank $ ffor draw $ \_ -> do
65+ postBuild <- getPostBuild
66+ widgetHold (text "Loading...") $ slow <$ postBuild
67+ return ()
68+ el "h1" $ text "Bad - with EventWriterT"
69+ el "div" $ do
70+ draw <- button "Draw"
71+ widgetHold blank $ ffor draw $ \_ -> do
72+ (_, w :: Dynamic t ()) <- runDynamicWriterT slow
73+ return ()
6274 el "h1" $ text "Good"
6375 el "div" $ do
6476 draw <- button "Draw"
6577 widgetHold blank $ ffor draw $ \_ -> do
6678 (df0, _) <- buildDomFragment $ text "Loading..."
67− (df', (doneBuilding, _)) <- buildDomFragment $ runPaused slow
79+ (df', (doneBuilding, _)) <- buildDomFragment $ do
80+ slow
81+ postBuild <- getPostBuild
82+ return (postBuild, ())
6883 mountDomFragment df0 $ df' <$ doneBuilding
6984 postBuild <- getPostBuild
7085 performEvent_ $ liftIO (threadDelay 0) <$ postBuild -- This is necessary so that ghcjs will release the thread back to the DOM so that we see the loading indicator immediately; we could instead adjust the parameters to GHCJS so that the thread quantum is smaller.