{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE BangPatterns #-} -- | Serializer into a mutable buffer -- -- >>> let w = do putWord8 0x01 ; putWord32BE 0x23456789 -- >>> b <- newBuffer 10 -- >>> void $ runBufferPut b 0 w -- >>> xs <- forM [0..4] (bufferReadWord8IO b) -- >>> xs == [0x01,0x23,0x45,0x67,0x89] -- True -- module Haskus.Format.Binary.Serialize.Buffer ( BufferPutT (..) , BufferPut , getPutOffset , getPutBuffer , setPutOffset , runBufferPut ) where import Haskus.Format.Binary.Serialize import Haskus.Memory.Buffer import Haskus.Utils.Monad import Data.Functor.Identity import Control.Monad.Trans.State as S import Control.Monad.Fail as F import Control.Monad.Fix data BufferPutState b = BufferPutState { bufferPutBuffer :: b -- ^ Buffer used for writing , bufferPutOffset :: Word -- ^ Current offset } -- | A Put monad than fails when there is not enough space in the target buffer newtype BufferPutT b m a = BufferPutT (StateT (BufferPutState b) m a) deriving newtype (Functor, Applicative, Monad, MonadFail, MonadFix, MonadIO, MonadTrans) type BufferPut b a = BufferPutT b Identity a -- | Run a buffer put runBufferPut :: Monad m => b -> Word -> BufferPutT b m a -> m (a,Word) runBufferPut b off (BufferPutT s) = do (a,s') <- runStateT s (BufferPutState b off) return (a,bufferPutOffset s') -- | Get current offset getPutOffset :: Monad m => BufferPutT b m Word getPutOffset = BufferPutT (bufferPutOffset <$> S.get) -- | Get buffer getPutBuffer :: Monad m => BufferPutT b m b getPutBuffer = BufferPutT (bufferPutBuffer <$> S.get) -- | Get current offset setPutOffset :: Monad m => Word -> BufferPutT b m () setPutOffset v = BufferPutT $ do S.modify (\s -> s { bufferPutOffset = v }) -- | Called when there is not enough space left in the buffer bufferPutNotEnoughSpace :: (MonadFail m, MonadIO m) => Word -> BufferPutT b m () bufferPutNotEnoughSpace reqSize = do F.fail $ "Not enough space in the target buffer (requiring " ++ show reqSize ++ " bytes)" -- | Helper to put something putSomething :: (MonadIO m, MonadFail m) => Word -> (Buffer mut pin fin heap -> Word -> t -> m ()) -> t -> BufferPutT (Buffer mut pin fin heap) m () {-# INLINABLE putSomething #-} putSomething sz act v = do off <- getPutOffset b <- getPutBuffer bs <- liftIO (bufferSizeIO b) let !newOff = off+sz when (newOff > bs) $ bufferPutNotEnoughSpace sz lift (act b off v) setPutOffset newOff -- | Helper to put some things putSomeThings :: (MonadIO m, MonadFail m) => Word -> (Buffer mut pin fin heap -> Word -> m ()) -> BufferPutT (Buffer mut pin fin heap) m () {-# INLINABLE putSomeThings #-} putSomeThings sz act = do off <- getPutOffset b <- getPutBuffer bs <- liftIO (bufferSizeIO b) let !newOff = off+sz when (newOff > bs) $ bufferPutNotEnoughSpace sz lift (act b off) setPutOffset newOff instance ( MonadIO m , MonadFail m ) => PutMonad (BufferPutT (Buffer 'Mutable pin gc heap) m) where putWord8 = putSomething 1 bufferWriteWord8IO putWord16 = putSomething 2 bufferWriteWord16IO putWord32 = putSomething 4 bufferWriteWord32IO putWord64 = putSomething 8 bufferWriteWord64IO putWord8s xs = putSomeThings (fromIntegral (length xs)) $ \b off -> do forM_ ([off,(off+1)..] `zip` xs) $ \(boff,v) -> do bufferWriteWord8IO b boff v putWord16s xs = putSomeThings (2*fromIntegral (length xs)) $ \b off -> do forM_ ([off,(off+2)..] `zip` xs) $ \(boff,v) -> do bufferWriteWord16IO b boff v putWord32s xs = putSomeThings (4*fromIntegral (length xs)) $ \b off -> do forM_ ([off,(off+4)..] `zip` xs) $ \(boff,v) -> do bufferWriteWord32IO b boff v putWord64s xs = putSomeThings (8*fromIntegral (length xs)) $ \b off -> do forM_ ([off,(off+8)..] `zip` xs) $ \(boff,v) -> do bufferWriteWord64IO b boff v preAllocateAtLeast l = do off <- getPutOffset b <- getPutBuffer bs <- liftIO (bufferSizeIO b) when (l+off > bs) $ bufferPutNotEnoughSpace l putBuffer x = do sz <- liftIO (bufferSizeIO x) putSomeThings sz (\b off -> copyBuffer x 0 b off sz)