packages feed

moonlight-triangulation-0.1.0.0: src-core/Moonlight/Triangulation/Internal/FaceQueue.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}

-- | The set of faces still owed a refinement decision.
module Moonlight.Triangulation.Internal.FaceQueue
  ( FaceQueue
  , newFaceQueue
  , pushFace
  , popFace
  ) where

import Control.Monad.ST (ST)
import qualified Data.Vector.Unboxed.Mutable as MUV
import Data.Word (Word32)
import Moonlight.Triangulation.Internal.PackedIndex (packIndex)

-- A worst-first heap has to be told a score for every face it is offered, so
-- the caller derives one — circumradius, area, the encroachment verdict — for
-- faces that are then found acceptable and dropped. Ruppert's termination does
-- not rest on that order; it rests on every bad face being reached before the
-- run ends. So this only has to be a set with a discipline for draining it.
--
-- A stack is that, and it makes membership O(1) with nothing to compute. The
-- pending flags are what keep it a set: a face touched by several insertions
-- before it is drained is decided once, not once per touch.
--
-- The size lives in an unboxed cell for the reason the growable stack's does:
-- it is written on every push and every pop, and a boxed counter allocates a
-- box per write once the size leaves the shared small-'Int' range.
data FaceQueue s = FaceQueue
  { fqFaces :: !(MUV.MVector s Word32)
  , fqPending :: !(MUV.MVector s Bool)
  , fqSize :: !(MUV.MVector s Int)
  }

newFaceQueue :: Int -> ST s (FaceQueue s)
newFaceQueue capacity = do
  let size = max 1 capacity
  fqFaces <- MUV.new size
  fqPending <- MUV.replicate size False
  fqSize <- MUV.replicate 1 0
  pure FaceQueue{..}

-- The checked read of the pending flag is the one bound this module does not
-- establish itself: the face arrives from mesh topology. Once it succeeds,
-- uniqueness proves the stack write: at most one slot exists for each pending
-- flag, and the two vectors have the same length.
pushFace :: FaceQueue s -> Int -> ST s ()
pushFace FaceQueue{fqFaces, fqPending, fqSize} face = do
  pending <- MUV.read fqPending face
  if pending
    then pure ()
    else do
      size <- MUV.unsafeRead fqSize 0
      MUV.unsafeWrite fqFaces size (packIndex face)
      MUV.unsafeWrite fqPending face True
      MUV.unsafeWrite fqSize 0 (size + 1)

popFace :: FaceQueue s -> ST s (Maybe Int)
popFace FaceQueue{fqFaces, fqPending, fqSize} = do
  size <- MUV.unsafeRead fqSize 0
  if size == 0
    then pure Nothing
    else do
      let !index = size - 1
      face <- fromIntegral <$> MUV.unsafeRead fqFaces index
      MUV.unsafeWrite fqPending face False
      MUV.unsafeWrite fqSize 0 index
      pure (Just face)