module Updater.Internal (
Signal(),
newSignal,
newSignalIO,
writeSignal,
readSignal,
addListener,
Updater(),
onCommit,
getEvent,
getBehavior,
runUpdater,
liftSTM,
onCleanup
) where
import Control.Concurrent.STM
import qualified Updater.List as List
import Control.Applicative
import Control.Exception.Base
import Control.Monad.Fix
putLine :: String -> Updater ()
putLine = onCommit . putStrLn
data Signal a = Signal {
signalValue :: TVar a,
signalListeners :: List.LinkedList (a -> Updater ())
}
newSignal :: a -> STM (Signal a)
newSignal a = do
value <- newTVar a
listeners <- List.empty
return (Signal value listeners)
newSignalIO :: a -> IO (Signal a)
newSignalIO a = do
value <- newTVarIO a
listeners <- List.emptyIO
return (Signal value listeners)
readSignal :: Signal a -> STM a
readSignal signal = readTVar $ signalValue signal
writeSignal :: Signal a -> a -> Updater ()
writeSignal (Signal valueVar listeners) value = do
liftSTM $ writeTVar valueVar value
onCommitUpdater $ liftSTM (List.start listeners) >>= recursion where
recursion Nothing = return ()
recursion (Just node) = do
List.value node value :: Updater ()
liftSTM (List.next node) >>= recursion
writeSignalNow :: Signal a -> a -> Updater ()
writeSignalNow (Signal valueVar listeners) value = do
listeners' <- liftSTM $ List.toList listeners
liftSTM $ writeTVar valueVar value
mapM_ ($ value) listeners'
addListener :: Signal a -> (a -> Updater ()) -> STM (STM ())
addListener signal listener = do
node <- List.append listener (signalListeners signal)
return (List.delete node)
addSingletonListener :: Signal a -> (a -> Updater ()) -> STM (STM ())
addSingletonListener signal listener = mfix add where
add remove = addListener signal (run remove)
run remove value = liftSTM remove >> listener value
data State = State {
stateOnCommitUpdater :: TVar ([Updater ()]),
stateOnCommitIO :: TVar ([IO ()]),
stateCleanup :: Signal ()
}
newtype Updater a = Updater { runUpdater' :: (a -> State -> STM ()) -> State -> STM () }
getCleanup :: Updater (Signal ())
getCleanup = fmap stateCleanup getState
onCleanup :: Updater () -> Updater ()
onCleanup cleanup = do
cleanupE <- getCleanup
liftSTM $ addSingletonListener cleanupE (const $ cleanup)
return ()
onCommit :: IO () -> Updater ()
onCommit action = do
state <- getState
liftSTM $ modifyTVar (stateOnCommitIO state) (action:)
onCommitUpdater :: Updater () -> Updater ()
onCommitUpdater action = do
state <- getState
liftSTM $ modifyTVar (stateOnCommitUpdater state) (action:)
getState :: Updater State
getState = Updater $ \restCalc state -> restCalc state state
putState :: State -> Updater ()
putState state = Updater $ \restCalc _ -> restCalc () state
getEvent :: Signal a -> Updater a
getEvent signal = Updater $ \restCalc state-> do
cleanupE <- newSignal ()
removeListener <- addListener signal
(\value -> do
writeSignalNow cleanupE ()
state' <- getState
liftSTM $ restCalc value (state' { stateCleanup = cleanupE })
)
addSingletonListener (stateCleanup state) (const $ do
liftSTM removeListener
writeSignalNow cleanupE ()
)
return ()
getBehavior :: Signal a -> Updater a
getBehavior signal = liftSTM (readSignal signal) <|> getEvent signal
runUpdater :: Updater a -> IO a
runUpdater updater' = wrapper where
wrapper = do
cleanupSignal <- atomically $ newSignal $ error "should not be accessible"
onException
(run updater' cleanupSignal)
(run (writeSignalNow cleanupSignal ()) cleanupSignal)
run updater cleanupSignal= do
(resultVar, onCommitAction) <- atomically $ do
onCommitVar <- newTVar []
onCommitUpdaterVar <- newTVar []
resultVar <- newEmptyTMVar
runUpdater'
( do
res <- updater
writeSignalNow cleanupSignal ()
onCommit $ atomically $ putTMVar resultVar res)
(const $ const $ return ())
(State {
stateCleanup = cleanupSignal,
stateOnCommitUpdater = onCommitUpdaterVar,
stateOnCommitIO = onCommitVar })
let runOnCommitUpdater onCommitUpdaterVal = do
onCommitUs <- newTVar []
runUpdater' (onCommitUpdaterVal) (const $ const $ return ()) (State
{ stateCleanup = error "should not be needed"
, stateOnCommitUpdater = onCommitUs
, stateOnCommitIO = onCommitVar
})
onCommitUs' <- readTVar onCommitUs
mapM_ runOnCommitUpdater onCommitUs'
readTVar onCommitUpdaterVar >>= mapM_ runOnCommitUpdater
onCommitAction <- readTVar onCommitVar
return (resultVar, onCommitAction)
sequence_ $ reverse onCommitAction
result <- atomically $ takeTMVar resultVar
return result
liftSTM :: STM a -> Updater a
liftSTM run = Updater (\restCalc state -> run >>= (\x -> restCalc x state))
instance Functor Updater where
fmap f (Updater giveMeNext) = Updater (\next -> giveMeNext (next . f))
instance Applicative Updater where
pure a = Updater $ \giveMeA -> giveMeA a
updater1 <*> updater2 = Updater $ updater where
updater restCalc state = do
signalF <- newSignal Nothing
signalX <- newSignal Nothing
runUpdater' (updater1 >>= writeSignalNow signalF . Just) (const $ const $ return ()) state
runUpdater' (updater2 >>= writeSignalNow signalX . Just) (const $ const $ return ()) state
runUpdater' (do
(Just f) <- getBehavior signalF
(Just x) <- getBehavior signalX
state' <- getState
liftSTM $ restCalc (f x) state'
) (const $ const $ return ()) state
return ()
instance Alternative Updater where
empty = Updater $ \_ _ -> return ()
updater1 <|> updater2 =Updater $ \restCalc state -> do
signal <-newSignal (error "should not be accessed")
cleanupSignal <- newSignal (error "should not be accessed")
runUpdater' (do
event <- getEvent signal
state' <- getState
liftSTM $ restCalc event state'
) (const $ const $ return ()) state
runUpdater' (updater1 >>= writeSignalNow signal) (const $ const $ return ()) state
runUpdater' (updater2 >>= writeSignalNow signal) (const $ const $ return ()) state
addSingletonListener (stateCleanup state) (writeSignalNow cleanupSignal)
return ()
instance Monad Updater where
(Updater giveMeNext) >>= valueToNextUpd = Updater $ updater where
updater end = giveMeNext $ \value -> runUpdater' (valueToNextUpd value) end
return a = Updater $ \end -> end a
fail _ = Updater $ \_ _ -> return ()