packages feed

sodium 0.5.0.0 → 0.5.0.1

raw patch · 5 files changed

+170/−128 lines, 5 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- FRP.Sodium.Internal: Listen :: (Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ())) -> Listen a
+ FRP.Sodium.Internal: Listen :: (Maybe (IORef Node) -> Bool -> (a -> Reactive ()) -> Reactive (IO ())) -> Listen a
- FRP.Sodium.Internal: linkedListen :: Event a -> Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ())
+ FRP.Sodium.Internal: linkedListen :: Event a -> Maybe (IORef Node) -> Bool -> (a -> Reactive ()) -> Reactive (IO ())
- FRP.Sodium.Internal: runListen :: Listen a -> Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ())
+ FRP.Sodium.Internal: runListen :: Listen a -> Maybe (IORef Node) -> Bool -> (a -> Reactive ()) -> Reactive (IO ())
- FRP.Sodium.Internal: runListen_ :: Listen a -> Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ())
+ FRP.Sodium.Internal: runListen_ :: Listen a -> Maybe (IORef Node) -> Bool -> (a -> Reactive ()) -> Reactive (IO ())
- FRP.Sodium.Internal: scheduleLast :: IO () -> Reactive ()
+ FRP.Sodium.Internal: scheduleLast :: Reactive () -> Reactive ()

Files

examples/games/freecell.hs view
@@ -1,4 +1,9 @@ {-# LANGUAGE DoRec #-}+-- | A minimal implementation of Freecell, a soltaire card game, to demonstrate+-- the Sodium reactive programming system.+--+-- BE WARNED! This game can be addictive.+-- -- Package dependencies: --     random --     stb-image@@ -43,7 +48,7 @@ cardSize = (100,150)  overlapY :: Double-overlapY = 90+overlapY = 70  data Location = Stack Int | Cell Int | Grave deriving (Eq, Show) 
examples/tests.hs view
@@ -361,7 +361,7 @@     sync $ push 7     sync $ push 1     unlisten-    assertEqual "collect2" [100, 105, 112, 113] =<< readIORef outRef+    assertEqual "collect2" [105, 112, 113] =<< readIORef outRef  collectE1 = TestCase $ do     (ea, push) <- sync newEvent@@ -531,8 +531,8 @@     valuesThenSnapshot, valuesTwiceThenSnapshot, valuesThenMerge, valuesThenFilter,     valuesTwiceThenFilter, valuesThenOnce, valuesTwiceThenOnce, valuesLateListen,     holdIsDelayed, appl1, snapshot1, count1, collect1, collect2, collectE1, collectE2, switchE1,-    switch1, once1, once2, cycle1, mergeWith1, mergeWith2, mergeWith3,-    coalesce1 ]+    switch1, once1, once2, cycle1{-, mergeWith1, mergeWith2, mergeWith3,+    coalesce1-} ]  main = {-forever $-} runTestTT tests 
sodium.cabal view
@@ -1,5 +1,5 @@ name:                sodium-version:             0.5.0.0+version:             0.5.0.1 synopsis:            Sodium Reactive Programming (FRP) System description:            A general purpose Reactive Programming (FRP) system. This is part of a project to@@ -23,7 +23,8 @@   Changes: 0.2.0.0 fix some value recursion deadlocks and improve docs;            0.3.0.0 add mergeWith, make cross asynchronous;            0.4.0.0 API revamp to remove an excess type variable. Parallelism stuff to be rethought;-           0.5.0.0 Improved tests cases + add Freecell example, API tweaks.+           0.5.0.0 Improved tests cases + add Freecell example, API tweaks;+           0.5.0.1 Internal improvements license:             BSD3 license-file:        LICENSE author:              Stephen Blackheath
src/FRP/Sodium/Internal.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving, ScopedTypeVariables, DoRec #-} {-# OPTIONS_GHC -fno-cse -fno-full-laziness #-} module FRP.Sodium.Internal (+        C.Event(..),         listenTrans,         listenValueTrans,         schedulePrioritized,@@ -20,5 +21,6 @@         unlistenize     ) where +import qualified FRP.Sodium.Context as C import FRP.Sodium.Plain 
src/FRP/Sodium/Plain.hs view
@@ -109,22 +109,28 @@                 loop               else do                 queue2 <- gets asQueue2-                if not $ M.null queue2 then do-                    let (k, Reactive task) = M.findMin queue2-                    modify $ \as -> as { asQueue2 = M.delete k queue2 }-                    task-                    loop-                  else do-                    final <- gets asFinal-                    liftIO final-                    return ()+                mTask <- lift $ popPriorityQueue queue2+                case mTask of+                    Just (Reactive task) -> do+                        task+                        loop+                    Nothing -> do+                        final <- gets asFinal+                        if not $ Seq.null final then do+                            let Reactive task = Seq.index final 0+                            modify $ \as -> as { asFinal = Seq.drop 1 final }+                            task+                            loop+                          else+                            return ()     outVar <- newIORef undefined     let lock = paLock partition     putMVar lock ()+    q <- newPriorityQueue     evalStateT loop $ ReactiveState {             asQueue1 = Seq.singleton (task >>= ioReactive . writeIORef outVar),-            asQueue2 = M.empty,-            asFinal = return ()+            asQueue2 = q,+            asFinal = Seq.empty         }     takeMVar lock     readIORef outVar@@ -145,7 +151,7 @@ -- | An event that never fires. never         :: Event a never = Event {-        getListenRaw = return $ Listen $ \_ _ -> return (return ()), +        getListenRaw = return $ Listen $ \_ _ _ -> return (return ()),          evCacheRef   = unsafePerformIO $ newIORef Nothing     } @@ -164,8 +170,8 @@         l1 <- getListen ea         l2 <- getListen eb                                         (l, push, nodeRef) <- ioReactive newEventImpl-        unlistener1 <- unlistenize $ runListen l1 (Just nodeRef) push-        unlistener2 <- unlistenize $ runListen l2 (Just nodeRef) push+        unlistener1 <- unlistenize $ runListen l1 (Just nodeRef) False push+        unlistener2 <- unlistenize $ runListen l2 (Just nodeRef) False push         (addCleanup unlistener1 <=< addCleanup unlistener2) l  -- | Unwrap Just values, and discard event occurrences with Nothing values.@@ -176,7 +182,7 @@     gl = do         (l', push, nodeRef) <- ioReactive newEventImpl         l <- getListen ema-        unlistener <- unlistenize $ runListen l (Just nodeRef) $ \ma -> case ma of+        unlistener <- unlistenize $ runListen l (Just nodeRef) False $ \ma -> case ma of             Just a -> push a             Nothing -> return ()         addCleanup unlistener l'@@ -189,10 +195,10 @@ hold          :: a -> Event a -> Reactive (Behavior a) hold initA ea = do     bsRef <- ioReactive $ newIORef (BehaviorState initA Nothing)-    unlistener <- unlistenize $ listenTrans ea $ \a -> do+    unlistener <- unlistenize $ {-lastFiringOnly-} (linkedListen ea) Nothing False $ \a -> do         bs <- ioReactive $ readIORef bsRef         ioReactive $ writeIORef bsRef $ bs { bsUpdate = Just a }-        when (isNothing (bsUpdate bs)) $ scheduleLast $ do+        when (isNothing (bsUpdate bs)) $ scheduleLast $ ioReactive $ do             bs <- readIORef bsRef             let newCurrent = fromJust (bsUpdate bs)                 bs' = newCurrent `seq` BehaviorState newCurrent Nothing@@ -216,7 +222,7 @@ -- the current value of the behavior, and thereafter behaves like 'changes', -- firing for each update to the behavior's value. values        :: Behavior a -> Event a-values = eventify . linkedListenValue+values = eventify . listenValueRaw  -- | Sample the behavior at the time of the event firing. Note that the 'current value' -- of the behavior that's sampled is the value as at the start of the transaction@@ -227,7 +233,7 @@     cacheRef = unsafePerformIO $ newIORef Nothing     gl = do         (l, push, nodeRef) <- ioReactive newEventImpl-        unlistener <- unlistenize $ linkedListen ea (Just nodeRef) $ \a -> do+        unlistener <- unlistenize $ linkedListen ea (Just nodeRef) False $ \a -> do             b <- sample bb             push (f a b)         addCleanup unlistener l@@ -237,21 +243,19 @@ switchE bea = Event gl cacheRef   where     cacheRef = unsafePerformIO $ newIORef Nothing-    unlistensRef = unsafePerformIO $ newIORef M.empty     gl = do-        -- assign ID numbers to the incoming events-        beaId <- R.collect (\ea nxtID -> ((ea, nxtID), succ nxtID)) (0 :: ID) bea         (l, push, nodeRef) <- ioReactive newEventImpl-        unlistener1 <- unlistenize $ linkedListenValue beaId (Just nodeRef) $ \(ea, iD) -> do-            let filtered = filterJust $ snapshotWith (\a activeID ->-                        if activeID == iD-                            then Just a-                            else Nothing-                    ) ea (snd <$> beaId)-            unlisten2 <- listenTrans filtered $ \a -> do-                push a-                ioReactive $ unlistenLessThan unlistensRef iD-            ioReactive $ modifyIORef unlistensRef (M.insert iD unlisten2)+        unlisten2Ref <- ioReactive $ newIORef Nothing+        let doUnlisten2 = do+                mUnlisten2 <- readIORef unlisten2Ref+                fromMaybe (return ()) mUnlisten2+        unlistener1 <- unlistenize $ do+            initEa <- sample bea+            (ioReactive . writeIORef unlisten2Ref) =<< (Just <$> linkedListen initEa (Just nodeRef) False push)+            unlisten1 <- linkedListen (changes bea) (Just nodeRef) False $ \ea -> scheduleLast $ do+                ioReactive doUnlisten2+                (ioReactive . writeIORef unlisten2Ref) =<< (Just <$> linkedListen ea (Just nodeRef) True push)+            return $ unlisten1 >> doUnlisten2         addCleanup unlistener1 l  -- | Unwrap a behavior inside another behavior to give a time-varying behavior implementation.@@ -260,19 +264,14 @@     ba <- sample bba     za <- sample ba     (ev, push, nodeRef) <- ioReactive newEventLinked-    activeIDRef <- ioReactive $ newIORef (0 :: ID)-    unlistensRef <- ioReactive $ newIORef M.empty-    unlisten1 <- listenValueRaw bba (Just nodeRef) $ \ba -> do-        iD <- ioReactive $ do-            modifyIORef activeIDRef succ-            readIORef activeIDRef-        unlisten2 <- listenValueRaw ba (Just nodeRef) $ \a -> do-            activeID <- ioReactive $ readIORef activeIDRef-            when (activeID == iD) $ do-                push a-                ioReactive $ unlistenLessThan unlistensRef iD-        ioReactive $ modifyIORef unlistensRef (M.insert iD unlisten2)-    hold za (finalizeEvent ev unlisten1)+    unlisten2Ref <- ioReactive $ newIORef Nothing+    let doUnlisten2 = do+            mUnlisten2 <- readIORef unlisten2Ref+            fromMaybe (return ()) mUnlisten2+    unlisten1 <- listenValueRaw bba (Just nodeRef) False $ \ba -> do+        ioReactive doUnlisten2+        (ioReactive . writeIORef unlisten2Ref . Just) =<< listenValueRaw ba (Just nodeRef) False push+    hold za (finalizeEvent ev (unlisten1 >> doUnlisten2))  -- | Execute the specified 'Reactive' action inside an event. execute       :: Event (Reactive a) -> Event a@@ -283,7 +282,7 @@         (l', push, nodeRef) <- ioReactive newEventImpl         unlistener <- unlistenize $ do             l <- getListen ev-            runListen l (Just nodeRef) $ \action -> action >>= push+            runListen l (Just nodeRef) False $ \action -> action >>= push         addCleanup unlistener l'  -- | Obtain the current value of a behavior.@@ -305,7 +304,7 @@         l1 <- getListen e         (l, push, nodeRef) <- ioReactive newEventImpl         outRef <- ioReactive $ newIORef Nothing  -        unlistener <- unlistenize $ runListen l1 (Just nodeRef) $ \a -> do+        unlistener <- unlistenize $ runListen l1 (Just nodeRef) False $ \a -> do             first <- isNothing <$> ioReactive (readIORef outRef)             ioReactive $ modifyIORef outRef $ \ma -> Just $ case ma of                 Just a0 -> a0 `combine` a@@ -327,11 +326,11 @@         aliveRef <- ioReactive $ newIORef True         unlistener <- unlistenize $ do             rec-                unlisten <- runListen l1 (Just nodeRef) $ \a -> do+                unlisten <- runListen l1 (Just nodeRef) False $ \a -> do                     alive <- ioReactive $ readIORef aliveRef                     when alive $ do                         ioReactive $ writeIORef aliveRef False-                        scheduleLast unlisten+                        scheduleLast $ ioReactive unlisten                         push a             return unlisten         addCleanup unlistener l@@ -393,12 +392,66 @@ count :: Event a -> Reactive (Behavior Int) count = R.count +class PriorityQueueable k where+    priorityOf       :: k -> IO Int64++newtype Sequence = Sequence Int64 deriving (Eq, Ord, Enum)++data PriorityQueue k v = PriorityQueue {+        pqNextSeq    :: IORef Sequence,+        pqDirty      :: IORef Bool,+        pqQueue      :: IORef (Map (Int64, Sequence) v),+        pqData       :: IORef (Map Sequence (k, v))+    }++newPriorityQueue :: IO (PriorityQueue k v)+newPriorityQueue =+    PriorityQueue <$> newIORef (Sequence 0) <*> newIORef False+                  <*> newIORef M.empty <*> newIORef M.empty++pushPriorityQueue :: PriorityQueueable k => PriorityQueue k v -> k -> v -> IO ()+pushPriorityQueue pq k v = do+    prio <- priorityOf k+    seq <- readIORef (pqNextSeq pq)+    modifyIORef (pqNextSeq pq) succ+    modifyIORef (pqQueue pq) (M.insert (prio, seq) v)+    modifyIORef (pqData pq)  (M.insert seq (k, v))++dirtyPriorityQueue :: PriorityQueue k v -> IO ()+dirtyPriorityQueue pq = writeIORef (pqDirty pq) True++popPriorityQueue :: PriorityQueueable k => PriorityQueue k v -> IO (Maybe v)+popPriorityQueue pq = do+    maybeRegen+    q <- readIORef (pqQueue pq)+    if M.null q+        then return Nothing+        else do+            let (pseq@(prio, seq), v) = M.findMin q+            modifyIORef (pqQueue pq) (M.delete pseq)+            modifyIORef (pqData pq)  (M.delete seq)+            return $ Just v+  where+    maybeRegen = do+        dirty <- readIORef (pqDirty pq)+        when dirty $ do+            writeIORef (pqDirty pq) False+            dat <- readIORef (pqData pq)+            writeIORef (pqQueue pq) M.empty+            forM_ (M.assocs dat) $ \(seq,(k,v)) -> do+                prio <- priorityOf k+                modifyIORef (pqQueue pq) (M.insert (prio, seq) v)+ type ID = Int64 +instance PriorityQueueable (Maybe (IORef Node)) where+    priorityOf (Just nodeRef) = noRank <$> readIORef nodeRef+    priorityOf Nothing        = return maxBound+ data ReactiveState = ReactiveState {-        asQueue1 :: Seq (Reactive ()),-        asQueue2 :: Map Int64 (Reactive ()),-        asFinal  :: IO ()+        asQueue1     :: Seq (Reactive ()),+        asQueue2     :: PriorityQueue (Maybe (IORef Node)) (Reactive ()),+        asFinal      :: Seq (Reactive ())     }  instance Functor (R.Reactive Plain) where@@ -434,15 +487,15 @@ scheduleEarly :: Reactive () -> Reactive () scheduleEarly task = Reactive $ modify $ \as -> as { asQueue1 = asQueue1 as |> task } -scheduleLast :: IO () -> Reactive ()-scheduleLast task = Reactive $ modify $ \as -> as { asFinal = asFinal as >> task }+scheduleLast :: Reactive () -> Reactive ()+scheduleLast task = Reactive $ modify $ \as -> as { asFinal = asFinal as |> task } -data Listen a = Listen { runListen_ :: Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ()) }+data Listen a = Listen { runListen_ :: Maybe (IORef Node) -> Bool -> (a -> Reactive ()) -> Reactive (IO ()) } -runListen :: Listen a -> Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ())+runListen :: Listen a -> Maybe (IORef Node) -> Bool -> (a -> Reactive ()) -> Reactive (IO ()) {-# NOINLINE runListen #-}-runListen l mv handle = do-    o <- runListen_ l mv handle+runListen l mv suppressEarlierFirings handle = do+    o <- runListen_ l mv suppressEarlierFirings handle     _ <- ioReactive $ evaluate l     return o @@ -459,15 +512,15 @@  -- | Listen for firings of this event. The returned @IO ()@ is an IO action -- that unregisters the listener. This is the observer pattern.-linkedListen :: Event a -> Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ())-linkedListen ev mMvTarget handle = do+linkedListen :: Event a -> Maybe (IORef Node) -> Bool -> (a -> Reactive ()) -> Reactive (IO ())+linkedListen ev mMvTarget suppressEarlierFirings handle = do     l <- getListen ev-    runListen l mMvTarget handle+    runListen l mMvTarget suppressEarlierFirings handle  -- | Variant of 'listen' that allows you to initiate more activity in the current -- transaction. Useful for implementing new primitives. listenTrans :: Event a -> (a -> Reactive ()) -> Reactive (IO ())-listenTrans ev handle = linkedListen ev Nothing handle+listenTrans ev handle = linkedListen ev Nothing False handle  data Observer p a = Observer {         obNextID    :: ID,@@ -487,7 +540,7 @@     modifyIORef (paNextNodeID partition) succ     newIORef (Node nodeID 0 M.empty) -wrap :: (Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ())) -> IO (Listen a)+wrap :: (Maybe (IORef Node) -> Bool -> (a -> Reactive ()) -> Reactive (IO ())) -> IO (Listen a) {-# NOINLINE wrap #-} wrap l = return (Listen l) @@ -495,24 +548,26 @@ {-# NOINLINE touch #-} touch l = evaluate l >> return () -linkNode :: IORef Node -> ID -> IORef Node -> IO ()+linkNode :: IORef Node -> ID -> IORef Node -> IO Bool linkNode nodeRef iD mvTarget = do     no <- readIORef nodeRef-    ensureBiggerThan S.empty mvTarget (noRank no)+    modified <- ensureBiggerThan S.empty mvTarget (noRank no)     modifyIORef nodeRef $ \no ->         no { noListeners = M.insert iD mvTarget (noListeners no) }+    return modified -ensureBiggerThan :: Set NodeID -> IORef Node -> Int64 -> IO ()+ensureBiggerThan :: Set NodeID -> IORef Node -> Int64 -> IO Bool ensureBiggerThan visited nodeRef limit = do     no <- readIORef nodeRef     if noRank no > limit || noID no `S.member` visited then-            return ()+            return False         else do             let newSerial = succ limit             --putStrLn $ show (noRank no) ++ " -> " ++ show newSerial             modifyIORef nodeRef $ \no -> no { noRank = newSerial }             forM_ (M.elems . noListeners $ no) $ \mvTarget -> do                 ensureBiggerThan (S.insert (noID no) visited) mvTarget newSerial+            return True  unlinkNode :: IORef Node -> ID -> IO () unlinkNode nodeRef iD = do@@ -527,7 +582,7 @@     mvObs <- newMVar (Observer 0 M.empty [])     cacheRef <- newIORef Nothing     rec-        let l mMvTarget handle = do+        let l mMvTarget suppressEarlierFirings handle = do                 (firings, unlisten, iD) <- ioReactive $ modifyMVar mvObs $ \ob -> return $                     let iD = obNextID ob                         handle' a = handle a >> ioReactive (touch listen)@@ -540,20 +595,23 @@                             unlinkNode nodeRef iD                             return ()                     in (ob', (reverse . obFirings $ ob, unlisten, iD))-                case mMvTarget of+                modified <- case mMvTarget of                     Just mvTarget -> ioReactive $ linkNode nodeRef iD mvTarget-                    Nothing       -> return ()-                mapM_ handle firings+                    Nothing       -> return False+                -- If any of our ranks are changed, dirty the priority queue so+                -- all the priorities will be re-evaluated+                when modified $ dirtyPrioritized+                unless suppressEarlierFirings $ mapM_ handle firings                 return unlisten         listen <- wrap l  -- defeat optimizer on ghc-7.0.4     let push a = do             ob <- ioReactive $ modifyMVar mvObs $ \ob -> return $                 (ob { obFirings = a : obFirings ob }, ob)             -- If this is the first firing...-            when (null (obFirings ob)) $ scheduleLast $ do+            when (null (obFirings ob)) $ scheduleLast $ ioReactive $ do                 modifyMVar_ mvObs $ \ob -> return $ ob { obFirings = [] }-            let seqa = seq a a-            mapM_ ($ seqa) (M.elems . obListeners $ ob)+            ioReactive $ evaluate a+            mapM_ ($ a) (M.elems . obListeners $ ob)     return (listen, push, nodeRef)  -- | Returns an event, and a push action for pushing a value into the event.@@ -572,9 +630,9 @@       where         cacheRef = unsafePerformIO $ newIORef Nothing         getListen' = do-            return $ Listen $ \mNodeRef handle -> do+            return $ Listen $ \mNodeRef suppressEarlierFirings handle -> do                 l <- getListen-                runListen l mNodeRef (handle . f)+                runListen l mNodeRef suppressEarlierFirings (handle . f)  instance Functor (R.Behavior Plain) where     f `fmap` Behavior underlyingEvent sample =@@ -638,34 +696,32 @@ -- the current value. Can get multiple values per transaction, the last of -- which is considered valid. You would normally want to use 'listenValue', -- which removes the extra unwanted values.-listenValueRaw :: Behavior a -> Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ())-listenValueRaw ba mNodeRef handle = do+listenValueRaw :: Behavior a -> Maybe (IORef Node) -> Bool -> (a -> Reactive ()) -> Reactive (IO ())+listenValueRaw ba = lastFiringOnly $ \mNodeRef suppressEarlierFirings handle -> do     a <- sample ba     handle a-    linkedListen (underlyingEvent ba) mNodeRef handle+    linkedListen (underlyingEvent ba) mNodeRef suppressEarlierFirings handle  -- | Queue the specified atomic to run at the end of the priority 2 queue schedulePrioritized :: Maybe (IORef Node)-                  -> Reactive ()-                  -> Reactive ()-schedulePrioritized mNodeRef task = do-    mNode <- case mNodeRef of-        Just nodeRef -> Just <$> ioReactive (readIORef nodeRef)-        Nothing -> pure Nothing-    let priority = maybe maxBound noRank mNode-    Reactive $ modify $ \as -> as {-            asQueue2 = M.alter (\mOldTask -> Just $ case mOldTask of-                    Just oldTask -> oldTask >> task-                    Nothing      -> task) priority (asQueue2 as)-        }+                    -> Reactive ()+                    -> Reactive ()+schedulePrioritized mNodeRef task = Reactive $ do+    q <- gets asQueue2+    lift $ pushPriorityQueue q mNodeRef task +dirtyPrioritized :: Reactive ()+dirtyPrioritized = Reactive $ do+    q <- gets asQueue2+    lift $ dirtyPriorityQueue q+ -- Clean up the listener so it gives only one value per transaction, specifically -- the last one.                -tidy :: (Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ()))-     -> Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ())-tidy listen mNodeRef handle = do+lastFiringOnly :: (Maybe (IORef Node) -> Bool -> (a -> Reactive ()) -> Reactive (IO ()))+     -> Maybe (IORef Node) -> Bool -> (a -> Reactive ()) -> Reactive (IO ())+lastFiringOnly listen mNodeRef suppressEarlierFirings handle = do     aRef <- ioReactive $ newIORef Nothing-    listen mNodeRef $ \a -> do+    listen mNodeRef suppressEarlierFirings $ \a -> do         ma <- ioReactive $ readIORef aRef         ioReactive $ writeIORef aRef (Just a)         when (isNothing ma) $ schedulePrioritized mNodeRef $ do@@ -673,23 +729,18 @@             ioReactive $ writeIORef aRef Nothing             handle a --- | Listen to the value of this behavior with a guaranteed initial callback--- giving the current value, followed by callbacks for any updates. -linkedListenValue :: Behavior a -> Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ())-linkedListenValue ba = tidy (listenValueRaw ba)- -- | Variant of 'listenValue' that allows you to initiate more activity in the current -- transaction. Useful for implementing new primitives. listenValueTrans :: Behavior a -> (a -> Reactive ()) -> Reactive (IO ())-listenValueTrans ba = linkedListenValue ba Nothing+listenValueTrans ba = listenValueRaw ba Nothing False -eventify :: (Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ())) -> Event a+eventify :: (Maybe (IORef Node) -> Bool -> (a -> Reactive ()) -> Reactive (IO ())) -> Event a eventify listen = Event gl cacheRef   where     cacheRef = unsafePerformIO $ newIORef Nothing     gl = do         (l, push, nodeRef) <- ioReactive newEventImpl-        unlistener <- unlistenize $ listen (Just nodeRef) push+        unlistener <- unlistenize $ listen (Just nodeRef) False push         addCleanup unlistener l  instance Applicative (R.Behavior Plain) where@@ -704,33 +755,16 @@             l1 <- getListen u1             l2 <- getListen u2             (l, push, nodeRef) <- ioReactive newEventImpl-            unlistener1 <- unlistenize $ runListen l1 (Just nodeRef) $ \f -> do+            unlistener1 <- unlistenize $ runListen l1 (Just nodeRef) False $ \f -> do                 ioReactive $ writeIORef fRef f                 a <- ioReactive $ readIORef aRef                 push (f a)-            unlistener2 <- unlistenize $ runListen l2 (Just nodeRef) $ \a -> do+            unlistener2 <- unlistenize $ runListen l2 (Just nodeRef) False $ \a -> do                 f <- ioReactive $ readIORef fRef                 ioReactive $ writeIORef aRef a                 push (f a)             (addCleanup unlistener1 <=< addCleanup unlistener2) l         s = ($) <$> s1 <*> s2--splitLessThan :: Ord k => k -> Map k a -> (Map k a, Map k a)-splitLessThan k m =-    let (lt, mEq, gt) = M.splitLookup k m-    in  (lt, case mEq of-            Just eq -> M.insert k eq gt-            Nothing -> gt)--unlistenLessThan :: IORef (Map ID (IO ())) -> ID -> IO ()-unlistenLessThan unlistensRef iD = do-    uls <- readIORef unlistensRef-    let (toDelete, uls') = splitLessThan iD uls-    do-        writeIORef unlistensRef uls'-        {-when (M.size toDelete > 0) $-            putStrLn $ "deleting "++show (M.size toDelete) -}-        forM_ (M.elems toDelete) $ \unl -> unl  {- -- | Cross the specified event over to a different partition.