{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} module LiveCoding.RuntimeIO.Launch where -- base import Control.Concurrent import Control.Monad import Data.Data -- transformers import Control.Monad.Trans.Except import Control.Monad.Trans.State.Strict -- essence-of-live-coding import LiveCoding.Cell.Monad.Trans import LiveCoding.Debugger import LiveCoding.Exceptions.Finite (Finite) import LiveCoding.Handle import LiveCoding.HandlingState import LiveCoding.LiveProgram import LiveCoding.LiveProgram.Except import LiveCoding.LiveProgram.HotCodeSwap {- | Monads in which live programs can be launched in 'IO', for example when you have special effects that have to be handled on every reload. The only thing necessary is to transform the 'LiveProgram' into one in the 'IO' monad, and the rest is taken care of in the framework. -} class Monad m => Launchable m where runIO :: LiveProgram m -> LiveProgram IO instance Launchable IO where runIO = id instance (Typeable m, Launchable m) => Launchable (HandlingStateT m) where runIO = runIO . runHandlingState {- | Upon an exception, the program is restarted. To handle or log the exception, see "LiveCoding.LiveProgram.Except". -} instance (Data e, Finite e, Launchable m) => Launchable (ExceptT e m) where runIO liveProgram = runIO $ foreverCLiveProgram $ try liveProgram {- | The standard top level @main@ for a live program. Typically, you will define a top level 'LiveProgram' in some monad like @'HandlingStateT' 'IO'@, and then add these two lines of boiler plate: @ main :: IO () main = liveMain liveProgram @ -} liveMain :: Launchable m => LiveProgram m -> IO () liveMain = foreground . runIO -- | Launch a 'LiveProgram' in the foreground thread (blocking). foreground :: Monad m => LiveProgram m -> m () foreground liveProgram = stepProgram liveProgram >>= foreground -- | A launched 'LiveProgram' and the thread in which it is running. data LaunchedProgram (m :: * -> *) = LaunchedProgram { programVar :: MVar (LiveProgram IO) , threadId :: ThreadId } {- | Launch a 'LiveProgram' in a separate thread. The 'MVar' can be used to 'update' the program while automatically migrating it. The 'ThreadId' represents the thread where the program runs in. You're advised not to kill it directly, but to run 'stop' instead. -} launch :: Launchable m => LiveProgram m -> IO (LaunchedProgram m) launch liveProg = do programVar <- newMVar $ runIO liveProg threadId <- forkIO $ background programVar return LaunchedProgram {..} -- | Migrate (using 'hotCodeSwap') the 'LiveProgram' to a new version. update :: Launchable m => LaunchedProgram m -> LiveProgram m -> IO () update LaunchedProgram {..} newProg = modifyMVarMasked_ programVar $ return . hotCodeSwap (runIO newProg) {- | Stops a thread where a 'LiveProgram' is being executed. Before the thread is killed, an empty program (in the monad @m@) is first inserted and stepped. This can be used to call cleanup actions encoded in the monad, such as 'HandlingStateT'. -} stop :: Launchable m => LaunchedProgram m -> IO () stop launchedProgram@LaunchedProgram {..} = do update launchedProgram mempty stepLaunchedProgram launchedProgram killThread threadId -- | Launch a 'LiveProgram', but first attach a debugger to it. launchWithDebugger :: (Monad m, Launchable m) => LiveProgram m -> Debugger m -> IO (LaunchedProgram m) launchWithDebugger liveProg debugger = launch $ liveProg `withDebugger` debugger -- | This is the background task executed by 'launch'. background :: MVar (LiveProgram IO) -> IO () background var = forever $ do liveProg <- takeMVar var liveProg' <- stepProgram liveProg putMVar var liveProg' -- | Advance a 'LiveProgram' by a single step. stepProgram :: Monad m => LiveProgram m -> m (LiveProgram m) stepProgram LiveProgram {..} = do liveState' <- liveStep liveState return LiveProgram {liveState = liveState', ..} -- | Advance a launched 'LiveProgram' by a single step and store the result. stepLaunchedProgram :: (Monad m, Launchable m) => LaunchedProgram m -> IO () stepLaunchedProgram LaunchedProgram {..} = modifyMVarMasked_ programVar stepProgram