streamly-core-0.3.1: src/Streamly/Internal/Data/RingArray/Generic.hs
-- |
-- Module : Streamly.Internal.Data.RingArray.Generic
-- Copyright : (c) 2021 Composewell Technologies
-- License : BSD-3-Clause
-- Maintainer : streamly@composewell.com
-- Stability : experimental
-- Portability : GHC
--
module Streamly.Internal.Data.RingArray.Generic
( RingArray(..)
, Ring
-- * Generation
, emptyOf
, createOf
-- * Modification
, seek
, unsafeInsertRingWith
-- * Conversion
, toMutArray
, copyToMutArray
, toStreamWith
) where
#include "assert.hs"
import Control.Monad.IO.Class (liftIO, MonadIO)
import Streamly.Internal.Data.Stream.Type (Stream)
import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
import Streamly.Internal.Data.Fold.Type (Fold(..))
import Streamly.Internal.Data.MutArray.Generic (MutArray(..))
-- import qualified Streamly.Internal.Data.Stream.Type as Stream
import qualified Streamly.Internal.Data.Fold.Type as Fold
import qualified Streamly.Internal.Data.MutArray.Generic as MutArray
-- XXX Use MutableArray rather than keeping a MutArray here.
data RingArray a = RingArray
{ ringArr :: MutArray a
-- XXX We can keep the current fill amount, Or we can keep a count of total
-- elements inserted and compute ring head as well using mod on that,
-- assuming it won't overflow. But mod could be expensive.
, ringHead :: !Int -- current index to be over-written
, ringMax :: !Int -- first index beyond allocated memory
}
{-# DEPRECATED Ring "Please use RingArray instead." #-}
type Ring = RingArray
-------------------------------------------------------------------------------
-- Generation
-------------------------------------------------------------------------------
-- XXX If we align the ringMax to nearest power of two then computation of the
-- index to write could be cheaper.
{-# INLINE emptyOf #-}
emptyOf :: MonadIO m => Int -> m (RingArray a)
emptyOf count = liftIO $ do
arr <- MutArray.emptyOf count
arr1 <- MutArray.uninit arr count
return (RingArray
{ ringArr = arr1
, ringHead = 0
, ringMax = count
})
-- | Note that it is not safe to return a reference to the mutable RingArray using a
-- scan as the RingArray is continuously getting mutated. You could however copy out
-- the RingArray.
{-# INLINE createOf #-}
createOf :: MonadIO m => Int -> Fold m a (RingArray a)
createOf n = Fold step initial extract extract
where
initial = do
if n <= 0
then Fold.Done <$> emptyOf 0
else do
rb <- emptyOf n
return $ Fold.Partial $ Tuple' rb (0 :: Int)
step (Tuple' rb cnt) x = do
rh1 <- liftIO $ unsafeInsertRingWith rb x
return $ Fold.Partial $ Tuple' (rb {ringHead = rh1}) (cnt + 1)
extract (Tuple' rb@RingArray{..} cnt) =
return $
if cnt < ringMax
then RingArray ringArr 0 ringHead
else rb
-------------------------------------------------------------------------------
-- Modification
-------------------------------------------------------------------------------
-- XXX This is safe
-- Take the ring head and return the new ring head.
{-# INLINE unsafeInsertRingWith #-}
unsafeInsertRingWith :: RingArray a -> a -> IO Int
unsafeInsertRingWith RingArray{..} x = do
assertM(ringMax >= 1)
assertM(ringHead < ringMax)
MutArray.unsafePutIndex ringHead ringArr x
let rh1 = ringHead + 1
next = if rh1 == ringMax then 0 else rh1
return next
-- | Move the ring head clockwise (+ve adj) or counter clockwise (-ve adj) by
-- the given amount.
{-# INLINE seek #-}
seek :: MonadIO m => Int -> RingArray a -> m (RingArray a)
seek adj rng@RingArray{..}
| ringMax > 0 = liftIO $ do
-- XXX try avoiding mod when in bounds
let idx1 = ringHead + adj
next = mod idx1 ringMax
return $ RingArray ringArr next ringMax
| otherwise = pure rng
-------------------------------------------------------------------------------
-- Conversion
-------------------------------------------------------------------------------
-- | @toMutArray rignHeadAdjustment lengthToRead ring@.
-- Convert the ring into a boxed mutable array. Note that the returned MutArray
-- shares the same underlying memory as the RingArray, the user of this API needs to
-- ensure that the ring is not mutated during and after the conversion.
--
{-# INLINE toMutArray #-}
toMutArray :: MonadIO m => Int -> Int -> RingArray a -> m (MutArray a)
toMutArray adj n RingArray{..} =
-- XXX for empty RingArray it will raise an Exception: divide by zero
if ringMax <= 0
then MutArray.nil
else do
let len = min ringMax n
let idx = mod (ringHead + adj) ringMax
end = idx + len
if end <= ringMax
then
return $ ringArr { arrStart = idx, arrEnd = end }
else do
-- XXX Just swap the elements in the existing ring and return the
-- same array without reallocation.
arr <- liftIO $ MutArray.emptyOf len
arr1 <- MutArray.uninit arr len
MutArray.unsafePutSlice ringArr idx arr1 0 (ringMax - idx)
MutArray.unsafePutSlice ringArr 0 arr1 (ringMax - idx) (end - ringMax)
return arr1
-- | Copy out the mutable ring to a mutable Array.
{-# INLINE copyToMutArray #-}
copyToMutArray :: MonadIO m => Int -> Int -> RingArray a -> m (MutArray a)
copyToMutArray adj n RingArray{..} = do
if ringMax <= 0
then MutArray.nil
else do
let len = min ringMax n
let idx = mod (ringHead + adj) ringMax
end = idx + len
arr <- MutArray.emptyOf len
arr1 <- MutArray.uninit arr len
MutArray.unsafePutSlice ringArr idx arr1 0 (ringMax - idx)
MutArray.unsafePutSlice ringArr 0 arr1 (ringMax - idx) (end - ringMax)
return arr1
-- This would be theoretically slower than toMutArray because of a branch
-- introduced for each element in the second half of the ring.
-- | Seek by n and then read the entire ring. Use 'take' on the stream to
-- restrict the reads.
toStreamWith :: Int -> RingArray a -> Stream m a
toStreamWith = undefined
{-
toStreamWith n RingArray{..}
| ringMax > 0 = concatEffect $ liftIO $ do
idx <- readIORef ringHead
let idx1 = idx + adj
next = mod idx1 ringMax
s1 = undefined -- stream initial slice
s2 = undefined -- stream next slice
return (s1 `Stream.append` s2)
| otherwise = Stream.nil
-}