hoogle-4.1: src/Data/Binary/Defer/Monad.hs
module Data.Binary.Defer.Monad(
DeferPut, putDefer, runDeferPut,
putInt, putByte, putChr, putByteString, putLazyByteString,
DeferGet, getDefer, runDeferGet,
getInt, getByte, getChr, getByteString, getLazyByteString,
getDeferGet, getDeferPut,
unwrapDeferGet
) where
import System.IO
import System.IO.Unsafe
import Data.Binary.Raw
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
import Data.IORef
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import Data.Typeable
import qualified Data.TypeMap as TypeMap
import Foreign
---------------------------------------------------------------------
-- Defer Put
-- Storing the position explicitly gives a ~5% speed up
-- and removes hGetPos as being a bottleneck
-- possibly still not worth it though
-- FIXME: Should be a newtype
type DeferPut a = ReaderT (Buffer, IORef [DeferPending], IORef [DeferPatchup]) IO a
data DeferPending = DeferPending !Int (DeferPut ())
data DeferPatchup = DeferPatchup !Int !Int -- a b = at position a, write out b
putValue :: Storable a => a -> DeferPut ()
putValue x = do
(buf,_,_) <- ask
liftIO $ bufferAdd buf x
putInt :: Int -> DeferPut ()
putInt x = putValue (fromIntegral x :: Int32)
putByte :: Word8 -> DeferPut ()
putByte x = putValue x
putChr :: Char -> DeferPut ()
putChr x = putByte $ fromIntegral $ fromEnum x
putByteString :: BS.ByteString -> DeferPut ()
putByteString x = do
(buf,_,_) <- ask
putInt $ BS.length x
liftIO $ bufferAddByteString buf x
putLazyByteString :: LBS.ByteString -> DeferPut ()
putLazyByteString x = do
(buf,_,_) <- ask
putInt $ fromIntegral $ LBS.length x
liftIO $ bufferAddLazyByteString buf x
putDefer :: DeferPut () -> DeferPut ()
putDefer x = do
(buf,ref,_) <- ask
liftIO $ do
p <- bufferPos buf
bufferAdd buf (0 :: Int32) -- to backpatch
modifyIORef ref (DeferPending p x :)
runDeferPut :: Handle -> DeferPut () -> IO ()
runDeferPut h m = do
buf <- bufferNew h
ref <- newIORef []
back <- newIORef []
runReaderT (m >> runDeferPendings) (buf,ref,back)
bufferFlush buf
patch <- readIORef back
mapM_ (\(DeferPatchup a b) -> do hSetPos h (toInteger a); hPutInt h b) patch
runDeferPendings :: DeferPut ()
runDeferPendings = do
(_,ref,back) <- ask
todo <- liftIO $ readIORef ref
liftIO $ writeIORef ref []
mapM_ runDeferPending todo
runDeferPending :: DeferPending -> DeferPut ()
runDeferPending (DeferPending pos act) = do
(buf,_,back) <- ask
liftIO $ do
p <- bufferPos buf
b <- bufferPatch buf pos (fromIntegral p :: Int32)
unless b $ modifyIORef back (DeferPatchup pos p :)
act
runDeferPendings
---------------------------------------------------------------------
-- Buffer for writing
bufferSize = 10000 :: Int
-- (number in file, number in buffer)
data Buffer = Buffer !Handle !(IORef Int) !(Ptr ()) !(IORef Int)
bufferNew :: Handle -> IO Buffer
bufferNew h = do
i <- hGetPos h
file <- newIORef $ fromInteger i
buf <- newIORef 0
ptr <- mallocBytes bufferSize
return $ Buffer h file ptr buf
bufferAdd :: Storable a => Buffer -> a -> IO ()
bufferAdd (Buffer h file ptr buf) x = do
let sz = sizeOf x
buf2 <- readIORef buf
if sz + buf2 >= bufferSize then do
hPutBuf h ptr buf2
pokeByteOff ptr 0 x
modifyIORef file (+buf2)
writeIORef buf sz
else do
pokeByteOff ptr buf2 x
writeIORef buf (buf2+sz)
bufferAddByteString :: Buffer -> BS.ByteString -> IO ()
bufferAddByteString (Buffer h file ptr buf) x = do
let sz = BS.length x
buf2 <- readIORef buf
when (buf2 /= 0) $ do
hPutBuf h ptr buf2
writeIORef buf 0
modifyIORef file (+ (buf2+sz))
BS.hPut h x
bufferAddLazyByteString :: Buffer -> LBS.ByteString -> IO ()
bufferAddLazyByteString (Buffer h file ptr buf) x = do
let sz = fromIntegral $ LBS.length x
buf2 <- readIORef buf
when (buf2 /= 0) $ do
hPutBuf h ptr buf2
writeIORef buf 0
modifyIORef file (+ (buf2+sz))
LBS.hPut h x
bufferFlush :: Buffer -> IO ()
bufferFlush (Buffer h file ptr buf) = do
buf2 <- readIORef buf
hPutBuf h ptr buf2
modifyIORef file (+buf2)
writeIORef buf 0
bufferPos :: Buffer -> IO Int
bufferPos (Buffer h file ptr buf) = do
i <- readIORef file
j <- readIORef buf
return $ i + j
-- Patch at position p, with value v
-- Return True if you succeeded
bufferPatch :: Buffer -> Int -> Int32 -> IO Bool
bufferPatch (Buffer h file ptr buf) p v = do
i <- readIORef file
if p < i then return False else do
pokeByteOff ptr (p-i) v
return True
---------------------------------------------------------------------
-- Defer Get
type DeferGet a = ReaderT (Handle, IORef TypeMap.TypeMap) IO a
getInt :: DeferGet Int
getInt = do h <- asks fst; liftIO $ hGetInt h
getByte :: DeferGet Word8
getByte = do h <- asks fst; liftIO $ fmap fromIntegral $ hGetByte h
getChr :: DeferGet Char
getChr = do h <- asks fst; liftIO $ hGetChar h
getByteString :: DeferGet BS.ByteString
getByteString = do
h <- asks fst
len <- liftIO $ hGetInt h
liftIO $ BS.hGet h len
getLazyByteString :: DeferGet LBS.ByteString
getLazyByteString = fmap (LBS.fromChunks . return) getByteString
getDefer :: DeferGet a -> DeferGet a
getDefer x = do
h <- asks fst
i <- liftIO $ hGetInt h
s <- ask
liftIO $ unsafeInterleaveIO $ do
hSetPos h (toInteger i)
runReaderT x s
runDeferGet :: Handle -> DeferGet a -> IO a
runDeferGet h m = do
ref <- newIORef TypeMap.empty
runReaderT m (h,ref)
getDeferGet :: Typeable a => DeferGet a
getDeferGet = do
ref <- asks snd
mp <- liftIO $ readIORef ref
return $ TypeMap.find mp
getDeferPut :: Typeable a => a -> DeferGet ()
getDeferPut x = do
ref <- asks snd
liftIO $ modifyIORef ref $ TypeMap.insert x
unwrapDeferGet :: DeferGet a -> DeferGet (IO a)
unwrapDeferGet act = do
s <- ask
return $ runReaderT act s