packages feed

moonlight-triangulation-1.2.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: discipline, growth, and seeding.
module Moonlight.Triangulation.Internal.DcelOperations.CandidateArena
  ( CandidateDiscipline (..)
  , growLegalizationArena
  , seedStarScratch
  , seedGenericPairInArena
  , seedGenericEdges
  ) where

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

-- | Every normalization epoch is homogeneous. A star epoch turns each edge so
-- the inserted vertex is the opposite apex; a generic epoch consumes the
-- directed edge exactly as seeded. Keeping that fact at the epoch boundary
-- prevents every candidate from carrying and decoding a tag for a distinction
-- that cannot vary inside the stack.
data CandidateDiscipline
  = StarCandidates !Int
  | GenericCandidates

-- | 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 (legalizationArenaLength arena /= legalizationArenaLength initialArena) (storeLegalizationArena operation arena)
  let LegalizationArena values = arena
  forM_ [0 .. candidateCount - 1] $ \index -> do
    edge <- readScratch operation index
    MUV.unsafeWrite values (top + index) (packIndex edge)
  pure (top + candidateCount)

-- | Append the fixed two-edge section produced by one closed hull turn.
-- Materializing @[left, right]@ only to count, zip and traverse it made the
-- dominant sweep rewrite pay list traffic for an arity known by construction.
-- The arena is explicit because a circle sweep borrows it once and glues it
-- back to the operation once, rather than performing three reference lookups
-- around every inserted point.
seedGenericPairInArena
  :: LegalizationArena s
  -> Int
  -> Int
  -> Int
  -> ST s (LegalizationArena s, Int)
seedGenericPairInArena initialArena top left right = do
  let !nextTop = top + 2
  arena <- growLegalizationArena initialArena nextTop
  let LegalizationArena values = arena
  MUV.unsafeWrite values top (packIndex left)
  MUV.unsafeWrite values (top + 1) (packIndex right)
  pure (arena, nextTop)
{-# INLINE seedGenericPairInArena #-}

-- | 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 (legalizationArenaLength arena /= legalizationArenaLength initialArena) (storeLegalizationArena operation arena)
  let LegalizationArena values = arena
  forM_ (zip [0 ..] edges) $ \(!index, !edge) ->
    MUV.unsafeWrite values (top + index) (packIndex edge)
  pure (top + count)

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

legalizationArenaLength :: LegalizationArena s -> Int
legalizationArenaLength (LegalizationArena values) = MUV.length values
{-# INLINE legalizationArenaLength #-}