-- |
-- Module      : Data.CompactList
-- Copyright   : (c) 2018 Composewell Technologies
--               (c) 2001-14 GHC Project
--
-- License     : BSD3
-- Maintainer  : harendra.kumar@gmail.com
-- Stability   : experimental
-- Portability : GHC
--
-- If you hold on to a large data structure in garbage collected (GC) memory
-- for relatively longer times it puts undue pressure on GC, unnecessarily
-- increasing the work done by GC and also increasing the duration of GC
-- pauses. A 'CompactList' allows you to keep a large list in a Compact Region
-- not touched by GC, thus avoiding any GC overhead.  This is essentially like
-- a convenient in-memory append only file where you can write a list of
-- Haskell values without having to marshall or serialize them.
--
-- A 'CompactList' is like a thread safe 'IORef' to a list in compact region.

{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}

module Data.CompactList
    ( CompactList
    , newCompactList
    , consCompactList
    , readCompactList
    ) where

import Control.Concurrent.MVar (MVar, newMVar, withMVar)
import GHC.IO (stToIO)
import GHC.Prim
       (Compact#, compactAdd#, compactNew#, int2Word#, RealWorld, State#)
import GHC.ST (ST(ST))
import GHC.STRef (STRef, newSTRef, readSTRef, writeSTRef)
import GHC.Types (Int(I#), IO(IO))

-- | A list with values of type @a@ living in a compact (non-GC) region.
data CompactList a = CompactList Compact# (STRef RealWorld [a]) (MVar ())

mkCompactList
  :: Compact# -> [a] -> State# RealWorld -> (# State# RealWorld, CompactList a #)
mkCompactList compact# as s =
  case unST (newSTRef as) s of { (# s0, ref #) ->
      case unIO (newMVar ()) s0 of { (# s1, lock #) ->
      (# s1, CompactList compact# ref lock #) } }
 where
  unST (ST a) = a
  unIO (IO a) = a

compactSized :: Int -> [a] -> IO (CompactList a)
compactSized (I# size) a = IO $ \s0 ->
  case compactNew# (int2Word# size) s0 of { (# s1, compact# #) ->
  case compactAdd# compact# a s1 of { (# s2, pk #) ->
  mkCompactList compact# pk s2 }}

-- | Make a new compact list from a list.
newCompactList :: [a] -> IO (CompactList a)
newCompactList = compactSized 31268

-- | Retrieve the compact list from the compact region.
readCompactList :: CompactList a -> IO [a]
readCompactList (CompactList _ ref _) = stToIO (readSTRef ref)

-- | Add an element at the head of the compact list. The modification is thread
-- safe.
consCompactList :: CompactList a -> a -> IO ()
consCompactList (CompactList compact# ref lock) x =
    withMVar lock $ \_ -> IO $ \s ->
        case unST (readSTRef ref) s of { (# s1, xs #) ->
            case compactAdd# compact# (x : xs) s1 of { (# s2, ys #) ->
              unST (writeSTRef ref ys) s2 }}
     where
      unST (ST a) = a