-
Jens Nolte authoredJens Nolte authored
Host.hs 5.60 KiB
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DuplicateRecordFields #-}
module QBar.Host where
import QBar.BlockOutput
import QBar.Core
import Control.Concurrent (forkIO, forkFinally, threadDelay)
import Control.Concurrent.Event as Event
import Control.Concurrent.STM.TChan (TChan, newTChanIO, tryReadTChan)
import Control.Exception (SomeException, catch)
import Control.Lens hiding (each, (.=))
import Control.Monad.STM (atomically)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.Maybe (catMaybes, mapMaybe)
import qualified Data.Text.Lazy as T
import Pipes
import System.IO (stderr, hPutStrLn)
import System.Posix.Signals
data HostHandle = HostHandle {
barUpdateEvent :: BarUpdateEvent,
newBlockChan :: TChan BlockCache,
eventHandlerListIORef :: IORef [(T.Text, BlockEventHandler)]
}
installSignalHandlers :: Bar -> IO ()
installSignalHandlers bar = void $ installHandler sigCONT (Catch sigContAction) Nothing
where
sigContAction :: IO ()
sigContAction = do
hPutStrLn stderr "SIGCONT received"
updateBar' bar
eventDispatcher :: Bar -> IORef [(T.Text, BlockEventHandler)] -> Consumer BlockEvent IO ()
eventDispatcher bar eventHandlerListIORef = eventDispatcher'
where
eventDispatcher' :: Consumer BlockEvent IO ()
eventDispatcher' = do
blockEvent <- await
eventHandlerList <- liftIO $ readIORef eventHandlerListIORef
let maybeEventHandler = getEventHandler eventHandlerList blockEvent
case maybeEventHandler of
Just eventHandler -> liftIO . void . forkIO $ catch (runBarIO bar $ eventHandler blockEvent) (\(e :: SomeException) -> hPutStrLn stderr $ "event handler failed: " <> show e)
Nothing -> return ()
eventDispatcher'
getEventHandler :: [(T.Text, BlockEventHandler)] -> BlockEvent -> Maybe BlockEventHandler
getEventHandler eventHandlerList blockEvent = lookup (name blockEvent) eventHandlerList
runBlocks :: Bar -> HostHandle -> Producer [BlockOutput] IO ()
runBlocks bar HostHandle{barUpdateEvent, newBlockChan, eventHandlerListIORef} = runBlocks' []
where
runBlocks' :: [BlockCache] -> Producer [BlockOutput] IO ()
runBlocks' blocks = do
liftIO $ do
-- Wait for an update request
Event.wait barUpdateEvent
-- Wait for 10ms after first events to catch (almost-)simultaneous event updates
threadDelay 10000
Event.clear barUpdateEvent
blocks' <- lift $ runBarIO bar $ addNewBlocks blocks
(blockStates, blocks'') <- lift $ runBarIO bar $ getBlockStates blocks'
-- Pass blocks to output
yield $ map fst $ catMaybes blockStates
-- Register new event handlers immediately after rendering
liftIO $ updateEventHandlers blockStates
-- Wait for 90ms after rendering a line to limit cpu load of rapid events
liftIO $ threadDelay 90000
-- Loop
runBlocks' blocks''
addNewBlocks :: [BlockCache] -> BarIO [BlockCache]
addNewBlocks blocks = do
maybeNewBlock <- liftIO $ atomically $ tryReadTChan newBlockChan
case maybeNewBlock of
Nothing -> return blocks
Just newBlock -> addNewBlocks (newBlock:blocks)
getBlockStates :: [BlockCache] -> BarIO ([BlockState], [BlockCache])
getBlockStates caches = do
(blockStates, newCaches) <- unzip . catMaybes <$> mapM readCache caches
return (concat blockStates, newCaches)
where
readCache :: BlockCache -> BarIO (Maybe ([BlockState], BlockCache))
readCache producer = do
next' <- next producer
return $ case next' of
Left _ -> Nothing
Right (blockStates, newProducer) -> Just (blockStates, newProducer)
updateEventHandlers :: [BlockState] -> IO ()
updateEventHandlers blockStates =
writeIORef eventHandlerListIORef eventHandlerList
where
eventHandlerList :: [(T.Text, BlockEventHandler)]
eventHandlerList = mapMaybe getEventHandler $ blockStates
getEventHandler :: BlockState -> Maybe (T.Text, BlockEventHandler)
getEventHandler Nothing = Nothing
getEventHandler (Just (_, Nothing)) = Nothing
getEventHandler (Just (blockOutput, Just eventHandler)) = do
blockName' <- blockOutput^.blockName
return (blockName', eventHandler)
filterDuplicates :: (Monad m, Eq a) => Pipe a a m r
filterDuplicates = do
value <- await
yield value
filterDuplicates' value
where
filterDuplicates' :: (Monad m, Eq a) => a -> Pipe a a m r
filterDuplicates' lastValue = do
value <- await
when (lastValue /= value) $ yield value
filterDuplicates' value
runBarHost :: BarIO (Consumer [BlockOutput] IO (), Producer BlockEvent IO ()) -> BarIO () -> IO ()
runBarHost createHost loadBlocks = do
-- Create an event used to signal bar updates
barUpdateEvent <- Event.newSet
let requestBarUpdate = Event.set barUpdateEvent
-- Create channel to send new block producers to render loop
newBlockChan <- newTChanIO
let bar = Bar { requestBarUpdate, newBlockChan }
-- Install signal handler for SIGCONT
installSignalHandlers bar
-- Create IORef for event handlers
eventHandlerListIORef <- newIORef []
let hostHandle = HostHandle {
barUpdateEvent,
newBlockChan,
eventHandlerListIORef
}
runBarIO bar loadBlocks
(host, barEventProducer) <- runBarIO bar createHost
let handleStdin = liftIO $ runEffect $ barEventProducer >-> eventDispatcher bar eventHandlerListIORef
-- Fork stdin handler
void $ forkFinally (runBarIO bar handleStdin) (\result -> hPutStrLn stderr $ "handleStdin failed: " <> show result)
-- Run bar host
runEffect $ runBlocks bar hostHandle >-> filterDuplicates >-> host