packages feed

theatre-dev-0.0.1: library/TheatreDev/StmBased/StmStructures/Runner.hs

{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE NoFieldSelectors #-}

module TheatreDev.StmBased.StmStructures.Runner
  ( Runner,
    start,
    tell,
    kill,
    wait,
    receiveSingle,
    receiveMultiple,
    releaseWithException,
  )
where

import Control.Concurrent.STM.TBQueue
import Control.Concurrent.STM.TMVar
import qualified TheatreDev.ExtrasFor.List as List
import TheatreDev.ExtrasFor.TBQueue
import TheatreDev.Prelude

data Runner a = Runner
  { queue :: TBQueue (Maybe a),
    aliveVar :: TVar Bool,
    resVar :: TMVar (Maybe SomeException)
  }

start :: STM (Runner a)
start =
  do
    queue <- newTBQueue 1000
    aliveVar <- newTVar True
    resVar <- newEmptyTMVar @(Maybe SomeException)
    return Runner {..}

tell :: Runner a -> a -> STM ()
tell Runner {..} message =
  do
    alive <- readTVar aliveVar
    when alive do
      writeTBQueue queue $ Just message

kill :: Runner a -> STM ()
kill Runner {..} =
  do
    alive <- readTVar aliveVar
    when alive do
      writeTBQueue queue Nothing

wait :: Runner a -> STM (Maybe SomeException)
wait Runner {..} =
  readTMVar resVar

receiveSingle ::
  Runner a ->
  -- | Action producing a message or nothing, after it's killed.
  STM (Maybe a)
receiveSingle Runner {..} =
  do
    message <- readTBQueue queue
    case message of
      Just message -> return (Just message)
      Nothing -> do
        writeTVar aliveVar False
        putTMVar resVar Nothing
        return Nothing

receiveMultiple ::
  Runner a ->
  STM (Maybe (NonEmpty a))
receiveMultiple Runner {..} =
  do
    (messages, remainingCommands) <- do
      queueLength <- lengthTBQueue queue
      head <- readTBQueue queue
      tail <- simplerFlushTBQueue queue
      return $ List.splitWhileJust $ head : tail
    case messages of
      -- Implies that the tail is not empty,
      -- because we have at least one element.
      -- And that it starts with a Nothing.
      [] -> do
        forM_ remainingCommands $ unGetTBQueue queue
        writeTVar aliveVar False
        putTMVar resVar Nothing
        return Nothing
      messagesHead : messagesTail -> do
        unless (null remainingCommands) do
          unGetTBQueue queue Nothing
        return $ Just $ messagesHead :| messagesTail

releaseWithException :: Runner a -> SomeException -> STM ()
releaseWithException Runner {..} exception =
  do
    simplerFlushTBQueue queue
    writeTVar aliveVar False
    putTMVar resVar (Just exception)