packages feed

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

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

-- | Interpretation of a recovery program against the thawed mesh: conflict
-- strips are flipped away segment by segment and the constraints are set.
module Moonlight.Triangulation.Internal.Cdt.Recovery
  ( ConflictRecovery (..)
  , applyMutableConstraint
  , emptyConstraintRequest
  , recoverMutableRequest
  , recoverMutableProgramPiece
  , recordRecoveredSegment
  , resolveConflictStrip
  , mutableEdgeCrosses
  ) where

import Control.Monad.ST (ST)
import Data.Bits (xor)
import Data.Either (isRight)
import Data.Foldable (traverse_)
import Moonlight.Triangulation.Handles.HandleDefs
import Moonlight.Triangulation.Internal.Cdt.Combinators
  ( foldWhileM
  , vertexInt
  )
import Moonlight.Triangulation.Internal.Cdt.Corridor
  ( constraintWorkspaceFor
  , existingProgramTag
  , readConstraintProgram
  , recoverProgramTag
  , scanMutableConstraint
  )
import Moonlight.Triangulation.Internal.Cdt.Query (findMutableEdge)
import Moonlight.Triangulation.Internal.Cdt.Types
import Moonlight.Triangulation.Internal.DcelOperations
import Moonlight.Triangulation.Internal.Growable (GrowableWord32)
import Moonlight.Triangulation.Internal.Mutable
import Moonlight.Triangulation.Internal.OperationState
  ( Counter (..)
  , OperationState
  , addCounter
  , readScratch
  , writeScratch
  )
import Moonlight.Triangulation.Internal.Types
import Moonlight.Triangulation.Scalar (orient2dCoordinates)

data ConflictRecovery = ConflictRecovery
  { recoveredConstraintEdge :: {-# UNPACK #-} !Int
  , recoveredConstraintFresh :: !Bool
  }

-- | Admit one request against the thawed mesh and recover it. Every constraint
-- verb in this module reaches the topology through here; the callers differ
-- only in what they do with a rejection and in when they publish.
applyMutableConstraint
  :: GrowableWord32 s
  -> MutableDcel s vertex directed undirected face
  -> OperationState s
  -> VertexId
  -> VertexId
  -> ST s (Either (CdtError) MutableConstraintOutcome)
applyMutableConstraint programWords mutable operation from to
  | from == to = pure (Right (MutableConstraintAccepted emptyConstraintRequest))
  | otherwise = do
      workspace <- constraintWorkspaceFor programWords mutable
      scanned <- scanMutableConstraint workspace mutable from to
      case scanned of
        Left failure -> pure (Left failure)
        Right (MutableConstraintScanBlocked blocking) ->
          pure (Right (MutableConstraintRejected (asUndirected blocking)))
        Right (MutableConstraintScanAdmitted program) ->
          fmap MutableConstraintAccepted
            <$> recoverMutableRequest workspace mutable operation program

emptyConstraintRequest :: ConstraintRequestAccumulator
emptyConstraintRequest =
  ConstraintRequestAccumulator
    { accumulatedRequestPath = []
    , accumulatedRequestAddedEdges = 0
    , accumulatedRequestCorridors = 0
    , accumulatedRequestReusedFaces = 0
    , accumulatedRequestCrossedEdges = 0
    }

recoverMutableRequest
  :: ConstraintWorkspace s
  -> MutableDcel s vertex directed undirected face
  -> OperationState s
  -> MutableConstraintProgram
  -> ST s (Either (CdtError) ConstraintRequestAccumulator)
recoverMutableRequest workspace mutable operation program = do
  interpreted <-
    foldWhileM
      isRight
      (recoverMutableProgramPiece workspace mutable operation (mutableProgramWordCount program))
      ( Right
          ConstraintProgramAccumulator
            { accumulatedProgramCursor = 0
            , accumulatedProgramRequest = emptyConstraintRequest
            }
      )
      [1 .. mutableProgramPieceCount program]
  pure $ case interpreted of
    Left obstruction -> Left obstruction
    Right completed
      | accumulatedProgramCursor completed == mutableProgramWordCount program ->
          Right (accumulatedProgramRequest completed)
      | otherwise ->
          Left
            ( ConstraintCorridorObstructed
                (CorridorProgramMalformed (accumulatedProgramCursor completed))
            )

recoverMutableProgramPiece
  :: ConstraintWorkspace s
  -> MutableDcel s vertex directed undirected face
  -> OperationState s
  -> Int
  -> Either (CdtError) ConstraintProgramAccumulator
  -> Int
  -> ST s (Either (CdtError) ConstraintProgramAccumulator)
recoverMutableProgramPiece _ _ _ _ failed@(Left _) _ = pure failed
recoverMutableProgramPiece workspace mutable operation wordCount (Right accumulator) _ = do
  let !cursor = accumulatedProgramCursor accumulator
      malformed =
        Left
          ( ConstraintCorridorObstructed
              (CorridorProgramMalformed cursor)
          )
  if cursor < 0 || cursor >= wordCount
    then pure malformed
    else do
      tag <- readConstraintProgram workspace cursor
      case tag of
        _
          | tag == existingProgramTag && cursor + 1 < wordCount -> do
              edge <- readConstraintProgram workspace (cursor + 1)
              fresh <- setConstraint mutable edge
              pure
                ( Right
                    accumulator
                      { accumulatedProgramCursor = cursor + 2
                      , accumulatedProgramRequest =
                          recordRecoveredSegment
                            (accumulatedProgramRequest accumulator)
                            edge
                            fresh
                            0
                            0
                      }
                )
          | tag == recoverProgramTag && cursor + 3 < wordCount -> do
              rawFrom <- readConstraintProgram workspace (cursor + 1)
              rawTo <- readConstraintProgram workspace (cursor + 2)
              conflictCount <- readConstraintProgram workspace (cursor + 3)
              let !conflictStart = cursor + 4
                  !nextCursor = conflictStart + conflictCount
                  !from = VertexId (fromIntegral rawFrom)
                  !to = VertexId (fromIntegral rawTo)
              if conflictCount < 0 || nextCursor > wordCount
                then pure malformed
                else do
                  direct <- findMutableEdge mutable rawFrom rawTo
                  recovered <-
                    case direct of
                      Just edge -> do
                        fresh <- setConstraint mutable edge
                        pure
                          ( Right
                              ConflictRecovery
                                { recoveredConstraintEdge = edge
                                , recoveredConstraintFresh = fresh
                                }
                          )
                      Nothing
                        | conflictCount == 0 ->
                            pure
                              ( Left
                                  ( ConstraintCorridorObstructed
                                      (CorridorTargetMissing from to)
                                  )
                              )
                        | otherwise ->
                            resolveConflictStrip
                              workspace
                              mutable
                              operation
                              from
                              to
                              conflictStart
                              conflictCount
                  pure $ case recovered of
                    Left obstruction -> Left obstruction
                    Right recovery ->
                      Right
                        accumulator
                          { accumulatedProgramCursor = nextCursor
                          , accumulatedProgramRequest =
                              recordRecoveredSegment
                                (accumulatedProgramRequest accumulator)
                                (recoveredConstraintEdge recovery)
                                (recoveredConstraintFresh recovery)
                                conflictCount
                                (if conflictCount == 0 then 0 else conflictCount + 1)
                          }
          | otherwise -> pure malformed

recordRecoveredSegment
  :: ConstraintRequestAccumulator
  -> Int
  -> Bool
  -> Int
  -> Int
  -> ConstraintRequestAccumulator
recordRecoveredSegment accumulator edge fresh crossed reusedFaces =
  accumulator
    { accumulatedRequestPath = DirectedEdgeId (fromIntegral edge) : accumulatedRequestPath accumulator
    , accumulatedRequestAddedEdges =
        accumulatedRequestAddedEdges accumulator + if fresh then 1 else 0
    , accumulatedRequestCorridors =
        accumulatedRequestCorridors accumulator + if crossed == 0 then 0 else 1
    , accumulatedRequestReusedFaces =
        accumulatedRequestReusedFaces accumulator + reusedFaces
    , accumulatedRequestCrossedEdges =
        accumulatedRequestCrossedEdges accumulator + crossed
    }

resolveConflictStrip
  :: ConstraintWorkspace s
  -> MutableDcel s vertex directed undirected face
  -> OperationState s
  -> VertexId
  -> VertexId
  -> Int
  -> Int
  -> ST s (Either (CdtError) ConflictRecovery)
resolveConflictStrip workspace mutable operation from to conflictStart stripLength = do
  fromPoint <- pointAt mutable (vertexInt from)
  toPoint <- pointAt mutable (vertexInt to)
  traverse_
    (\index -> do
      edge <- readConstraintProgram workspace (conflictStart + index)
      writeScratch operation index edge
    )
    [0 .. stripLength - 1]
  recovered <-
    recover
      stripLength
      safetyBudget
      0
      0
      stripLength
      stripLength
      []
      fromPoint
      toPoint
  case recovered of
    Left failure -> pure (Left failure)
    Right (edge, flipped) -> do
      fresh <- setConstraint mutable edge
      legalizeEdges mutable operation flipped
      pure
        ( Right
            ConflictRecovery
              { recoveredConstraintEdge = edge
              , recoveredConstraintFresh = fresh
              }
        )
 where
  safetyBudget = max 64 (32 * (stripLength + 1) * (stripLength + 1))

  -- The corridor is an ordered section through the current triangulation.
  -- Until an original section is dequeued, no flip can have changed that
  -- edge's endpoints: flips only repurpose the identity being flipped, and
  -- requeued identities remain behind every unseen original in this FIFO.
  -- Consequently the first visit inherits the crossing proof established by
  -- the corridor walk. Only requeued edges require the predicate again.
  recover !capacity !remaining !stalled !headIndex !pendingCount !unseenOriginalCount !flipped !fromPoint !toPoint
    | remaining <= 0 =
        pure
          ( Left
              ( ConstraintRecoverySafetyBudgetExhausted
                  from
                  to
                  safetyBudget
                  (safetyBudget - remaining)
              )
          )
    | pendingCount <= 0 = do
        direct <- findMutableEdge mutable (vertexInt from) (vertexInt to)
        case direct of
          Just edge -> pure (Right (edge, flipped))
          Nothing ->
            pure
              (Left (ConstraintRecoveryStripExhausted from to stripLength))
    | otherwise = do
        edge <- readScratch operation headIndex
        let !nextHead = advance capacity headIndex
            !restCount = pendingCount - 1
            !nextUnseenOriginalCount = max 0 (unseenOriginalCount - 1)
        stillCrosses <-
          if unseenOriginalCount > 0
            then pure True
            else mutableEdgeCrosses mutable fromPoint toPoint edge
        if not stillCrosses
          then recover capacity (remaining - 1) 0 nextHead restCount nextUnseenOriginalCount flipped fromPoint toPoint
          else do
            flippable <- isFlippableEdge mutable edge
            if flippable
              then do
                rewritten <- flipEdge mutable edge
                case rewritten of
                  Left obstruction -> pure (Left (CdtBuildError obstruction))
                  Right () -> do
                    addCounter operation CounterEdgeFlips 1
                    crossesAfterFlip <- mutableEdgeCrosses mutable fromPoint toPoint edge
                    if crossesAfterFlip
                      then do
                        enqueue capacity nextHead restCount edge
                        recover
                          capacity
                          (remaining - 1)
                          0
                          nextHead
                          (restCount + 1)
                          nextUnseenOriginalCount
                          (edge : flipped)
                          fromPoint
                          toPoint
                      else recover capacity (remaining - 1) 0 nextHead restCount nextUnseenOriginalCount (edge : flipped) fromPoint toPoint
              else do
                enqueue capacity nextHead restCount edge
                let !nextCount = restCount + 1
                    !nextStalled = stalled + 1
                if nextStalled >= nextCount
                  then
                    pure
                      ( Left
                          ( ConstraintRecoveryStripUnflippable
                              from
                              to
                              (DirectedEdgeId (fromIntegral edge))
                              nextCount
                          )
                      )
                  else recover capacity (remaining - 1) nextStalled nextHead nextCount nextUnseenOriginalCount flipped fromPoint toPoint

  enqueue !capacity !headIndex !count !edge =
    writeScratch operation (wrap capacity (headIndex + count)) edge

  advance :: Int -> Int -> Int
  advance !capacity !index
    | index + 1 == capacity = 0
    | otherwise = index + 1

  wrap :: Int -> Int -> Int
  wrap !capacity !index
    | index >= capacity = index - capacity
    | otherwise = index

mutableEdgeCrosses
  :: MutableDcel s vertex directed undirected face
  -> Point
  -> Point
  -> Int
  -> ST s Bool
mutableEdgeCrosses
  mutable
  (Point lineFromX lineFromY)
  (Point lineToX lineToY)
  edge = do
    edgeFromVertex <- readOrigin mutable edge
    edgeToVertex <- readOrigin mutable (edge `xor` 1)
    Point edgeFromX edgeFromY <- pointAt mutable edgeFromVertex
    Point edgeToX edgeToY <- pointAt mutable edgeToVertex
    let !edgeFromSide =
          orient2dCoordinates
            lineFromX lineFromY lineToX lineToY edgeFromX edgeFromY
        !edgeToSide =
          orient2dCoordinates
            lineFromX lineFromY lineToX lineToY edgeToX edgeToY
        !lineFromSide =
          orient2dCoordinates
            edgeFromX edgeFromY edgeToX edgeToY lineFromX lineFromY
        !lineToSide =
          orient2dCoordinates
            edgeFromX edgeFromY edgeToX edgeToY lineToX lineToY
    pure
      ( opposite edgeFromSide edgeToSide
          && opposite lineFromSide lineToSide
      )
 where
  opposite LT GT = True
  opposite GT LT = True
  opposite _ _ = False