packages feed

ychr-0.1.0.0: src/YCHR/Internal/Runtime/Reactivation.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedRecordDot #-}

-- | Reactivation queue for the CHR Haskell runtime.
--
-- Accumulates constraint suspension IDs that need reactivation (typically
-- enqueued as a side effect of unification) and provides a drain operation
-- that processes them one at a time. The drain re-reads the queue on every
-- iteration so that IDs enqueued by reentrant unifications during the
-- callback are picked up.
module YCHR.Internal.Runtime.Reactivation
  ( enqueue,
    drainQueue,
  )
where

import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Reader (ask)
import Data.IORef
import Data.Sequence (Seq (..))
import Data.Sequence qualified as Seq
import YCHR.Internal.Runtime.Monad (Chr, SessionEnv (..))
import YCHR.Internal.Runtime.Types (SuspensionId)

-- | Append suspension IDs to the back of the queue.
enqueue :: [SuspensionId] -> Chr ()
enqueue ids = do
  SessionEnv {reactQueue} <- ask
  liftIO $ modifyIORef' reactQueue (<> Seq.fromList ids)

-- | Drain the queue one element at a time, calling the callback for each.
-- Re-reads the queue on every iteration so that IDs enqueued by the
-- callback (via reentrant unifications) are picked up.
drainQueue :: (SuspensionId -> Chr ()) -> Chr ()
drainQueue callback = go
  where
    go = do
      SessionEnv {reactQueue} <- ask
      mNext <- liftIO $ atomicModifyIORef' reactQueue $ \case
        Empty -> (Seq.empty, Nothing)
        x :<| rest -> (rest, Just x)
      case mNext of
        Nothing -> pure ()
        Just sid -> callback sid >> go