on develop help sign in
Merge branch 'cale-' of github.com:ryantrinkle/reflex into cale-

Conflicts: src/Reflex/Class.hs

commit8246ce18b2fcd37ce826c4195a329e030f917d04 authorCale Gibbard <cgibbard@gmail.com> dateThu 14 Jan 2016, 20:26 (11 years ago) parent08e613ccc523e5
7 files changed+540−18
bench/RunAll.hs+88 default.nix+2−2 src/Reflex/Class.hs+1−1 src/Reflex/Spider/Internal.hs+16−15 test/Reflex/Bench/Focused.hs+267 test/Reflex/Plan/Reflex.hs+121 test/Reflex/TestPlan.hs+45
bench/RunAll.hsnew file+88
@@ -0,0 +1,88 @@
1+{-# LANGUAGE ConstraintKinds, TypeSynonymInstances, BangPatterns, ScopedTypeVariables, TupleSections, GADTs, RankNTypes, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, GeneralizedNewtypeDeriving #-}
2+{-# OPTIONS_GHC -fno-warn-orphans #-}
3+
4+module Main where
5+
6+import Criterion.Main
7+import Criterion.Types
8+
9+import Reflex
10+import Reflex.Host.Class
11+
12+import Reflex.TestPlan
13+import Reflex.Plan.Reflex
14+
15+import Reflex.Spider.Internal (SpiderEventHandle)
16+import qualified Reflex.Bench.Focused as Focused
17+
18+import Control.Applicative
19+import Control.DeepSeq (NFData (..))
20+
21+import System.IO
22+import System.Mem
23+import Prelude
24+
25+type MonadReflexHost' t m = (MonadReflexHost t m, MonadIORef m, MonadIORef (HostFrame t))
26+
27+
28+setupFiring :: (MonadReflexHost t m, MonadIORef m) => Plan t (Event t a) -> m (Ignore (EventHandle t a), Schedule t)
29+setupFiring p = do
30+ (e, s) <- runPlan p
31+ h <- subscribeEvent e
32+ return (Ignore h, s)
33+
34+-- Hack to avoid the NFData constraint for EventHandle which is a synonym
35+newtype Ignore a = Ignore a
36+instance NFData (Ignore a) where
37+ rnf !_ = ()
38+
39+instance NFData (SpiderEventHandle a) where
40+ rnf !_ = ()
41+
42+instance NFData (Behavior t a) where
43+ rnf !_ = ()
44+
45+instance NFData (Firing t) where
46+ rnf !(Firing _ _) = ()
47+
48+-- Measure the running time
49+benchFiring :: (MonadReflexHost' t m, MonadSample t m) => (forall a. m a -> IO a) -> (String, TestCase) -> Benchmark
50+benchFiring runHost (name, TestE p) = env setup (\e -> bench name $ whnfIO $ run e) where
51+ run (Ignore h, s) = runHost (readSchedule s (readEvent' h)) >> performGC
52+ setup = runHost $ setupFiring p
53+
54+benchFiring runHost (name, TestB p) = env setup (\e -> bench name $ whnfIO $ run e) where
55+ run (b, s) = runHost (readSchedule s (sample b)) >> performGC
56+ setup = runHost $ do
57+ (b, s) <- runPlan p
58+ return (b, makeDense s)
59+
60+main :: IO ()
61+main = do
62+ hSetBuffering stdout LineBuffering
63+ defaultMainWith (defaultConfig { timeLimit = 10, csvFile = Just "dmap-new.csv" })
64+ [ benchImpl "spider" runSpiderHost
65+ ]
66+
67+benchImpl :: (MonadReflexHost' t m, MonadSample t m) => String -> (forall a. m a -> IO a) -> Benchmark
68+benchImpl name runHost = bgroup name
69+ [ sub 100 40
70+ , dynamics 100
71+ , dynamics 1000
72+ , firing 1000
73+ , firing 10000
74+ , merging 10
75+ , merging 50
76+ , merging 100
77+ , merging 200
78+ ]
79+ where
80+ sub n frames = runGroup ("subscribing " ++ show (n, frames)) $ Focused.subscribing n frames
81+ firing n = runGroup ("firing " ++ show n) $ Focused.firing n
82+ merging n = runGroup ("merging " ++ show n) $ Focused.merging n
83+ dynamics n = runGroup ("dynamics " ++ show n) $ Focused.dynamics n
84+
85+ runGroup name' benchmarks = bgroup name' (benchFiring runHost <$> benchmarks)
86+
87+
88+
default.nix+2−2
@@ -1,6 +1,6 @@
11{ mkDerivation, dependent-map, dependent-sum
22, mtl, ref-tf, semigroups, these, MemoTrie, exception-transformers
3−, haskell-src-exts, haskell-src-meta
3+, haskell-src-exts, haskell-src-meta, criterion, split
44}:
55mkDerivation {
66 pname = "reflex";
@@ -11,7 +11,7 @@ mkDerivation {
1111 haskell-src-exts haskell-src-meta
1212 ];
1313 testDepends = [
14− MemoTrie
14+ MemoTrie criterion split
1515 ];
1616 license = null;
1717}
src/Reflex/Class.hs+1−1
@@ -322,7 +322,7 @@ instance (Semigroup a, Reflex t) => Monoid (Event t a) where
322322mergeWith :: Reflex t => (a -> a -> a) -> [Event t a] -> Event t a
323323mergeWith f es = fmap (Prelude.foldl1 f . map (\(Const2 _ :=> Identity v) -> v) . DMap.toList)
324324 . merge
325− . DMap.fromAscList
325+ . DMap.fromDistinctAscList
326326 . map (\(k, v) -> (Const2 k) :=> v)
327327 $ zip [0 :: Int ..] es
328328
src/Reflex/Spider/Internal.hs+16−15
@@ -22,6 +22,7 @@ import qualified Data.IntMap as IntMap
2222import Control.Monad.Ref
2323import Control.Monad.Exception
2424import Data.Monoid ((<>))
25+import Data.Coerce
2526
2627import System.IO.Unsafe
2728import Unsafe.Coerce
@@ -599,7 +600,7 @@ traverseAndCleanWeakList_ deRef ws f = go ws
599600propagate :: a -> [WeakSubscriber a] -> EventM [WeakSubscriber a]
600601propagate a subscribers = do
601602 traverseAndCleanWeakList_ (liftIO . deRefWeakSubscriber) subscribers $ \s -> case s of
602− SubscriberPush compute subscribed -> do
603+ SubscriberPush compute subscribed -> {-# SCC "pg->SubscriberPush" #-} do
603604 when debugPropagate $ liftIO $ putStrLn $ "SubscriberPush" <> showNodeId subscribed
604605 occ <- compute a
605606 case occ of
@@ -608,17 +609,17 @@ propagate a subscribers = do
608609 liftIO $ writeIORef (pushSubscribedOccurrence subscribed) occ
609610 scheduleClear $ pushSubscribedOccurrence subscribed
610611 liftIO . writeIORef (pushSubscribedSubscribers subscribed) =<< propagate o =<< liftIO (readIORef (pushSubscribedSubscribers subscribed))
611− SubscriberMerge k subscribed -> do
612+ SubscriberMerge k subscribed -> {-# SCC "pg->SubscriberMerge" #-} do
612613 when debugPropagate $ liftIO $ putStrLn $ "SubscriberMerge" <> showNodeId subscribed
613614 oldM <- liftIO $ readIORef $ mergeSubscribedAccum subscribed
614− liftIO $ writeIORef (mergeSubscribedAccum subscribed) $ DMap.insertWith (error "Same key fired multiple times for") k (Identity a) oldM
615+ liftIO $ writeIORef (mergeSubscribedAccum subscribed) $ {-# SCC "pg->sm->insertWith" #-} DMap.insertWith (error "Same key fired multiple times for") k (Identity a) oldM
615616 when (DMap.null oldM) $ do -- Only schedule the firing once
616617 height <- liftIO $ readIORef $ mergeSubscribedHeight subscribed
617618 --TODO: assertions about height
618− currentHeight <- getCurrentHeight
619+ currentHeight <- {-# SCC "getCurrentHeight" #-} getCurrentHeight
619620 when (height <= currentHeight) $ error $ "Height (" ++ show height ++ ") is not greater than current height (" ++ show currentHeight ++ ")"
620− scheduleMerge height subscribed
621− SubscriberFan subscribed -> do
621+ {-# SCC "scheduleMerge" #-} scheduleMerge height subscribed
622+ SubscriberFan subscribed -> {-# SCC "pg->SubscriberFan" #-} do
622623 subs <- liftIO $ readIORef $ fanSubscribedSubscribers subscribed
623624 when debugPropagate $ liftIO $ putStrLn $ "SubscriberFan" <> showNodeId subscribed <> ": " ++ show (DMap.size subs) ++ " keys subscribed, " ++ show (DMap.size a) ++ " keys firing"
624625 --TODO: We need a better DMap intersection; here, we are assuming that the number of firing keys is small and the number of subscribers is large
@@ -636,18 +637,18 @@ propagate a subscribers = do
636637 then Nothing
637638 else Just $ FanSubscriberKey k :=> Identity subsubs') :: DSum (FanSubscriberKey k) Identity -> IO (Maybe (DSum (FanSubscriberKey k) Identity)))
638639 liftIO $ writeIORef (fanSubscribedSubscribers subscribed) $ DMap.fromDistinctAscList $ catMaybes subs'
639− SubscriberHold h -> do
640+ SubscriberHold h -> {-# SCC "pg->SubscriberHold" #-} do
640641 invalidators <- liftIO $ readIORef $ holdInvalidators h
641642 when debugPropagate $ liftIO $ putStrLn $ "SubscriberHold" <> showNodeId h <> ": " ++ show (length invalidators)
642643 toAssignRef <- askToAssignRef
643644 liftIO $ modifyIORef' toAssignRef (SomeAssignment h a :)
644− SubscriberSwitch subscribed -> do
645+ SubscriberSwitch subscribed -> {-# SCC "pg->SubscriberSwitch" #-} do
645646 when debugPropagate $ liftIO $ putStrLn $ "SubscriberSwitch" <> showNodeId subscribed
646647 liftIO $ writeIORef (switchSubscribedOccurrence subscribed) $ Just a
647648 scheduleClear $ switchSubscribedOccurrence subscribed
648649 subs <- liftIO $ readIORef $ switchSubscribedSubscribers subscribed
649650 liftIO . writeIORef (switchSubscribedSubscribers subscribed) =<< propagate a subs
650− SubscriberCoincidenceOuter subscribed -> do
651+ SubscriberCoincidenceOuter subscribed -> {-# SCC "pg->SubscriberCoincidenceOuter" #-} do
651652 when debugPropagate $ liftIO $ putStrLn $ "SubscriberCoincidenceOuter" <> showNodeId subscribed
652653 outerHeight <- liftIO $ readIORef $ coincidenceSubscribedHeight subscribed
653654 when debugPropagate $ liftIO $ putStrLn $ " outerHeight = " <> show outerHeight
@@ -666,7 +667,7 @@ propagate a subscribers = do
666667 liftIO $ writeIORef (coincidenceSubscribedOccurrence subscribed) occ
667668 scheduleClear $ coincidenceSubscribedOccurrence subscribed
668669 liftIO . writeIORef (coincidenceSubscribedSubscribers subscribed) =<< propagate o =<< liftIO (readIORef (coincidenceSubscribedSubscribers subscribed))
669− SubscriberCoincidenceInner subscribed -> do
670+ SubscriberCoincidenceInner subscribed -> {-# SCC "pg->SubscriberCoincidenceInner" #-} do
670671 when debugPropagate $ liftIO $ putStrLn $ "SubscriberCoincidenceInner" <> showNodeId subscribed
671672 liftIO $ writeIORef (coincidenceSubscribedOccurrence subscribed) $ Just a
672673 scheduleClear $ coincidenceSubscribedOccurrence subscribed
@@ -733,7 +734,7 @@ readBehaviorTracked b = case b of
733734
734735readEvent :: Event a -> ResultM (Maybe a)
735736readEvent e = case e of
736− EventRoot k r -> liftIO . liftM (fmap runIdentity . DMap.lookup k) . readIORef $ rootOccurrence r
737+ EventRoot k r -> liftIO . liftM (coerce . DMap.lookup k) . readIORef $ rootOccurrence r
737738 EventNever -> return Nothing
738739 EventPush p -> do
739740 subscribed <- getPushSubscribed p
@@ -749,7 +750,7 @@ readEvent e = case e of
749750 return result
750751 EventFan k f -> do
751752 parentOcc <- readEvent $ fanParent f
752− return . fmap runIdentity $ DMap.lookup k =<< parentOcc
753+ return . coerce $ DMap.lookup k =<< parentOcc
753754 EventSwitch s -> do
754755 subscribed <- getSwitchSubscribed s
755756 liftIO $ do
@@ -814,7 +815,7 @@ getEventSubscribedOcc es = case es of
814815 EventSubscribedPush subscribed -> readIORef $ pushSubscribedOccurrence subscribed
815816 EventSubscribedFan k subscribed -> do
816817 parentOcc <- getEventSubscribedOcc $ fanSubscribedParent subscribed
817− let occ = fmap runIdentity $ DMap.lookup k =<< parentOcc
818+ let occ = coerce $ DMap.lookup k =<< parentOcc
818819 return occ
819820 EventSubscribedMerge subscribed -> readIORef $ mergeSubscribedOccurrence subscribed
820821 EventSubscribedSwitch subscribed -> readIORef $ switchSubscribedOccurrence subscribed
@@ -847,7 +848,7 @@ getRootSubscribed k r = do
847848 Just subscribed -> return subscribed
848849 Nothing -> liftIO $ do
849850 subscribersRef <- newIORef []
850− subscribed <- newRootSubscribed (liftM (fmap runIdentity . DMap.lookup k) $ readIORef $ rootOccurrence r) subscribersRef
851+ subscribed <- newRootSubscribed (liftM (coerce . DMap.lookup k) $ readIORef $ rootOccurrence r) subscribersRef
851852 -- Strangely, init needs the same stuff as a RootSubscribed has, but it must not be the same as the one that everyone's subscribing to, or it'll leak memory
852853 uninit <- rootInit r k $ RootTrigger (subscribersRef, rootOccurrence r, k)
853854 addFinalizer subscribed $ do
@@ -900,7 +901,7 @@ getMergeSubscribed m = {-# SCC "getMergeSubscribed.entire" #-} do
900901 parentSubd <- {-# SCC "getMergeSubscribed.a.parentSubd" #-} subscribe e $ WeakSubscriberMerge k ws
901902 parentOcc <- {-# SCC "getMergeSubscribed.a.parentOcc" #-} liftIO $ getEventSubscribedOcc parentSubd
902903 height <- {-# SCC "getMergeSubscribed.a.height" #-} liftIO $ readIORef $ eventSubscribedHeightRef parentSubd
903− return $ {-# SCC "getMergeSubscribed.a.returnVal" #-} (unsafeCoerce s :: Any, fmap ((k :=>) . Identity) parentOcc, height, k :=> parentSubd)
904+ return $ {-# SCC "getMergeSubscribed.a.returnVal" #-} (unsafeCoerce s :: Any, fmap (\x -> k :=> Identity x) parentOcc, height, k :=> parentSubd)
904905 let dm = DMap.fromDistinctAscList $ catMaybes $ map (\(_, x, _, _) -> x) subscribers
905906 subscriberHeights = map (\(_, _, x, _) -> x) subscribers
906907 myHeight =
test/Reflex/Bench/Focused.hsnew file+267
@@ -0,0 +1,267 @@
1+{-# LANGUAGE RecursiveDo, ConstraintKinds, TypeSynonymInstances, BangPatterns, ScopedTypeVariables, TupleSections, GADTs, RankNTypes, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, GeneralizedNewtypeDeriving #-}
2+
3+module Reflex.Bench.Focused where
4+
5+import Reflex
6+import Reflex.TestPlan
7+
8+import Control.Monad.Fix
9+import Control.Monad
10+import Control.Applicative
11+import Data.Foldable
12+import Data.Traversable
13+
14+import Data.Maybe
15+import Data.Map (Map)
16+import qualified Data.Map as Map
17+
18+import Data.Tuple
19+import Data.List
20+import Data.List.Split
21+
22+import Prelude
23+
24+mergeTree :: Num a => (Monoid (f [a]), Functor f) => Int -> [f a] -> f a
25+mergeTree n es | length es <= n = sum' es
26+ | otherwise = mergeTree n subTrees
27+ where
28+ merges = chunksOf n es
29+ subTrees = map sum' merges
30+ sum' = fmap sum . mconcat . fmap (fmap (:[]) )
31+
32+mergeListDyn :: (Reflex t, MonadHold t m) => [Dynamic t a] -> m (Dynamic t [a])
33+mergeListDyn dyns = traverse (mapDyn pure) dyns >>= mconcatDyn
34+
35+mergeTreeDyn :: (Num a, Reflex t, MonadHold t m, MonadFix m) => Int -> [Dynamic t a] -> m (Dynamic t a)
36+mergeTreeDyn n dyns | length dyns <= n = sumDyns dyns
37+ | otherwise = traverse sumDyns merges >>= mergeTreeDyn n
38+ where
39+ merges = chunksOf n dyns
40+ sumDyns ds = mergeListDyn ds >>= mapDyn sum
41+
42+-- N events all firing for N frames
43+continuous :: TestPlan t m => Word -> Word -> m [Event t Word]
44+continuous n frames = for [1..n] $ \i -> plan $ (, i) <$> [1..frames]
45+
46+-- Fire N events once every P frames for total N frames (interspersed evenly)
47+occasional :: TestPlan t m => Word -> Word -> Word -> m [Event t Word]
48+occasional n period frames = traverse plan $ zipWith occs [1..] phases
49+ where
50+ phases = take (fromIntegral n) $ concat (repeat [1..period])
51+ occs i phase = (, i) <$> [1, phase+1..frames]
52+
53+-- Event which never fires (but isn't 'never')
54+never' :: TestPlan t m => m (Event t Word)
55+never' = plan []
56+
57+-- Event which fires once on first frame
58+event :: TestPlan t m => m (Event t Word)
59+event = plan [(1, 0)]
60+
61+-- Event which fires constantly over N frames
62+events :: TestPlan t m => Word -> m (Event t Word)
63+events n = plan $ (\i -> (i, i)) <$> [1..n]
64+
65+-- N events all originating from one event
66+fmapFan :: Reflex t => Word -> Event t Word -> [Event t Word]
67+fmapFan n e = (\i -> (+i) <$> e) <$> [1..n]
68+
69+-- Create an Event with a Map which is dense to size N
70+denseMap :: TestPlan t m => Word -> m (Event t (Map Word Word))
71+denseMap n = plan [(1, m)] where
72+ m = Map.fromList $ zip [1..n] [1..n]
73+
74+fmapChain :: Reflex t => Word -> Event t Word -> (Event t Word)
75+fmapChain 0 e = e
76+fmapChain n e = fmapChain (n - 1) (fmap (+1) e)
77+
78+mapDynChain :: (Reflex t, MonadHold t m) => Word -> Dynamic t Word -> m (Dynamic t Word)
79+mapDynChain 0 d = return d
80+mapDynChain n d = mapDynChain (n - 1) =<< mapDyn (+1) d
81+
82+joinDynChain :: (Reflex t, MonadHold t m) => Word -> Dynamic t Word -> m (Dynamic t Word)
83+joinDynChain 0 d = return d
84+joinDynChain n d = joinDynChain (n - 1) =<< joinDyn <$> mapDyn (const d) d
85+
86+combineDynChain :: (Reflex t, MonadHold t m) => Word -> Dynamic t Word -> m (Dynamic t Word)
87+combineDynChain 0 d = return d
88+combineDynChain n d = combineDynChain (n - 1) =<< combineDyn (+) d d
89+
90+pingPong :: (Reflex t, MonadHold t m, MonadFix m) => Event t a -> Event t a -> m (Event t a)
91+pingPong e1 e2 = do
92+ rec
93+ d <- foldDyn (const swap) (e1, e2) e
94+ let e = switch (fst <$> current d)
95+ return e
96+
97+switchFactors :: (Reflex t, MonadHold t m) => Word -> Event t Word -> m (Event t Word)
98+switchFactors 0 e = return e
99+switchFactors i e = do
100+ b <- hold ((+1) <$> e) (e <$ ffilter ((== 0) . (i `mod`)) e)
101+ switchFactors (i - 1) (switch b)
102+
103+switchChain :: (Reflex t, MonadHold t m, MonadFix m) => Word -> Event t Word -> m (Event t Word)
104+switchChain 0 e = return e
105+switchChain i e = switchChain (i - 1) =<< pingPong e ((+1) <$> e)
106+
107+switchChain2 :: (Reflex t, MonadHold t m, MonadFix m) => Word -> Event t Word -> m (Event t Word)
108+switchChain2 0 e = return e
109+switchChain2 i e = switchChain2 (i - 1) =<< pingPong e ((+1) <$> leftmost [e, e])
110+
111+switchPromptlyChain :: (Reflex t, MonadHold t m) => Word -> Event t Word -> m (Event t Word)
112+switchPromptlyChain 0 e = return e
113+switchPromptlyChain i e = do
114+ d <- holdDyn e (e <$ e)
115+ switchPromptlyChain (i - 1) (switchPromptlyDyn d)
116+
117+coinChain :: Reflex t => Word -> Event t Word -> Event t Word
118+coinChain 0 e = e
119+coinChain n e = coinChain (n - 1) $ coincidence (e <$ e)
120+
121+pullChain :: Reflex t => Word -> Behavior t Word -> Behavior t Word
122+pullChain 0 b = b
123+pullChain n b = pullChain (n - 1) $ (+1) <$> b
124+
125+-- | Data type for a collection (a map) which is updated by providing differences
126+data UpdatedMap t k a = UpdatedMap (Map k a) (Event t (Map k (Maybe a)))
127+
128+-- This will hopefully become a primitive (faster!)
129+switchMergeEvents :: (MonadFix m, MonadHold t m, Reflex t, Ord k) => UpdatedMap t k (Event t a) -> m (Event t (Map k a))
130+switchMergeEvents mapChanges = switch . fmap mergeMap <$> holdMap mapChanges
131+
132+switchMergeBehaviors :: forall a t m k. (MonadFix m, MonadHold t m, Reflex t, Ord k) => UpdatedMap t k (Behavior t a) -> m (Behavior t (Map k a))
133+switchMergeBehaviors mapChanges = pull <$> joinMap <$> holdMap mapChanges
134+ where joinMap m = traverse sample =<< sample m
135+
136+-- | Turn an UpdatedMap into a Dynamic by applying the differences to the initial value
137+holdMapDyn :: (Reflex t, MonadHold t m, MonadFix m, Ord k) => UpdatedMap t k a -> m (Dynamic t (Map k a))
138+holdMapDyn (UpdatedMap initial changes) = foldDyn (flip (Map.foldWithKey modify)) initial changes
139+ where
140+ modify k Nothing items = Map.delete k items
141+ modify k (Just item) items = Map.insert k item items
142+
143+-- | Hold an UpdatedMap as a behavior by applying differences to the initial value
144+holdMap :: (Reflex t, MonadHold t m, MonadFix m, Ord k) => UpdatedMap t k a -> m (Behavior t (Map k a))
145+holdMap = (current <$>) . holdMapDyn
146+
147+increasingMerge :: TestPlan t m => [a] -> m (UpdatedMap t Int a)
148+increasingMerge xs = do
149+ e <- planList (zipWith Map.singleton [1..] (Just <$> xs) ++ [reset])
150+ return (UpdatedMap Map.empty e)
151+ where
152+ reset = Map.fromList $ zip [1..] (const Nothing <$> xs)
153+
154+decreasingMerge :: (TestPlan t m) => [a] -> m (UpdatedMap t Int a)
155+decreasingMerge xs = do
156+ e <- planList (zipWith Map.singleton [1..] (const Nothing <$> xs) ++ [reset])
157+ return (UpdatedMap initial e)
158+ where
159+ initial = Map.fromList $ zip [1..] xs
160+ reset = Just <$> initial
161+
162+modifyMerge :: (TestPlan t m) => [a] -> m (UpdatedMap t Int a)
163+modifyMerge xs = do
164+ e <- planList $ zipWith Map.singleton [1..] (Just <$> xs)
165+ return (UpdatedMap initial e)
166+ where
167+ initial = Map.fromList $ zip [1..] xs
168+
169+countMany :: (Reflex t, MonadHold t m, MonadFix m) => [Event t a] -> m [Behavior t Int]
170+countMany = traverse (fmap current . count)
171+
172+countManyDyn :: (Reflex t, MonadHold t m, MonadFix m) => [Event t a] -> m [Dynamic t Int]
173+countManyDyn = traverse count
174+
175+-- Given a function which creates a sub-network (from a single event)
176+-- Create a plan which uses a switch to substitute it
177+switches :: TestPlan t m => Word -> (forall n. (MonadHold t n, MonadFix n) => Event t Word -> n (Event t a)) -> m (Event t a)
178+switches numFrames f = do
179+ es <- events numFrames
180+ switch <$> hold never (makeEvents es)
181+ where
182+ makeEvents es = pushAlways (\n -> f (fmap (+n) es)) es
183+
184+-- Two sets of benchmarks, one which we're interested in testing subscribe time (as well as time to fire frames)
185+-- the other, which we're only interested in time for running frames
186+subscribing :: Word -> Word -> [(String, TestCase)]
187+subscribing n frames =
188+ [ testE "fmapFan merge" $ switches frames (return . mergeList . fmapFan n)
189+ , testE "fmapFan/mergeTree" $ switches frames (return . mergeTree 8 . fmapFan n)
190+ , testE "fmapChain" $ switches frames (return . fmapChain n)
191+ , testE "switchChain" $ switches frames (switchChain n)
192+ , testE "switchPromptlyChain" $ switches frames (switchPromptlyChain n)
193+ , testE "switchFactors" $ switches frames (switchFactors n)
194+ , testE "coincidenceChain" $ switches frames (return . coinChain n)
195+ ]
196+
197+-- Set of benchmark to test specifically merging dynamic collections
198+-- a pattern which occurs frequently in reflex-dom collections
199+merging :: Word -> [(String, TestCase)]
200+merging n =
201+ [ testE "static events" $ mergeList <$> sparse
202+ , testE "increasing events" $ switchMergeEvents =<< increasingMerge =<< sparse
203+ , testE "decreasing events" $ switchMergeEvents =<< decreasingMerge =<< sparse
204+ , testE "modify events" $ switchMergeEvents =<< modifyMerge =<< sparse
205+
206+ , testB "static behaviors" $ pull . traverse sample <$> counters
207+ , testB "increasing behaviors" $ switchMergeBehaviors =<< increasingMerge =<< counters
208+ , testB "decreasing behaviors" $ switchMergeBehaviors =<< decreasingMerge =<< counters
209+ , testB "modify behaviors" $ switchMergeBehaviors =<< modifyMerge =<< counters
210+ ]
211+ where
212+ sparse :: TestPlan t m => m [Event t Word]
213+ sparse = fmap (fmap (+1)) <$> occasional n 8 n
214+
215+ counters :: TestPlan t m => m [Behavior t Int]
216+ counters = countMany =<< sparse
217+
218+dynamics :: Word -> [(String, TestCase)]
219+dynamics n =
220+ [ testE "mapDynChain" $ fmap updated $ mapDynChain n =<< d
221+ , testE "joinDynChain" $ fmap updated $ joinDynChain n =<< d
222+ , testE "combineDynChain" $ fmap updated $ combineDynChain n =<< d
223+ , testE "dense mergeTree" $ fmap updated $ mergeTreeDyn 8 =<< dense
224+ , testE "sparse mergeTree" $ fmap updated $ mergeTreeDyn 8 =<< sparse
225+ , testE "mergeDyn" $ fmap updated $ mapDyn sum =<< mergeListDyn =<< sparse
226+ ] where
227+ d :: TestPlan t m => m (Dynamic t Word)
228+ d = count =<< events 10
229+
230+ sparse, dense :: TestPlan t m => m [Dynamic t Int]
231+ sparse = countManyDyn =<< occasional n 8 16
232+ dense = countManyDyn =<< continuous n 2
233+
234+firing :: Word -> [(String, TestCase)]
235+firing n =
236+ [ testE "fmapChain" $ fmapChain n <$> e
237+ , testE "switchChain2" $ switchChain2 n =<< e
238+ , testE "switchPromptlyChain" $ switchPromptlyChain n =<< e
239+ , testE "switchFactors" $ switchFactors n =<< e
240+ , testE "coincidenceChain" $ coinChain n <$> e
241+ , testE "dense mergeTree" $ mergeTree 8 <$> dense
242+ , testE "sparse mergeTree" $ mergeTree 8 <$> sparse
243+ , testE "runFrame" $ events n
244+ , testB "sum counters" $ do
245+ counts <- counters
246+ return $ pull $ sum <$> traverse sample counts
247+
248+ , testB "pullChain" $ pullChain n . current <$> (count =<< events 4)
249+ , testB "pullChain2" $ do
250+ e' <- events 4
251+ b <- hold (constant 0) $ leftmost
252+ [ constant 0 <$ ffilter (==4) e'
253+ , pushAlways (const $ pullChain n <$> hold 0 e') e'
254+ ]
255+ return (join b)
256+ , testB "counters/pullTree" $ mergeTree 8 <$> counters
257+ ]
258+ where
259+ e :: TestPlan t m => m (Event t Word)
260+ e = events 10
261+
262+ dense, sparse :: TestPlan t m => m [Event t Word]
263+ dense = continuous n 2
264+ sparse = occasional n 8 16
265+
266+ counters :: TestPlan t m => m [Behavior t Int]
267+ counters = countMany =<< sparse
test/Reflex/Plan/Reflex.hsnew file+121
@@ -0,0 +1,121 @@
1+{-# LANGUAGE FunctionalDependencies, BangPatterns, UndecidableInstances, ConstraintKinds, GADTs, ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, GeneralizedNewtypeDeriving, RankNTypes, RecursiveDo, FlexibleContexts, StandaloneDeriving #-}
2+module Reflex.Plan.Reflex
3+ ( TestPlan(..)
4+ , runPlan
5+ , Plan
6+ , Schedule
7+ , Firing (..)
8+ , MonadIORef
9+
10+
11+ , readSchedule
12+ , testSchedule
13+ , readEvent'
14+ , makeDense
15+
16+ , runTestE
17+ , runTestB
18+
19+ ) where
20+
21+import Reflex.Class
22+import Reflex.Host.Class
23+import Reflex.TestPlan
24+
25+import Control.Applicative
26+import Control.Monad
27+import Control.Monad.Identity
28+import Control.Monad.State.Strict
29+
30+import Data.Dependent.Sum (DSum (..))
31+import Data.Monoid
32+import Data.Maybe
33+import qualified Data.IntMap.Strict as IntMap
34+import Control.Monad.Ref
35+
36+
37+import Data.IntMap.Strict (IntMap)
38+import Data.IORef
39+import System.Mem
40+
41+-- Note: this import must come last to silence warnings from AMP
42+import Prelude
43+
44+type MonadIORef m = (MonadIO m, MonadRef m, Ref m ~ Ref IO)
45+
46+data Firing t where
47+ Firing :: IORef (Maybe (EventTrigger t a)) -> a -> Firing t
48+
49+
50+type Schedule t = IntMap [Firing t]
51+
52+-- Implementation of a TestPlan in terms of ReflexHost
53+newtype Plan t a = Plan (StateT (Schedule t) (HostFrame t) a)
54+
55+deriving instance ReflexHost t => Functor (Plan t)
56+deriving instance ReflexHost t => Applicative (Plan t)
57+deriving instance ReflexHost t => Monad (Plan t)
58+
59+deriving instance ReflexHost t => MonadSample t (Plan t)
60+deriving instance ReflexHost t => MonadHold t (Plan t)
61+deriving instance ReflexHost t => MonadFix (Plan t)
62+
63+
64+instance (ReflexHost t, MonadRef (HostFrame t), Ref (HostFrame t) ~ Ref IO) => TestPlan t (Plan t) where
65+ plan occurances = Plan $ do
66+ (e, ref) <- newEventWithTriggerRef
67+ modify (IntMap.unionWith mappend (firings ref))
68+ return e
69+
70+ where
71+ firings ref = IntMap.fromList (makeFiring ref <$> occurances)
72+ makeFiring ref (t, a) = (fromIntegral t, [Firing ref a])
73+
74+
75+firingTrigger :: (MonadReflexHost t m, MonadIORef m) => Firing t -> m (Maybe (DSum (EventTrigger t) Identity))
76+firingTrigger (Firing ref a) = fmap (:=> Identity a) <$> readRef ref
77+
78+runPlan :: (MonadReflexHost t m, MonadIORef m) => Plan t a -> m (a, Schedule t)
79+runPlan (Plan p) = runHostFrame $ runStateT p mempty
80+
81+
82+makeDense :: Schedule t -> Schedule t
83+makeDense s = fromMaybe (emptyRange 0) $ do
84+ (end, _) <- fst <$> IntMap.maxViewWithKey s
85+ return $ IntMap.union s (emptyRange end)
86+ where
87+ emptyRange end = IntMap.fromList (zip [0..end + 1] (repeat []))
88+
89+
90+-- For the purposes of testing, we add in a zero frame and extend one frame (to observe changes to behaviors
91+-- after the last event)
92+-- performGC is called at each frame to test for GC issues
93+testSchedule :: (MonadReflexHost t m, MonadIORef m) => Schedule t -> ReadPhase m a -> m (IntMap a)
94+testSchedule schedule readResult = IntMap.traverseWithKey (\t occs -> liftIO performGC *> triggerFrame readResult t occs) (makeDense schedule)
95+
96+readSchedule :: (MonadReflexHost t m, MonadIORef m) => Schedule t -> ReadPhase m a -> m (IntMap a)
97+readSchedule schedule readResult = IntMap.traverseWithKey (triggerFrame readResult) schedule
98+
99+triggerFrame :: (MonadReflexHost t m, MonadIORef m) => ReadPhase m a -> Int -> [Firing t] -> m a
100+triggerFrame readResult _ occs = do
101+ triggers <- {-# SCC "catMaybes/traverse" #-} catMaybes <$> traverse firingTrigger occs
102+ {-# SCC fireEventsAndRead #-} fireEventsAndRead triggers readResult
103+
104+
105+readEvent' :: MonadReadEvent t m => EventHandle t a -> m (Maybe a)
106+readEvent' = readEvent >=> sequence
107+
108+
109+-- Convenience functions for running tests producing Events/Behaviors
110+runTestB :: (MonadReflexHost t m, MonadIORef m) => Plan t (Behavior t a) -> m (IntMap a)
111+runTestB p = do
112+ (b, s) <- runPlan p
113+ testSchedule s $ sample b
114+
115+runTestE :: (MonadReflexHost t m, MonadIORef m) => Plan t (Event t a) -> m (IntMap (Maybe a))
116+runTestE p = do
117+ (e, s) <- runPlan p
118+ h <- subscribeEvent e
119+ testSchedule s (readEvent' h)
120+
121+
test/Reflex/TestPlan.hsnew file+45
@@ -0,0 +1,45 @@
1+{-# LANGUAGE FunctionalDependencies, BangPatterns, UndecidableInstances, ConstraintKinds, GADTs, ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, GeneralizedNewtypeDeriving, RankNTypes, RecursiveDo, FlexibleContexts, StandaloneDeriving #-}
2+module Reflex.TestPlan
3+ ( TestPlan(..)
4+
5+ , TestCase (..)
6+ , testE, testB
7+ , TestE, TestB
8+
9+ , planList
10+
11+ ) where
12+
13+import Reflex.Class
14+import Control.Monad.Fix
15+
16+import qualified Data.IntMap as IntMap
17+import Data.IntMap
18+
19+import Prelude
20+
21+class (Reflex t, MonadHold t m, MonadFix m) => TestPlan t m where
22+ -- | Speicify a plan of an input Event firing
23+ -- Occurances must be in the future (i.e. Time > 0)
24+ -- Initial specification is
25+
26+ plan :: [(Word, a)] -> m (Event t a)
27+
28+
29+planList :: TestPlan t m => [a] -> m (Event t a)
30+planList xs = plan $ zip [1..] xs
31+
32+type TestE a = forall t m. TestPlan t m => m (Event t a)
33+type TestB a = forall t m. TestPlan t m => m (Behavior t a)
34+
35+data TestCase where
36+ TestE :: (Show a, Eq a) => TestE a -> TestCase
37+ TestB :: (Show a, Eq a) => TestB a -> TestCase
38+
39+-- Helpers to declare test cases
40+testE :: (Eq a, Show a) => String -> TestE a -> (String, TestCase)
41+testE name test = (name, TestE test)
42+
43+testB :: (Eq a, Show a) => String -> TestB a -> (String, TestCase)
44+testB name test = (name, TestB test)
45+