packages feed

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

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

-- | The corridor walk: one traversal of the requested segment that yields
-- either the first oriented blocking witness or a complete recovery program.
module Moonlight.Triangulation.Internal.Cdt.Corridor
  ( constraintWorkspaceFor
  , scanMutableConstraint
  , advanceMutablePlan
  , continueMutablePlan
  , beginMutableRecover
  , finishMutableRecover
  , settleMutableProgram
  , mutablePlanIsActive
  , writeConstraintProgram
  , readConstraintProgram
  , existingProgramTag
  , recoverProgramTag
  ) where

import Control.Monad.ST (ST)
import Data.Bits (xor)
import Moonlight.Triangulation.Handles.HandleDefs
import Moonlight.Triangulation.IntersectionIterator (Intersection (..))
import Moonlight.Triangulation.Internal.Cdt.Combinators
  ( directedInt
  , foldWhileM
  , vertexInt
  )
import Moonlight.Triangulation.Internal.Cdt.Corridor.Trace (nextMutableIntersection)
import Moonlight.Triangulation.Internal.Cdt.Types
import Moonlight.Triangulation.Internal.Growable
  ( GrowableWord32
  , clearGrowable
  , readGrowable
  , writeGrowable
  )
import Moonlight.Triangulation.Internal.Mutable
import Moonlight.Triangulation.Internal.Types (Point)

-- | The walk budget bounds a corridor through the mesh as it stands, so it is
-- taken per request rather than per transaction: a verb that inserts before it
-- recovers has changed the mesh the budget describes. Recovery itself adds
-- neither vertex nor edge, so a batch reads the same number every time.
constraintWorkspaceFor
  :: GrowableWord32 s
  -> MutableDcel s vertex directed undirected face
  -> ST s (ConstraintWorkspace s)
constraintWorkspaceFor constraintProgramWords mutable = do
  halfEdges <- directedEdgeCount mutable
  vertices <- pointCount mutable
  pure
    ConstraintWorkspace
      { constraintProgramWords
      , constraintWalkBudget = 2 * halfEdges + vertices + 8
      }

writeConstraintProgram :: ConstraintWorkspace s -> Int -> Int -> ST s ()
writeConstraintProgram ConstraintWorkspace{constraintProgramWords} index value =
  writeGrowable constraintProgramWords index (fromIntegral value)
{-# INLINE writeConstraintProgram #-}

readConstraintProgram :: ConstraintWorkspace s -> Int -> ST s Int
readConstraintProgram ConstraintWorkspace{constraintProgramWords} index =
  fromIntegral <$> readGrowable constraintProgramWords index
{-# INLINE readConstraintProgram #-}

-- | Walk one corridor once, gluing the local observations into either the
-- first oriented blocking witness or a complete recovery program. The former
-- admission/planning pair traversed every admitted corridor twice and could
-- disagree if either walk evolved independently; this scanner is their single
-- semantic owner.
scanMutableConstraint
  :: ConstraintWorkspace s
  -> MutableDcel s vertex directed undirected face
  -> VertexId
  -> VertexId
  -> ST s (Either (CdtError) MutableConstraintScan)
scanMutableConstraint workspace@ConstraintWorkspace{constraintProgramWords, constraintWalkBudget} mutable from to = do
  clearGrowable constraintProgramWords
  lineFrom <- pointAt mutable (vertexInt from)
  lineTo <- pointAt mutable (vertexInt to)
  initialCursor <- beginMutableRecover workspace 0 from 0 False
  walked <-
    foldWhileM
      mutablePlanIsActive
      (advanceMutablePlan workspace mutable lineFrom lineTo to)
      (MutablePlanActive (VertexIntersection from) initialCursor)
      [1 .. constraintWalkBudget]
  pure $ case walked of
    MutablePlanComplete plan -> Right (MutableConstraintScanAdmitted plan)
    MutablePlanBlocked blocking -> Right (MutableConstraintScanBlocked blocking)
    MutablePlanFailed obstruction -> Left (ConstraintCorridorObstructed obstruction)
    MutablePlanActive _ _ ->
      Left
        ( ConstraintCorridorObstructed
            (CorridorWalkDidNotTerminate constraintWalkBudget)
        )

advanceMutablePlan
  :: ConstraintWorkspace s
  -> MutableDcel s vertex directed undirected face
  -> Point
  -> Point
  -> VertexId
  -> MutablePlanWalk
  -> Int
  -> ST s MutablePlanWalk
advanceMutablePlan _ _ _ _ _ complete@(MutablePlanComplete _) _ = pure complete
advanceMutablePlan _ _ _ _ _ blocked@(MutablePlanBlocked _) _ = pure blocked
advanceMutablePlan _ _ _ _ _ failed@(MutablePlanFailed _) _ = pure failed
advanceMutablePlan workspace mutable lineFrom lineTo target (MutablePlanActive event cursor) _ =
  case event of
    EdgeIntersection directed -> do
      constrained <- readConstraint mutable (directedInt directed)
      if constrained
        then pure (MutablePlanBlocked directed)
        else do
          writeConstraintProgram workspace (mutableCursorWriteAt cursor) (directedInt directed)
          continueMutablePlan
            workspace
            mutable
            lineFrom
            lineTo
            target
            event
            cursor
              { mutableCursorWriteAt = mutableCursorWriteAt cursor + 1
              , mutableCursorConflictCount = mutableCursorConflictCount cursor + 1
              , mutableCursorAfterOverlap = False
              }
    VertexIntersection vertex
      | mutableCursorAfterOverlap cursor ->
          continueMutablePlan
            workspace
            mutable
            lineFrom
            lineTo
            target
            event
            cursor{mutableCursorAfterOverlap = False}
      | vertex == mutableCursorAt cursor ->
          continueMutablePlan
            workspace
            mutable
            lineFrom
            lineTo
            target
            event
            cursor{mutableCursorAfterOverlap = False}
      | otherwise -> do
          finished <- finishMutableRecover workspace vertex cursor
          nextCursor <-
            beginMutableRecover
              workspace
              (mutableCursorWriteAt finished)
              vertex
              (mutableCursorPieceCount finished)
              False
          continueMutablePlan workspace mutable lineFrom lineTo target event nextCursor
    EdgeOverlap rawDirected -> do
      let rawEdge = directedInt rawDirected
      rawOrigin <- readOrigin mutable rawEdge
      rawDestination <- readOrigin mutable (rawEdge `xor` 1)
      let current = vertexInt (mutableCursorAt cursor)
          oriented
            | rawOrigin == current = Just rawEdge
            | rawDestination == current = Just (rawEdge `xor` 1)
            | otherwise = Nothing
      case oriented of
        Nothing ->
          pure
            ( MutablePlanFailed
                ( CorridorBoundaryMissing
                    (mutableCursorAt cursor)
                    (VertexId (fromIntegral rawOrigin))
                )
            )
        Just edge -> do
          prefix <-
            if mutableCursorConflictCount cursor == 0
              then pure cursor
              else finishMutableRecover workspace (mutableCursorAt cursor) cursor
          let !existingAt =
                if mutableCursorConflictCount cursor == 0
                  then mutableCursorHeader cursor
                  else mutableCursorWriteAt prefix
              !edgeDestination =
                VertexId
                  ( fromIntegral
                      (if edge == rawEdge then rawDestination else rawOrigin)
                  )
              !pieceCount = mutableCursorPieceCount prefix + 1
              !nextHeader = existingAt + 2
          writeConstraintProgram workspace existingAt existingProgramTag
          writeConstraintProgram workspace (existingAt + 1) edge
          nextCursor <-
            beginMutableRecover
              workspace
              nextHeader
              edgeDestination
              pieceCount
              True
          continueMutablePlan workspace mutable lineFrom lineTo target event nextCursor

continueMutablePlan
  :: ConstraintWorkspace s
  -> MutableDcel s vertex directed undirected face
  -> Point
  -> Point
  -> VertexId
  -> Intersection
  -> MutableProgramCursor
  -> ST s MutablePlanWalk
continueMutablePlan workspace mutable lineFrom lineTo target event cursor = do
  following <- nextMutableIntersection mutable lineFrom lineTo event
  case following of
    Just nextEvent -> pure (MutablePlanActive nextEvent cursor)
    Nothing -> MutablePlanComplete <$> settleMutableProgram workspace target cursor

beginMutableRecover
  :: ConstraintWorkspace s
  -> Int
  -> VertexId
  -> Int
  -> Bool
  -> ST s MutableProgramCursor
beginMutableRecover workspace header from pieceCount afterOverlap = do
  writeConstraintProgram workspace header recoverProgramTag
  writeConstraintProgram workspace (header + 1) (vertexInt from)
  writeConstraintProgram workspace (header + 2) 0
  writeConstraintProgram workspace (header + 3) 0
  pure
    MutableProgramCursor
      { mutableCursorAt = from
      , mutableCursorHeader = header
      , mutableCursorWriteAt = header + 4
      , mutableCursorConflictCount = 0
      , mutableCursorPieceCount = pieceCount
      , mutableCursorAfterOverlap = afterOverlap
      }

finishMutableRecover
  :: ConstraintWorkspace s
  -> VertexId
  -> MutableProgramCursor
  -> ST s MutableProgramCursor
finishMutableRecover workspace to cursor = do
  writeConstraintProgram workspace (mutableCursorHeader cursor + 2) (vertexInt to)
  writeConstraintProgram workspace (mutableCursorHeader cursor + 3) (mutableCursorConflictCount cursor)
  pure
    cursor
      { mutableCursorAt = to
      , mutableCursorPieceCount = mutableCursorPieceCount cursor + 1
      }

settleMutableProgram
  :: ConstraintWorkspace s
  -> VertexId
  -> MutableProgramCursor
  -> ST s MutableConstraintProgram
settleMutableProgram workspace target cursor
  | mutableCursorAt cursor == target =
      pure
        MutableConstraintProgram
          { mutableProgramWordCount = mutableCursorHeader cursor
          , mutableProgramPieceCount = mutableCursorPieceCount cursor
          }
  | otherwise = do
      finished <- finishMutableRecover workspace target cursor
      pure
        MutableConstraintProgram
          { mutableProgramWordCount = mutableCursorWriteAt finished
          , mutableProgramPieceCount = mutableCursorPieceCount finished
          }

existingProgramTag :: Int
existingProgramTag = 0

recoverProgramTag :: Int
recoverProgramTag = 1

mutablePlanIsActive :: MutablePlanWalk -> Bool
mutablePlanIsActive (MutablePlanActive _ _) = True
mutablePlanIsActive _ = False