packages feed

moonlight-triangulation-0.1.0.0: src-build/Moonlight/Triangulation/Internal/Cdt/Batch.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}

-- | The batch interpreter: many constraint requests in order inside one sealed
-- dense transaction, with rejection carried as a value.
module Moonlight.Triangulation.Internal.Cdt.Batch
  ( recoverConstraints
  , recoverConstraintBatch
  , initialConstraintBatchStats
  , interpretConstraintRequest
  , interpretConstraintRequests
  ) where

import Control.Monad.ST (ST)
import Data.Either (isRight)
import qualified Data.Vector as V
import Moonlight.Triangulation.Handles.HandleDefs
import Moonlight.Triangulation.Internal.Cdt.Combinators (foldWhileM)
import Moonlight.Triangulation.Internal.Cdt.Query (validateEndpoints)
import Moonlight.Triangulation.Internal.Cdt.Recovery (applyMutableConstraint)
import Moonlight.Triangulation.Internal.Cdt.Types
import Moonlight.Triangulation.Internal.Growable
  ( GrowableWord32
  , newGrowableWord32
  )
import Moonlight.Triangulation.Internal.Mutable
import Moonlight.Triangulation.Internal.OperationState
  ( OperationState )
import Moonlight.Triangulation.Internal.Paged (TransactionShape (DenseTransaction))
import Moonlight.Triangulation.Internal.Representation
import Moonlight.Triangulation.Internal.Transaction (runTransaction)
import Moonlight.Triangulation.Internal.Types (ConstraintMode (..))

-- | Interpret constraint requests in order inside one sealed mutable DCEL
-- transaction. Rejections are values because an earlier accepted request can
-- lawfully obstruct a later request; structural recovery failures remain typed
-- errors and prevent a partially rewritten mesh from escaping. The public batch
-- interpreter amortizes dense materialization; known singleton repair sites
-- reuse this algebra under the sparse persistent-page interpretation.
recoverConstraints
  :: Triangulation 'Constrained vertex directed undirected face
  -> V.Vector (VertexId, VertexId)
  -> Either (CdtError) (ConstraintBatchResult vertex directed undirected face)
recoverConstraints triangulation requests
  | V.null requests =
      Right
        ConstraintBatchResult
          { constraintBatchTriangulation = triangulation
          , constraintBatchOutcomes = V.empty
          , constraintBatchStats = initialConstraintBatchStats 0
          }
  | otherwise =
      recoverConstraintBatch
        triangulation
        requests

recoverConstraintBatch
  :: Triangulation 'Constrained vertex directed undirected face
  -> V.Vector (VertexId, VertexId)
  -> Either (CdtError) (ConstraintBatchResult vertex directed undirected face)
recoverConstraintBatch triangulation requests = do
  V.mapM_ (uncurry (validateEndpoints triangulation)) requests
  (completed, frozen, _) <-
    runTransaction
      CdtBuildError
      DenseTransaction
      triangulation
      0
      (interpretConstraintRequests requests)
  pure (finalizeConstraintBatch frozen completed)
{-# INLINE recoverConstraintBatch #-}

-- | Interpret a complete constraint request section against an already-open
-- transaction. Both ordinary recovery and asymmetric constrained extension
-- use this one interpreter; callers decide only which request section is
-- resident before it begins.
interpretConstraintRequests
  :: V.Vector (VertexId, VertexId)
  -> MutableDcel s vertex directed undirected face
  -> OperationState s
  -> ST s (Either (CdtError) ConstraintBatchAccumulator)
interpretConstraintRequests requests mutable operation = do
  programWords <- newGrowableWord32 256
  let initialAccumulator =
        ConstraintBatchAccumulator
          { accumulatedConstraintOutcomes = []
          , accumulatedConstraintStats =
              initialConstraintBatchStats (V.length requests)
          }
  foldWhileM
    isRight
    (interpretConstraintRequest programWords mutable operation)
    (Right initialAccumulator)
    requests
{-# INLINE interpretConstraintRequests #-}

-- | Materialize the ordinary recovery receipt only after the enclosing
-- transaction has frozen. The mutable accumulator cannot escape as a partial
-- constrained mesh.
finalizeConstraintBatch
  :: Triangulation 'Constrained vertex directed undirected face
  -> ConstraintBatchAccumulator
  -> ConstraintBatchResult vertex directed undirected face
finalizeConstraintBatch frozen completed =
  ConstraintBatchResult
    { constraintBatchTriangulation = frozen
    , constraintBatchOutcomes = V.fromList (reverse (accumulatedConstraintOutcomes completed))
    , constraintBatchStats = accumulatedConstraintStats completed
    }
{-# INLINE finalizeConstraintBatch #-}

initialConstraintBatchStats :: Int -> ConstraintBatchStats
initialConstraintBatchStats requestCount =
  ConstraintBatchStats
    { constraintBatchRequests = requestCount
    , constraintBatchAccepted = 0
    , constraintBatchRejected = 0
    , constraintBatchCorridors = 0
    , constraintBatchReusedFaces = 0
    , constraintBatchCrossedEdges = 0
    }

interpretConstraintRequest
  :: GrowableWord32 s
  -> MutableDcel s vertex directed undirected face
  -> OperationState s
  -> Either (CdtError) ConstraintBatchAccumulator
  -> (VertexId, VertexId)
  -> ST s (Either (CdtError) ConstraintBatchAccumulator)
interpretConstraintRequest _ _ _ rejected@(Left _) _ = pure rejected
interpretConstraintRequest programWords mutable operation (Right accumulator) (from, to) = do
  applied <- applyMutableConstraint programWords mutable operation from to
  pure $ case applied of
    Left obstruction -> Left obstruction
    Right (MutableConstraintRejected blocking) ->
      let previousStats = accumulatedConstraintStats accumulator
       in Right
            accumulator
              { accumulatedConstraintOutcomes =
                  ConstraintRejected blocking : accumulatedConstraintOutcomes accumulator
              , accumulatedConstraintStats =
                  previousStats
                    { constraintBatchRejected = constraintBatchRejected previousStats + 1
                    }
              }
    Right (MutableConstraintAccepted request) ->
      let previousStats = accumulatedConstraintStats accumulator
       in Right
            accumulator
              { accumulatedConstraintOutcomes =
                  ConstraintAccepted
                    (V.fromList (reverse (accumulatedRequestPath request)))
                    (accumulatedRequestAddedEdges request)
                    : accumulatedConstraintOutcomes accumulator
              , accumulatedConstraintStats =
                  previousStats
                    { constraintBatchAccepted = constraintBatchAccepted previousStats + 1
                    , constraintBatchCorridors =
                        constraintBatchCorridors previousStats + accumulatedRequestCorridors request
                    , constraintBatchReusedFaces =
                        constraintBatchReusedFaces previousStats + accumulatedRequestReusedFaces request
                    , constraintBatchCrossedEdges =
                        constraintBatchCrossedEdges previousStats + accumulatedRequestCrossedEdges request
                    }
              }