module Updater.Internal (
Event (..),
Behavior (..),
Updater (..),
newEvent',
cacheStateless',
cacheStateful',
runUpdater,
unsafeLiftIO,
debug,
debugCleanup,
onCommit,
justOne,
UpState (..),
DownState (..)
) where
import Control.Concurrent.MVar
import qualified Updater.List as List
import Control.Applicative
import Control.Monad
import Data.Monoid
import Control.Monad.Fix
import System.Mem.Weak
import Data.IORef
import System.IO.Unsafe
newtype Event a = Event { getEvent' :: Updater a }
deriving (Functor, Applicative, Alternative, Monad)
newtype Behavior a = Behavior { getBehavior' :: Updater a }
deriving (Functor, Applicative, Monad, MonadFix)
unsafeLiftIO :: IO a -> Behavior a
unsafeLiftIO = Behavior . liftIO
globalLock :: MVar ()
globalLock = unsafePerformIO $ newMVar ()
signalNumVar :: MVar Int
signalNumVar = unsafePerformIO $ newMVar 1
withGlobalLock :: IO a -> IO a
withGlobalLock io = do
takeMVar globalLock
res <- io
putMVar globalLock ()
return res
debug :: String -> Behavior ()
debug = unsafeLiftIO . putStrLn
debugCleanup :: String -> Behavior ()
debugCleanup string = Behavior $ Updater $ \restCalc downState -> do
upState <- restCalc () downState
return $ mempty { stateOnCleanup = putStrLn string } <> upState
onCommit :: IO () -> Behavior ()
onCommit io = Behavior $ Updater $ \restCalc downState -> do
upState <- restCalc () downState
return $ mempty { stateOnCommit = io } <> upState
data Signal a = Signal {
signalValue :: IORef a,
signalListeners :: List.LinkedList (Weak (Signal a, a -> DownState -> IO UpState)),
signalNum :: Int
}
newSignal :: a -> IO (Signal a)
newSignal a = do
value <- newIORef a
listeners <- List.empty
num <- modifyMVar signalNumVar $ \n -> return (n+1,n)
return (Signal value listeners num)
readSignal :: Signal a -> IO a
readSignal signal = readIORef $ signalValue signal
writeSignal :: Signal a -> a -> DownState -> IO UpState
writeSignal (Signal valueVar listeners num) value downState = do
writeIORef valueVar value
list <- List.toList listeners
let f weakRef = do
res <- deRefWeak weakRef
case res of
(Just (_,listener)) -> listener value downState
_ -> return mempty
upStates <- mapM f list
return (foldl (<>) mempty upStates)
addListener :: Signal a -> (a -> DownState -> IO UpState) -> IO (IO ())
addListener signal listener = do
let listener' a downState = listener a downState
weakRef <- newIORef (error "should not be readable")
node <- List.append (unsafePerformIO $ readIORef weakRef) (signalListeners signal)
key <- newIORef undefined
let remove = (List.delete node) >> newIORef key >> return ()
weak <- mkWeak key (signal, listener') $ Just $ do
remove
writeIORef weakRef weak
return (remove )
data DownState = DownState {
}
data UpState = UpState {
stateOnCleanup :: IO (),
stateOnCommit :: IO ()
}
instance Monoid UpState where
mempty = UpState (return ()) (return ())
(UpState c1 d1) `mappend` (UpState c2 d2) = UpState (c1 >> c2) (d1 >> d2)
newtype Updater a = Updater {
runUpdater' :: (a -> DownState -> IO UpState) -> DownState -> IO UpState
}
instance MonadFix Updater where
mfix = fixUpdater
fixUpdater :: (a -> Updater a) -> Updater a
fixUpdater toUpdater = Updater $ \restCalc downState -> do
inputVar <- newEmptyMVar
runUpdater' (toUpdater $ unsafePerformIO $ takeMVar inputVar)
(\x downState2 -> do
isEmpty <- isEmptyMVar inputVar
when (not isEmpty) (error "continuous run twice")
putMVar inputVar x
restCalc x downState2
)
downState
cacheStateful' :: Updater a -> Updater (Updater a)
cacheStateful' updater = Updater $ \restCalc downState-> do
signal <- newSignal Nothing
cleanup <- newIORef (return ())
upstate1 <- restCalc (Updater $ \restCalc2 downState2 -> do
res <- readSignal signal
upState <- case res of
(Just res') -> do
upState' <- restCalc2 res' downState2
oldCleanup <- readIORef cleanup
writeIORef cleanup (oldCleanup >> stateOnCleanup upState')
return upState' { stateOnCleanup = join $ readIORef cleanup }
Nothing -> return mempty
removeListener <- addListener signal (\x downState3 -> case x of
(Just x') -> restCalc2 x' downState3
Nothing -> return mempty)
return $ upState <> mempty { stateOnCleanup = removeListener }
) downState
upstate2 <- runUpdater' updater
(\x downState' -> do
join $ readIORef cleanup
upState <- writeSignal signal (Just x) downState'
writeIORef cleanup (stateOnCleanup upState)
return upState { stateOnCleanup = join $ readIORef cleanup }
)
downState
return (upstate1 <> upstate2)
cacheStateless' :: Updater a -> Updater (Updater a)
cacheStateless' updater = Updater $ \restCalc downState-> do
signal <- newSignal (error "unreadable event")
cleanup <- newIORef (return ())
upstate1 <- restCalc (Updater $ \restCalc2 _ -> do
removeListener <- addListener signal restCalc2
return $ mempty { stateOnCleanup = removeListener }
) downState
upstate2 <- runUpdater' updater
(\x downState' -> do
join $ readIORef cleanup
upState <- writeSignal signal x downState'
writeIORef cleanup (stateOnCleanup upState)
return upState { stateOnCleanup = join $ readIORef cleanup }
)
downState
return (upstate1 <> upstate2)
newEvent' :: IO (Updater a, a -> IO ())
newEvent' = do
signal <- newSignal (error "unreadable")
cleanupVar <- newIORef (return () :: IO ())
let
updater = Updater $ \restCalc _ -> do
removeListener <- addListener signal (\a downState2 -> restCalc a downState2)
return mempty { stateOnCleanup = removeListener }
button a = do
takeMVar globalLock
join $ readIORef cleanupVar
upState <- writeSignal signal a (error "no down state yet")
writeIORef cleanupVar (stateOnCleanup upState)
putMVar globalLock ()
stateOnCommit upState
return (updater, button)
runUpdater :: Updater (Either (IO ()) res) -> IO res
runUpdater (Updater giveMeNext) = do
resVar <- newEmptyMVar
upState <- withGlobalLock $ do
giveMeNext (\val _ -> do
resMay <-isEmptyMVar resVar
if resMay
then case val of
(Left io) -> return mempty { stateOnCommit = io }
(Right res) -> putMVar resVar res >> return mempty
else return mempty
) DownState {}
stateOnCommit upState
res <- takeMVar resVar
withGlobalLock $ stateOnCleanup upState
return res
justOne :: Updater a -> Updater a
justOne (Updater giveMeNext) = Updater $ \restCalc downState -> do
restVar <- newIORef restCalc
cleanupVar <- newIORef (return ())
upState' <- giveMeNext (\x downState2 -> do
rest <- readIORef restVar
writeIORef restVar (\_ _ -> return mempty)
upState <- rest x downState2
writeIORef cleanupVar $ stateOnCleanup upState
return upState { stateOnCleanup = return () }
) downState
return $ upState' <> mempty { stateOnCleanup = join $ readIORef cleanupVar }
liftIO :: IO a -> Updater a
liftIO run = Updater (\restCalc state -> run >>= (\x -> restCalc x state))
instance Applicative Updater where
pure a = Updater $ \giveMeA -> giveMeA a
(Updater giveMeNext1) <*> (Updater giveMeNext2) = Updater $ \restCalc state -> do
varF <- newIORef Nothing
varX <- newIORef Nothing
varCleanup <- newIORef $ return ()
let update state' = do
f' <- readIORef varF
x' <- readIORef varX
case (f', x') of
(Just f, Just x) -> do
join $ readIORef varCleanup
upstateC <- restCalc (f x) state'
writeIORef varCleanup $ stateOnCleanup upstateC
return $ upstateC {
stateOnCleanup = return ()
}
_ -> return mempty
upState1 <- giveMeNext1 (\x state' -> writeIORef varF (Just x) >> update state') state
upState2 <- giveMeNext2 (\x state' -> writeIORef varX (Just x) >> update state') state
return $ upState1 `mappend` upState2 `mappend` mempty {
stateOnCleanup = join $ readIORef varCleanup
}
instance Alternative Updater where
empty = Updater $ \_ _ -> return mempty
(Updater giveMeNext1) <|> (Updater giveMeNext2) = Updater $ \restCalc state -> do
var <-newIORef (error "should not be accessed")
varCleanup <- newIORef $ return ()
let update state' = do
val <- readIORef var
join (readIORef varCleanup)
upstate <- restCalc val state'
writeIORef varCleanup $ stateOnCleanup upstate
return $ upstate {
stateOnCleanup = return ()
}
cleanup1 <- giveMeNext1 (\x state' -> writeIORef var x >> update state') state
cleanup2 <-giveMeNext2 (\x state' -> writeIORef var x >> update state') state
return $ cleanup1 `mappend` cleanup2 `mappend` mempty {
stateOnCleanup = join $ readIORef varCleanup
}
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 mempty
instance Functor Updater where
fmap f (Updater giveMeNext) = Updater (\next -> giveMeNext (next . f))