reflex-gi-gtk-0.1.0.0: src/Reflex/GI/Gtk/Run/Base.hs
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
{-# LANGUAGE GeneralizedNewtypeDeriving, RecordWildCards, UndecidableInstances, TypeFamilies #-}
{-# LANGUAGE StandaloneDeriving, FlexibleInstances, MultiParamTypeClasses, TypeApplications #-}
{-# LANGUAGE PatternSynonyms #-}
module Reflex.GI.Gtk.Run.Base
( RunGtkT
, runGtkT
) where
import Control.Concurrent ( ThreadId
, myThreadId
, newEmptyMVar
, putMVar
, readMVar
)
import Control.Concurrent.STM.TChan ( TChan
, newTChanIO
, readTChan
, tryReadTChan
, writeTChan
)
import Control.Concurrent.STM.TVar ( newTVarIO
, readTVar
, writeTVar
)
import Control.Exception ( SomeException
, catch
, mask_
, throwIO
, try
)
import Control.Monad ( join
, void
)
import Control.Monad.Exception (MonadException)
import Control.Monad.Fix (MonadFix)
import Control.Monad.IO.Class ( MonadIO
, liftIO
)
import Control.Monad.Reader ( ReaderT
, asks
, runReaderT
)
import Control.Monad.Ref (MonadRef)
import Control.Monad.STM ( STM
, atomically
, orElse
, retry
, throwSTM
)
import Control.Monad.Trans (MonadTrans)
import Data.Function (fix)
import GI.GLib.Constants ( pattern PRIORITY_HIGH_IDLE
, pattern SOURCE_REMOVE
)
import GI.Gdk (threadsAddIdle)
import Reflex ( Adjustable( runWithReplace
, traverseIntMapWithKeyWithAdjust
, traverseDMapWithKeyWithAdjust
, traverseDMapWithKeyWithAdjustWithMove
)
, MonadHold
, MonadSample
, NotReady( notReady
, notReadyUntil
)
, PerformEvent( Performable
, performEvent
, performEvent_
)
, PerformEventT
)
import Reflex.GI.Gtk.Run.Class (MonadRunGtk( askRunGtk_
, askRunGtkPromise
, askMakeSynchronousFire
)
)
import Reflex.Host.Class ( MonadReflexCreateTrigger
, MonadReflexHost
, MonadSubscribeEvent
, ReflexHost
)
data RunGtkEnv = RunGtkEnv
{ actionQueue :: TChan (IO ())
, gtkThreadId :: ThreadId
, waitEventThreadException :: STM SomeException
}
newtype RunGtkT t m a = RunGtkT
{ unGtkT :: ReaderT RunGtkEnv m a
}
deriving ( Functor
, Applicative
, Monad
, MonadTrans
, MonadIO
, MonadRef
, MonadException
, MonadFix
)
deriving instance MonadSubscribeEvent t m => MonadSubscribeEvent t (RunGtkT t m)
deriving instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (RunGtkT t m)
deriving instance MonadReflexHost t m => MonadReflexHost t (RunGtkT t m)
deriving instance MonadSample t m => MonadSample t (RunGtkT t m)
deriving instance MonadHold t m => MonadHold t (RunGtkT t m)
deriving instance NotReady t m => NotReady t (RunGtkT t m)
instance Adjustable t m => Adjustable t (RunGtkT t m) where
runWithReplace (RunGtkT a) e = RunGtkT $ runWithReplace a $ unGtkT <$> e
traverseIntMapWithKeyWithAdjust f m a = RunGtkT $ traverseIntMapWithKeyWithAdjust f' m a
where f' k v = unGtkT $ f k v
traverseDMapWithKeyWithAdjust f m e =
RunGtkT $ traverseDMapWithKeyWithAdjust (\k v -> unGtkT $ f k v) m e
traverseDMapWithKeyWithAdjustWithMove f m e =
RunGtkT $ traverseDMapWithKeyWithAdjustWithMove (\k v -> unGtkT $ f k v) m e
instance PerformEvent t m => PerformEvent t (RunGtkT t m) where
type Performable (RunGtkT t m) = RunGtkT t (Performable m)
performEvent = RunGtkT . performEvent . fmap unGtkT
performEvent_ = RunGtkT . performEvent_ . fmap unGtkT
instance (MonadIO m) => MonadRunGtk (RunGtkT t m) where
askRunGtk_ = do
actionChan <- RunGtkT $ asks actionQueue
gtkTId <- RunGtkT $ asks gtkThreadId
pure $ \a -> do
myTId <- myThreadId
let execute = if myTId == gtkTId
then id
else scheduleAction actionChan
execute $ void a
askRunGtkPromise = do
actionQueue <- RunGtkT $ asks actionQueue
gtkTId <- RunGtkT $ asks gtkThreadId
pure $ \a -> do
myTId <- myThreadId
if myTId == gtkTId
then pure <$> a
else do
answerMVar <- newEmptyMVar
scheduleAction actionQueue $ try @SomeException a >>= putMVar answerMVar
pure $ readMVar answerMVar >>= either throwIO pure
askMakeSynchronousFire = do
actionChan <- RunGtkT $ asks actionQueue
waitEventThreadException' <- RunGtkT $ asks waitEventThreadException
pure $ \fireAsynchronously x -> do
firedTVar <- newTVarIO False
fireAsynchronously x $ atomically $ writeTVar firedTVar True
let waitCompleted = do
hasFired <- readTVar firedTVar
if hasFired
then pure ()
else retry
fix $ \loop -> join $ atomically $
(pure () <$ waitCompleted)
`orElse` ( do
gtkAction <- readTChan actionChan
pure $ runGtkAction gtkAction >> loop
)
`orElse` (waitEventThreadException' >>= throwSTM)
scheduleAction :: TChan (IO ()) -> IO () -> IO ()
scheduleAction actionChan action =
atomically (writeTChan actionChan action)
>> void ( threadsAddIdle PRIORITY_HIGH_IDLE $
SOURCE_REMOVE <$ runScheduledActions actionChan
)
runScheduledActions :: TChan (IO ()) -> IO ()
runScheduledActions actionChan =
atomically (tryReadTChan actionChan)
>>= mapM_ (\gtkAction -> runGtkAction gtkAction >> runScheduledActions actionChan)
runGtkAction :: IO () -> IO ()
runGtkAction a = mask_ $ catch a (const $ pure () :: SomeException -> IO ())
runGtkT :: (MonadIO m)
=> RunGtkT t m a
-> STM SomeException
-> ThreadId
-> m a
runGtkT (RunGtkT a) waitEventThreadException gtkThreadId = do
actionQueue <- liftIO newTChanIO
runReaderT a RunGtkEnv{..}
instance ( NotReady t m
, ReflexHost t
) => NotReady t (PerformEventT t (RunGtkT t m)) where
notReady = pure ()
notReadyUntil _ = pure ()