{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE TypeFamilies #-} -- | -- Module: Internal.AppendVec -- Description: Helpers for efficient appending to vectors. module Internal.AppendVec ( AppendVec, fromVector, makeEmpty, getVector, getCapacity, grow, canGrowWithoutCopy, ) where import Capnp.Errors (Error (SizeError)) import Control.Monad (when) import Control.Monad.Catch (MonadThrow (throwM)) import Control.Monad.Primitive (PrimMonad, PrimState) import qualified Data.Vector.Generic.Mutable as GMV -- | 'AppendVec' wraps a mutable vector, and affords amortized O(1) appending. data AppendVec v s a = AppendVec { mutVec :: v s a, mutVecLen :: !Int } -- | 'fromVector' wraps a mutable vector in an appendVector, with no initial -- spare capacity. fromVector :: GMV.MVector v a => v s a -> AppendVec v s a fromVector vec = AppendVec { mutVec = vec, mutVecLen = GMV.length vec } -- | 'makeEmpty' makes an initially empty 'AppendVec', using the argument -- as allocation space for 'grow'. makeEmpty :: GMV.MVector v a => v s a -> AppendVec v s a makeEmpty vec = AppendVec { mutVec = vec, mutVecLen = 0 } -- | 'getVector' returns the valid portion of the underlying mutable vector. getVector :: GMV.MVector v a => AppendVec v s a -> v s a getVector AppendVec {mutVec, mutVecLen} = GMV.slice 0 mutVecLen mutVec getCapacity :: GMV.MVector v a => AppendVec v s a -> Int getCapacity AppendVec {mutVec} = GMV.length mutVec -- | @'grow' vec amount maxSize@ grows the vector @vec@ by @amount@ elements, -- provided the result does not exceed @maxSize@. Amortized O(@amount@). Returns -- the new vector; the original should not be used. -- . -- If the result does exceed @maxSize@, throws 'SizeError'. grow :: (MonadThrow m, PrimMonad m, s ~ PrimState m, GMV.MVector v a) => AppendVec v s a -> Int -> Int -> m (AppendVec v s a) grow vec@AppendVec {mutVec, mutVecLen} amount maxSize = do when (maxSize - amount < mutVecLen) $ throwM SizeError mutVec <- if canGrowWithoutCopy vec amount then -- we have enough un-allocated space already; leave the vector -- itself alone. pure mutVec else -- Allocate some more space. we at least double the underlying -- vector's size, to make appending amortized O(1), but if the -- vector is small enough and the allocation is big enough, we -- may need to do more to satisfy the request: GMV.grow mutVec (max amount (mutVecLen * 2)) pure AppendVec { mutVec = mutVec, mutVecLen = mutVecLen + amount } canGrowWithoutCopy :: (GMV.MVector v a) => AppendVec v s a -> Int -> Bool canGrowWithoutCopy AppendVec {mutVec, mutVecLen} amount = mutVecLen + amount <= GMV.length mutVec