{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE RecordWildCards #-} module LiveCoding.HandlingState where -- base import Control.Arrow (returnA, arr, (>>>)) import Data.Data -- transformers import Control.Monad.Trans.Class (MonadTrans(lift)) import Control.Monad.Trans.State.Strict import Data.Foldable (traverse_) -- containers import Data.IntMap import qualified Data.IntMap as IntMap -- essence-of-live-coding import LiveCoding.Cell import LiveCoding.Cell.Monad import LiveCoding.Cell.Monad.Trans import LiveCoding.LiveProgram import LiveCoding.LiveProgram.Monad.Trans data Handling h where Handling :: { key :: Key , handle :: h } -> Handling h Uninitialized :: Handling h type Destructors m = IntMap (Destructor m) -- | Hold a map of registered handle keys and destructors data HandlingState m = HandlingState { nHandles :: Key , destructors :: Destructors m } deriving Data -- | In this monad, handles can be registered, -- and their destructors automatically executed. -- It is basically a monad in which handles are automatically garbage collected. type HandlingStateT m = StateT (HandlingState m) m initHandlingState :: HandlingState m initHandlingState = HandlingState { nHandles = 0 , destructors = IntMap.empty } -- | Handle the 'HandlingStateT' effect _without_ garbage collection. -- Apply this to your main loop after calling 'foreground'. -- Since there is no garbage collection, don't use this function for live coding. runHandlingStateT :: Monad m => HandlingStateT m a -> m a runHandlingStateT = flip evalStateT initHandlingState {- | Apply this to your main live cell before passing it to the runtime. On the first tick, it initialises the 'HandlingState' at "no handles". On every step, it does: 1. Unregister all handles 2. Register currently present handles 3. Destroy all still unregistered handles (i.e. those that were removed in the last tick) -} runHandlingStateC :: forall m a b . (Monad m, Typeable m) => Cell (HandlingStateT m) a b -> Cell m a b runHandlingStateC cell = flip runStateC_ initHandlingState $ hoistCellOutput garbageCollected cell -- | Like 'runHandlingStateC', but for whole live programs. runHandlingState :: (Monad m, Typeable m) => LiveProgram (HandlingStateT m) -> LiveProgram m runHandlingState LiveProgram { .. } = flip runStateL initHandlingState LiveProgram { liveStep = garbageCollected . liveStep , .. } garbageCollected :: Monad m => HandlingStateT m a -> HandlingStateT m a garbageCollected action = unregisterAll >> action <* destroyUnregistered data Destructor m = Destructor { isRegistered :: Bool , action :: m () } register :: Monad m => m () -- ^ Destructor -> HandlingStateT m Key register destructor = do HandlingState { .. } <- get let key = nHandles + 1 put HandlingState { nHandles = key , destructors = insertDestructor destructor key destructors } return key reregister :: Monad m => m () -> Key -> HandlingStateT m () reregister action key = do HandlingState { .. } <- get put HandlingState { destructors = insertDestructor action key destructors, .. } insertDestructor :: m () -> Key -> Destructors m -> Destructors m insertDestructor action key destructors = let destructor = Destructor { isRegistered = True, .. } in insert key destructor destructors unregisterAll :: Monad m => HandlingStateT m () unregisterAll = do HandlingState { .. } <- get let newDestructors = IntMap.map (\destructor -> destructor { isRegistered = False }) destructors put HandlingState { destructors = newDestructors, .. } destroyUnregistered :: Monad m => HandlingStateT m () destroyUnregistered = do HandlingState { .. } <- get let (registered, unregistered) = partition isRegistered destructors traverse_ (lift . action) unregistered put HandlingState { destructors = registered, .. } -- * 'Data' instances dataTypeHandling :: DataType dataTypeHandling = mkDataType "Handling" [handlingConstr, uninitializedConstr] handlingConstr :: Constr handlingConstr = mkConstr dataTypeHandling "Handling" [] Prefix uninitializedConstr :: Constr uninitializedConstr = mkConstr dataTypeHandling "Uninitialized" [] Prefix instance (Typeable h) => Data (Handling h) where dataTypeOf _ = dataTypeHandling toConstr Handling { .. } = handlingConstr toConstr Uninitialized = uninitializedConstr gunfold _cons nil constructor = nil Uninitialized dataTypeDestructor :: DataType dataTypeDestructor = mkDataType "Destructor" [ destructorConstr ] destructorConstr :: Constr destructorConstr = mkConstr dataTypeDestructor "Destructor" [] Prefix instance Typeable m => Data (Destructor m) where dataTypeOf _ = dataTypeDestructor toConstr Destructor { .. } = destructorConstr gunfold _ _ = error "Destructor.gunfold"