packages feed

moonlight-triangulation-0.1.0.0: src-dcel/Moonlight/Triangulation/Internal/DcelOperations/CandidateArena.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

-- | The legalization candidate stack: tagging, growth, and seeding.
module Moonlight.Triangulation.Internal.DcelOperations.CandidateArena
  ( starCandidate
  , genericCandidate
  , growLegalizationArena
  , seedStarScratch
  , seedGenericEdges
  , noStarVertex
  ) where

import Control.Monad (forM_, when)
import Control.Monad.ST (ST)
import Data.Word (Word32)
import qualified Data.Vector.Unboxed.Mutable as MUV
import Moonlight.Triangulation.Internal.OperationState
  ( OperationState
  , legalizationArena
  , readScratch
  , storeLegalizationArena
  )
import Moonlight.Triangulation.Internal.PackedIndex (packIndex)

-- A candidate carries the discipline under which it must be tested. One
-- seeded from an insertion star names an edge that has to be re-oriented so
-- the inserted vertex is opposite it; one seeded generically already names the
-- edge to test. Building a geometric patch used to start and drain the work
-- stack once per primitive because the two could not share it — the fan, then
-- every closed hull turn separately, each re-reading the same neighbourhood.
-- With the discipline travelling on the candidate they share one drain.
starCandidate :: Int -> Int
starCandidate edge = edge * 2
{-# INLINE starCandidate #-}

genericCandidate :: Int -> Int
genericCandidate edge = edge * 2 + 1
{-# INLINE genericCandidate #-}

-- | Seed candidates from the scratch arena, returning the new stack top.
-- Transaction-sized preallocation covers the normal path; rare adversarial
-- overflow grows the operation-owned vector without changing LIFO order.
seedStarScratch :: OperationState s -> Int -> Int -> ST s Int
seedStarScratch operation top candidateCount = do
  initialArena <- legalizationArena operation
  arena <- growLegalizationArena initialArena (top + candidateCount)
  when (MUV.length arena /= MUV.length initialArena) (storeLegalizationArena operation arena)
  forM_ [0 .. candidateCount - 1] $ \index -> do
    edge <- readScratch operation index
    MUV.unsafeWrite arena (top + index) (packIndex (starCandidate edge))
  pure (top + candidateCount)

-- | Seed generic candidates from a list, returning the new stack top.
seedGenericEdges :: OperationState s -> Int -> [Int] -> ST s Int
seedGenericEdges operation top edges = do
  initialArena <- legalizationArena operation
  let !count = length edges
  arena <- growLegalizationArena initialArena (top + count)
  when (MUV.length arena /= MUV.length initialArena) (storeLegalizationArena operation arena)
  forM_ (zip [0 ..] edges) $ \(!index, !edge) ->
    MUV.unsafeWrite arena (top + index) (packIndex (genericCandidate edge))
  pure (top + count)

growLegalizationArena :: MUV.MVector s Word32 -> Int -> ST s (MUV.MVector s Word32)
growLegalizationArena arena required
  | required <= current = pure arena
  | otherwise = MUV.grow arena (max (required - current) (max 1 current))
 where
  !current = MUV.length arena
{-# INLINE growLegalizationArena #-}

-- | No star candidate can be seeded against this, so a drain given it must
-- have been seeded generically throughout.
noStarVertex :: Int
noStarVertex = -1