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 +6/−1
- examples/tests.hs +3/−3
- sodium.cabal +3/−2
- src/FRP/Sodium/Internal.hs +2/−0
- src/FRP/Sodium/Plain.hs +156/−122
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.