moonlight-triangulation-1.4.0.1: 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.IntSet as IntSet
import Data.STRef (STRef, modifySTRef', newSTRef, readSTRef)
import Data.Word (Word32)
import qualified Data.Vector.Unboxed.Mutable as MUV
import Moonlight.Triangulation.Internal.Growable
( GrowableWord32
, newGrowableWord32
, popGrowableOr
, pushGrowable
)
import Moonlight.Triangulation.Internal.PackedIndex (noIndex, 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
= DenseFaceQueue
!(MUV.MVector s Word32)
!(MUV.MVector s Bool)
!(MUV.MVector s Int)
| SparseFaceQueue
!(GrowableWord32 s)
!(STRef s IntSet.IntSet)
newFaceQueue :: Maybe Int -> ST s (FaceQueue s)
newFaceQueue capacity =
case capacity of
Just size -> do
faces <- MUV.new (max 1 size)
pending <- MUV.replicate (max 1 size) False
stackSize <- MUV.replicate 1 0
pure (DenseFaceQueue faces pending stackSize)
Nothing -> do
faces <- newGrowableWord32 16
pending <- newSTRef IntSet.empty
pure (SparseFaceQueue faces pending)
-- 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 queue face =
case queue of
DenseFaceQueue faces pending stackSize -> do
isPending <- MUV.unsafeRead pending face
if isPending
then pure ()
else do
size <- MUV.unsafeRead stackSize 0
MUV.unsafeWrite faces size (packIndex face)
MUV.unsafeWrite pending face True
MUV.unsafeWrite stackSize 0 (size + 1)
SparseFaceQueue faces pending -> do
isPending <- IntSet.member face <$> readSTRef pending
if isPending
then pure ()
else do
pushGrowable faces (packIndex face)
modifySTRef' pending (IntSet.insert face)
popFace :: FaceQueue s -> ST s (Maybe Int)
popFace queue =
case queue of
DenseFaceQueue faces pending stackSize -> do
size <- MUV.unsafeRead stackSize 0
if size == 0
then pure Nothing
else do
let !index = size - 1
face <- fromIntegral <$> MUV.unsafeRead faces index
MUV.unsafeWrite pending face False
MUV.unsafeWrite stackSize 0 index
pure (Just face)
SparseFaceQueue faces pending -> do
packed <- popGrowableOr noIndex faces
if packed == noIndex
then pure Nothing
else do
let !face = fromIntegral packed
modifySTRef' pending (IntSet.delete face)
pure (Just face)