on develop help sign in
Merge pull request #85 from reflex-frp/dan-perf

Reduce the number of cache events in basic Reflex code; expose cheap …

commitdaec2bbc6e098ce9c2056cfeea286de67135d85f authorRyan Trinkle <ryan.trinkle@gmail.com> dateThu 26 Jan 2017, 16:44 (10 years ago) parentb38fae626eea36
14 files changed+334−96
reflex.cabal+31 src/Reflex/Class.hs+67−9 src/Reflex/Dynamic.hs+1−1 src/Reflex/EventWriter.hs+17−7 src/Reflex/Patch.hs+24−14 src/Reflex/PerformEvent/Base.hs+4−4 src/Reflex/PostBuild/Base.hs+2−2 src/Reflex/Pure.hs+3 src/Reflex/Requester/Base.hs+2−2 src/Reflex/Requester/Class.hs+1−1 src/Reflex/Spider/Internal.hs+85−55 test/EventWriterT.hs+1−1 test/GC.hs+95 test/hlint.hs+1
reflex.cabal+31
@@ -28,6 +28,16 @@ flag use-template-haskell
2828 default: True
2929 manual: True
3030
31+flag expose-all-unfoldings
32+ description: Build the library with -fexpose-all-unfoldings which can help client code specialize better
33+ default: False
34+ manual: True
35+
36+flag debug-trace-events
37+ description: add instrumentation that outputs the stack trace of the definition of an event whenever it is subscribed to. Warning: It is very slow!
38+ default: False
39+ manual: True
40+
3141library
3242 hs-source-dirs: src
3343 build-depends:
@@ -84,6 +94,10 @@ library
8494 else
8595 exposed-modules: Reflex.Pure
8696
97+ if flag(debug-trace-events)
98+ cpp-options: -DDEBUG_TRACE_EVENTS
99+ build-depends: bytestring
100+
87101 if flag(use-reflex-optimizer)
88102 cpp-options: -DUSE_REFLEX_OPTIMIZER
89103 build-depends: ghc
@@ -103,6 +117,9 @@ library
103117 build-depends:
104118 dependent-sum == 0.4.*
105119
120+ if flag(expose-all-unfoldings)
121+ ghc-options: -fexpose-all-unfoldings
122+
106123test-suite semantics
107124 type: exitcode-stdio-1.0
108125 main-is: semantics.hs
@@ -152,6 +169,20 @@ test-suite RequesterT
152169 , reflex
153170 , ref-tf
154171
172+test-suite GC-Semantics
173+ type: exitcode-stdio-1.0
174+ main-is: GC.hs
175+ hs-source-dirs: test
176+ if flag(specialize-to-spidertimeline-global)
177+ buildable: False
178+ build-depends: base
179+ , dependent-sum
180+ , dependent-map
181+ , these
182+ , transformers
183+ , reflex
184+ , ref-tf
185+
155186benchmark spider-bench
156187 type: exitcode-stdio-1.0
157188 hs-source-dirs: bench
src/Reflex/Class.hs+67−9
@@ -97,6 +97,14 @@ module Reflex.Class
9797 , appendEvents
9898 , onceE
9999 , sequenceThese
100+ , fmapMaybeCheap
101+ , fmapCheap
102+ , fforCheap
103+ , fforMaybeCheap
104+ , pushAlwaysCheap
105+ , tagCheap
106+ , mergeWithCheap
107+ , mergeWithCheap'
100108#ifdef SPECIALIZE_TO_SPIDERTIMELINE_GLOBAL
101109 , Spider
102110 , SpiderEnv
@@ -109,6 +117,7 @@ module Reflex.Class
109117 , never
110118 , constant
111119 , push
120+ , pushCheap
112121 , pull
113122 , merge
114123 , fan
@@ -225,6 +234,8 @@ class ( MonadHold t (PushM t)
225234 -- 'Behavior's and hold 'Event's, and use the results to produce a occurring
226235 -- (Just) or non-occurring (Nothing) result
227236 push :: (a -> PushM t (Maybe b)) -> Event t a -> Event t b
237+ -- | Like 'push' but intended for functions that the implementation can consider cheap to compute for performance considerations. WARNING: The function passed to 'pushCheap' may be run multiple times without any caching.
238+ pushCheap :: (a -> PushM t (Maybe b)) -> Event t a -> Event t b
228239 -- | Create a 'Behavior' by reading from other 'Behavior's; the result will be
229240 -- recomputed whenever any of the read 'Behavior's changes
230241 pull :: PullM t a -> Behavior t a
@@ -283,6 +294,7 @@ instance t ~ SpiderTimeline Global => Reflex t where
283294never :: Reflex t => Event t a
284295constant :: Reflex t => a -> Behavior t a
285296push :: Reflex t => (a -> PushM t (Maybe b)) -> Event t a -> Event t b
297+pushCheap :: Reflex t => (a -> PushM t (Maybe b)) -> Event t a -> Event t b
286298pull :: Reflex t => PullM t a -> Behavior t a
287299merge :: (Reflex t, GCompare k) => DMap k (Event t) -> Event t (DMap k Identity)
288300fan :: (Reflex t, GCompare k) => Event t (DMap k Identity) -> EventSelector t k
@@ -305,6 +317,8 @@ never = SpiderEvent S.eventNever
305317constant = SpiderBehavior . S.behaviorConst
306318{-# INLINE push #-}
307319push f = SpiderEvent . S.push (coerce f) . unSpiderEvent
320+{-# INLINE pushCheap #-}
321+pushCheap f = SpiderEvent . S.pushCheap (coerce f) . unSpiderEvent
308322{-# INLINE pull #-}
309323pull = SpiderBehavior . S.pull . coerce
310324{-# INLINE merge #-}
@@ -318,7 +332,7 @@ coincidence = SpiderEvent . S.coincidence . (coerce :: S.Event x (Event (SpiderT
318332{-# INLINE current #-}
319333current = SpiderBehavior . S.dynamicCurrent . unSpiderDynamic
320334{-# INLINE updated #-}
321−updated = SpiderEvent . fmap runIdentity . dynamicUpdated . unSpiderDynamic
335+updated = coerceEvent . SpiderEvent . dynamicUpdated . unSpiderDynamic
322336{-# INLINE unsafeBuildDynamic #-}
323337unsafeBuildDynamic readV0 v' = SpiderDynamic $ dynamicDynIdentity $ unsafeDyn (coerce readV0) $ coerce $ unSpiderEvent v'
324338{-# INLINE unsafeBuildIncremental #-}
@@ -638,11 +652,15 @@ instance (Semigroup a, Reflex t) => Monoid (Event t a) where
638652-- with the given function.
639653{-# INLINE mergeWith #-}
640654mergeWith :: Reflex t => (a -> a -> a) -> [Event t a] -> Event t a
641−mergeWith f es = fmap (Prelude.foldl1 f . map (\(Const2 _ :=> Identity v) -> v) . DMap.toList)
642− . merge
643− . DMap.fromDistinctAscList
644− . map (\(k, v) -> Const2 k :=> v)
645− $ zip [0 :: Int ..] es
655+mergeWith = mergeWith' id
656+
657+{-# INLINE mergeWith' #-}
658+mergeWith' :: Reflex t => (a -> b) -> (b -> b -> b) -> [Event t a] -> Event t b
659+mergeWith' f g es = fmap (Prelude.foldl1 g . map (\(Const2 _ :=> Identity v) -> f v) . DMap.toList)
660+ . merge
661+ . DMap.fromDistinctAscList
662+ . map (\(k, v) -> Const2 k :=> v)
663+ $ zip [0 :: Int ..] es
646664
647665-- | Create a new 'Event' that occurs if at least one of the 'Event's in the
648666-- list occurs. If multiple occur at the same time the value is the value of the
@@ -656,7 +674,7 @@ leftmost = mergeWith const
656674-- time.
657675mergeList :: Reflex t => [Event t a] -> Event t (NonEmpty a)
658676mergeList [] = never
659−mergeList es = mergeWith (<>) $ map (fmap (:|[])) es
677+mergeList es = mergeWithCheap' (:|[]) (<>) es
660678
661679-- | Create a new 'Event' combining the map of 'Event's into an 'Event' that
662680-- occurs if at least one of them occurs and has a map of values of all 'Event's
@@ -896,6 +914,46 @@ instance (Reflex t, MonadAdjust t m) => MonadAdjust t (ReaderT r m) where
896914 DMap.map (\(ComposeMaybe mv) -> ComposeMaybe $ fmap (`runReaderT` r) mv) p
897915 lift $ sequenceDMapWithAdjust loweredDm0 loweredDm'
898916
917+------------------
918+-- Cheap Functions
919+------------------
920+
921+{-# INLINE pushAlwaysCheap #-}
922+pushAlwaysCheap :: Reflex t => (a -> PushM t b) -> Event t a -> Event t b
923+pushAlwaysCheap f = pushCheap (fmap Just . f)
924+
925+{-# INLINE fmapMaybeCheap #-}
926+fmapMaybeCheap :: Reflex t => (a -> Maybe b) -> Event t a -> Event t b
927+fmapMaybeCheap f = pushCheap $ return . f
928+
929+{-# INLINE fforMaybeCheap #-}
930+fforMaybeCheap :: Reflex t => Event t a -> (a -> Maybe b) -> Event t b
931+fforMaybeCheap = flip fmapMaybeCheap
932+
933+{-# INLINE fforCheap #-}
934+fforCheap :: Reflex t => Event t a -> (a -> b) -> Event t b
935+fforCheap = flip fmapCheap
936+
937+{-# INLINE fmapCheap #-}
938+fmapCheap :: Reflex t => (a -> b) -> Event t a -> Event t b
939+fmapCheap f = pushCheap $ return . Just . f
940+
941+{-# INLINE tagCheap #-}
942+tagCheap :: Reflex t => Behavior t b -> Event t a -> Event t b
943+tagCheap b = pushAlwaysCheap $ \_ -> sample b
944+
945+{-# INLINE mergeWithCheap #-}
946+mergeWithCheap :: Reflex t => (a -> a -> a) -> [Event t a] -> Event t a
947+mergeWithCheap = mergeWithCheap' id
948+
949+{-# INLINE mergeWithCheap' #-}
950+mergeWithCheap' :: Reflex t => (a -> b) -> (b -> b -> b) -> [Event t a] -> Event t b
951+mergeWithCheap' f g es = fmapCheap (Prelude.foldl1 g . map (\(Const2 _ :=> Identity v) -> f v) . DMap.toList)
952+ . merge
953+ . DMap.fromDistinctAscList
954+ . map (\(k, v) -> Const2 k :=> v)
955+ $ zip [0 :: Int ..] es
956+
899957--------------------------------------------------------------------------------
900958-- Deprecated functions
901959--------------------------------------------------------------------------------
@@ -918,6 +976,6 @@ onceE = headE
918976#endif
919977sequenceThese :: Monad m => These (m a) (m b) -> m (These a b)
920978sequenceThese t = case t of
921− This ma -> liftM This ma
979+ This ma -> fmap This ma
922980 These ma mb -> liftM2 These ma mb
923− That mb -> liftM That mb
981+ That mb -> fmap That mb
src/Reflex/Dynamic.hs+1−1
@@ -522,7 +522,7 @@ apDyn :: forall t m a b. (Reflex t, Monad m)
522522 => m (Dynamic t (a -> b))
523523 -> Dynamic t a
524524 -> m (Dynamic t b)
525−apDyn m a = liftM (<*> a) m
525+apDyn m a = fmap (<*> a) m
526526
527527--TODO: The pattern of using hold (sample b0) can be reused in various places as a safe way of building certain kinds of Dynamics; see if we can factor this out
528528-- | This function no longer needs to be monadic, so it has been replaced by
src/Reflex/EventWriter.hs+17−7
@@ -26,6 +26,7 @@ import Reflex.Class
2626import Reflex.Host.Class
2727import Reflex.PerformEvent.Class
2828import Reflex.PostBuild.Class
29+import Reflex.Requester.Class
2930import Reflex.TriggerEvent.Class
3031
3132import Control.Monad.Exception
@@ -53,7 +54,7 @@ class (Monad m, Monoid w) => EventWriter t w m | m -> t w where
5354
5455-- | A basic implementation of 'EventWriter'.
5556newtype EventWriterT t w m a = EventWriterT { unEventWriterT :: StateT (Seq (Event t w)) m a }
56− deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException)
57+ deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException, MonadAsyncException)
5758
5859-- | Run a 'EventWriterT' action.
5960runEventWriterT :: (Reflex t, Monad m, Semigroup w) => EventWriterT t w m a -> m (a, Event t w)
@@ -79,6 +80,12 @@ instance (Reflex t, MonadAdjust t m, MonadHold t m, Monoid w, Semigroup w) => Mo
7980 runWithReplace = runWithReplaceEventWriterTWith $ \dm0 dm' -> lift $ runWithReplace dm0 dm'
8081 sequenceDMapWithAdjust = sequenceDMapWithAdjustEventWriterTWith $ \dm0 dm' -> lift $ sequenceDMapWithAdjust dm0 dm'
8182
83+instance Requester t m => Requester t (EventWriterT t w m) where
84+ type Request (EventWriterT t w m) = Request m
85+ type Response (EventWriterT t w m) = Response m
86+ withRequesting f = EventWriterT $ withRequesting $ unEventWriterT . f
87+
88+
8289-- | Given a function like 'runWithReplace' for the underlying monad, implement
8390-- 'runWithReplace' for 'EventWriterT'. This is necessary when the underlying
8491-- monad doesn't have a 'MonadAdjust' instance or to override the default
@@ -91,12 +98,12 @@ runWithReplaceEventWriterTWith :: forall m t w a b. (Reflex t, MonadHold t m, Mo
9198runWithReplaceEventWriterTWith f a0 a' = do
9299 let g :: EventWriterT t w m c -> m (c, Seq (Event t w))
93100 g (EventWriterT r) = runStateT r mempty
94− (result0, result') <- f (g a0) $ g <$> a'
95− request <- holdDyn (fmap (mconcat . NonEmpty.toList) $ mergeList $ toList $ snd result0) $ fmap (mconcat . NonEmpty.toList) . mergeList . toList . snd <$> result'
101+ (result0, result') <- f (g a0) $ fmap g a'
102+ request <- holdDyn (fmapCheap (mconcat . NonEmpty.toList) $ mergeList $ toList $ snd result0) $ fmapCheap (fmapCheap (mconcat . NonEmpty.toList) . mergeList . toList . snd) result'
96103 -- We add these two separately to take advantage of the free merge being done later. The coincidence case must come first so that it has precedence if both fire simultaneously. (Really, we should probably block the 'switch' whenever 'updated' fires, but switchPromptlyDyn has the same issue.)
97104 EventWriterT $ modify $ flip (|>) $ coincidence $ updated request
98105 EventWriterT $ modify $ flip (|>) $ switch $ current request
99− return (fst result0, fst <$> result')
106+ return (fst result0, fmapCheap fst result')
100107
101108-- | Like 'runWithReplaceEventWriterTWith', but for 'sequenceDMapWithAdjust'.
102109sequenceDMapWithAdjustEventWriterTWith :: (GCompare k, Reflex t, MonadHold t m, Monoid w, Semigroup w)
@@ -113,16 +120,16 @@ sequenceDMapWithAdjustEventWriterTWith f (dm0 :: DMap k (EventWriterT t w m)) dm
113120 inputTransform = DMapTransform WrapArg (\(EventWriterT v) -> swap <$> runStateT v mempty)
114121 (children0, children') <- f (mapKeysAndValuesMonotonic inputTransform dm0) $ mapPatchKeysAndValuesMonotonic inputTransform <$> dm'
115122 let result0 = mapKeyValuePairsMonotonic (\(WrapArg k :=> Identity (_, v)) -> k :=> Identity v) children0
116− result' = ffor children' $ \(PatchDMap p) -> PatchDMap $
123+ result' = fforCheap children' $ \(PatchDMap p) -> PatchDMap $
117124 mapKeyValuePairsMonotonic (\(WrapArg k :=> ComposeMaybe mv) -> k :=> ComposeMaybe (fmap (Identity . snd . runIdentity) mv)) p
118125 requests0 :: DMap (Const2 (Some k) w) (Event t)
119126 requests0 = mapKeyValuePairsMonotonic (\(WrapArg k :=> Identity (r, _)) -> Const2 (Some.This k) :=> mconcat (toList r)) children0
120127 requests' :: Event t (PatchDMap (Const2 (Some k) w) (Event t))
121− requests' = ffor children' $ \(PatchDMap p) -> PatchDMap $
128+ requests' = fforCheap children' $ \(PatchDMap p) -> PatchDMap $
122129 mapKeyValuePairsMonotonic (\(WrapArg k :=> ComposeMaybe mv) -> Const2 (Some.This k) :=> ComposeMaybe (fmap (mconcat . toList . fst . runIdentity) mv)) p
123130 childRequestMap <- holdIncremental requests0 requests'
124131 -- We add these two separately to take advantage of the free merge being done later. The coincidence case must come first so that it has precedence if both fire simultaneously. (Really, we should probably block the 'switch' whenever 'updated' fires, but switchPromptlyDyn has the same issue.)
125− EventWriterT $ modify $ flip (|>) $ coincidence $ ffor requests' $ \(PatchDMap p) -> mconcat $ toList $ catMaybes $ ffor (DMap.toList p) $ \(Const2 _ :=> ComposeMaybe me) -> me
132+ EventWriterT $ modify $ flip (|>) $ coincidence $ fforCheap requests' $ \(PatchDMap p) -> mconcat $ toList $ catMaybes $ ffor (DMap.toList p) $ \(Const2 _ :=> ComposeMaybe me) -> me
126133 EventWriterT $ modify $ flip (|>) $ ffor (mergeIncremental childRequestMap) $ \m ->
127134 mconcat $ (\(Const2 _ :=> Identity reqs) -> reqs) <$> DMap.toList m
128135 return (result0, result')
@@ -151,6 +158,9 @@ instance MonadRef m => MonadRef (EventWriterT t w m) where
151158 readRef = lift . readRef
152159 writeRef r = lift . writeRef r
153160
161+instance MonadAtomicRef m => MonadAtomicRef (EventWriterT t w m) where
162+ atomicModifyRef r = lift . atomicModifyRef r
163+
154164instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (EventWriterT t w m) where
155165 newEventWithTrigger = lift . newEventWithTrigger
156166 newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
src/Reflex/Patch.hs+24−14
@@ -5,11 +5,15 @@
55{-# LANGUAGE LambdaCase #-}
66{-# LANGUAGE StandaloneDeriving #-}
77{-# LANGUAGE TypeFamilies #-}
8+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
89module Reflex.Patch
910 ( Patch (..)
1011 , PatchDMap (..)
1112 , ComposeMaybe (..)
1213 , PatchMap (..)
14+ , Group (..)
15+ , Additive
16+ , AdditivePatch (..)
1317 ) where
1418
1519import Control.Monad.Identity
@@ -42,21 +46,9 @@ instance Patch (Identity a) where
4246-- deleted.
4347newtype PatchDMap k v = PatchDMap (DMap k (ComposeMaybe v))
4448
45−instance GCompare k => Semigroup (PatchDMap k v) where
46− PatchDMap a <> PatchDMap b = PatchDMap $ a `mappend` b --TODO: Add a semigroup instance for DMap
47− -- PatchDMap is idempotent, so stimes n is id for every n
48−#if MIN_VERSION_semigroups(0,17,0)
49− stimes = stimesIdempotentMonoid
50−#else
51− times1p n x = case compare n 0 of
52− LT -> error "stimesIdempotentMonoid: negative multiplier"
53− EQ -> mempty
54− GT -> x
55−#endif
49+deriving instance GCompare k => Semigroup (PatchDMap k v)
5650
57−instance GCompare k => Monoid (PatchDMap k v) where
58− mempty = PatchDMap mempty
59− mappend = (<>)
51+deriving instance GCompare k => Monoid (PatchDMap k v)
6052
6153instance GCompare k => Patch (PatchDMap k v) where
6254 type PatchTarget (PatchDMap k v) = DMap k v
@@ -95,3 +87,21 @@ instance Ord k => Semigroup (PatchMap k v) where
9587instance Ord k => Monoid (PatchMap k v) where
9688 mempty = PatchMap mempty
9789 mappend = (<>)
90+
91+---- Patches based on commutative groups
92+
93+-- | A 'Group' is a 'Monoid' where every element has an inverse.
94+class (Semigroup q, Monoid q) => Group q where
95+ negateG :: q -> q
96+ (~~) :: q -> q -> q
97+ r ~~ s = r <> negateG s
98+
99+-- | An 'Additive' 'Semigroup' is one where the multiplication is commutative
100+class Semigroup q => Additive q where
101+
102+-- | The elements of an 'Additive' 'Semigroup' can be considered as patches of their own type.
103+newtype AdditivePatch p = AdditivePatch { unAdditivePatch :: p }
104+
105+instance Additive p => Patch (AdditivePatch p) where
106+ type PatchTarget (AdditivePatch p) = p
107+ apply (AdditivePatch p) q = Just $ p <> q
src/Reflex/PerformEvent/Base.hs+4−4
@@ -74,15 +74,15 @@ instance (ReflexHost t, PrimMonad (HostFrame t)) => MonadAdjust t (PerformEventT
7474 where f :: HostFrame t a -> Event t (HostFrame t b) -> RequesterT t (HostFrame t) Identity (HostFrame t) (a, Event t b)
7575 f a0 a' = do
7676 result0 <- lift a0
77− result' <- requesting a'
78− return (result0, fmap runIdentity result')
77+ result' <- requestingIdentity a'
78+ return (result0, result')
7979 sequenceDMapWithAdjust (outerDm0 :: DMap k (PerformEventT t m)) outerDm' = PerformEventT $ sequenceDMapWithAdjustRequesterTWith f (coerce outerDm0) (coerceEvent outerDm')
8080 where f :: DMap k' (HostFrame t) -> Event t (PatchDMap k' (HostFrame t)) -> RequesterT t (HostFrame t) Identity (HostFrame t) (DMap k' Identity, Event t (PatchDMap k' Identity))
8181 f dm0 dm' = do
8282 result0 <- lift $ DMap.traverseWithKey (\_ v -> Identity <$> v) dm0
83− result' <- requesting $ ffor dm' $ \(PatchDMap p) -> do
83+ result' <- requestingIdentity $ ffor dm' $ \(PatchDMap p) -> do
8484 PatchDMap <$> DMap.traverseWithKey (\_ (ComposeMaybe mv) -> ComposeMaybe <$> mapM (fmap Identity) mv) p
85− return (result0, fmap runIdentity result')
85+ return (result0, result')
8686
8787instance ReflexHost t => MonadReflexCreateTrigger t (PerformEventT t m) where
8888 {-# INLINABLE newEventWithTrigger #-}
src/Reflex/PostBuild/Base.hs+2−2
@@ -106,14 +106,14 @@ instance (Reflex t, MonadHold t m, MonadFix m, MonadAdjust t m, PerformEvent t m
106106 postBuild <- getPostBuild
107107 lift $ do
108108 rec result@(_, result') <- runWithReplace (runPostBuildT a0 postBuild) $ fmap (\v -> runPostBuildT v =<< headE voidResult') a'
109− let voidResult' = void result'
109+ let voidResult' = fmapCheap (\_ -> ()) result'
110110 return result
111111 sequenceDMapWithAdjust dm0 dm' = do
112112 postBuild <- getPostBuild
113113 let loweredDm0 = DMap.map (`runPostBuildT` postBuild) dm0
114114 lift $ do
115115 rec (result0, result') <- sequenceDMapWithAdjust loweredDm0 loweredDm'
116− let voidResult' = void result'
116+ let voidResult' = fmapCheap (\_ -> ()) result'
117117 let loweredDm' = ffor dm' $ \(PatchDMap p) -> PatchDMap $
118118 DMap.map (ComposeMaybe . fmap (\v -> runPostBuildT v =<< headE voidResult') . getComposeMaybe) p --TODO: Avoid doing this headE so many times; once per loweredDm' firing ought to be OK, but it's not totally trivial to do because result' might be firing at the same time, and we don't want *that* to be the postBuild occurrence
119119 return (result0, result')
src/Reflex/Pure.hs+3
@@ -66,6 +66,9 @@ instance (Enum t, HasTrie t, Ord t) => Reflex (Pure t) where
6666 push :: (a -> PushM (Pure t) (Maybe b)) -> Event (Pure t) a -> Event (Pure t) b
6767 push f e = Event $ memo $ \t -> unEvent e t >>= \o -> f o t
6868
69+ pushCheap :: (a -> PushM (Pure t) (Maybe b)) -> Event (Pure t) a -> Event (Pure t) b
70+ pushCheap = push
71+
6972 pull :: PullM (Pure t) a -> Behavior (Pure t) a
7073 pull = Behavior . memo
7174
src/Reflex/Requester/Base.hs+2−2
@@ -70,7 +70,7 @@ instance (Reflex t, PrimMonad m) => Requester t (RequesterT t request response m
7070 t <- lift newTag
7171 s <- RequesterT ask
7272 (req, result) <- a $ select s $ WrapArg t
73− RequesterT $ tellEvent $ DMap.singleton t <$> req
73+ RequesterT $ tellEvent $ fmapCheap (DMap.singleton t) req
7474 return result
7575
7676instance MonadTrans (RequesterT t request response) where
@@ -111,7 +111,7 @@ runWithReplaceRequesterTWith f a0 a' =
111111 -> EventWriterT t (DMap (Tag (PrimState m)) request) (ReaderT (EventSelector t (WrapArg response (Tag (PrimState m)))) m) (a', Event t b')
112112 f' x y = do
113113 r <- EventWriterT $ ask
114− unRequesterT (f (runReaderT x r) (fmap (`runReaderT` r) y))
114+ unRequesterT (f (runReaderT x r) (fmapCheap (`runReaderT` r) y))
115115 in RequesterT $ runWithReplaceEventWriterTWith f' (coerce a0) (coerceEvent a')
116116
117117sequenceDMapWithAdjustRequesterTWith :: (GCompare k, Reflex t, MonadHold t m)
src/Reflex/Requester/Class.hs+1−1
@@ -84,4 +84,4 @@ requesting_ req = withRequesting $ \_ -> return (req, ())
8484-- | Emit a request whenever the given 'Event' fires, and unwrap the responses
8585-- before returning them. @Response m@ must be 'Identity'.
8686requestingIdentity :: (Requester t m, Response m ~ Identity) => Event t (Request m a) -> m (Event t a)
87−requestingIdentity = fmap (fmap runIdentity) . requesting
87+requestingIdentity = fmap coerceEvent . requesting
src/Reflex/Spider/Internal.hs+85−55
@@ -26,7 +26,7 @@
2626#endif
2727-- | This module is the implementation of the 'Spider' 'Reflex' engine. It uses
2828-- a graph traversal algorithm to propagate 'Event's and 'Behavior's.
29−module Reflex.Spider.Internal where
29+module Reflex.Spider.Internal (module Reflex.Spider.Internal) where
3030
3131import Control.Applicative
3232import Control.Concurrent
@@ -89,6 +89,20 @@ import Reflex.Patch
8989-- Note: must come last to silence warnings due to AMP on GHC < 7.10
9090import Prelude hiding (any, concat, mapM, mapM_, sequence)
9191
92+#ifdef DEBUG_TRACE_EVENTS
93+import System.IO (stderr)
94+import qualified Data.ByteString.Char8 as BS8
95+#endif
96+
97+#ifdef DEBUG_TRACE_EVENTS
98+
99+withStackOneLine :: (BS8.ByteString -> a) -> a
100+withStackOneLine expr = unsafePerformIO $ do
101+ stack <- currentCallStack
102+ return (expr (BS8.pack (unwords (reverse stack))))
103+
104+#endif
105+
92106debugPropagate :: Bool
93107
94108debugInvalidateHeight :: Bool
@@ -190,6 +204,20 @@ unsafeNodeId a = unsafePerformIO $ do
190204 newNodeId
191205#endif
192206
207+--------------------------------------------------------------------------------
208+-- EventSubscription
209+--------------------------------------------------------------------------------
210+
211+--NB: Once you subscribe to an Event, you must always hold on the the WHOLE EventSubscription you get back
212+-- If you do not retain the subscription, you may be prematurely unsubscribed from the parent event.
213+data EventSubscription x = EventSubscription
214+ { _eventSubscription_unsubscribe :: !(IO ())
215+ , _eventSubscription_subscribed :: {-# UNPACK #-} !(EventSubscribed x)
216+ }
217+
218+unsubscribe :: EventSubscription x -> IO ()
219+unsubscribe (EventSubscription u _) = u
220+
193221--------------------------------------------------------------------------------
194222-- Event
195223--------------------------------------------------------------------------------
@@ -236,44 +264,53 @@ data CacheSubscribed x a
236264--subscriber joins
237265{-# NOINLINE cacheEvent #-}
238266cacheEvent :: HasSpiderTimeline x => Event x a -> Event x a
239−cacheEvent e = Event $ \sub -> {-# SCC "cacheEvent" #-} do
240− let !mSubscribedRef = unsafeNewIORef e Nothing
241− mSubscribed <- liftIO $ readIORef mSubscribedRef
242− case mSubscribed of
243− Just subscribed -> liftIO $ do
244− ticket <- WeakBag.insert sub (_cacheSubscribed_subscribers subscribed) (_cacheSubscribed_weakSelf subscribed) cleanupCacheSubscribed
245− occ <- readIORef $ _cacheSubscribed_occurrence subscribed
246− let es = EventSubscribed
247− { eventSubscribedHeightRef = eventSubscribedHeightRef $ _eventSubscription_subscribed $ _cacheSubscribed_parent subscribed
248− , eventSubscribedRetained = toAny subscribed
249− }
250− return (EventSubscription (WeakBag.remove ticket >> touch ticket) es, occ)
251− Nothing -> do
252− weakSelf <- liftIO $ newIORef $ error "cacheEvent: weakSelf not yet intialized"
253− (subscribers, ticket) <- liftIO $ WeakBag.singleton sub weakSelf cleanupCacheSubscribed
254− occRef <- liftIO $ newIORef undefined
255− (parentSub, occ) <- subscribeAndRead e $ Subscriber
256− { subscriberPropagate = \a -> do
257− liftIO $ writeIORef occRef $ Just a
258− scheduleClear occRef
259− propagate a subscribers
260− , subscriberInvalidateHeight = \old -> do
261− WeakBag.traverse subscribers $ invalidateSubscriberHeight old
262− , subscriberRecalculateHeight = \new -> do
263− WeakBag.traverse subscribers $ recalculateSubscriberHeight new
264− }
265− liftIO $ writeIORef occRef occ
266− when (isJust occ) $ scheduleClear occRef
267− let !subscribed = CacheSubscribed
268− { _cacheSubscribed_subscribers = subscribers
269− , _cacheSubscribed_weakSelf = weakSelf
270− , _cacheSubscribed_parent = parentSub
271− , _cacheSubscribed_cached = mSubscribedRef
272− , _cacheSubscribed_occurrence = occRef
273− }
274− liftIO $ writeIORef mSubscribedRef $ Just subscribed
275− liftIO $ writeIORef weakSelf =<< evaluate =<< mkWeakPtrWithDebug subscribed "PushSubscribed"
276− return (EventSubscription (WeakBag.remove ticket >> touch ticket) (EventSubscribed (eventSubscribedHeightRef $ _eventSubscription_subscribed parentSub) (toAny subscribed)), occ)
267+cacheEvent e =
268+#ifdef DEBUG_TRACE_EVENTS
269+ withStackOneLine $ \callSite -> Event $
270+#else
271+ Event $
272+#endif
273+ let !mSubscribedRef = unsafeNewIORef e Nothing
274+ in \sub -> {-# SCC "cacheEvent" #-} do
275+#ifdef DEBUG_TRACE_EVENTS
276+ unless (BS8.null callSite) $ liftIO $ BS8.hPutStrLn stderr callSite
277+#endif
278+ mSubscribed <- liftIO $ readIORef mSubscribedRef
279+ case mSubscribed of
280+ Just subscribed -> liftIO $ do
281+ ticket <- WeakBag.insert sub (_cacheSubscribed_subscribers subscribed) (_cacheSubscribed_weakSelf subscribed) cleanupCacheSubscribed
282+ occ <- readIORef $ _cacheSubscribed_occurrence subscribed
283+ let es = EventSubscribed
284+ { eventSubscribedHeightRef = eventSubscribedHeightRef $ _eventSubscription_subscribed $ _cacheSubscribed_parent subscribed
285+ , eventSubscribedRetained = toAny subscribed
286+ }
287+ return (EventSubscription (WeakBag.remove ticket >> touch ticket) es, occ)
288+ Nothing -> do
289+ weakSelf <- liftIO $ newIORef $ error "cacheEvent: weakSelf not yet intialized"
290+ (subscribers, ticket) <- liftIO $ WeakBag.singleton sub weakSelf cleanupCacheSubscribed
291+ occRef <- liftIO $ newIORef undefined
292+ (parentSub, occ) <- subscribeAndRead e $ Subscriber
293+ { subscriberPropagate = \a -> do
294+ liftIO $ writeIORef occRef $ Just a
295+ scheduleClear occRef
296+ propagate a subscribers
297+ , subscriberInvalidateHeight = \old -> do
298+ WeakBag.traverse subscribers $ invalidateSubscriberHeight old
299+ , subscriberRecalculateHeight = \new -> do
300+ WeakBag.traverse subscribers $ recalculateSubscriberHeight new
301+ }
302+ liftIO $ writeIORef occRef occ
303+ when (isJust occ) $ scheduleClear occRef
304+ let !subscribed = CacheSubscribed
305+ { _cacheSubscribed_subscribers = subscribers
306+ , _cacheSubscribed_weakSelf = weakSelf
307+ , _cacheSubscribed_parent = parentSub
308+ , _cacheSubscribed_cached = mSubscribedRef
309+ , _cacheSubscribed_occurrence = occRef
310+ }
311+ liftIO $ writeIORef mSubscribedRef $ Just subscribed
312+ liftIO $ writeIORef weakSelf =<< evaluate =<< mkWeakPtrWithDebug subscribed "PushSubscribed"
313+ return (EventSubscription (WeakBag.remove ticket >> touch ticket) (EventSubscribed (eventSubscribedHeightRef $ _eventSubscription_subscribed parentSub) (toAny subscribed)), occ)
277314
278315cleanupCacheSubscribed :: CacheSubscribed x a -> IO ()
279316cleanupCacheSubscribed self = do
@@ -558,18 +595,6 @@ walkInvalidHeightParents s0 = do
558595subscribeHoldEvent :: CanSubscribe x m => Hold x p -> Subscriber x p -> m (EventSubscription x, Maybe p)
559596subscribeHoldEvent = subscribeAndRead . holdEvent
560597
561−--------------------------------------------------------------------------------
562−-- EventSubscription
563−--------------------------------------------------------------------------------
564−
565−data EventSubscription x = EventSubscription
566− { _eventSubscription_unsubscribe :: !(IO ())
567− , _eventSubscription_subscribed :: {-# UNPACK #-} !(EventSubscribed x)
568− }
569−
570−unsubscribe :: EventSubscription x -> IO ()
571−unsubscribe (EventSubscription u _) = u
572−
573598--------------------------------------------------------------------------------
574599-- Behavior
575600--------------------------------------------------------------------------------
@@ -1344,7 +1369,7 @@ getRootSubscribed k r sub = do
13441369{-# ANN cleanupRootSubscribed "HLint: ignore Redundant bracket" #-}
13451370#endif
13461371cleanupRootSubscribed :: RootSubscribed x a -> IO ()
1347−cleanupRootSubscribed self@(RootSubscribed { rootSubscribedKey = k, rootSubscribedCachedSubscribed = cached }) = do
1372+cleanupRootSubscribed self@RootSubscribed { rootSubscribedKey = k, rootSubscribedCachedSubscribed = cached } = do
13481373 rootSubscribedUninit self
13491374 modifyIORef' cached $ DMap.delete k
13501375
@@ -1641,9 +1666,12 @@ mergeCheap d = Event $ \sub -> do
16411666 , subscriberInvalidateHeight = \_ -> return ()
16421667 , subscriberRecalculateHeight = \_ -> return ()
16431668 }
1644− (EventSubscription _ changeSubd, change) <- subscribeAndRead (dynamicUpdated d) s
1669+ (changeSubscription, change) <- subscribeAndRead (dynamicUpdated d) s
16451670 forM_ change $ \c -> defer $ SomeMergeUpdate (updateMe c) invalidateMyHeight revalidateMyHeight
1646− liftIO $ writeIORef changeSubdRef (s, changeSubd)
1671+ -- We explicitly hold on to the unsubscribe function from subscribing to the update event.
1672+ -- If we don't do this, there are certain cases where mergeCheap will fail to properly retain
1673+ -- its subscription.
1674+ liftIO $ writeIORef changeSubdRef (s, changeSubscription)
16471675 let unsubscribeAll = do
16481676 parents <- readIORef parentsRef
16491677 forM_ (DMap.toList parents) $ \(_ :=> MergeSubscribedParent s) -> unsubscribe s
@@ -1922,6 +1950,8 @@ instance HasSpiderTimeline x => R.Reflex (SpiderTimeline x) where
19221950 constant = SpiderBehavior . behaviorConst
19231951 {-# INLINE push #-}
19241952 push f = SpiderEvent . push (coerce f) . unSpiderEvent
1953+ {-# INLINE pushCheap #-}
1954+ pushCheap f = SpiderEvent . pushCheap (coerce f) . unSpiderEvent
19251955 {-# INLINABLE pull #-}
19261956 pull = SpiderBehavior . pull . coerce
19271957 {-# INLINABLE merge #-}
@@ -1935,7 +1965,7 @@ instance HasSpiderTimeline x => R.Reflex (SpiderTimeline x) where
19351965 {-# INLINABLE current #-}
19361966 current = SpiderBehavior . dynamicCurrent . unSpiderDynamic
19371967 {-# INLINABLE updated #-}
1938− updated = SpiderEvent . fmap runIdentity . dynamicUpdated . unSpiderDynamic
1968+ updated = coerce $ SpiderEvent . dynamicUpdated . unSpiderDynamic
19391969 {-# INLINABLE unsafeBuildDynamic #-}
19401970 unsafeBuildDynamic readV0 v' = SpiderDynamic $ dynamicDynIdentity $ unsafeDyn (coerce readV0) $ coerce $ unSpiderEvent v'
19411971 {-# INLINABLE unsafeBuildIncremental #-}
test/EventWriterT.hs+1−1
@@ -14,7 +14,7 @@ import Reflex.EventWriter
1414import Reflex.Host.Class
1515import Reflex.PerformEvent.Base
1616
17−import System.Exit (exitFailure, exitSuccess)
17+import System.Exit (exitFailure)
1818
1919main :: IO ()
2020main = do
test/GC.hsnew file+95
@@ -0,0 +1,95 @@
1+{-# LANGUAGE OverloadedStrings, FlexibleContexts, GADTs, TypeFamilies, RankNTypes, ScopedTypeVariables, LambdaCase #-}
2+module Main where
3+
4+import Control.Monad
5+import Control.Monad.IO.Class
6+import Control.Monad.Ref
7+import Data.Align
8+import Data.Dependent.Map (DMap)
9+import qualified Data.Dependent.Map as DMap
10+import Data.Dependent.Sum
11+import Data.Functor.Identity
12+import Data.GADT.Compare
13+import Data.IORef
14+import Data.Semigroup
15+import Data.These
16+
17+import Data.Functor.Misc
18+import Reflex.Patch
19+
20+import qualified Reflex.Spider.Internal as S
21+import qualified Reflex.Host.Class as Host
22+
23+import System.Mem
24+import System.Exit
25+
26+main :: IO ()
27+main = do
28+ ref <- newIORef Nothing
29+ hostPerf ref
30+ readIORef ref >>= maybe exitFailure (putStrLn . ("Result " <>) . show)
31+
32+hostPerf :: IORef (Maybe Int) -> IO ()
33+hostPerf ref = S.runSpiderHost $ do
34+ ---- body
35+ liftIO $ putStrLn "#creating triggers"
36+ (response', responseTrigger) <- Host.newEventWithTriggerRef
37+ (eadd', addTriggerRef) <- Host.newEventWithTriggerRef
38+ let response = S.unSpiderEvent response'
39+ eadd = S.unSpiderEvent eadd'
40+ liftIO $ putStrLn "#creating event graph"
41+ eventToPerform <- Host.runHostFrame $ do
42+ (reqMap :: S.Event S.Global (DMap (Const2 Int (DMap Tell (S.SpiderHostFrame S.Global))) Identity))
43+ <- S.SpiderHostFrame
44+ $ fmap ( S.merge
45+ . S.dynamicHold)
46+ $ S.hold DMap.empty
47+ -- Construct a new heap object for the subscriber, invalidating any weak references to the subscriber if they are not retained
48+ $ (\e -> S.Event $ \sub -> do
49+ (s, o) <- S.subscribeAndRead e $ sub
50+ { S.subscriberPropagate = S.subscriberPropagate sub
51+ }
52+ return (s, o))
53+ $ runIdentity <$> S.select
54+ (S.fan $ S.pushCheap (return . Just . mapKeyValuePairsMonotonic (\(t :=> e) -> WrapArg t :=> Identity e)) response)
55+ (WrapArg Request)
56+ return $ alignWith (mergeThese (<>))
57+ (flip S.pushCheap eadd $ \_ -> return $ Just $ DMap.singleton Request $ do
58+ liftIO $ putStrLn "#eadd fired"
59+ return $ PatchDMap $ DMap.singleton (Const2 (1 :: Int)) $ ComposeMaybe $ Just
60+ $ S.pushCheap (return . Just . DMap.singleton Action . (\_ -> liftIO (writeIORef ref (Just 1)))) $ eadd)
61+ (flip S.pushCheap reqMap $ \m -> return $ Just $ mconcat $ (\(Const2 _ :=> Identity reqs) -> reqs) <$> DMap.toList m)
62+ ---- epilogue
63+ eventToPerformHandle <- Host.subscribeEvent (S.SpiderEvent eventToPerform)
64+ liftIO $ putStrLn "#performing GC" >> performMajorGC
65+ liftIO $ putStrLn "#attempting to fire eadd"
66+ mAddTrigger <- readRef addTriggerRef
67+ forM_ mAddTrigger $ \t -> replicateM_ 2 $ do
68+ liftIO $ putStrLn "#firing eadd"
69+ mToPerform <- Host.fireEventsAndRead [t :=> Identity ()] $ sequence =<< Host.readEvent eventToPerformHandle
70+ case mToPerform of
71+ Nothing -> return ()
72+ Just toPerform -> do
73+ responses <- Host.runHostFrame $ DMap.traverseWithKey (\_ v -> Identity <$> v) toPerform
74+ mrt <- readRef responseTrigger
75+ let followupEventTriggers = case mrt of
76+ Just rt -> [rt :=> Identity responses]
77+ Nothing -> []
78+ Host.fireEventsAndRead followupEventTriggers $ return ()
79+ return ()
80+ return ()
81+
82+data Tell a where
83+ Action :: Tell ()
84+ Request :: Tell (PatchDMap (Const2 Int (DMap Tell (S.SpiderHostFrame S.Global))) (S.Event S.Global))
85+
86+instance GEq Tell where
87+ geq Action Action = Just Refl
88+ geq Request Request = Just Refl
89+ geq _ _ = Nothing
90+
91+instance GCompare Tell where
92+ gcompare Action Action = GEQ
93+ gcompare Request Request = GEQ
94+ gcompare Action Request = GLT
95+ gcompare Request Action = GGT
test/hlint.hs+1
@@ -12,5 +12,6 @@ main = do
1212 , "--ignore=Use const"
1313 , "--ignore=Use >=>"
1414 , "--ignore=Use ."
15+ , "--cpp-define=USE_TEMPLATE_HASKELL"
1516 ]
1617 if null ideas then exitSuccess else exitFailure