packages feed

reactivity-0.2.3.0: src/Data/XSizeable.hs

{-# LANGUAGE FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, ScopedTypeVariables, OverlappingInstances, DeriveDataTypeable #-}

-- | A resizable array, equivalent to C++'s std::vector. Manipulate them with the Data.Array.MArray class.
--
--   Use with multiple threads has to be synchronized.
module Data.XSizeable (memset, SA, getPtr, Resizable(..)) where

import Foreign.Storable
import Foreign.Ptr
import Foreign.Marshal.Alloc
import Foreign.C.Types
import System.Mem.Weak
import Data.Int
import Data.Word
import Control.Monad
import Data.IORef
import Data.Array.IO
import Data.Array.Base
import Data.Typeable
import System.Win32.Mem

newtype SA i e = SA (IORef (i, i, Int, Ptr e)) deriving (Eq, Typeable)

-- | Returns internal data about the array. But it cannot be counted on to be valid
--   after the next call to 'resize'.
--
--   Also use this to free the array once it is not needed.
getPtr (SA ref) = liftM (\(_, _, sz, p) -> (sz, p)) $ readIORef ref

instance (Storable e) => MArray SA e IO where
	getBounds (SA ref) = liftM (\(l, h, _, _) -> (l, h)) $ readIORef ref
	getNumElements = liftM rangeSize . getBounds
	newArray_ (l, h) = do
		let required = sizeOf (undefined :: e) * rangeSize (l, h)
		p <- mallocBytes required
		memset p 0 (fromIntegral required)
		ref <- newIORef (l, h, required, p)
		return (SA ref)
	unsafeRead (SA ref) i = do
		tup@(_, _, _, p) <- readIORef ref
		peekByteOff p (sizeOf (undefined :: e) * i)
	unsafeWrite (SA ref) i x = do
		tup@(_, _, _, p) <- readIORef ref
		let loc = sizeOf (undefined :: e) * i
		pokeByteOff p loc x

-- A resize that only works on single-dimensional arrays.
resizeBuffer :: forall i e. (Storable e) => (Ix i) => (i, i) -> SA i e -> IO ()
resizeBuffer (l', h') sa@(SA ref) = do
	(l, h, sz, p) <- readIORef ref
	let wasRequired = sizeOf (undefined :: e) * rangeSize (l, h)
	let nowRequired = sizeOf (undefined :: e) * rangeSize (l', h')
	-- I want to maintain about 50% residency in the array.
	(sz, p) <- if 4 * rangeSize (l', h') <= sz then
			-- If the array gets small enough, resize it so it is 50% used.
			let newSz = 2 * rangeSize (l', h') in
			liftM ((,) newSz) $ reallocBytes p newSz
		else if rangeSize (l', h') <= sz then
			return (sz, p)
		else
			-- If the array exceeds its buffer, at least double it in size.
			let newSz = nowRequired `max` (2 * wasRequired) in
			liftM ((,) newSz) $ reallocBytes p newSz
	when (nowRequired > wasRequired) $ memset (plusPtr p wasRequired) 0 (fromIntegral $ nowRequired - wasRequired)
	writeIORef ref (l', h', sz, p)

__resize (l', h') sa = do
	(l, _) <- getBounds sa
	resizeBuffer (l', h') sa
	when (l /= l') $ __resize1 (l', h') sa

__resize1 oldBnd sa = mapM_ (\i -> unsafeRead sa (index oldBnd i) >>= writeArray sa i) (range oldBnd)

-- | Mutable arrays that can in addition be resized.
class (Ix i, MArray a e m) => Resizable a i e m where
	resize :: (i, i) -> a i e -> m ()

instance (Storable e) => Resizable SA Int e IO where
	resize = __resize

instance (Storable e) => Resizable SA Int8 e IO where
	resize = __resize

instance (Storable e) => Resizable SA Int16 e IO where
	resize = __resize

instance (Storable e) => Resizable SA Int32 e IO where
	resize = __resize

instance (Storable e) => Resizable SA Word8 e IO where
	resize = __resize

instance (Storable e) => Resizable SA Word16 e IO where
	resize = __resize

instance (Storable e) => Resizable SA Word32 e IO where
	resize = __resize

instance (Storable e, Ix i) => Resizable SA i e IO where
	resize bnd sa = do
		resizeBuffer bnd sa
		__resize1 bnd sa