packages feed

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