packages feed

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
-}