-- | Delayers handle delaying of actions; the main purpose is to delay -- graph redrawing actions during complex updates. module Util.Delayer( -- Client side Delayer, newDelayer, -- :: IO Delayer HasDelayer(..), -- :: Class of things which have a delayer. -- Delayer itself is an instance. delay, -- :: HasDelayer object => object -> IO a -> IO a -- carry out the given action preventing the Delayer from doing anything. -- Producer side DelayedAction, newDelayedAction, -- :: IO () -> IO DelayedAction delayedAct, -- :: Delayer -> DelayedAction -> IO () -- If no delay is taking place, perform the DelayedAction action -- immediately. Otherwise remember to do it when are no longer inside -- a delay. -- If the same DelayedAction is queued multiple times when a Delayer -- is delay'd, we nevertheless only do it once. cancelDelayedAct, -- :: Delayer -> DelayedAction -> IO () -- If this DelayedAction is queued, remove it from the queue. HasAddDelayer(..), -- Instances of HasAddDelayer are event sources to which you can attach -- a delayer, to indicate you are currently not interested in events. HasAddDelayerIO(..), -- Like HasAddDelayer, but allows an IO action. ) where import Control.Concurrent.MVar import Control.Exception import qualified Data.Set as Set import Util.Object import Util.Computation(done) -- ------------------------------------------------------------------------ -- Data types -- ------------------------------------------------------------------------ data DelayedAction = DelayedAction { oId :: ObjectID, action :: IO () } data DelayerState = DelayerState { delayCount :: ! Int, -- ^ 0 when not delay'd. delayedActions :: Set.Set DelayedAction } data Delayer = Delayer (MVar DelayerState) -- ------------------------------------------------------------------------ -- HasAddDelayer -- ------------------------------------------------------------------------ -- | Instances of HasAddDelayer are event sources to which you can attach -- a delayer, to indicate you are currently not interested in events. class HasAddDelayer eventSource where addDelayer :: Delayer -> eventSource -> eventSource -- | Like HasAddDelayer, but allows an IO action. class HasAddDelayerIO eventSource where addDelayerIO :: Delayer -> eventSource -> IO eventSource -- ------------------------------------------------------------------------ -- HasDelayer -- ------------------------------------------------------------------------ class HasDelayer object where toDelayer :: object -> Delayer -- ------------------------------------------------------------------------ -- Instances -- ------------------------------------------------------------------------ instance Eq DelayedAction where (==) act1 act2 = (==) (oId act1) (oId act2) instance Ord DelayedAction where compare act1 act2 = compare (oId act1) (oId act2) instance HasDelayer Delayer where toDelayer delayer = delayer -- ------------------------------------------------------------------------ -- Client Side -- ------------------------------------------------------------------------ newDelayer :: IO Delayer newDelayer = do mVar <- newMVar emptyDelayerState return (Delayer mVar) -- | carry out the given action preventing the Delayer from doing anything. delay :: HasDelayer object => object -> IO a -> IO a delay object action = do let delayer = toDelayer object beginDelay delayer finally action (endDelay delayer) beginDelay :: Delayer -> IO () beginDelay (Delayer mVar) = modifyMVar_ mVar (\ delayerState0 -> do let delayCount1 = delayCount delayerState0 + 1 delayerState1 = delayerState0 {delayCount = delayCount1} seq delayerState1 (return delayerState1) ) endDelay :: Delayer -> IO () endDelay (Delayer mVar) = do -- to reduce the danger of deadlocks, we don't perform the actions while -- the MVar is empty. afterAct <- modifyMVar mVar (\ delayerState0 -> do let delayCount1 = delayCount delayerState0 - 1 return (if delayCount1 > 0 then (delayerState0 {delayCount = delayCount1},done) else let afterAct = mapM_ (\ delayedAction -> action delayedAction) (Set.toList (delayedActions delayerState0)) in (emptyDelayerState,afterAct) ) ) afterAct emptyDelayerState :: DelayerState emptyDelayerState = DelayerState { delayCount = 0, delayedActions = Set.empty } -- ------------------------------------------------------------------------ -- Producer side -- ------------------------------------------------------------------------ newDelayedAction :: IO () -> IO DelayedAction newDelayedAction action = do oId <- newObject let delayedAction = DelayedAction { oId = oId, action = action } return delayedAction -- } If no delay is taking place, perform the DelayedAction action -- immediately. Otherwise remember to do it when are no longer inside -- a delay. -- If the same DelayedAction is queued multiple times when a Delayer -- is delay'd, we nevertheless only do it once. delayedAct :: Delayer -> DelayedAction -> IO () delayedAct (Delayer mVar) delayedAct = do afterAct <- modifyMVar mVar (\ delayerState0 -> return ( if delayCount delayerState0 == 0 then (delayerState0,action delayedAct) else let delayedActions1 = Set.insert delayedAct (delayedActions delayerState0) delayerState1 = delayerState0 { delayedActions = delayedActions1 } in (delayerState1,done) ) ) afterAct -- | If this DelayedAction is queued, remove it from the queue. cancelDelayedAct :: Delayer -> DelayedAction -> IO () cancelDelayedAct (Delayer mVar) delayedAction = modifyMVar_ mVar (\ delayerState0 -> let delayedActions1 = Set.delete delayedAction (delayedActions delayerState0) delayerState1 = delayerState0 {delayedActions = delayedActions1} in return delayerState1 )