eventuo11y-0.8.0.0: src/Observe/Event/Render/InMemory.hs
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneKindSignatures #-}
-- |
-- Description : EventBackend for rendering events as Haskell values
-- Copyright : Copyright 2023 Shea Levy.
-- License : Apache-2.0
-- Maintainer : shea@shealevy.com
module Observe.Event.Render.InMemory
( listInMemoryBackend,
timelessListInMemoryBackend,
inMemoryBackend,
-- * MemoryEvent
MemoryEvent (..),
TimedEventAction (..),
EventAction (..),
-- * Effects
-- ** InMemoryEffects
InMemoryEffects (..),
hoistInMemoryEffects,
listInMemoryEffects,
timelessListInMemoryEffects,
-- ** AppendVectorEffects
AppendVectorEffects (..),
hoistAppendVectorEffects,
ListAppendVector (..),
listAppendVectorEffects,
-- ** TimestampEffects
TimestampEffects (..),
hoistTimestampEffects,
dummyTimestampEffects,
ioTimestampEffects,
)
where
import Control.Exception
import Control.Monad
import Control.Monad.Primitive
import Data.Kind
import Data.Maybe
import Data.Primitive.MutVar
import Data.Time.Clock
import Observe.Event.Backend
-- | An 'EventBackend' whose 'Event's are essentially plain Haskell values.
--
-- The 'reference' of an 'Event' from this 'EventBackend' will be a 'MemoryEvent',
-- which can be examined to extract information about the 'Event'.
listInMemoryBackend ::
-- | Notify of an 'Event' with no parents or causes
(MemoryEvent IO (ListAppendVector IO) UTCTime s -> IO ()) ->
EventBackend IO (MemoryEvent IO (ListAppendVector IO) UTCTime s) s
listInMemoryBackend = inMemoryBackend listInMemoryEffects
-- | An 'EventBackend' whose 'Event's are essentially plain Haskell values.
--
-- The 'reference' of an 'Event' from this 'EventBackend' will be a 'MemoryEvent',
-- which can be examined to extract information about the 'Event'.
timelessListInMemoryBackend ::
(PrimMonad m) =>
-- | Notify of an 'Event' with no parents or causes
(MemoryEvent m (ListAppendVector m) () s -> m ()) ->
EventBackend m (MemoryEvent m (ListAppendVector m) () s) s
timelessListInMemoryBackend = inMemoryBackend timelessListInMemoryEffects
-- | An 'EventBackend' whose 'Event's are essentially plain Haskell values.
--
-- The 'reference' of an 'Event' from this 'EventBackend' will be a 'MemoryEvent',
-- which can be examined to extract information about the 'Event'.
--
-- [@appvec@]: An append-only vector type, see 'AppendVectorEffects'
-- [@ts@]: A timestamp, see 'TimestampEffects'
inMemoryBackend ::
(Monad m) =>
InMemoryEffects m appvec ts ->
-- | Notify of an 'Event' with no parents or causes
(MemoryEvent m appvec ts s -> m ()) ->
EventBackend m (MemoryEvent m appvec ts s) s
inMemoryBackend (InMemoryEffects {..}) emitDisconnectedEvent =
EventBackend
{ newEvent = \initArgs -> do
start <- getTimestamp
dynamicValues <- newVector
childEvents <- newVector
causedEvents <- newVector
let reference = MemoryEvent {dynamicValues = Just dynamicValues, ..}
hasParents <-
isJust
<$> traverse
(\(MemoryEvent {childEvents = cevs}) -> appendVector cevs reference)
(newEventParent initArgs)
hasCauses <-
not . null
<$> traverse
(\(MemoryEvent {causedEvents = cevs}) -> appendVector cevs reference)
(newEventCauses initArgs)
unless (hasParents || hasCauses) $ emitDisconnectedEvent reference
pure $
Event
{ addField = \f -> do
eventTime <- getTimestamp
appendVector dynamicValues $ TimedEventAction {act = AddField f, ..},
finalize = \me -> do
eventTime <- getTimestamp
appendVector dynamicValues $ TimedEventAction {act = Finalize me, ..},
..
},
emitImmediateEvent = \initArgs -> do
start <- getTimestamp
let dynamicValues = Nothing
childEvents <- newVector
causedEvents <- newVector
pure $ MemoryEvent {..}
}
where
AppendVectorEffects {..} = appendVectorEffects
TimestampEffects {..} = timestampEffects
-- | An plain data type representing an 'Event'
--
-- [@appvec@]: An append-only vector type, see 'AppendVectorEffects'
-- [@ts@]: A timestamp, see 'TimestampEffects'
type MemoryEvent :: (Type -> Type) -> (Type -> Type) -> Type -> (Type -> Type) -> Type
data MemoryEvent m appvec ts s = forall f.
MemoryEvent
{ -- | The arguments the 'Event' was initialized with
initArgs :: !(NewEventArgs (MemoryEvent m appvec ts s) s f),
-- | The start time of the 'Event'
start :: !ts,
-- | Event information added during the event's lifecycle
--
-- 'Nothing' if this was the result of 'emitImmediateEvent' and thus had no lifecycle
dynamicValues :: !(Maybe (appvec (TimedEventAction ts f))),
-- | Direct children of this event
childEvents :: !(appvec (MemoryEvent m appvec ts s)),
-- | Events directly caused by this event
causedEvents :: !(appvec (MemoryEvent m appvec ts s))
}
-- | An action that occurred during an 'Event' at some time.
--
-- [@ts@]: A timestamp, see 'TimestampEffects'
data TimedEventAction ts f = TimedEventAction
{ -- | When the event occurred
eventTime :: !ts,
-- | The action that occurred
act :: !(EventAction f)
}
-- | An action on an 'Event'
data EventAction f
= -- | A field was added with 'addField'
AddField !f
| -- | The 'Event' was finalized with 'finalize'
Finalize !(Maybe SomeException)
-- | Monadic effects needed to construct an 'inMemoryBackend'
--
-- [@appvec@]: An append-only vector type, see 'AppendVectorEffects'
-- [@ts@]: A timestamp, see 'TimestampEffects'
data InMemoryEffects m appvec ts = InMemoryEffects
{ -- | Manipulate append-only vectors
appendVectorEffects :: !(AppendVectorEffects m appvec),
-- | Get timestamps
timestampEffects :: !(TimestampEffects m ts)
}
-- | Hoist 'InMemoryEffects' along a given natural transformation into a new monad
hoistInMemoryEffects :: (forall x. m x -> n x) -> InMemoryEffects m appvec ts -> InMemoryEffects n appvec ts
hoistInMemoryEffects nt (InMemoryEffects {..}) =
InMemoryEffects
{ appendVectorEffects = hoistAppendVectorEffects nt appendVectorEffects,
timestampEffects = hoistTimestampEffects nt timestampEffects
}
-- | 'InMemoryEffects' based on 'listAppendVectorEffects' and 'ioTimestampEffects'.
listInMemoryEffects :: InMemoryEffects IO (ListAppendVector IO) UTCTime
listInMemoryEffects =
InMemoryEffects
{ appendVectorEffects = listAppendVectorEffects,
timestampEffects = ioTimestampEffects
}
-- | 'InMemoryEffects' based on 'listAppendVectorEffects' with meaningless timestamps.
timelessListInMemoryEffects :: (PrimMonad m) => InMemoryEffects m (ListAppendVector m) ()
timelessListInMemoryEffects =
InMemoryEffects
{ appendVectorEffects = listAppendVectorEffects,
timestampEffects = dummyTimestampEffects
}
-- | Monadic effects to manipulate append-only vectors.
data AppendVectorEffects m appvec = AppendVectorEffects
{ -- | Create a new vector
newVector :: !(forall a. m (appvec a)),
-- | Append a value to a vector
appendVector :: !(forall a. appvec a -> a -> m ())
}
-- | Hoist 'AppendVectorEffects' along a given natural transformation into a new monad
hoistAppendVectorEffects :: (forall x. m x -> n x) -> AppendVectorEffects m appvec -> AppendVectorEffects n appvec
hoistAppendVectorEffects nt (AppendVectorEffects {..}) =
AppendVectorEffects
{ newVector = nt $ newVector,
appendVector = \v -> nt . appendVector v
}
-- | An append-only vector in some 'PrimMonad' based on lists.
newtype ListAppendVector m a = ListAppendVector (MutVar (PrimState m) [a])
-- | 'AppendVectorEffects' in some 'PrimMonad' based on lists.
listAppendVectorEffects :: (PrimMonad m) => AppendVectorEffects m (ListAppendVector m)
listAppendVectorEffects =
AppendVectorEffects
{ newVector = ListAppendVector <$> newMutVar [],
appendVector = \(ListAppendVector v) x -> atomicModifyMutVar v (\l -> (x : l, ()))
}
-- | Monadic effects to manage timestamps.
newtype TimestampEffects m ts = TimestampEffects
{ getTimestamp :: m ts
}
-- | Hoist 'TimestampEffects' along a given natural transformation into a new monad
hoistTimestampEffects :: (forall x. m x -> n x) -> TimestampEffects m ts -> TimestampEffects n ts
hoistTimestampEffects nt (TimestampEffects {..}) = TimestampEffects $ nt getTimestamp
-- | 'TimestampEffects' with meaningless timestamps.
dummyTimestampEffects :: (Applicative m) => TimestampEffects m ()
dummyTimestampEffects = TimestampEffects $ pure ()
-- | 'TimestampEffects' using the system clock
ioTimestampEffects :: TimestampEffects IO UTCTime
ioTimestampEffects = TimestampEffects getCurrentTime