diff --git a/ChangeLog.md b/ChangeLog.md index 91ffe976..94f4f31f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -22,6 +22,25 @@ * Add cabal flags `debug-propagation` and `debug-event-cycles` to build in debugging code for performance and for cyclic dependencies between events +* Refactor of `Reflex.Requester`: + * Updated: + * `RequesterData` to `RequestData`, `RequesterEnvelope`, and `ResponseData` + * `singletonRequesterData` to `singletonRequestData` and `singletonResponseData` + * `requesterDataToList` to `requestEnvelopesToDSums` + * Removed: + * `RequesterDataKey` + * `multiEntry` + * `unMultiEntry` + * `withRequesterT` + * `runWithReplaceRequesterTWith` + * `requesting'` + * `traverseIntMapWithKeyWithAdjustRequesterTWith` + * `traverseDMapWithKeyWithAdjustRequesterTWith` + +* Added `Data.List.Deferred` and `Data.List.NonEmpty.Deferred` for optimizing `<>` operations. + +* Added `Data.TagMap`, `Reflex.FanTag`, and `Data.Unique.Tag.Local` to improve request and response tagging. + ## 0.6.3 * `Data.WeakBag.traverse` and `Data.FastWeakBag.traverse` have been deprecated. diff --git a/bench/HoldDynChain.hs b/bench/HoldDynChain.hs new file mode 100644 index 00000000..86e7dcdc --- /dev/null +++ b/bench/HoldDynChain.hs @@ -0,0 +1,142 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ForeignFunctionInterface #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE ViewPatterns #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module Main where + +import Criterion.Main +import Criterion.Types + +import Reflex +import Reflex.Host.Class + +import Reflex.Plan.Reflex +import Reflex.TestPlan + +import qualified Reflex.Bench.Focused as Focused +import Reflex.Spider.Internal (SpiderEventHandle) + +import Control.Applicative +import Control.DeepSeq (NFData (..)) + +import Prelude +import System.IO +import System.Mem + +import Control.Arrow +import Control.Concurrent +import Control.Concurrent.STM +import Control.Exception +import Control.Monad +import Control.Monad.Trans +import Data.Bool +import Data.Function +import Data.Int +import Data.IORef +import Data.Monoid +import Data.Time.Clock +import Debug.Trace.LocationTH +import GHC.Stats +import System.Environment +import System.Mem.Weak +import System.Process +import Text.Read + +import Unsafe.Coerce + +import Data.Map (Map) +import qualified Data.Map as Map + + +type MonadReflexHost' t m = (MonadReflexHost t m, MonadIORef m, MonadIORef (HostFrame t)) + + +setupFiring :: (MonadReflexHost t m, MonadIORef m) => Plan t (Event t a) -> m (EventHandle t a, Schedule t) +setupFiring p = do + (e, s) <- runPlan p + h <- subscribeEvent e + return (h, s) + +-- Hack to avoid the NFData constraint for EventHandle which is a synonym +newtype Ignore a = Ignore a +instance NFData (Ignore a) where + rnf !_ = () + +instance NFData (SpiderEventHandle x a) where + rnf !_ = () + +instance NFData (Behavior t a) where + rnf !_ = () + +instance NFData (Firing t) where + rnf !_ = () + +-- Measure the running time +benchFiring :: forall t m. (MonadReflexHost' t m, MonadSample t m) => (forall a. m a -> IO a) -> TestCase -> Int -> IO () +benchFiring runHost tc n = runHost $ do + let runIterations :: m a -> m () + runIterations test = replicateM_ (10*n) $ do + result <- test + liftIO $ evaluate result + case tc of + TestE p -> do + (h, s) <- setupFiring p + runIterations $ readSchedule_ s $ readEvent' h + TestB p -> do + (b, s) <- runPlan p + runIterations $ readSchedule_ (makeDense s) $ sample b + +benchmarks :: [(String, Int -> IO ())] +benchmarks = implGroup "spider" runSpiderHost cases + where + implGroup :: (MonadReflexHost' t m, MonadSample t m) => String -> (forall a. m a -> IO a) -> [(String, TestCase)] -> [(String, Int -> IO ())] + implGroup name runHost = group name . fmap (second (benchFiring runHost)) + group name = fmap $ first ((name <> "/") <>) + dynamics n = group ("dynamics " <> show n) $ dynamics' n + dynamics' :: Word -> [(String, TestCase)] + dynamics' n = [ testE "holdDynChain" $ fmap updated $ holdDynChain n =<< d ] + d :: TestPlan t m => m (Dynamic t Word) + d = count =<< Focused.events 10 + cases = concat + [ dynamics 100 + , dynamics 1000 + ] + + holdDynChain :: (Reflex t, MonadHold t m) => Word -> Dynamic t Word -> m (Dynamic t Word) + holdDynChain = Focused.iterM (\d' -> sample (current d') >>= flip holdDyn (updated d')) + +pattern RunTestCaseFlag = "--run-test-case" + +spawnBenchmark :: String -> Benchmark +spawnBenchmark name = bench name . toBenchmarkable $ \n -> do + self <- getExecutablePath + callProcess self [RunTestCaseFlag, name, show n, "+RTS", "-N1"] + +foreign import ccall unsafe "myCapabilityHasOtherRunnableThreads" myCapabilityHasOtherRunnableThreads :: IO Bool + +main :: IO () +main = do + args <- getArgs + case args of + RunTestCaseFlag : t -> case t of + [name, readMaybe -> Just count] -> do + case lookup name benchmarks of + Just testCase -> testCase count + performGC + fix $ \loop -> bool (return ()) (yield >> loop) =<< myCapabilityHasOtherRunnableThreads + return () + _ -> error "--run-test-case: expected test name and iteration count to follow" + _ -> defaultMainWith (defaultConfig { timeLimit = 20, csvFile = Just "dmap-original.csv", reportFile = Just "report.html" }) $ fmap (spawnBenchmark . fst) benchmarks diff --git a/reflex.cabal b/reflex.cabal index f7158955..1d42ab6e 100644 --- a/reflex.cabal +++ b/reflex.cabal @@ -68,6 +68,7 @@ library containers >= 0.6 && < 0.7, data-default >= 0.5 && < 0.8, dependent-map >= 0.3 && < 0.4, + dlist == 0.8.*, exception-transformers == 0.4.*, lens >= 4.7 && < 5, monad-control >= 1.0.1 && < 1.1, @@ -100,13 +101,19 @@ library Data.AppendMap, Data.FastMutableIntMap, Data.FastWeakBag, + Data.List.Deferred, + Data.List.NonEmpty.Deferred, + Data.List.NonEmpty.Deferred.Internal, Data.Map.Misc, + Data.TagMap, + Data.Unique.Tag.Local, + Data.Unique.Tag.Local.Internal, Data.WeakBag, Reflex, - Reflex.Class, Reflex.Adjustable.Class, Reflex.BehaviorWriter.Base, Reflex.BehaviorWriter.Class, + Reflex.Class, Reflex.Collection, Reflex.Dynamic, Reflex.Dynamic.Uniq, @@ -116,6 +123,7 @@ library Reflex.EventWriter, Reflex.EventWriter.Base, Reflex.EventWriter.Class, + Reflex.FanTag, Reflex.FastWeak, Reflex.FunctorMaybe, Reflex.Host.Class, @@ -130,6 +138,7 @@ library Reflex.Query.Base, Reflex.Query.Class, Reflex.Requester.Base, + Reflex.Requester.Base.Internal, Reflex.Requester.Class, Reflex.Spider, Reflex.Spider.Internal, @@ -150,6 +159,7 @@ library patch:Data.Patch.MapWithMove as Reflex.Patch.MapWithMove ghc-options: -Wall -fwarn-redundant-constraints -fwarn-tabs -funbox-strict-fields -O2 -fspecialise-aggressively + ghc-prof-options: -fprof-auto-calls if flag(debug-trace-events) cpp-options: -DDEBUG_TRACE_EVENTS @@ -192,6 +202,7 @@ test-suite semantics main-is: semantics.hs hs-source-dirs: test ghc-options: -O2 -Wall -rtsopts + ghc-prof-options: -fprof-auto-calls build-depends: base, bifunctors, @@ -218,6 +229,7 @@ test-suite CrossImpl main-is: Reflex/Test/CrossImpl.hs hs-source-dirs: test ghc-options: -O2 -Wall -rtsopts + ghc-prof-options: -fprof-auto-calls build-depends: base, containers, @@ -285,12 +297,15 @@ test-suite DebugCycles , hspec , lens , mtl + , primitive + , proctest + , ref-tf + , ref-tf + , reflex + , reflex , these , transformers - , reflex - , ref-tf , witherable - , proctest if flag(split-these) @@ -314,6 +329,7 @@ test-suite RequesterT , dependent-sum , lens , mtl + , primitive , ref-tf , reflex , text @@ -329,7 +345,6 @@ test-suite RequesterT Test.Run test-suite Adjustable - default-language: Haskell2010 type: exitcode-stdio-1.0 main-is: Adjustable.hs hs-source-dirs: test @@ -421,6 +436,7 @@ benchmark spider-bench hs-source-dirs: bench test main-is: Main.hs ghc-options: -Wall -O2 -rtsopts + ghc-prof-options: -fprof-auto-calls build-depends: base, containers, @@ -447,6 +463,7 @@ benchmark saulzar-bench c-sources: bench-cbits/checkCapability.c main-is: RunAll.hs ghc-options: -Wall -O2 -rtsopts -threaded + ghc-prof-options: -fprof-auto-calls build-depends: base, containers, @@ -469,6 +486,49 @@ benchmark saulzar-bench Reflex.Plan.Reflex Reflex.Bench.Focused +executable holddynchain + default-language: Haskell2010 + hs-source-dirs: bench test + c-sources: bench-cbits/checkCapability.c + main-is: HoldDynChain.hs + ghc-options: -Wall -O2 -rtsopts -threaded + ghc-prof-options: -fprof-auto-calls + build-depends: + base, + containers, + criterion, + deepseq, + dependent-map, + dependent-sum, + loch-th, + mtl, + primitive, + process, + ref-tf, + reflex, + split, + stm, + time, + transformers, + patch, + these, + semialign, + witherable, + data-default, + semigroupoids, + monoidal-containers, + exception-transformers, + unbounded-delays, + lens, + random, + profunctors, + monad-control, + reflection + other-modules: + Reflex.TestPlan + Reflex.Plan.Reflex + Reflex.Bench.Focused + source-repository head type: git location: https://github.com/reflex-frp/reflex diff --git a/src/Data/List/Deferred.hs b/src/Data/List/Deferred.hs new file mode 100644 index 00000000..8c8c2424 --- /dev/null +++ b/src/Data/List/Deferred.hs @@ -0,0 +1,60 @@ +{-# LANGUAGE LambdaCase #-} +module Data.List.Deferred + ( Deferred + , empty + , singleton + , toNonEmpty + , fromNonEmpty + , toList + ) where + +import Data.List.NonEmpty.Deferred.Internal (NonEmptyDeferred (..)) +import qualified Data.List.NonEmpty.Deferred as NonEmpty + +data Deferred a + = Deferred_Empty + | Deferred_Singleton a + | Deferred_Append !(NonEmptyDeferred a) !(NonEmptyDeferred a) + +{-# INLINE toNonEmpty #-} +toNonEmpty :: Deferred a -> Maybe (NonEmptyDeferred a) +toNonEmpty = \case + Deferred_Empty -> Nothing + Deferred_Singleton a -> Just $ NonEmpty.singleton a + Deferred_Append a b -> Just $ a <> b + +{-# INLINE fromNonEmpty #-} +fromNonEmpty :: NonEmptyDeferred a -> Deferred a +fromNonEmpty = \case + NonEmptyDeferred_Singleton a -> Deferred_Singleton a + NonEmptyDeferred_Append a b -> Deferred_Append a b + +{-# INLINE empty #-} +empty :: Deferred a +empty = Deferred_Empty + +{-# INLINE singleton #-} +singleton :: a -> Deferred a +singleton = fromNonEmpty . NonEmpty.singleton + +{-# INLINE toList #-} +toList :: Deferred a -> [a] +toList = \case + Deferred_Empty -> [] + Deferred_Singleton a -> [a] + Deferred_Append a b -> NonEmpty.toList $ a <> b + +instance Semigroup (Deferred a) where + (<>) = \case + Deferred_Empty -> id + a@(Deferred_Singleton va) -> \case + Deferred_Empty -> a + Deferred_Singleton vb -> Deferred_Append (NonEmpty.singleton va) (NonEmpty.singleton vb) + Deferred_Append b1 b2 -> Deferred_Append (NonEmpty.singleton va) (b1 <> b2) + a@(Deferred_Append a1 a2) -> \case + Deferred_Empty -> a + Deferred_Singleton vb -> Deferred_Append (a1 <> a2) (NonEmpty.singleton vb) + Deferred_Append b1 b2 -> Deferred_Append (a1 <> a2) (b1 <> b2) + +instance Monoid (Deferred a) where + mempty = Deferred_Empty diff --git a/src/Data/List/NonEmpty/Deferred.hs b/src/Data/List/NonEmpty/Deferred.hs new file mode 100644 index 00000000..322eeee7 --- /dev/null +++ b/src/Data/List/NonEmpty/Deferred.hs @@ -0,0 +1,13 @@ +-- | Uses a non-associative internal structure to represent a NonEmpty list, but +-- prevents external observers from observing the non-associativity. This +-- allows O(1) '(<>)'. + +{-# LANGUAGE LambdaCase #-} +module Data.List.NonEmpty.Deferred + ( NonEmptyDeferred + , singleton + , toNonEmpty + , toList + ) where + +import Data.List.NonEmpty.Deferred.Internal diff --git a/src/Data/List/NonEmpty/Deferred/Internal.hs b/src/Data/List/NonEmpty/Deferred/Internal.hs new file mode 100644 index 00000000..09baa708 --- /dev/null +++ b/src/Data/List/NonEmpty/Deferred/Internal.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE LambdaCase #-} +module Data.List.NonEmpty.Deferred.Internal where + +import Data.List.NonEmpty (NonEmpty (..)) +import qualified Data.List.NonEmpty as NonEmpty + +data NonEmptyDeferred a + = NonEmptyDeferred_Singleton a + | NonEmptyDeferred_Append !(NonEmptyDeferred a) !(NonEmptyDeferred a) + +{-# INLINE singleton #-} +singleton :: a -> NonEmptyDeferred a +singleton = NonEmptyDeferred_Singleton + +{-# INLINE toNonEmpty #-} +toNonEmpty :: NonEmptyDeferred a -> NonEmpty a +toNonEmpty = go [] + where go t = \case + NonEmptyDeferred_Singleton a -> a :| t + NonEmptyDeferred_Append a b -> go (NonEmpty.toList $ go t b) a + +{-# INLINE toList #-} +toList :: NonEmptyDeferred a -> [a] +toList = go [] + where go t = \case + NonEmptyDeferred_Singleton a -> a : t + NonEmptyDeferred_Append a b -> go (go t b) a + +instance Semigroup (NonEmptyDeferred a) where + (<>) = NonEmptyDeferred_Append diff --git a/src/Data/TagMap.hs b/src/Data/TagMap.hs new file mode 100644 index 00000000..b1944845 --- /dev/null +++ b/src/Data/TagMap.hs @@ -0,0 +1,43 @@ +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} +module Data.TagMap + ( TagMap + , unTagMap + , fromDMap + , toDMap + , fromList + , insert + , size + , singletonTagMap + ) where + +import Data.IntMap (IntMap) +import qualified Data.IntMap as IntMap +import Data.Dependent.Map (DMap) +import qualified Data.Dependent.Map as DMap +import Data.Dependent.Sum (DSum (..)) +import Data.Unique.Tag.Local + +import GHC.Exts (Any) +import Unsafe.Coerce + +-- | Like DMap, but with 'Data.Unique.Tag.Tag' as the keys. Implemented using 'Data.IntMap.IntMap' under the hood. +newtype TagMap x (v :: k -> *) = TagMap { unTagMap :: IntMap Any } + +fromDMap :: forall k x (v :: k -> *). DMap (Tag x) v -> TagMap x v +fromDMap = TagMap . IntMap.fromDistinctAscList . fmap (\((k :: Tag x (a :: k)) :=> v) -> (tagId k, (unsafeCoerce :: v a -> Any) v)) . DMap.toAscList + +toDMap :: forall x v. TagMap x v -> DMap (Tag x) v +toDMap = DMap.fromDistinctAscList . fmap (\(k, v) -> (unsafeTagFromId k :=> (unsafeCoerce :: Any -> v a) v)) . IntMap.toAscList . unTagMap + +insert :: forall x a v. Tag x a -> v a -> TagMap x v -> TagMap x v +insert k v = TagMap . IntMap.insert (tagId k) ((unsafeCoerce :: v a -> Any) v) . unTagMap + +fromList :: [DSum (Tag x) v] -> TagMap x v +fromList = TagMap . IntMap.fromList . fmap (\(t :=> v) -> (tagId t, (unsafeCoerce :: v a -> Any) v)) + +size :: TagMap x v -> Int +size = IntMap.size . unTagMap + +singletonTagMap :: forall ps k v a. Tag ps k -> v a -> TagMap k v +singletonTagMap tag v = TagMap $ IntMap.singleton (tagId tag) $ (unsafeCoerce :: v a -> Any) v diff --git a/src/Data/Unique/Tag/Local.hs b/src/Data/Unique/Tag/Local.hs new file mode 100644 index 00000000..2e10b8c7 --- /dev/null +++ b/src/Data/Unique/Tag/Local.hs @@ -0,0 +1,11 @@ +module Data.Unique.Tag.Local + ( Tag + , TagGen (..) + , tagId + , unsafeTagFromId + , newTag + , newTagGen + , withTagGen + ) where + +import Data.Unique.Tag.Local.Internal diff --git a/src/Data/Unique/Tag/Local/Internal.hs b/src/Data/Unique/Tag/Local/Internal.hs new file mode 100644 index 00000000..07747dba --- /dev/null +++ b/src/Data/Unique/Tag/Local/Internal.hs @@ -0,0 +1,59 @@ +-- | The type-safety of this module depends on two assumptions: +-- 1. If the `s` parameters on two `TagGen`s can unify, then they contain the same MutVar +-- 2. Two Tag values made from the same TagGen never contain the same Int + +{-# LANGUAGE EmptyDataDecls #-} +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE MagicHash #-} +module Data.Unique.Tag.Local.Internal where + +import Control.Monad.Primitive +import Data.Primitive.MutVar +import Data.GADT.Compare +import Data.Some +import GHC.Exts (Int (..), Int#, MutVar#, unsafeCoerce#) + +import Unsafe.Coerce + +-- `x` is which generator it's from +-- `a` is the type of the thing it's tagging +newtype Tag x a = Tag Int + +tagId :: Tag x a -> Int +tagId (Tag n) = n + +-- | WARNING: If you construct a tag with the wrong type, it will result in +-- incorrect unsafeCoerce applications, which can segfault or cause arbitrary +-- other damage to your program +unsafeTagFromId :: Int -> Tag x a +unsafeTagFromId n = Tag n + +-- We use Int because it is supported by e.g. IntMap +newtype TagGen ps s = TagGen { unTagGen :: MutVar ps Int } + +instance Show (TagGen ps s) where + show (TagGen (MutVar m)) = show $ I# ((unsafeCoerce# :: MutVar# ps Int -> Int#) m) + +instance GEq (TagGen ps) where + TagGen a `geq` TagGen b = + if a == b + then Just $ unsafeCoerce Refl + else Nothing + +newTag :: PrimMonad m => TagGen (PrimState m) s -> m (Tag s a) +newTag (TagGen r) = do + n <- atomicModifyMutVar' r $ \x -> (succ x, x) + pure $ Tag n + +newTagGen :: PrimMonad m => m (Some (TagGen (PrimState m))) +newTagGen = Some . TagGen <$> newMutVar minBound + +withTagGen :: PrimMonad m => (forall s. TagGen (PrimState m) s -> m a) -> m a +withTagGen f = do + g <- newTagGen + withSome g f diff --git a/src/Reflex/Class.hs b/src/Reflex/Class.hs index b29b1017..5731fdda 100644 --- a/src/Reflex/Class.hs +++ b/src/Reflex/Class.hs @@ -169,6 +169,8 @@ module Reflex.Class , tagCheap , mergeWithCheap , mergeWithCheap' + , sconcatCheap + , mconcatCheap -- * Slow, but general, implementations , slowHeadE ) where @@ -210,7 +212,8 @@ import Data.IntMap.Strict (IntMap) import qualified Data.IntMap.Strict as IntMap import Data.List.NonEmpty (NonEmpty (..)) import Data.Map (Map) -import Data.Semigroup (Semigroup (..)) +import qualified Data.Map as Map +import Data.Semigroup (Semigroup, sconcat, stimes, (<>)) import Data.Some (Some(Some)) import Data.String import Data.These @@ -739,11 +742,6 @@ instance Reflex t => Functor (Event t) where {-# INLINE (<$) #-} x <$ e = fmapCheap (const x) e --- TODO Remove this instance -instance Reflex t => FunctorMaybe (Event t) where - {-# INLINE fmapMaybe #-} - fmapMaybe = mapMaybe - instance Reflex t => Filterable (Event t) where {-# INLINE mapMaybe #-} mapMaybe f = push $ return . f @@ -897,6 +895,12 @@ instance (Semigroup a, Reflex t) => Semigroup (Event t a) where sconcat = fmap sconcat . mergeList . toList stimes n = fmap $ stimes n +sconcatCheap :: (Semigroup a, Reflex t) => NonEmpty (Event t a) -> Event t a +sconcatCheap = fmapCheap sconcat . mergeList . toList + +mconcatCheap :: (Semigroup a, Reflex t) => [Event t a] -> Event t a +mconcatCheap = fmapCheap sconcat . mergeList + instance (Semigroup a, Reflex t) => Monoid (Event t a) where mempty = never mappend = (<>) @@ -911,6 +915,8 @@ mergeWith = mergeWith' id {-# INLINE mergeWith' #-} mergeWith' :: Reflex t => (a -> b) -> (b -> b -> b) -> [Event t a] -> Event t b +mergeWith' _ _ [] = never +mergeWith' f _ [e] = fmap f e mergeWith' f g es = fmap (Prelude.foldl1 g . fmap f) . mergeInt . IntMap.fromDistinctAscList @@ -928,6 +934,7 @@ leftmost = mergeWith const -- time. mergeList :: Reflex t => [Event t a] -> Event t (NonEmpty a) mergeList [] = never +mergeList [e] = fmapCheap (:|[]) e mergeList es = mergeWithFoldCheap' id es unsafeMapIncremental @@ -944,10 +951,6 @@ unsafeMapIncremental f g a = unsafeBuildIncremental (fmap f $ sample $ currentIn mergeMap :: (Reflex t, Ord k) => Map k (Event t a) -> Event t (Map k a) mergeMap = fmap dmapToMap . merge . mapWithFunctorToDMap --- | Like 'mergeMap' but for 'IntMap'. -mergeIntMap :: Reflex t => IntMap (Event t a) -> Event t (IntMap a) -mergeIntMap = fmap dmapToIntMap . merge . intMapWithFunctorToDMap - -- | Create a merge whose parents can change over time mergeMapIncremental :: (Reflex t, Ord k) => Incremental t (PatchMap k (Event t a)) -> Event t (Map k a) mergeMapIncremental = fmap dmapToMap . mergeIncremental . unsafeMapIncremental mapWithFunctorToDMap (const2PatchDMapWith id) @@ -1024,33 +1027,45 @@ switchHoldPromptly ea0 eea = do switchHoldPromptOnly :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a) switchHoldPromptOnly e0 e' = do eLag <- switch <$> hold e0 e' - return $ coincidence $ leftmost [e', eLag <$ eLag] + return $ fmapMaybeCheap id $ leftmost + [ fmapCheap Just $ coincidence e' + , fmapCheap (const Nothing) e' + , fmapCheap Just eLag + ] -- | When the given outer event fires, condense the inner events into the contained patch. Non-firing inner events will be replaced with deletions. coincidencePatchMap :: (Reflex t, Ord k) => Event t (PatchMap k (Event t v)) -> Event t (PatchMap k v) -coincidencePatchMap e = fmapCheap PatchMap $ coincidence $ ffor e $ \(PatchMap m) -> mergeMap $ ffor m $ \case - Nothing -> fmapCheap (const Nothing) e - Just ev -> leftmost [fmapCheap Just ev, fmapCheap (const Nothing) e] +coincidencePatchMap e = fmapCheap PatchMap $ coincidence $ fforCheap e $ \(PatchMap m) -> if Map.null m then never else + let firingNewItems = mergeMap $ fforMaybe m $ \case + Nothing -> Nothing + Just ev -> Just $ fmapCheap Just ev + oldItemMask = (Nothing <$ m) <$ e + in alignWith (mergeThese Map.union) firingNewItems oldItemMask -- Must be left-biased -- | See 'coincidencePatchMap' coincidencePatchIntMap :: Reflex t => Event t (PatchIntMap (Event t v)) -> Event t (PatchIntMap v) -coincidencePatchIntMap e = fmapCheap PatchIntMap $ coincidence $ ffor e $ \(PatchIntMap m) -> mergeIntMap $ ffor m $ \case - Nothing -> fmapCheap (const Nothing) e - Just ev -> leftmost [fmapCheap Just ev, fmapCheap (const Nothing) e] +coincidencePatchIntMap e = fmapCheap PatchIntMap $ coincidence $ fforCheap e $ \(PatchIntMap m) -> if IntMap.null m then never else + let firingNewItems = mergeIntMap $ fforMaybe m $ \case + Nothing -> Nothing + Just ev -> Just $ fmapCheap Just ev + oldItemMask = (Nothing <$ m) <$ e + in alignWith (mergeThese IntMap.union) firingNewItems oldItemMask -- Must be left-biased -- | See 'coincidencePatchMap' coincidencePatchMapWithMove :: (Reflex t, Ord k) => Event t (PatchMapWithMove k (Event t v)) -> Event t (PatchMapWithMove k v) -coincidencePatchMapWithMove e = fmapCheap unsafePatchMapWithMove $ coincidence $ ffor e $ \p -> mergeMap $ ffor (unPatchMapWithMove p) $ \ni -> case PatchMapWithMove._nodeInfo_from ni of - PatchMapWithMove.From_Delete -> fforCheap e $ \_ -> - ni { PatchMapWithMove._nodeInfo_from = PatchMapWithMove.From_Delete } - PatchMapWithMove.From_Move k -> fforCheap e $ \_ -> - ni { PatchMapWithMove._nodeInfo_from = PatchMapWithMove.From_Move k } - PatchMapWithMove.From_Insert ev -> leftmost - [ fforCheap ev $ \v -> - ni { PatchMapWithMove._nodeInfo_from = PatchMapWithMove.From_Insert v } - , fforCheap e $ \_ -> - ni { PatchMapWithMove._nodeInfo_from = PatchMapWithMove.From_Delete } - ] +coincidencePatchMapWithMove e = fmapCheap unsafePatchMapWithMove $ coincidence $ fforCheap e $ \p -> if Map.null (unPatchMapWithMove p) then never else + let firingNewItems = mergeMap $ fforMaybe (unPatchMapWithMove p) $ \ni -> case PatchMapWithMove._nodeInfo_from ni of + PatchMapWithMove.From_Insert ev -> Just $ fforCheap ev $ \v -> + ni { PatchMapWithMove._nodeInfo_from = PatchMapWithMove.From_Insert v } + _ -> Nothing + oldItemMask = fforCheap e $ \_ -> ffor (unPatchMapWithMove p) $ \ni -> case PatchMapWithMove._nodeInfo_from ni of + PatchMapWithMove.From_Delete -> + ni { PatchMapWithMove._nodeInfo_from = PatchMapWithMove.From_Delete } + PatchMapWithMove.From_Move k -> + ni { PatchMapWithMove._nodeInfo_from = PatchMapWithMove.From_Move k } + PatchMapWithMove.From_Insert _ -> + ni { PatchMapWithMove._nodeInfo_from = PatchMapWithMove.From_Delete } + in alignWith (mergeThese Map.union) firingNewItems oldItemMask -- Must be left-biased -- | Given a 'PatchTarget' of events (e.g., a 'Map' with 'Event' values) and an event of 'Patch'es -- (e.g., a 'PatchMap' with 'Event' values), produce an 'Event' of the 'PatchTarget' type that @@ -1303,7 +1318,7 @@ accumMaybeMDyn -> Event t b -> m (Dynamic t a) accumMaybeMDyn f z e = do - rec let e' = flip push e $ \o -> do + rec let e' = flip pushCheap e $ \o -> do v <- sample $ current d' f v o d' <- holdDyn z e' @@ -1359,8 +1374,8 @@ mapAccumMaybeMDyn f z e = do return $ case result of (Nothing, Nothing) -> Nothing _ -> Just result - d' <- holdDyn z $ mapMaybe fst e' - return (d', mapMaybe snd e') + d' <- holdDyn z $ fmapMaybeCheap fst e' + return (d', fmapMaybeCheap snd e') -- | Accumulate a 'Behavior' by folding occurrences of an 'Event' -- with the provided function. @@ -1398,7 +1413,7 @@ accumMaybeB f = accumMaybeMB $ \v o -> return $ f v o {-# INLINE accumMaybeMB #-} accumMaybeMB :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> PushM t (Maybe a)) -> a -> Event t b -> m (Behavior t a) accumMaybeMB f z e = do - rec let e' = flip push e $ \o -> do + rec let e' = flip pushCheap e $ \o -> do v <- sample d' f v o d' <- hold z e' @@ -1448,8 +1463,8 @@ mapAccumMaybeMB f z e = do return $ case result of (Nothing, Nothing) -> Nothing _ -> Just result - d' <- hold z $ mapMaybe fst e' - return (d', mapMaybe snd e') + d' <- hold z $ fmapMaybeCheap fst e' + return (d', fmapMaybeCheap snd e') -- | Accumulate occurrences of an 'Event', producing an output occurrence each -- time. Discard the underlying 'Accumulator'. @@ -1659,6 +1674,8 @@ mergeWithCheap' f g = mergeWithFoldCheap' $ foldl1 g . fmap f -- | A "cheap" version of 'mergeWithFoldCheap''. See the performance note on 'pushCheap'. {-# INLINE mergeWithFoldCheap' #-} mergeWithFoldCheap' :: Reflex t => (NonEmpty a -> b) -> [Event t a] -> Event t b +mergeWithFoldCheap' _ [] = never +mergeWithFoldCheap' f [e] = fmapCheap (f . (:|[])) e mergeWithFoldCheap' f es = fmapCheap (f . (\(h : t) -> h :| t) . IntMap.elems) . mergeInt @@ -1673,7 +1690,18 @@ mergeWithFoldCheap' f es = -- | See 'switchHoldPromptly' switchPromptly :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a) switchPromptly = switchHoldPromptly + {-# DEPRECATED switchPromptOnly "Use 'switchHoldPromptOnly' instead. The 'switchHold*' naming convention was chosen because those functions are more closely related to each other than they are to 'switch'. " #-} -- | See 'switchHoldPromptOnly' switchPromptOnly :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a) switchPromptOnly = switchHoldPromptOnly + +{-# DEPRECATED mergeIntMap "Use 'mergeInt' instead" #-} +-- | Like 'mergeMap' but for 'IntMap'. +mergeIntMap :: Reflex t => IntMap (Event t a) -> Event t (IntMap a) +mergeIntMap = mergeInt + +-- NOTE: A deprecation warning is expected on this instance +instance Reflex t => FunctorMaybe (Event t) where + {-# INLINE fmapMaybe #-} + fmapMaybe = mapMaybe diff --git a/src/Reflex/Dynamic.hs b/src/Reflex/Dynamic.hs index a0c08b59..f519d80d 100644 --- a/src/Reflex/Dynamic.hs +++ b/src/Reflex/Dynamic.hs @@ -80,7 +80,6 @@ import Data.Functor.Compose import Data.Functor.Misc import Reflex.Class -import Control.Monad import Control.Monad.Fix import Control.Monad.Identity import Data.Align @@ -192,6 +191,7 @@ switchPromptlyDyn de = let eLag = switch $ current de eCoincidences = coincidence $ updated de in leftmost [eCoincidences, eLag] +--TODO: switchPromptlyDyn should get the `only` treatment like switchHoldPromptOnly -- | Split a 'Dynamic' pair into a pair of 'Dynamic's splitDynPure :: Reflex t => Dynamic t (a, b) -> (Dynamic t a, Dynamic t b) diff --git a/src/Reflex/EventWriter/Base.hs b/src/Reflex/EventWriter/Base.hs index 517af0db..03940ca6 100644 --- a/src/Reflex/EventWriter/Base.hs +++ b/src/Reflex/EventWriter/Base.hs @@ -39,82 +39,34 @@ import Control.Monad.Primitive import Control.Monad.Reader import Control.Monad.Ref import Control.Monad.State.Strict -import Data.Dependent.Map (DMap, DSum (..)) +import Data.Dependent.Map (DMap) import qualified Data.Dependent.Map as DMap import Data.Functor.Compose import Data.Functor.Misc -import Data.GADT.Compare (GCompare (..), GEq (..), GOrdering (..)) +import Data.GADT.Compare (GCompare (..)) import Data.IntMap.Strict (IntMap) import qualified Data.IntMap.Strict as IntMap import Data.List.NonEmpty (NonEmpty (..)) +import Data.List.Deferred (Deferred) +import qualified Data.List.Deferred as Deferred import Data.Map (Map) import qualified Data.Map as Map import Data.Semigroup import Data.Some (Some) import Data.Tuple -import Data.Type.Equality - -import Unsafe.Coerce - -{-# DEPRECATED TellId "Do not construct this directly; use tellId instead" #-} -newtype TellId w x - = TellId Int -- ^ WARNING: Do not construct this directly; use 'tellId' instead - deriving (Show, Eq, Ord, Enum) - -tellId :: Int -> TellId w w -tellId = TellId -{-# INLINE tellId #-} - -tellIdRefl :: TellId w x -> w :~: x -tellIdRefl _ = unsafeCoerce Refl - -withTellIdRefl :: TellId w x -> (w ~ x => r) -> r -withTellIdRefl tid r = case tellIdRefl tid of - Refl -> r - -instance GEq (TellId w) where - a `geq` b = - withTellIdRefl a $ - withTellIdRefl b $ - if a == b - then Just Refl - else Nothing - -instance GCompare (TellId w) where - a `gcompare` b = - withTellIdRefl a $ - withTellIdRefl b $ - case a `compare` b of - LT -> GLT - EQ -> GEQ - GT -> GGT - -data EventWriterState t w = EventWriterState - { _eventWriterState_nextId :: {-# UNPACK #-} !Int -- Always negative (and decreasing over time) - , _eventWriterState_told :: ![DSum (TellId w) (Event t)] -- In increasing order - } -- | A basic implementation of 'EventWriter'. -newtype EventWriterT t w m a = EventWriterT { unEventWriterT :: StateT (EventWriterState t w) m a } +newtype EventWriterT t w m a = EventWriterT { unEventWriterT :: StateT (Deferred (Event t w)) m a } deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException, MonadAsyncException) -- | Run a 'EventWriterT' action. runEventWriterT :: forall t m w a. (Reflex t, Monad m, Semigroup w) => EventWriterT t w m a -> m (a, Event t w) runEventWriterT (EventWriterT a) = do - (result, requests) <- runStateT a $ EventWriterState (-1) [] - let combineResults :: DMap (TellId w) Identity -> w - combineResults = sconcat - . (\(h : t) -> h :| t) -- Unconditional; 'merge' guarantees that it will only fire with non-empty DMaps - . DMap.foldlWithKey (\vs tid (Identity v) -> withTellIdRefl tid $ v : vs) [] -- This is where we finally reverse the DMap to get things in the correct order - return (result, fmap combineResults $ merge $ DMap.fromDistinctAscList $ _eventWriterState_told requests) --TODO: We can probably make this fromDistinctAscList more efficient by knowing the length in advance, but this will require exposing internals of DMap; also converting it to use a strict list might help + (result, requests) <- runStateT a mempty + return (result, mconcatCheap $ Deferred.toList requests) instance (Reflex t, Monad m, Semigroup w) => EventWriter t w (EventWriterT t w m) where - tellEvent w = EventWriterT $ modify $ \old -> - let myId = _eventWriterState_nextId old - in EventWriterState - { _eventWriterState_nextId = pred myId - , _eventWriterState_told = (tellId myId :=> w) : _eventWriterState_told old - } + tellEvent w = EventWriterT $ modify (<> Deferred.singleton w) instance MonadTrans (EventWriterT t w) where lift = EventWriterT . lift diff --git a/src/Reflex/FanTag.hs b/src/Reflex/FanTag.hs new file mode 100644 index 00000000..b94eea67 --- /dev/null +++ b/src/Reflex/FanTag.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} +module Reflex.FanTag + ( EventSelectorTag + , unEventSelectorTag + , fanTag + , selectTag + ) where + +import Data.Unique.Tag.Local +import Data.TagMap +import Reflex.Class + +import GHC.Exts (Any) +import Unsafe.Coerce + +newtype EventSelectorTag t x (v :: k -> *) = EventSelectorTag { unEventSelectorTag :: EventSelectorInt t Any } + +fanTag :: Reflex t => Event t (TagMap x v) -> EventSelectorTag t x v +fanTag = EventSelectorTag . fanInt . fmapCheap unTagMap + +selectTag :: forall t x v a. Reflex t => EventSelectorTag t x v -> Tag x a -> Event t (v a) +selectTag (EventSelectorTag s) = fmapCheap (unsafeCoerce :: Any -> v a) . selectInt s . tagId diff --git a/src/Reflex/NotReady/Class.hs b/src/Reflex/NotReady/Class.hs index 7d1232bd..44f42592 100644 --- a/src/Reflex/NotReady/Class.hs +++ b/src/Reflex/NotReady/Class.hs @@ -25,6 +25,7 @@ import Reflex.PerformEvent.Base (PerformEventT (..)) import Reflex.PostBuild.Base (PostBuildT) import Reflex.Query.Base (QueryT) import Reflex.Requester.Base (RequesterT) +import Reflex.Requester.Base.Internal (RequesterInternalT) import Reflex.TriggerEvent.Base (TriggerEventT) class Monad m => NotReady t m | m -> t where @@ -72,6 +73,10 @@ instance NotReady t m => NotReady t (RequesterT t request response m) where notReadyUntil = lift . notReadyUntil notReady = lift notReady +instance NotReady t m => NotReady t (RequesterInternalT s t request response m) where + notReadyUntil = lift . notReadyUntil + notReady = lift notReady + instance NotReady t m => NotReady t (TriggerEventT t m) where notReadyUntil = lift . notReadyUntil notReady = lift notReady diff --git a/src/Reflex/PerformEvent/Base.hs b/src/Reflex/PerformEvent/Base.hs index 32f7fa3b..e749e05d 100644 --- a/src/Reflex/PerformEvent/Base.hs +++ b/src/Reflex/PerformEvent/Base.hs @@ -28,8 +28,10 @@ import Reflex.Class import Reflex.Adjustable.Class import Reflex.Host.Class import Reflex.PerformEvent.Class -import Reflex.Requester.Base +import Reflex.Requester.Base.Internal import Reflex.Requester.Class +import Reflex.EventWriter.Class +import Reflex.EventWriter.Base import Control.Lens import Control.Monad.Exception @@ -37,13 +39,21 @@ import Control.Monad.Identity import Control.Monad.Primitive import Control.Monad.Reader import Control.Monad.Ref -import Data.Coerce import Data.Dependent.Map (DMap) import qualified Data.Dependent.Map as DMap import Data.Dependent.Sum +import Data.Functor.Compose +import Data.Functor.Misc import Data.IntMap.Strict (IntMap) import qualified Data.IntMap.Strict as IntMap +import Data.List.NonEmpty (NonEmpty (..)) +import Data.List.NonEmpty.Deferred (NonEmptyDeferred) +import Data.Map (Map) +import qualified Data.Map as Map +import Data.Semigroup (Semigroup (sconcat)) import qualified Data.Semigroup as S +import Data.Unique.Tag.Local +import Data.Tuple -- | A function that fires events for the given 'EventTrigger's and then runs -- any followup actions provided via 'PerformEvent'. The given 'ReadPhase' @@ -68,7 +78,7 @@ instance (PrimMonad (HostFrame t), ReflexHost t) => PrimMonad (PerformEventT t m type PrimState (PerformEventT t m) = PrimState (HostFrame t) primitive = PerformEventT . lift . primitive -instance (ReflexHost t, Ref m ~ Ref IO) => PerformEvent t (PerformEventT t m) where +instance (ReflexHost t, Ref m ~ Ref IO, PrimMonad (HostFrame t)) => PerformEvent t (PerformEventT t m) where type Performable (PerformEventT t m) = HostFrame t {-# INLINABLE performEvent_ #-} performEvent_ = PerformEventT . requesting_ @@ -76,37 +86,74 @@ instance (ReflexHost t, Ref m ~ Ref IO) => PerformEvent t (PerformEventT t m) wh performEvent = PerformEventT . requestingIdentity instance (ReflexHost t, PrimMonad (HostFrame t)) => Adjustable t (PerformEventT t m) where - runWithReplace outerA0 outerA' = PerformEventT $ runWithReplaceRequesterTWith f (coerce outerA0) (coerceEvent outerA') - where f :: HostFrame t a -> Event t (HostFrame t b) -> RequesterT t (HostFrame t) Identity (HostFrame t) (a, Event t b) - f a0 a' = do - result0 <- lift a0 - result' <- requestingIdentity a' - return (result0, result') - traverseIntMapWithKeyWithAdjust f outerDm0 outerDm' = PerformEventT $ traverseIntMapWithKeyWithAdjustRequesterTWith (defaultAdjustIntBase traverseIntMapPatchWithKey) patchIntMapNewElementsMap mergeIntIncremental (\k v -> unPerformEventT $ f k v) (coerce outerDm0) (coerceEvent outerDm') - traverseDMapWithKeyWithAdjust f outerDm0 outerDm' = PerformEventT $ traverseDMapWithKeyWithAdjustRequesterTWith (defaultAdjustBase traversePatchDMapWithKey) mapPatchDMap weakenPatchDMapWith patchMapNewElementsMap mergeMapIncremental (\k v -> unPerformEventT $ f k v) (coerce outerDm0) (coerceEvent outerDm') - traverseDMapWithKeyWithAdjustWithMove f outerDm0 outerDm' = PerformEventT $ traverseDMapWithKeyWithAdjustRequesterTWith (defaultAdjustBase traversePatchDMapWithMoveWithKey) mapPatchDMapWithMove weakenPatchDMapWithMoveWith patchMapWithMoveNewElementsMap mergeMapIncrementalWithMove (\k v -> unPerformEventT $ f k v) (coerce outerDm0) (coerceEvent outerDm') - -defaultAdjustBase :: forall t v v2 k' p. (Monad (HostFrame t), PrimMonad (HostFrame t), Reflex t) - => ((forall a. k' a -> v a -> HostFrame t (v2 a)) -> p k' v -> HostFrame t (p k' v2)) - -> (forall a. k' a -> v a -> HostFrame t (v2 a)) - -> DMap k' v - -> Event t (p k' v) - -> RequesterT t (HostFrame t) Identity (HostFrame t) (DMap k' v2, Event t (p k' v2)) -defaultAdjustBase traversePatchWithKey f' dm0 dm' = do - result0 <- lift $ DMap.traverseWithKey f' dm0 - result' <- requestingIdentity $ ffor dm' $ traversePatchWithKey f' - return (result0, result') - -defaultAdjustIntBase :: forall t v v2 p. (Monad (HostFrame t), PrimMonad (HostFrame t), Reflex t) - => ((IntMap.Key -> v -> HostFrame t v2) -> p v -> HostFrame t (p v2)) - -> (IntMap.Key -> v -> HostFrame t v2) - -> IntMap v - -> Event t (p v) - -> RequesterT t (HostFrame t) Identity (HostFrame t) (IntMap v2, Event t (p v2)) -defaultAdjustIntBase traversePatchWithKey f' dm0 dm' = do - result0 <- lift $ IntMap.traverseWithKey f' dm0 - result' <- requestingIdentity $ ffor dm' $ traversePatchWithKey f' - return (result0, result') + {-# INLINE runWithReplace #-} + runWithReplace a0 a' = PerformEventT $ RequesterT $ do + env@(_, _ :: TagGen (PrimState (HostFrame t)) FakeRequesterStatePhantom) <- RequesterInternalT ask + let runA :: forall a. PerformEventT t m a -> HostFrame t (a, Event t (NonEmptyDeferred (RequestEnvelope FakeRequesterStatePhantom (HostFrame t)))) + runA (PerformEventT (RequesterT a)) = runEventWriterT $ runReaderT (unRequesterInternalT a) env + (result0, requests0) <- lift $ runA a0 + newA <- requestingIdentity $ runA <$> a' + requests <- switchHoldPromptOnly requests0 $ fmapCheap snd newA + --TODO: promptly *prevent* events, then sign up the new ones; this is a serious breaking change to PerformEvent + RequesterInternalT $ tellEvent requests + pure (result0, fmapCheap fst newA) + {-# INLINE traverseIntMapWithKeyWithAdjust #-} + traverseIntMapWithKeyWithAdjust f a0 a' = PerformEventT $ RequesterT $ do + env@(_, _ :: TagGen (PrimState (HostFrame t)) FakeRequesterStatePhantom) <- RequesterInternalT ask + let runA :: forall a. PerformEventT t m a -> HostFrame t (a, Event t (NonEmptyDeferred (RequestEnvelope FakeRequesterStatePhantom (HostFrame t)))) + runA (PerformEventT (RequesterT a)) = runEventWriterT $ runReaderT (unRequesterInternalT a) env + children' <- requestingIdentity $ itraverse (\k -> runA . f k) <$> a' + children0 <- lift $ itraverse (\k -> runA . f k) a0 + let results0 = fmap fst children0 + requests0 = fmap snd children0 + results' = fmap fst <$> children' + requests' = fmap snd `fmapCheap` children' + requests <- switchHoldPromptOnlyIncremental mergeIntIncremental coincidencePatchIntMap requests0 requests' + --TODO: promptly *prevent* events, then sign up the new ones; this is a serious breaking change to PerformEvent + RequesterInternalT $ tellEvent $ fforMaybeCheap requests concatIntMapMaybe + pure (results0, results') + {-# INLINE traverseDMapWithKeyWithAdjust #-} + traverseDMapWithKeyWithAdjust (f :: forall a. k a -> v a -> PerformEventT t m (v' a)) (a0 :: DMap k v) a' = PerformEventT $ RequesterT $ do + env@(_, _ :: TagGen (PrimState (HostFrame t)) FakeRequesterStatePhantom) <- RequesterInternalT ask + let runA :: forall a. k a -> v a -> HostFrame t (Compose ((,) (Event t (NonEmptyDeferred (RequestEnvelope FakeRequesterStatePhantom (HostFrame t))))) v' a) + runA k v = fmap (Compose . swap) $ runEventWriterT $ runReaderT (unRequesterInternalT a) env + where (PerformEventT (RequesterT a)) = f k v + children' <- requestingIdentity $ traversePatchDMapWithKey runA <$> a' + children0 <- lift $ DMap.traverseWithKey runA a0 + let results0 = DMap.map (snd . getCompose) children0 + requests0 = weakenDMapWith (fst . getCompose) children0 + results' = mapPatchDMap (snd . getCompose) <$> children' + requests' = weakenPatchDMapWith (fst . getCompose) `fmapCheap` children' + requests <- switchHoldPromptOnlyIncremental mergeMapIncremental coincidencePatchMap requests0 requests' + --TODO: promptly *prevent* events, then sign up the new ones; this is a serious breaking change to PerformEvent + RequesterInternalT $ tellEvent $ fforMaybeCheap requests concatMapMaybe + pure (results0, results') + {-# INLINE traverseDMapWithKeyWithAdjustWithMove #-} + traverseDMapWithKeyWithAdjustWithMove (f :: forall a. k a -> v a -> PerformEventT t m (v' a)) (a0 :: DMap k v) a' = PerformEventT $ RequesterT $ do + env@(_, _ :: TagGen (PrimState (HostFrame t)) FakeRequesterStatePhantom) <- RequesterInternalT ask + let runA :: forall a. k a -> v a -> HostFrame t (Compose ((,) (Event t (NonEmptyDeferred (RequestEnvelope FakeRequesterStatePhantom (HostFrame t))))) v' a) + runA k v = fmap (Compose . swap) $ runEventWriterT $ runReaderT (unRequesterInternalT a) env + where (PerformEventT (RequesterT a)) = f k v + children' <- requestingIdentity $ traversePatchDMapWithMoveWithKey runA <$> a' + children0 <- lift $ DMap.traverseWithKey runA a0 + let results0 = DMap.map (snd . getCompose) children0 + requests0 = weakenDMapWith (fst . getCompose) children0 + results' = mapPatchDMapWithMove (snd . getCompose) <$> children' + requests' = weakenPatchDMapWithMoveWith (fst . getCompose) `fmapCheap` children' + requests <- switchHoldPromptOnlyIncremental mergeMapIncrementalWithMove coincidencePatchMapWithMove requests0 requests' + --TODO: promptly *prevent* events, then sign up the new ones; this is a serious breaking change to PerformEvent + RequesterInternalT $ tellEvent $ fforMaybeCheap requests concatMapMaybe + pure (results0, results') + +concatIntMapMaybe :: Semigroup a => IntMap a -> Maybe a +concatIntMapMaybe m = case IntMap.elems m of + [] -> Nothing + h : t -> Just $ sconcat $ h :| t + +concatMapMaybe :: Semigroup a => Map k a -> Maybe a +concatMapMaybe m = case Map.elems m of + [] -> Nothing + h : t -> Just $ sconcat $ h :| t instance ReflexHost t => MonadReflexCreateTrigger t (PerformEventT t m) where {-# INLINABLE newEventWithTrigger #-} @@ -124,6 +171,7 @@ hostPerformEventT :: forall t m a. , MonadReflexHost t m , MonadRef m , Ref m ~ Ref IO + , PrimMonad (HostFrame t) ) => PerformEventT t m a -> m (a, FireCommand t m) @@ -141,7 +189,7 @@ hostPerformEventT a = do case mToPerform of Nothing -> return [result'] Just toPerform -> do - responses <- runHostFrame $ traverseRequesterData (fmap Identity) toPerform + responses <- runHostFrame $ traverseRequesterData (Identity <$>) toPerform mrt <- readRef responseTrigger let followupEventTriggers = case mrt of Just rt -> [rt :=> Identity responses] diff --git a/src/Reflex/Profiled.hs b/src/Reflex/Profiled.hs index f70d1e2a..bdc7bf1c 100644 --- a/src/Reflex/Profiled.hs +++ b/src/Reflex/Profiled.hs @@ -47,7 +47,6 @@ import Reflex.Host.Class import Reflex.PerformEvent.Class import System.IO.Unsafe -import Unsafe.Coerce data ProfiledTimeline t diff --git a/src/Reflex/Requester/Base.hs b/src/Reflex/Requester/Base.hs index 95440eb2..02d31779 100644 --- a/src/Reflex/Requester/Base.hs +++ b/src/Reflex/Requester/Base.hs @@ -1,537 +1,23 @@ -- | This module provides 'RequesterT', the standard implementation of -- 'Requester'. -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE CPP #-} -{-# LANGUAGE EmptyDataDecls #-} -{-# LANGUAGE ExistentialQuantification #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE Rank2Types #-} -{-# LANGUAGE RecursiveDo #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE StandaloneDeriving #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE UndecidableInstances #-} -#ifdef USE_REFLEX_OPTIMIZER -{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-} -#endif module Reflex.Requester.Base ( RequesterT (..) + , RequestData (..) + , ResponseData (..) + , RequestEnvelope (..) , runRequesterT - , withRequesterT - , runWithReplaceRequesterTWith - , traverseIntMapWithKeyWithAdjustRequesterTWith - , traverseDMapWithKeyWithAdjustRequesterTWith - , RequesterData - , RequesterDataKey +-- , withRequesterT +-- , RequesterData +-- , RequesterDataKey , traverseRequesterData , forRequesterData - , requesterDataToList - , singletonRequesterData + , requestEnvelopesToDSums + , singletonRequestData + , singletonResponseData , matchResponsesWithRequests - , multiEntry - , unMultiEntry - , requesting' +-- , multiEntry +-- , unMultiEntry +-- , requesting' ) where -import Reflex.Class -import Reflex.Adjustable.Class -import Reflex.Dynamic -import Reflex.Host.Class -import Reflex.PerformEvent.Class -import Reflex.PostBuild.Class -import Reflex.Requester.Class -import Reflex.TriggerEvent.Class - -import Control.Applicative (liftA2) -import Control.Monad.Exception -import Control.Monad.Identity -import Control.Monad.Primitive -import Control.Monad.Reader -import Control.Monad.Ref -import Control.Monad.State.Strict -import Data.Bits -import Data.Coerce -import Data.Dependent.Map (DMap, DSum (..)) -import qualified Data.Dependent.Map as DMap -import Data.Functor.Compose -import Data.Functor.Misc -import Data.IntMap.Strict (IntMap) -import qualified Data.IntMap.Strict as IntMap -import Data.Map (Map) -import qualified Data.Map as Map -import Data.Monoid ((<>)) -import Data.Proxy -import qualified Data.Semigroup as S -import Data.Some (Some(Some)) -import Data.Type.Equality -import Data.Unique.Tag - -import GHC.Exts (Any) -import Unsafe.Coerce - ---TODO: Make this module type-safe - -newtype TagMap (f :: * -> *) = TagMap (IntMap Any) - -newtype RequesterData f = RequesterData (TagMap (Entry f)) - -data RequesterDataKey a where - RequesterDataKey_Single :: {-# UNPACK #-} !(MyTag (Single a)) -> RequesterDataKey a - RequesterDataKey_Multi :: {-# UNPACK #-} !(MyTag Multi) -> {-# UNPACK #-} !Int -> !(RequesterDataKey a) -> RequesterDataKey a --TODO: Don't put a second Int here (or in the other Multis); use a single Int instead - RequesterDataKey_Multi2 :: {-# UNPACK #-} !(MyTag (Multi2 k)) -> !(Some k) -> {-# UNPACK #-} !Int -> !(RequesterDataKey a) -> RequesterDataKey a - RequesterDataKey_Multi3 :: {-# UNPACK #-} !(MyTag Multi3) -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> !(RequesterDataKey a) -> RequesterDataKey a - -singletonRequesterData :: RequesterDataKey a -> f a -> RequesterData f -singletonRequesterData rdk v = case rdk of - RequesterDataKey_Single k -> RequesterData $ singletonTagMap k $ Entry v - RequesterDataKey_Multi k k' k'' -> RequesterData $ singletonTagMap k $ Entry $ IntMap.singleton k' $ singletonRequesterData k'' v - RequesterDataKey_Multi2 k k' k'' k''' -> RequesterData $ singletonTagMap k $ Entry $ Map.singleton k' $ IntMap.singleton k'' $ singletonRequesterData k''' v - RequesterDataKey_Multi3 k k' k'' k''' -> RequesterData $ singletonTagMap k $ Entry $ IntMap.singleton k' $ IntMap.singleton k'' $ singletonRequesterData k''' v - -requesterDataToList :: RequesterData f -> [DSum RequesterDataKey f] -requesterDataToList (RequesterData m) = do - k :=> Entry e <- tagMapToList m - case myKeyType k of - MyTagType_Single -> return $ RequesterDataKey_Single k :=> e - MyTagType_Multi -> do - (k', e') <- IntMap.toList e - k'' :=> e'' <- requesterDataToList e' - return $ RequesterDataKey_Multi k k' k'' :=> e'' - MyTagType_Multi2 -> do - (k', e') <- Map.toList e - (k'', e'') <- IntMap.toList e' - k''' :=> e''' <- requesterDataToList e'' - return $ RequesterDataKey_Multi2 k k' k'' k''' :=> e''' - MyTagType_Multi3 -> do - (k', e') <- IntMap.toList e - (k'', e'') <- IntMap.toList e' - k''' :=> e''' <- requesterDataToList e'' - return $ RequesterDataKey_Multi3 k k' k'' k''' :=> e''' - -singletonTagMap :: forall f a. MyTag a -> f a -> TagMap f -singletonTagMap (MyTag k) v = TagMap $ IntMap.singleton k $ (unsafeCoerce :: f a -> Any) v - -tagMapToList :: forall f. TagMap f -> [DSum MyTag f] -tagMapToList (TagMap m) = f <$> IntMap.toList m - where f :: (Int, Any) -> DSum MyTag f - f (k, v) = MyTag k :=> (unsafeCoerce :: Any -> f a) v - -traverseTagMapWithKey :: forall t f g. Applicative t => (forall a. MyTag a -> f a -> t (g a)) -> TagMap f -> t (TagMap g) -traverseTagMapWithKey f (TagMap m) = TagMap <$> IntMap.traverseWithKey g m - where - g :: Int -> Any -> t Any - g k v = (unsafeCoerce :: g a -> Any) <$> f (MyTag k) ((unsafeCoerce :: Any -> f a) v) - --- | Runs in reverse to accommodate for the fact that we accumulate it in reverse -traverseRequesterData :: forall m request response. Applicative m => (forall a. request a -> m (response a)) -> RequesterData request -> m (RequesterData response) -traverseRequesterData f (RequesterData m) = RequesterData <$> traverseTagMapWithKey go m --TODO: reverse this, since our tags are in reverse order - where go :: forall x. MyTag x -> Entry request x -> m (Entry response x) - go k (Entry request) = Entry <$> case myKeyType k of - MyTagType_Single -> f request - MyTagType_Multi -> traverse (traverseRequesterData f) request - MyTagType_Multi2 -> traverse (traverse (traverseRequesterData f)) request - MyTagType_Multi3 -> traverse (traverse (traverseRequesterData f)) request - --- | 'traverseRequesterData' with its arguments flipped -forRequesterData :: forall request response m. Applicative m => RequesterData request -> (forall a. request a -> m (response a)) -> m (RequesterData response) -forRequesterData r f = traverseRequesterData f r - -data MyTagType :: * -> * where - MyTagType_Single :: MyTagType (Single a) - MyTagType_Multi :: MyTagType Multi - MyTagType_Multi2 :: MyTagType (Multi2 k) - MyTagType_Multi3 :: MyTagType Multi3 - -myKeyType :: MyTag x -> MyTagType x -myKeyType (MyTag k) = case k .&. 0x3 of - 0x0 -> unsafeCoerce MyTagType_Single - 0x1 -> unsafeCoerce MyTagType_Multi - 0x2 -> unsafeCoerce MyTagType_Multi2 - 0x3 -> unsafeCoerce MyTagType_Multi3 - t -> error $ "Reflex.Requester.Base.myKeyType: no such key type" <> show t - -data Single a -data Multi -data Multi2 (k :: * -> *) -data Multi3 - -class MyTagTypeOffset x where - myTagTypeOffset :: proxy x -> Int - -instance MyTagTypeOffset (Single a) where - myTagTypeOffset _ = 0x0 - -instance MyTagTypeOffset Multi where - myTagTypeOffset _ = 0x1 - -instance MyTagTypeOffset (Multi2 k) where - myTagTypeOffset _ = 0x2 - -instance MyTagTypeOffset Multi3 where - myTagTypeOffset _ = 0x3 - -type family EntryContents request a where - EntryContents request (Single a) = request a - EntryContents request Multi = IntMap (RequesterData request) - EntryContents request (Multi2 k) = Map (Some k) (IntMap (RequesterData request)) - EntryContents request Multi3 = IntMap (IntMap (RequesterData request)) - -newtype Entry request x = Entry { unEntry :: EntryContents request x } - -{-# INLINE singleEntry #-} -singleEntry :: f a -> Entry f (Single a) -singleEntry = Entry - -{-# INLINE multiEntry #-} -multiEntry :: IntMap (RequesterData f) -> Entry f Multi -multiEntry = Entry - -{-# INLINE unMultiEntry #-} -unMultiEntry :: Entry f Multi -> IntMap (RequesterData f) -unMultiEntry = unEntry - --- | We use a hack here to pretend we have x ~ request a; we don't want to use a GADT, because GADTs (even with zero-size existential contexts) can't be newtypes --- WARNING: This type should never be exposed. In particular, this is extremely unsound if a MyTag from one run of runRequesterT is ever compared against a MyTag from another -newtype MyTag x = MyTag Int deriving (Show, Eq, Ord, Enum) - -newtype MyTagWrap (f :: * -> *) x = MyTagWrap Int deriving (Show, Eq, Ord, Enum) - -{-# INLINE castMyTagWrap #-} -castMyTagWrap :: MyTagWrap f (Entry f x) -> MyTagWrap g (Entry g x) -castMyTagWrap = coerce - -instance GEq MyTag where - (MyTag a) `geq` (MyTag b) = - if a == b - then Just $ unsafeCoerce Refl - else Nothing - -instance GCompare MyTag where - (MyTag a) `gcompare` (MyTag b) = - case a `compare` b of - LT -> GLT - EQ -> unsafeCoerce GEQ - GT -> GGT - -instance GEq (MyTagWrap f) where - (MyTagWrap a) `geq` (MyTagWrap b) = - if a == b - then Just $ unsafeCoerce Refl - else Nothing - -instance GCompare (MyTagWrap f) where - (MyTagWrap a) `gcompare` (MyTagWrap b) = - case a `compare` b of - LT -> GLT - EQ -> unsafeCoerce GEQ - GT -> GGT - -data RequesterState t (request :: * -> *) = RequesterState - { _requesterState_nextMyTag :: {-# UNPACK #-} !Int -- Starts at -4 and goes down by 4 each time, to accommodate two 'type' bits at the bottom - , _requesterState_requests :: ![(Int, Event t Any)] - } - --- | A basic implementation of 'Requester'. -newtype RequesterT t request (response :: * -> *) m a = RequesterT { unRequesterT :: StateT (RequesterState t request) (ReaderT (EventSelectorInt t Any) m) a } - deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException --- MonadAsyncException can't be derived on ghc-8.0.1; we use base-4.9.1 as a proxy for ghc-8.0.2 -#if MIN_VERSION_base(4,9,1) - , MonadAsyncException -#endif - ) - -deriving instance MonadSample t m => MonadSample t (RequesterT t request response m) -deriving instance MonadHold t m => MonadHold t (RequesterT t request response m) -deriving instance PostBuild t m => PostBuild t (RequesterT t request response m) -deriving instance TriggerEvent t m => TriggerEvent t (RequesterT t request response m) - -instance PrimMonad m => PrimMonad (RequesterT t request response m) where - type PrimState (RequesterT t request response m) = PrimState m - primitive = lift . primitive - --- TODO: Monoid and Semigroup can likely be derived once StateT has them. -instance (Monoid a, Monad m) => Monoid (RequesterT t request response m a) where - mempty = pure mempty - mappend = liftA2 mappend - -instance (S.Semigroup a, Monad m) => S.Semigroup (RequesterT t request response m a) where - (<>) = liftA2 (S.<>) - - --- | Run a 'RequesterT' action. The resulting 'Event' will fire whenever --- requests are made, and responses should be provided in the input 'Event'. --- The 'Tag' keys will be used to return the responses to the same place the --- requests were issued. -runRequesterT :: (Reflex t, Monad m) - => RequesterT t request response m a - -> Event t (RequesterData response) --TODO: This DMap will be in reverse order, so we need to make sure the caller traverses it in reverse - -> m (a, Event t (RequesterData request)) --TODO: we need to hide these 'MyTag's here, because they're unsafe to mix in the wild -runRequesterT (RequesterT a) responses = do - (result, s) <- runReaderT (runStateT a $ RequesterState (-4) []) $ fanInt $ - coerceEvent responses - return (result, fmapCheap (RequesterData . TagMap) $ mergeInt $ IntMap.fromDistinctAscList $ _requesterState_requests s) - --- | Map a function over the request and response of a 'RequesterT' -withRequesterT - :: (Reflex t, MonadFix m) - => (forall x. req x -> req' x) -- ^ The function to map over the request - -> (forall x. rsp' x -> rsp x) -- ^ The function to map over the response - -> RequesterT t req rsp m a -- ^ The internal 'RequesterT' whose input and output will be transformed - -> RequesterT t req' rsp' m a -- ^ The resulting 'RequesterT' -withRequesterT freq frsp child = do - rec let rsp = fmap (runIdentity . traverseRequesterData (Identity . frsp)) rsp' - (a, req) <- lift $ runRequesterT child rsp - rsp' <- fmap (flip selectInt 0 . fanInt . fmapCheap unMultiEntry) $ requesting' $ - fmapCheap (multiEntry . IntMap.singleton 0) $ fmap (runIdentity . traverseRequesterData (Identity . freq)) req - return a - -instance (Reflex t, Monad m) => Requester t (RequesterT t request response m) where - type Request (RequesterT t request response m) = request - type Response (RequesterT t request response m) = response - requesting = fmap coerceEvent . responseFromTag . castMyTagWrap <=< tagRequest . (coerceEvent :: Event t (request a) -> Event t (Entry request (Single a))) - requesting_ = void . tagRequest . fmapCheap singleEntry - -{-# INLINE tagRequest #-} -tagRequest :: forall m x t request response. (Monad m, MyTagTypeOffset x) => Event t (Entry request x) -> RequesterT t request response m (MyTagWrap request (Entry request x)) -tagRequest req = do - old <- RequesterT get - let n = _requesterState_nextMyTag old .|. myTagTypeOffset (Proxy :: Proxy x) - t = MyTagWrap n - RequesterT $ put $ RequesterState - { _requesterState_nextMyTag = _requesterState_nextMyTag old - 0x4 - , _requesterState_requests = (n, (unsafeCoerce :: Event t (Entry request x) -> Event t Any) req) : _requesterState_requests old - } - return t - -{-# INLINE responseFromTag #-} -responseFromTag :: Monad m => MyTagWrap response (Entry response x) -> RequesterT t request response m (Event t (Entry response x)) -responseFromTag (MyTagWrap t) = do - responses :: EventSelectorInt t Any <- RequesterT ask - return $ (unsafeCoerce :: Event t Any -> Event t (Entry response x)) $ selectInt responses t - -instance MonadTrans (RequesterT t request response) where - lift = RequesterT . lift . lift - -instance PerformEvent t m => PerformEvent t (RequesterT t request response m) where - type Performable (RequesterT t request response m) = Performable m - performEvent_ = lift . performEvent_ - performEvent = lift . performEvent - -instance MonadRef m => MonadRef (RequesterT t request response m) where - type Ref (RequesterT t request response m) = Ref m - newRef = lift . newRef - readRef = lift . readRef - writeRef r = lift . writeRef r - -instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (RequesterT t request response m) where - newEventWithTrigger = lift . newEventWithTrigger - newFanEventWithTrigger f = lift $ newFanEventWithTrigger f - -instance MonadReader r m => MonadReader r (RequesterT t request response m) where - ask = lift ask - local f (RequesterT a) = RequesterT $ mapStateT (mapReaderT $ local f) a - reader = lift . reader - -instance (Reflex t, Adjustable t m, MonadHold t m, MonadFix m) => Adjustable t (RequesterT t request response m) where - runWithReplace = runWithReplaceRequesterTWith $ \dm0 dm' -> lift $ runWithReplace dm0 dm' - traverseIntMapWithKeyWithAdjust = traverseIntMapWithKeyWithAdjustRequesterTWith (\f dm0 dm' -> lift $ traverseIntMapWithKeyWithAdjust f dm0 dm') patchIntMapNewElementsMap mergeIntIncremental - {-# INLINABLE traverseDMapWithKeyWithAdjust #-} - traverseDMapWithKeyWithAdjust = traverseDMapWithKeyWithAdjustRequesterTWith (\f dm0 dm' -> lift $ traverseDMapWithKeyWithAdjust f dm0 dm') mapPatchDMap weakenPatchDMapWith patchMapNewElementsMap mergeMapIncremental - traverseDMapWithKeyWithAdjustWithMove = traverseDMapWithKeyWithAdjustRequesterTWith (\f dm0 dm' -> lift $ traverseDMapWithKeyWithAdjustWithMove f dm0 dm') mapPatchDMapWithMove weakenPatchDMapWithMoveWith patchMapWithMoveNewElementsMap mergeMapIncrementalWithMove - -requesting' :: (MyTagTypeOffset x, Monad m) => Event t (Entry request x) -> RequesterT t request response m (Event t (Entry response x)) -requesting' = responseFromTag . castMyTagWrap <=< tagRequest - -{-# INLINABLE runWithReplaceRequesterTWith #-} -runWithReplaceRequesterTWith :: forall m t request response a b. (Reflex t, MonadHold t m - , MonadFix m - ) - => (forall a' b'. m a' -> Event t (m b') -> RequesterT t request response m (a', Event t b')) - -> RequesterT t request response m a - -> Event t (RequesterT t request response m b) - -> RequesterT t request response m (a, Event t b) -runWithReplaceRequesterTWith f a0 a' = do - rec na' <- numberOccurrencesFrom 1 a' - responses <- fmap (fmapCheap unMultiEntry) $ requesting' $ fmapCheap multiEntry $ switchPromptlyDyn requests --TODO: Investigate whether we can really get rid of the prompt stuff here - let responses' = fanInt responses - ((result0, requests0), v') <- f (runRequesterT a0 (selectInt responses' 0)) $ fmapCheap (\(n, a) -> fmap ((,) n) $ runRequesterT a $ selectInt responses' n) na' - requests <- holdDyn (fmapCheap (IntMap.singleton 0) requests0) $ fmapCheap (\(n, (_, reqs)) -> fmapCheap (IntMap.singleton n) reqs) v' - return (result0, fmapCheap (fst . snd) v') - -{-# INLINE traverseIntMapWithKeyWithAdjustRequesterTWith #-} -traverseIntMapWithKeyWithAdjustRequesterTWith :: forall t request response m v v' p. - ( Reflex t - , MonadHold t m - , PatchTarget (p (Event t (IntMap (RequesterData request)))) ~ IntMap (Event t (IntMap (RequesterData request))) - , Patch (p (Event t (IntMap (RequesterData request)))) - , Functor p - , MonadFix m - ) - => ( (IntMap.Key -> (IntMap.Key, v) -> m (Event t (IntMap (RequesterData request)), v')) - -> IntMap (IntMap.Key, v) - -> Event t (p (IntMap.Key, v)) - -> RequesterT t request response m (IntMap (Event t (IntMap (RequesterData request)), v'), Event t (p (Event t (IntMap (RequesterData request)), v'))) - ) - -> (p (Event t (IntMap (RequesterData request))) -> IntMap (Event t (IntMap (RequesterData request)))) - -> (Incremental t (p (Event t (IntMap (RequesterData request)))) -> Event t (IntMap (IntMap (RequesterData request)))) - -> (IntMap.Key -> v -> RequesterT t request response m v') - -> IntMap v - -> Event t (p v) - -> RequesterT t request response m (IntMap v', Event t (p v')) -traverseIntMapWithKeyWithAdjustRequesterTWith base patchNewElements mergePatchIncremental f dm0 dm' = do - rec response <- requesting' $ fmapCheap pack $ promptRequests `mappend` mergePatchIncremental requests --TODO: Investigate whether we can really get rid of the prompt stuff here - let responses :: EventSelectorInt t (IntMap (RequesterData response)) - responses = fanInt $ fmapCheap unpack response - unpack :: Entry response Multi3 -> IntMap (IntMap (RequesterData response)) - unpack = unEntry - pack :: IntMap (IntMap (RequesterData request)) -> Entry request Multi3 - pack = Entry - f' :: IntMap.Key -> (Int, v) -> m (Event t (IntMap (RequesterData request)), v') - f' k (n, v) = do - (result, myRequests) <- runRequesterT (f k v) $ mapMaybeCheap (IntMap.lookup n) $ selectInt responses k --TODO: Instead of doing mapMaybeCheap, can we share a fanInt across all instances of a given key, or at least the ones that are adjacent in time? - return (fmapCheap (IntMap.singleton n) myRequests, result) - ndm' <- numberOccurrencesFrom 1 dm' - (children0, children') <- base f' (fmap ((,) 0) dm0) $ fmap (\(n, dm) -> fmap ((,) n) dm) ndm' --TODO: Avoid this somehow, probably by adding some sort of per-cohort information passing to Adjustable - let result0 = fmap snd children0 - result' = fforCheap children' $ fmap snd - requests0 :: IntMap (Event t (IntMap (RequesterData request))) - requests0 = fmap fst children0 - requests' :: Event t (p (Event t (IntMap (RequesterData request)))) - requests' = fforCheap children' $ fmap fst - promptRequests :: Event t (IntMap (IntMap (RequesterData request))) - promptRequests = coincidence $ fmapCheap (mergeInt . patchNewElements) requests' --TODO: Create a mergeIncrementalPromptly, and use that to eliminate this 'coincidence' - requests <- holdIncremental requests0 requests' - return (result0, result') - -{-# INLINE traverseDMapWithKeyWithAdjustRequesterTWith #-} -traverseDMapWithKeyWithAdjustRequesterTWith :: forall k t request response m v v' p p'. - ( GCompare k - , Reflex t - , MonadHold t m - , PatchTarget (p' (Some k) (Event t (IntMap (RequesterData request)))) ~ Map (Some k) (Event t (IntMap (RequesterData request))) - , Patch (p' (Some k) (Event t (IntMap (RequesterData request)))) - , MonadFix m - ) - => (forall k' v1 v2. GCompare k' - => (forall a. k' a -> v1 a -> m (v2 a)) - -> DMap k' v1 - -> Event t (p k' v1) - -> RequesterT t request response m (DMap k' v2, Event t (p k' v2)) - ) - -> (forall v1 v2. (forall a. v1 a -> v2 a) -> p k v1 -> p k v2) - -> (forall v1 v2. (forall a. v1 a -> v2) -> p k v1 -> p' (Some k) v2) - -> (forall v2. p' (Some k) v2 -> Map (Some k) v2) - -> (forall a. Incremental t (p' (Some k) (Event t a)) -> Event t (Map (Some k) a)) - -> (forall a. k a -> v a -> RequesterT t request response m (v' a)) - -> DMap k v - -> Event t (p k v) - -> RequesterT t request response m (DMap k v', Event t (p k v')) -traverseDMapWithKeyWithAdjustRequesterTWith base mapPatch weakenPatchWith patchNewElements mergePatchIncremental f dm0 dm' = do - rec response <- requesting' $ fmapCheap pack $ promptRequests `mappend` mergePatchIncremental requests --TODO: Investigate whether we can really get rid of the prompt stuff here - let responses :: EventSelector t (Const2 (Some k) (IntMap (RequesterData response))) - responses = fanMap $ fmapCheap unpack response - unpack :: Entry response (Multi2 k) -> Map (Some k) (IntMap (RequesterData response)) - unpack = unEntry - pack :: Map (Some k) (IntMap (RequesterData request)) -> Entry request (Multi2 k) - pack = Entry - f' :: forall a. k a -> Compose ((,) Int) v a -> m (Compose ((,) (Event t (IntMap (RequesterData request)))) v' a) - f' k (Compose (n, v)) = do - (result, myRequests) <- runRequesterT (f k v) $ mapMaybeCheap (IntMap.lookup n) $ select responses (Const2 (Some k)) - return $ Compose (fmapCheap (IntMap.singleton n) myRequests, result) - ndm' <- numberOccurrencesFrom 1 dm' - (children0, children') <- base f' (DMap.map (\v -> Compose (0, v)) dm0) $ fmap (\(n, dm) -> mapPatch (\v -> Compose (n, v)) dm) ndm' - let result0 = DMap.map (snd . getCompose) children0 - result' = fforCheap children' $ mapPatch $ snd . getCompose - requests0 :: Map (Some k) (Event t (IntMap (RequesterData request))) - requests0 = weakenDMapWith (fst . getCompose) children0 - requests' :: Event t (p' (Some k) (Event t (IntMap (RequesterData request)))) - requests' = fforCheap children' $ weakenPatchWith $ fst . getCompose - promptRequests :: Event t (Map (Some k) (IntMap (RequesterData request))) - promptRequests = coincidence $ fmapCheap (mergeMap . patchNewElements) requests' --TODO: Create a mergeIncrementalPromptly, and use that to eliminate this 'coincidence' - requests <- holdIncremental requests0 requests' - return (result0, result') - -data Decoder rawResponse response = - forall a. Decoder (RequesterDataKey a) (rawResponse -> response a) - --- | Matches incoming responses with previously-sent requests --- and uses the provided request "decoder" function to process --- incoming responses. -matchResponsesWithRequests - :: forall t rawRequest rawResponse request response m. - ( MonadFix m - , MonadHold t m - , Reflex t - ) - => (forall a. request a -> (rawRequest, rawResponse -> response a)) - -- ^ Given a request (from 'Requester'), produces the wire format of the - -- request and a function used to process the associated response - -> Event t (RequesterData request) - -- ^ The outgoing requests - -> Event t (Int, rawResponse) - -- ^ The incoming responses, tagged by an identifying key - -> m ( Event t (Map Int rawRequest) - , Event t (RequesterData response) - ) - -- ^ A map of outgoing wire-format requests and an event of responses keyed - -- by the 'RequesterData' key of the associated outgoing request -matchResponsesWithRequests f send recv = do - rec nextId <- hold 1 $ fmap (\(next, _, _) -> next) outgoing - waitingFor :: Incremental t (PatchMap Int (Decoder rawResponse response)) <- - holdIncremental mempty $ leftmost - [ fmap (\(_, outstanding, _) -> outstanding) outgoing - , snd <$> incoming - ] - let outgoing = processOutgoing nextId send - incoming = processIncoming waitingFor recv - return (fmap (\(_, _, rawReqs) -> rawReqs) outgoing, fst <$> incoming) - where - -- Tags each outgoing request with an identifying integer key - -- and returns the next available key, a map of response decoders - -- for requests for which there are outstanding responses, and the - -- raw requests to be sent out. - processOutgoing - :: Behavior t Int - -- The next available key - -> Event t (RequesterData request) - -- The outgoing request - -> Event t ( Int - , PatchMap Int (Decoder rawResponse response) - , Map Int rawRequest ) - -- The new next-available-key, a map of requests expecting responses, and the tagged raw requests - processOutgoing nextId out = flip pushAlways out $ \dm -> do - oldNextId <- sample nextId - let (result, newNextId) = flip runState oldNextId $ forM (requesterDataToList dm) $ \(k :=> v) -> do - n <- get - put $ succ n - let (rawReq, rspF) = f v - return (n, rawReq, Decoder k rspF) - patchWaitingFor = PatchMap $ Map.fromList $ - (\(n, _, dec) -> (n, Just dec)) <$> result - toSend = Map.fromList $ (\(n, rawReq, _) -> (n, rawReq)) <$> result - return (newNextId, patchWaitingFor, toSend) - -- Looks up the each incoming raw response in a map of response - -- decoders and returns the decoded response and a patch that can - -- be used to clear the ID of the consumed response out of the queue - -- of expected responses. - processIncoming - :: Incremental t (PatchMap Int (Decoder rawResponse response)) - -- A map of outstanding expected responses - -> Event t (Int, rawResponse) - -- A incoming response paired with its identifying key - -> Event t (RequesterData response, PatchMap Int v) - -- The decoded response and a patch that clears the outstanding responses queue - processIncoming waitingFor inc = flip push inc $ \(n, rawRsp) -> do - wf <- sample $ currentIncremental waitingFor - case Map.lookup n wf of - Nothing -> return Nothing -- TODO How should lookup failures be handled here? They shouldn't ever happen.. - Just (Decoder k rspF) -> do - let rsp = rspF rawRsp - return $ Just - ( singletonRequesterData k rsp - , PatchMap $ Map.singleton n Nothing - ) +import Reflex.Requester.Base.Internal diff --git a/src/Reflex/Requester/Base/Internal.hs b/src/Reflex/Requester/Base/Internal.hs new file mode 100644 index 00000000..1886b3c2 --- /dev/null +++ b/src/Reflex/Requester/Base/Internal.hs @@ -0,0 +1,309 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE EmptyDataDecls #-} +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE Rank2Types #-} +{-# LANGUAGE RecursiveDo #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE InstanceSigs #-} +#ifdef USE_REFLEX_OPTIMIZER +{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-} +#endif +module Reflex.Requester.Base.Internal where + +import Reflex.Class +import Reflex.Adjustable.Class +import Reflex.Host.Class +import Reflex.PerformEvent.Class +import Reflex.PostBuild.Class +import Reflex.Requester.Class +import Reflex.TriggerEvent.Class +import Reflex.EventWriter.Base +import Reflex.EventWriter.Class + +import Control.Applicative (liftA2) +import Control.Monad.Exception +import Control.Monad.Fail +import Control.Monad.Identity +import Control.Monad.Primitive +import Control.Monad.Reader +import Control.Monad.Ref +import Control.Monad.State +import Data.Coerce +import Data.Dependent.Map (DSum (..)) +import qualified Data.List.NonEmpty as NonEmpty +import Data.List.NonEmpty.Deferred (NonEmptyDeferred) +import qualified Data.List.NonEmpty.Deferred as NonEmptyDeferred +import Data.Monoid ((<>)) +import qualified Data.Semigroup as S +import Data.Some (Some(Some)) +import Data.Type.Equality +import Data.TagMap (TagMap) +import qualified Data.TagMap as TagMap +import Reflex.FanTag +import Data.Unique.Tag.Local +import Data.GADT.Compare +import Data.Witherable + +import Unsafe.Coerce + +import Debug.Trace + +import Data.Map (Map) +import qualified Data.Map as Map +import qualified Data.IntMap as IntMap +import GHC.Exts (Any) + +data RequestData ps request = forall s. RequestData !(TagGen ps s) !(NonEmptyDeferred (RequestEnvelope s request)) + +data RequestEnvelope s request = forall a. RequestEnvelope {-# UNPACK #-} !(Maybe (Tag s a)) !(request a) + +data ResponseData ps response = forall s. ResponseData !(TagGen ps s) !(TagMap s response) + +traverseRequesterData :: forall m request response. Applicative m => (forall a. request a -> m (response a)) -> RequestData (PrimState m) request -> m (ResponseData (PrimState m) response) +traverseRequesterData f (RequestData tg es) = ResponseData tg . TagMap.fromList <$> wither g (NonEmpty.toList $ NonEmptyDeferred.toNonEmpty es) + where g (RequestEnvelope mt req) = case mt of + Just t -> Just . (t :=>) <$> f req + Nothing -> Nothing <$ f req + +-- | 'traverseRequesterData' with its arguments flipped +forRequesterData :: forall request response m. Applicative m => RequestData (PrimState m) request -> (forall a. request a -> m (response a)) -> m (ResponseData (PrimState m) response) +forRequesterData r f = traverseRequesterData f r + +runRequesterT :: forall t request response m a + . ( Reflex t + , PrimMonad m + , MonadFix m + ) + => RequesterT t request response m a + -> Event t (ResponseData (PrimState m) response) + -> m (a, Event t (RequestData (PrimState m) request)) +runRequesterT (RequesterT a) wrappedResponses = withRequesterInternalT $ \requests -> do + (_, tg) <- RequesterInternalT ask + result <- a + let responses = fforMaybe wrappedResponses $ \(ResponseData tg' m) -> case tg `geq` tg' of + Nothing -> trace ("runRequesterT: bad TagGen: expected " <> show tg <> " but got " <> show tg') Nothing --TODO: Warn somehow + Just Refl -> Just m + pure (responses, (result, fmapCheap (RequestData tg) requests)) + +instance MonadTrans (RequesterInternalT s t request response) where + lift = RequesterInternalT . lift . lift + +instance MonadTrans (RequesterT t request response) where + lift a = RequesterT $ RequesterInternalT $ lift $ lift a + +-- | Run a 'RequesterT' action. The resulting 'Event' will fire whenever +-- requests are made, and responses should be provided in the input 'Event'. +-- The 'Tag' keys will be used to return the responses to the same place the +-- requests were issued. +withRequesterInternalT + :: forall t m request response a. + ( Reflex t + , PrimMonad m + , MonadFix m + ) + => (Event t (NonEmptyDeferred (RequestEnvelope FakeRequesterStatePhantom request)) -> RequesterInternalT FakeRequesterStatePhantom t request response m (Event t (TagMap FakeRequesterStatePhantom response), a)) + -> m a +withRequesterInternalT f = do + stg <- newTagGen + let tg = case stg of + Some tg' -> (unsafeCoerce :: TagGen (PrimState m) s -> TagGen (PrimState m) FakeRequesterStatePhantom) tg' + rec let RequesterInternalT a = (unsafeCoerce :: RequesterInternalT s t request response m (Event t (TagMap FakeRequesterStatePhantom response), a) -> RequesterInternalT FakeRequesterStatePhantom t request response m (Event t (TagMap FakeRequesterStatePhantom response), a)) $ f requests + ((responses, result), requests) <- runEventWriterT $ runReaderT a (fanTag responses, tg) + pure result + +-- This is because using forall ruins inlining +data FakeRequesterStatePhantom + +newtype RequesterT t (request :: * -> *) (response :: * -> *) m a = RequesterT { unRequesterT :: RequesterInternalT FakeRequesterStatePhantom t request response m a } + deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException) + +newtype RequesterInternalT s t request response m a = RequesterInternalT { unRequesterInternalT :: ReaderT (EventSelectorTag t s response, TagGen (PrimState m) s) (EventWriterT t (NonEmptyDeferred (RequestEnvelope s request)) m) a } + deriving + ( Functor, Applicative, Monad, MonadFix, MonadIO, MonadException +#if MIN_VERSION_base(4,9,1) + , MonadAsyncException +#endif + ) + +-- I don't think this can actually be supported without unsafeCoercing around the fact that the phantoms don't match up. In fact, implementations could probably supply `unmask` functions that would actually do the wrong thing. +-- #if MIN_VERSION_base(4,9,1) +-- instance MonadAsyncException m => MonadAsyncException (RequesterT t request response m) where +-- mask f = RequesterT $ mask $ \unmask -> unRequesterT $ f $ \x -> RequesterT $ unmask $ unRequesterT x +-- #endif + +deriving instance MonadSample t m => MonadSample t (RequesterT t request response m) +deriving instance MonadHold t m => MonadHold t (RequesterT t request response m) +deriving instance PostBuild t m => PostBuild t (RequesterT t request response m) +deriving instance TriggerEvent t m => TriggerEvent t (RequesterT t request response m) + +deriving instance MonadSample t m => MonadSample t (RequesterInternalT s t request response m) +deriving instance MonadHold t m => MonadHold t (RequesterInternalT s t request response m) +deriving instance PostBuild t m => PostBuild t (RequesterInternalT s t request response m) +deriving instance TriggerEvent t m => TriggerEvent t (RequesterInternalT s t request response m) + +instance PrimMonad m => PrimMonad (RequesterT t request response m) where + type PrimState (RequesterT t request response m) = PrimState m + primitive = lift . primitive + +-- TODO: Monoid and Semigroup can likely be derived once StateT has them. +instance (Monoid a, Monad m) => Monoid (RequesterT t request response m a) where + mempty = pure mempty + mappend = liftA2 mappend + +instance (S.Semigroup a, Monad m) => S.Semigroup (RequesterT t request response m a) where + (<>) = liftA2 (S.<>) + +instance PerformEvent t m => PerformEvent t (RequesterT t request response m) where + type Performable (RequesterT t request response m) = Performable m + performEvent_ = lift . performEvent_ + performEvent = lift . performEvent + +instance MonadRef m => MonadRef (RequesterT t request response m) where + type Ref (RequesterT t request response m) = Ref m + newRef = lift . newRef + readRef = lift . readRef + writeRef r = lift . writeRef r + +instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (RequesterT t request response m) where + newEventWithTrigger = lift . newEventWithTrigger + newFanEventWithTrigger f = lift $ newFanEventWithTrigger f + +instance MonadReader r m => MonadReader r (RequesterT t request response m) where + ask = lift ask + local f (RequesterT a) = RequesterT $ RequesterInternalT $ mapReaderT (mapEventWriterT $ local f) $ unRequesterInternalT a + reader = lift . reader + +instance (Reflex t, PrimMonad m) => Requester t (RequesterT t request response m) where + type Request (RequesterT t request response m) = request + type Response (RequesterT t request response m) = response + requesting e = RequesterT $ requesting e + requesting_ e = RequesterT $ requesting_ e + +instance (Reflex t, PrimMonad m) => Requester t (RequesterInternalT s t request response m) where + type Request (RequesterInternalT s t request response m) = request + type Response (RequesterInternalT s t request response m) = response + requesting e = RequesterInternalT $ do + (s, tg) <- ask + t <- lift $ lift $ newTag tg + tellEvent $ fmapCheap (NonEmptyDeferred.singleton . RequestEnvelope (Just t)) e + pure $ selectTag s t + requesting_ e = RequesterInternalT $ do + tellEvent $ fmapCheap (NonEmptyDeferred.singleton . RequestEnvelope Nothing) e + +instance (Adjustable t m, MonadHold t m) => Adjustable t (RequesterInternalT s t request response m) where + {-# INLINE runWithReplace #-} + runWithReplace (RequesterInternalT a0) a' = RequesterInternalT $ runWithReplace a0 (coerceEvent a') + {-# INLINE traverseIntMapWithKeyWithAdjust #-} + traverseIntMapWithKeyWithAdjust f dm0 dm' = RequesterInternalT $ traverseIntMapWithKeyWithAdjust (coerce . f) dm0 dm' + {-# INLINE traverseDMapWithKeyWithAdjust #-} + traverseDMapWithKeyWithAdjust f dm0 dm' = RequesterInternalT $ traverseDMapWithKeyWithAdjust (coerce . f) dm0 dm' + {-# INLINE traverseDMapWithKeyWithAdjustWithMove #-} + traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = RequesterInternalT $ traverseDMapWithKeyWithAdjustWithMove (coerce . f) dm0 dm' + +instance (Adjustable t m, MonadHold t m) => Adjustable t (RequesterT t request response m) where + {-# INLINE runWithReplace #-} + runWithReplace (RequesterT a0) a' = RequesterT $ runWithReplace a0 (fmapCheap unRequesterT a') + {-# INLINE traverseIntMapWithKeyWithAdjust #-} + traverseIntMapWithKeyWithAdjust f dm0 dm' = RequesterT $ traverseIntMapWithKeyWithAdjust (\k v -> unRequesterT $ f k v) dm0 dm' + {-# INLINE traverseDMapWithKeyWithAdjust #-} + traverseDMapWithKeyWithAdjust f dm0 dm' = RequesterT $ traverseDMapWithKeyWithAdjust (\k v -> unRequesterT $ f k v) dm0 dm' + {-# INLINE traverseDMapWithKeyWithAdjustWithMove #-} + traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = RequesterT $ traverseDMapWithKeyWithAdjustWithMove (\k v -> unRequesterT $ f k v) dm0 dm' + + +data Decoder rawResponse response = + forall s a. Decoder (Tag s a) (rawResponse -> response a) + +-- | Matches incoming responses with previously-sent requests +-- and uses the provided request "decoder" function to process +-- incoming responses. +matchResponsesWithRequests + :: forall t rawRequest rawResponse request response m. + ( MonadFix m + , MonadHold t m + , PrimMonad m + , Reflex t + ) + => (forall a. request a -> (rawRequest, rawResponse -> response a)) + -- ^ Given a request (from 'Requester'), produces the wire format of the + -- request and a function used to process the associated response + -> Event t (RequestData (PrimState m) request) + -- ^ The outgoing requests + -> Event t (Int, rawResponse) + -- ^ The incoming responses, tagged by an identifying key + -> m ( Event t (Map Int rawRequest) + , Event t (ResponseData (PrimState m) response) + ) + -- ^ A map of outgoing wire-format requests and an event of responses keyed + -- by the 'RequesterData' key of the associated outgoing request +matchResponsesWithRequests f send recv = withTagGen $ \tagGen -> mdo + waitingFor <- holdIncremental mempty $ + leftmost [ fst <$> outgoing, snd <$> incoming ] + let outgoing = processOutgoing send + incoming = processIncoming waitingFor tagGen recv + return (snd <$> outgoing, fst <$> incoming) + where + + -- Tags each outgoing request with an identifying integer key + -- and returns the next available key, a map of response decoders + -- for requests for which there are outstanding responses, and the + -- raw requests to be sent out. + processOutgoing + :: Event t (RequestData (PrimState m) request) + -- The outgoing request + -> Event t ( PatchMap Int (Decoder rawResponse response) + , Map Int rawRequest ) + -- A map of requests expecting responses, and the tagged raw requests + processOutgoing eventOutgoingRequest = flip pushAlways eventOutgoingRequest $ \(RequestData _ requestEnvelopes) -> do + let results = ffor (requestEnvelopesToDSums requestEnvelopes) $ \(k :=> v) -> + let (rawRequest, responseDecoder) = f v + in (tagId k, rawRequest, Decoder k responseDecoder) + let patchWaitingFor = PatchMap $ Map.fromList $ (\(n, _, dec) -> (n, Just dec)) <$> results + let toSend = Map.fromList $ (\(n, rawRequest, _) -> (n, rawRequest)) <$> results + return (patchWaitingFor, toSend) + + -- Looks up the each incoming raw response in a map of response + -- decoders and returns the decoded response and a patch that can + -- be used to clear the ID of the consumed response out of the queue + -- of expected responses. + processIncoming + :: Incremental t (PatchMap Int (Decoder rawResponse response)) + -- A map of outstanding expected responses + -> TagGen (PrimState m) s + -- A tag generator + -> Event t (Int, rawResponse) + -- A incoming response paired with its identifying key + -> Event t (ResponseData (PrimState m) response, PatchMap Int v) + -- The decoded response and a patch that clears the outstanding responses queue + processIncoming incWaitingFor tagGen inc = flip push inc $ \(n, rawRsp) -> do + waitingFor <- sample $ currentIncremental incWaitingFor + case Map.lookup n waitingFor of + Nothing -> return Nothing -- TODO How should lookup failures be handled here? They shouldn't ever happen.. + Just (Decoder tag responseDecoder) -> + return $ Just + ( singletonResponseData tagGen (unsafeTagFromId n) (responseDecoder rawRsp) + , PatchMap $ Map.singleton n Nothing + ) + +requestEnvelopesToDSums :: forall s request. NonEmptyDeferred (RequestEnvelope s request) -> [DSum (Tag s) request] +requestEnvelopesToDSums requestEnvelopes = catMaybes $ f <$> NonEmptyDeferred.toList requestEnvelopes + where f :: RequestEnvelope s request -> Maybe (DSum (Tag s) request) + f (RequestEnvelope (Just tag) v) = Just (tag :=> v) + f (RequestEnvelope Nothing _) = Nothing + +singletonRequestData :: TagGen ps s -> Maybe (Tag s a) -> request a -> RequestData ps request +singletonRequestData tagGen maybeTag request = + RequestData tagGen $ NonEmptyDeferred.singleton $ RequestEnvelope maybeTag request + +singletonResponseData :: TagGen ps s -> Tag ps s -> response a -> ResponseData ps response +singletonResponseData tagGen tag response = + ResponseData tagGen $ TagMap.singletonTagMap tag response diff --git a/src/Reflex/Requester/Class.hs b/src/Reflex/Requester/Class.hs index db4ddb99..e05204de 100644 --- a/src/Reflex/Requester/Class.hs +++ b/src/Reflex/Requester/Class.hs @@ -40,7 +40,7 @@ class (Reflex t, Monad m) => Requester t m | m -> t where requesting :: Event t (Request m a) -> m (Event t (Response m a)) -- | Emit a request whenever the given 'Event' fires, and ignore all responses. requesting_ :: Event t (Request m a) -> m () - +--TODO: Make Requester polykinded, where Request m and Response m both take an argument of the same kind instance Requester t m => Requester t (ReaderT r m) where type Request (ReaderT r m) = Request m diff --git a/src/Reflex/Spider/Internal.hs b/src/Reflex/Spider/Internal.hs index 2b210f2a..10302ce6 100644 --- a/src/Reflex/Spider/Internal.hs +++ b/src/Reflex/Spider/Internal.hs @@ -113,6 +113,7 @@ import Reflex.PerformEvent.Base (PerformEventT) import qualified Data.ByteString.Char8 as BS8 import System.IO (stderr) import Data.List (isPrefixOf) +import Debug.Trace #endif -- TODO stdout might not be the best channel for debug output @@ -1646,8 +1647,8 @@ fanInt p = unsafePerformIO $ do { subscriberPropagate = \m -> do liftIO $ writeIORef (_fanInt_occRef self) m scheduleIntClear $ _fanInt_occRef self - FastMutableIntMap.forIntersectionWithImmutable_ (_fanInt_subscribers self) m $ \b v -> --TODO: Do we need to know that no subscribers are being added as we traverse? - FastWeakBag.traverse_ b $ \s -> + FastMutableIntMap.forIntersectionWithImmutable_ (_fanInt_subscribers self) m $ \b v -> do --TODO: Do we need to know that no subscribers are being added as we traverse? + FastWeakBag.traverse b $ \s -> do subscriberPropagate s v , subscriberInvalidateHeight = \old -> FastMutableIntMap.for_ (_fanInt_subscribers self) $ \b -> diff --git a/test/Adjustable.hs b/test/Adjustable.hs index 82ea5161..150c3303 100644 --- a/test/Adjustable.hs +++ b/test/Adjustable.hs @@ -22,7 +22,7 @@ main = do -- If the final counter value in the adjusted widgets corresponds to the number of times it has -- been incremented, we know that the networks haven't broken. let expectedCount = length $ ffilter (== Increment) actions - -- let !True = last (last os) == [expectedCount,expectedCount] -- TODO re-enable this test after issue #369 has been resolved + let !True = last (last os) == [expectedCount,expectedCount] return () data PatchMapTestAction @@ -59,4 +59,4 @@ testPatchMapWithMove pulse = do (Map.fromList $ zip [0..] "ab") (fmapMaybe id pulseAction) return () - return result \ No newline at end of file + return result diff --git a/test/EventWriterT.hs b/test/EventWriterT.hs index cd6c3146..2916d809 100644 --- a/test/EventWriterT.hs +++ b/test/EventWriterT.hs @@ -24,26 +24,30 @@ import Test.Run main :: IO () main = do - os1@[[Just [10,9,8,7,6,5,4,3,2,1]]] <- runApp' (unwrapApp testOrdering) $ + os1 <- runApp' (unwrapApp testOrdering) $ [ Just () ] print os1 - os2@[[Just [1,3,5,7,9]],[Nothing,Nothing],[Just [2,4,6,8,10]],[Just [2,4,6,8,10],Nothing]] - <- runApp' (unwrapApp testSimultaneous) $ map Just $ - [ This () - , That () - , This () - , These () () - ] + os2 <- runApp' (unwrapApp testSimultaneous) $ map Just $ + [ This () + , That () + , This () + , These () () + ] print os2 - os3@[[Nothing, Just [2]]] <- runApp' (unwrapApp testMoribundTellEvent) [Just ()] + os3 <- runApp' (unwrapApp testMoribundTellEvent) [Just ()] print os3 - os4@[[Nothing, Just [2]]] <- runApp' (unwrapApp testMoribundTellEventDMap) [Just ()] + os4 <- runApp' (unwrapApp testMoribundTellEventDMap) [Just ()] print os4 - os5@[[Nothing, Just [1, 2]]] <- runApp' (unwrapApp testLiveTellEventDMap) [Just ()] + os5 <- runApp' (unwrapApp testLiveTellEventDMap) [Just ()] print os5 os6 <- runApp' (unwrapApp delayedPulse) [Just ()] print os6 + let ![[Just [10,9,8,7,6,5,4,3,2,1]]] = os1 + let ![[Just [1,3,5,7,9]],[Nothing,Nothing],[Just [2,4,6,8,10]],[Just [2,4,6,8,10],Nothing]] = os2 + let ![[Nothing, Just [2]]] = os3 + let ![[Nothing, Just [2]]] = os4 + let ![[Nothing, Just [1, 2]]] = os5 let ![[Nothing, Nothing]] = os6 return () diff --git a/test/RequesterT.hs b/test/RequesterT.hs index 2f34a586..198df9e8 100644 --- a/test/RequesterT.hs +++ b/test/RequesterT.hs @@ -5,6 +5,7 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecursiveDo #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} @@ -14,8 +15,13 @@ module Main where import Control.Lens hiding (has) import Control.Monad +import Control.Monad.Fail (MonadFail) import Control.Monad.Fix -import Control.Monad.IO.Class (MonadIO) +import Control.Monad.IO.Class (MonadIO, liftIO) +import Data.Constraint.Extras +import Data.Constraint.Extras.TH +import Data.Constraint.Forall +import Control.Monad.Primitive import Data.Constraint.Extras import Data.Constraint.Extras.TH import Data.Constraint.Forall @@ -24,6 +30,7 @@ import Data.Dependent.Sum import Data.Functor.Misc import Data.List (words) import Data.Map (Map) +import Data.Map (Map) import qualified Data.Map as M #if !MIN_VERSION_these(4,11,0) import Data.Semigroup ((<>)) @@ -31,6 +38,9 @@ import Data.Semigroup ((<>)) import Data.Text (Text) import Data.These import Text.Read (readMaybe) +import Data.List.NonEmpty.Deferred +import Text.Read (readMaybe) + #if defined(MIN_VERSION_these_lens) || (MIN_VERSION_these(0,8,0) && !MIN_VERSION_these(0,9,0)) import Data.These.Lens @@ -41,6 +51,8 @@ import Reflex.Requester.Base import Reflex.Requester.Class import Test.Run +import Debug.Trace hiding (traceEvent) + data RequestInt a where RequestInt :: Int -> RequestInt Int @@ -49,7 +61,7 @@ main = do os1 <- runApp' (unwrapApp testOrdering) $ [ Just () ] - print os1 + --print os1 os2 <- runApp' (unwrapApp testSimultaneous) $ map Just $ [ This () , That () @@ -65,31 +77,36 @@ main = do print os5 os6 <- runApp' (unwrapApp delayedPulse) [Just ()] print os6 - os7 <- runApp' testMatchRequestsWithResponses [ Just $ TestRequest_Increment 1 ] + os7 <- runApp' testMatchRequestsWithResponses $ map Just [ TestRequest_Increment 1, TestRequest_Increment 2 ] print os7 - os8 <- runApp' testMatchRequestsWithResponses [ Just $ TestRequest_Reverse "yoyo" ] + os8 <- runApp' testMatchRequestsWithResponses [ Just $ TestRequest_Reverse "abcd" ] print os8 - let ![[Just [1,2,3,4,5,6,7,8,9,10]]] = os1 -- The order is reversed here: see the documentation for 'runRequesterT' - let ![[Just [9,7,5,3,1]],[Nothing,Nothing],[Just [10,8,6,4,2]],[Just [10,8,6,4,2],Nothing]] = os2 + os9 <- runApp' testMoribundPerformEvent $ map Just [ 1 .. 3 ] + print os9 + let ![[Just [10,9,8,7,6,5,4,3,2,1]]] = os1 + let ![[Just [1,3,5,7,9]],[Nothing,Nothing],[Just [2,4,6,8,10]],[Just [2,4,6,8,10],Nothing]] = os2 let ![[Nothing, Just [2]]] = os3 let ![[Nothing, Just [2]]] = os4 let ![[Nothing, Just [1, 2]]] = os5 - -- let ![[Nothing, Nothing]] = os6 -- TODO re-enable this test after issue #233 has been resolved - let !(Just [(1,"2")]) = M.toList <$> head (head os7) - let !(Just [(1,"oyoy")]) = M.toList <$> head (head os8) - + let ![[Nothing, Nothing]] = os6 -- TODO re-enable this test after issue #233 has been resolved + let !(Just [(-9223372036854775808,"2")]) = M.toList <$> head (head os7) + let !(Just [(-9223372036854775808,"dcba")]) = M.toList <$> head (head os8) + let ![[Nothing,Just "0:1"],[Nothing,Just "1:2"],[Nothing,Just "2:3"]] = os9 return () -unwrapRequest :: DSum tag RequestInt -> Int -unwrapRequest (_ :=> RequestInt i) = i - -unwrapApp :: ( Reflex t, Monad m ) +unwrapApp :: forall t m a. + ( Reflex t + , MonadFix m + , PrimMonad m + ) => (a -> RequesterT t RequestInt Identity m ()) -> a -> m (Event t [Int]) unwrapApp x appIn = do ((), e) <- runRequesterT (x appIn) never - return $ fmap (map unwrapRequest . requesterDataToList) e + let unwrapRequests :: forall x. RequestData (PrimState m) RequestInt -> [Int] + unwrapRequests (RequestData _ es) = fmap (\(RequestEnvelope _ (RequestInt i)) -> i) $ toList es + return $ fmap unwrapRequests e testOrdering :: ( Response m ~ Identity , Request m ~ RequestInt @@ -200,6 +217,11 @@ data TestRequest a where TestRequest_Reverse :: String -> TestRequest String TestRequest_Increment :: Int -> TestRequest Int +instance Show (TestRequest a) where + show = \case + TestRequest_Reverse str -> "reverse " <> str + TestRequest_Increment i -> "increment " <> show i + testMatchRequestsWithResponses :: forall m t req a . ( MonadFix m @@ -209,17 +231,21 @@ testMatchRequestsWithResponses , MonadIO (Performable m) , ForallF Show req , Has Read req + , PrimMonad m + , Show (req a) + , Show a + , MonadIO m ) => Event t (req a) -> m (Event t (Map Int String)) testMatchRequestsWithResponses pulse = mdo (_, requests) <- runRequesterT (requesting pulse) responses - let rawResponses = M.map (\v -> + let rawResponseMap = M.map (\v -> case words v of ["reverse", str] -> reverse str ["increment", i] -> show $ succ $ (read i :: Int) ) <$> rawRequestMap - (rawRequestMap, responses) <- matchResponsesWithRequests reqEncoder requests (head . M.toList <$> rawResponses) - pure rawResponses + (rawRequestMap, responses) <- matchResponsesWithRequests reqEncoder requests (head . M.toList <$> rawResponseMap) + pure rawResponseMap where reqEncoder :: forall a. req a -> (String, String -> Maybe a) reqEncoder r = @@ -227,6 +253,24 @@ testMatchRequestsWithResponses pulse = mdo , \x -> has @Read r $ readMaybe x ) +-- If a widget is destroyed, and simultaneously it tries to use performEvent, the event does not get performed. +-- TODO Determine whether this is actually the behavior we want. +testMoribundPerformEvent + :: forall t m + . ( Adjustable t m + , PerformEvent t m + , MonadHold t m + , Reflex t + ) + => Event t Int -> m (Event t String) +testMoribundPerformEvent pulse = do + (outputInitial, outputReplaced) <- runWithReplace (performPrint 0 pulse) $ ffor pulse $ \i -> performPrint i pulse + switchHold outputInitial outputReplaced + where + performPrint i evt = + performEvent $ ffor evt $ \output -> + return $ show i <> ":" <> show output + deriveArgDict ''TestRequest instance Show (TestRequest a) where