moonlight-triangulation-0.1.0.0: src-core/Moonlight/Triangulation/Internal/Growable.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE NamedFieldPuns #-}
module Moonlight.Triangulation.Internal.Growable
( GrowableWord32
, newGrowableWord32
, clearGrowable
, growableLength
, pushGrowable
, popGrowableOr
, readGrowable
, writeGrowable
) where
import Control.Monad (when)
import Control.Monad.ST (ST)
import Data.STRef (STRef, newSTRef, readSTRef, writeSTRef)
import qualified Data.Vector.Unboxed.Mutable as MUV
import Data.Word (Word32)
-- | Transaction-local vector. It starts small and grows only with the local
-- frontier; no operation allocates scratch memory proportional to mesh size.
-- The size lives in an unboxed cell: the stack is popped and pushed inside
-- every legalization drain, and a boxed counter would allocate on each step.
data GrowableWord32 s = GrowableWord32
{ growableVector :: !(STRef s (MUV.MVector s Word32))
, growableSize :: !(MUV.MVector s Int)
}
newGrowableWord32 :: Int -> ST s (GrowableWord32 s)
newGrowableWord32 requested = do
vector <- MUV.new (max 8 requested)
growableVector <- newSTRef vector
growableSize <- MUV.replicate 1 0
pure GrowableWord32{growableVector, growableSize}
clearGrowable :: GrowableWord32 s -> ST s ()
clearGrowable GrowableWord32{growableSize} = MUV.unsafeWrite growableSize 0 0
{-# INLINE clearGrowable #-}
growableLength :: GrowableWord32 s -> ST s Int
growableLength GrowableWord32{growableSize} = MUV.unsafeRead growableSize 0
{-# INLINE growableLength #-}
pushGrowable :: GrowableWord32 s -> Word32 -> ST s ()
pushGrowable growable@GrowableWord32{growableSize} value = do
index <- MUV.unsafeRead growableSize 0
vector <- ensureCapacity growable (index + 1)
MUV.unsafeWrite vector index value
MUV.unsafeWrite growableSize 0 (index + 1)
{-# INLINE pushGrowable #-}
-- | Pop, answering the caller's sentinel on emptiness instead of allocating a
-- 'Maybe' per step. Sound only against a sentinel no push can store.
popGrowableOr :: Word32 -> GrowableWord32 s -> ST s Word32
popGrowableOr sentinel GrowableWord32{growableVector, growableSize} = do
size <- MUV.unsafeRead growableSize 0
if size <= 0
then pure sentinel
else do
let !index = size - 1
vector <- readSTRef growableVector
value <- MUV.unsafeRead vector index
MUV.unsafeWrite growableSize 0 index
pure value
{-# INLINE popGrowableOr #-}
readGrowable :: GrowableWord32 s -> Int -> ST s Word32
readGrowable GrowableWord32{growableVector} index = do
vector <- readSTRef growableVector
MUV.unsafeRead vector index
{-# INLINE readGrowable #-}
writeGrowable :: GrowableWord32 s -> Int -> Word32 -> ST s ()
writeGrowable growable@GrowableWord32{growableSize} index value = do
vector <- ensureCapacity growable (index + 1)
MUV.unsafeWrite vector index value
size <- MUV.unsafeRead growableSize 0
when (index >= size) (MUV.unsafeWrite growableSize 0 (index + 1))
{-# INLINE writeGrowable #-}
-- Answering with the vector is what keeps a push to one cell read: the caller
-- would otherwise re-read the reference this just proved current.
ensureCapacity :: GrowableWord32 s -> Int -> ST s (MUV.MVector s Word32)
ensureCapacity GrowableWord32{growableVector} required = do
vector <- readSTRef growableVector
let !current = MUV.length vector
if required <= current
then pure vector
else do
let !next = until (>= required) (* 2) current
grown <- MUV.grow vector (next - current)
writeSTRef growableVector grown
pure grown