{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RecordWildCards #-}
module LiveCoding.HandlingState where
-- base
import Control.Arrow (arr, returnA, (>>>))
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 = Handling
{ key :: Key
, handle :: 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 =>
-- | Destructor
m () ->
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
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"