streaming-commons-0.1.10.0: Data/Streaming/ByteString/Builder/Buffer.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE Rank2Types #-}
-- | Buffers for 'Builder's. This is a partial copy of blaze-builder-0.3.3.4's
-- "Blaze.ByteString.Builder.Internal.Buffer" module, which was removed in
-- blaze-builder-0.4.
--
-- If you are using blaze-builder 0.3.*, this module just re-exports from
-- "Blaze.ByteString.Builder.Internal.Buffer".
--
-- Since 0.1.10.0
--
module Data.Streaming.ByteString.Builder.Buffer
(
-- * Buffers
Buffer (..)
-- ** Status information
, freeSize
, sliceSize
, bufferSize
-- ** Creation and modification
, allocBuffer
, reuseBuffer
, nextSlice
, updateEndOfSlice
-- ** Conversion to bytestings
, unsafeFreezeBuffer
, unsafeFreezeNonEmptyBuffer
-- * Buffer allocation strategies
, BufferAllocStrategy
, allNewBuffersStrategy
, reuseBufferStrategy
, defaultStrategy
) where
import Data.ByteString.Lazy.Internal (defaultChunkSize)
#if MIN_VERSION_blaze_builder(0,4,0)
import qualified Data.ByteString as S
import qualified Data.ByteString.Internal as S
import Foreign (Word8, ForeignPtr, Ptr, plusPtr, minusPtr)
import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
------------------------------------------------------------------------------
-- Buffers
------------------------------------------------------------------------------
-- | A buffer @Buffer fpbuf p0 op ope@ describes a buffer with the underlying
-- byte array @fpbuf..ope@, the currently written slice @p0..op@ and the free
-- space @op..ope@.
--
-- Since 0.1.10.0
--
data Buffer = Buffer {-# UNPACK #-} !(ForeignPtr Word8) -- underlying pinned array
{-# UNPACK #-} !(Ptr Word8) -- beginning of slice
{-# UNPACK #-} !(Ptr Word8) -- next free byte
{-# UNPACK #-} !(Ptr Word8) -- first byte after buffer
-- | The size of the free space of the buffer.
--
-- Since 0.1.10.0
--
freeSize :: Buffer -> Int
freeSize (Buffer _ _ op ope) = ope `minusPtr` op
-- | The size of the written slice in the buffer.
--
-- Since 0.1.10.0
--
sliceSize :: Buffer -> Int
sliceSize (Buffer _ p0 op _) = op `minusPtr` p0
-- | The size of the whole byte array underlying the buffer.
--
-- Since 0.1.10.0
--
bufferSize :: Buffer -> Int
bufferSize (Buffer fpbuf _ _ ope) =
ope `minusPtr` unsafeForeignPtrToPtr fpbuf
-- | @allocBuffer size@ allocates a new buffer of size @size@.
--
-- Since 0.1.10.0
--
{-# INLINE allocBuffer #-}
allocBuffer :: Int -> IO Buffer
allocBuffer size = do
fpbuf <- S.mallocByteString size
let !pbuf = unsafeForeignPtrToPtr fpbuf
return $! Buffer fpbuf pbuf pbuf (pbuf `plusPtr` size)
-- | Resets the beginning of the next slice and the next free byte such that
-- the whole buffer can be filled again.
--
-- Since 0.1.10.0
--
{-# INLINE reuseBuffer #-}
reuseBuffer :: Buffer -> Buffer
reuseBuffer (Buffer fpbuf _ _ ope) = Buffer fpbuf p0 p0 ope
where
p0 = unsafeForeignPtrToPtr fpbuf
-- | Convert the buffer to a bytestring. This operation is unsafe in the sense
-- that created bytestring shares the underlying byte array with the buffer.
-- Hence, depending on the later use of this buffer (e.g., if it gets reset and
-- filled again) referential transparency may be lost.
--
-- Since 0.1.10.0
--
{-# INLINE unsafeFreezeBuffer #-}
unsafeFreezeBuffer :: Buffer -> S.ByteString
unsafeFreezeBuffer (Buffer fpbuf p0 op _) =
S.PS fpbuf (p0 `minusPtr` unsafeForeignPtrToPtr fpbuf) (op `minusPtr` p0)
-- | Convert a buffer to a non-empty bytestring. See 'unsafeFreezeBuffer' for
-- the explanation of why this operation may be unsafe.
--
-- Since 0.1.10.0
--
{-# INLINE unsafeFreezeNonEmptyBuffer #-}
unsafeFreezeNonEmptyBuffer :: Buffer -> Maybe S.ByteString
unsafeFreezeNonEmptyBuffer buf
| sliceSize buf <= 0 = Nothing
| otherwise = Just $ unsafeFreezeBuffer buf
-- | Update the end of slice pointer.
--
-- Since 0.1.10.0
--
{-# INLINE updateEndOfSlice #-}
updateEndOfSlice :: Buffer -- Old buffer
-> Ptr Word8 -- New end of slice
-> Buffer -- Updated buffer
updateEndOfSlice (Buffer fpbuf p0 _ ope) op' = Buffer fpbuf p0 op' ope
-- | Move the beginning of the slice to the next free byte such that the
-- remaining free space of the buffer can be filled further. This operation
-- is safe and can be used to fill the remaining part of the buffer after a
-- direct insertion of a bytestring or a flush.
--
-- Since 0.1.10.0
--
{-# INLINE nextSlice #-}
nextSlice :: Int -> Buffer -> Maybe Buffer
nextSlice minSize (Buffer fpbuf _ op ope)
| ope `minusPtr` op <= minSize = Nothing
| otherwise = Just (Buffer fpbuf op op ope)
------------------------------------------------------------------------------
-- Buffer allocation strategies
------------------------------------------------------------------------------
-- | A buffer allocation strategy @(buf0, nextBuf)@ specifies the initial
-- buffer to use and how to compute a new buffer @nextBuf minSize buf@ with at
-- least size @minSize@ from a filled buffer @buf@. The double nesting of the
-- @IO@ monad helps to ensure that the reference to the filled buffer @buf@ is
-- lost as soon as possible, but the new buffer doesn't have to be allocated
-- too early.
--
-- Since 0.1.10.0
--
type BufferAllocStrategy = (IO Buffer, Int -> Buffer -> IO (IO Buffer))
-- | The simplest buffer allocation strategy: whenever a buffer is requested,
-- allocate a new one that is big enough for the next build step to execute.
--
-- NOTE that this allocation strategy may spill quite some memory upon direct
-- insertion of a bytestring by the builder. Thats no problem for garbage
-- collection, but it may lead to unreasonably high memory consumption in
-- special circumstances.
--
-- Since 0.1.10.0
--
allNewBuffersStrategy :: Int -- Minimal buffer size.
-> BufferAllocStrategy
allNewBuffersStrategy bufSize =
( allocBuffer bufSize
, \reqSize _ -> return (allocBuffer (max reqSize bufSize)) )
-- | An unsafe, but possibly more efficient buffer allocation strategy:
-- reuse the buffer, if it is big enough for the next build step to execute.
--
-- Since 0.1.10.0
--
reuseBufferStrategy :: IO Buffer
-> BufferAllocStrategy
reuseBufferStrategy buf0 =
(buf0, tryReuseBuffer)
where
tryReuseBuffer reqSize buf
| bufferSize buf >= reqSize = return $ return (reuseBuffer buf)
| otherwise = return $ allocBuffer reqSize
#else /* !MIN_VERSION_blaze_builder(0,4,0) */
import Blaze.ByteString.Builder.Internal.Buffer
#endif /* !MIN_VERSION_blaze_builder(0,4,0) */
defaultStrategy :: BufferAllocStrategy
defaultStrategy = allNewBuffersStrategy defaultChunkSize