packages feed

mischief-ecs-0.1.0.0: src/Mischief/ECS/Vec.hs

-- Adapted from `grow-vector` as the package seems unmaintained
-- Module defines mutable vector that can grow in size automatically when an user
-- adds new elements at the end of vector.
--
-- We reallocate vector with 1.5x length to get amortized append.
module Mischief.ECS.Vec
  ( Vec (..),
    IOVec,

    -- * Quering info about vector
    length,
    null,
    capacity,

    -- * Creation
    new,
    newSized,

    -- * Quering subvectors
    slice,

    -- * Converting to immutable
    thaw,
    freeze,
    toList,

    -- * Capacity maninuplation
    ensure,
    ensureAppend,

    -- * Accessing individual elements
    read,
    write,
    unsafeRead,
    unsafeWrite,

    -- * Appending to vector
    pushBack,
    unsafePushBack,

    -- * modify an element
    modify,
    modify_,
    modifyM,
    modifyM_,

    -- * general utilities
    swap,
    tap,
    shrink,

    -- * O(1) amortized swap backed operations
    takeSwap,
    removeSwap,

    -- * cloning, very naive approach
    clone,
  )
where

import Control.Monad
import Control.Monad.Primitive
import Data.Foldable (for_)
import Data.Primitive.MutVar
import Data.Vector (Vector)
import Data.Vector qualified as Vector
import Data.Vector.Mutable (MVector)
import Data.Vector.Mutable qualified as MVector
import Debug.Trace (traceShow)
import GHC.Generics
import GHC.Stack (HasCallStack)
import Prelude hiding (length, null, read)

-- | Normal rust-like vector with buffer (the MVector) + len (the len field) + cap (the `buffer` length)
data Vec s a = Vec
  { buffer :: !(MutVar s (MVector s a)),
    len :: !(MutVar s Int)
  }
  deriving (Generic)

type IOVec a = Vec RealWorld a

-- | Return current capacity of the vector (amount of elements that it can fit without realloc)
capacity :: (PrimMonad m) => Vec (PrimState m) a -> m Int
capacity v =
  MVector.length <$> readMutVar v.buffer
{-# INLINE capacity #-}

-- | Return current amount of elements in the vector
length :: (PrimMonad m) => Vec (PrimState m) a -> m Int
length v = readMutVar v.len
{-# INLINE length #-}

-- | Return 'True' if there is no elements inside the vector
null :: (PrimMonad m) => Vec (PrimState m) a -> m Bool
null v = (== 0) <$> length v

-- | Allocation of new growable vector with given capacity.
new :: (PrimMonad m) => Int -> m (Vec (PrimState m) a)
new = newSized 0
{-# INLINE new #-}

-- | Allocation of new growable vector with given filled size and capacity.
-- Elements is not initialized. Capacity must be greater than filled size.
newSized :: (PrimMonad m) => Int -> Int -> m (Vec (PrimState m) a)
newSized n cap = do
  buffer <- MVector.new cap
  buffer <- newMutVar buffer
  len <- newMutVar n
  pure Vec {buffer, len}
{-# INLINEABLE newSized #-}

-- | Yield a part of mutable vector without copying it. The vector must contain at least i+n elements.
slice ::
  (PrimMonad m) =>
  -- | i starting index
  Int ->
  -- | n number of elements
  Int ->
  Vec (PrimState m) a ->
  m (Vec (PrimState m) a)
slice i n v = do
  len <- newMutVar n
  mv <- readMutVar v.buffer
  buffer <- newMutVar $! MVector.slice i n mv
  pure $! Vec {len, buffer}
{-# INLINEABLE slice #-}

-- | Convert immutable vector to grow mutable version. Doesn't allocate additonal memory for appending,
-- use 'ensure' to add capacity to the vector.
thaw ::
  (PrimMonad m) =>
  Vector a ->
  m (Vec (PrimState m) a)
thaw u = do
  buffer <- newMutVar =<< Vector.thaw u
  len <- newMutVar $! Vector.length u
  pure Vec {buffer, len}
{-# INLINEABLE thaw #-}

-- | Freezing growable vector. It will contain only actual elements of the vector not including capacity
-- space, but you should call 'U.force' on resulting vector to not hold the allocated capacity of original
-- vector in memory.
freeze ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  m (Vector a)
freeze v = do
  n <- length v
  mv <- readMutVar v.buffer
  Vector.freeze $ MVector.take n mv
{-# INLINEABLE freeze #-}

toList ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  m [a]
toList vec = Vector.toList <$> freeze vec

ensure_not_oob ::
  (HasCallStack, PrimMonad m) =>
  String ->
  -- | the name of the function
  Int ->
  -- | The element we want to check
  Vec (PrimState m) a ->
  -- | The length of the vec
  m ()
ensure_not_oob fname i vec = do
  len <- length vec
  when (i < 0 || i >= len) $ do
    error $ mconcat [fname, ": index ", show i, " is out bounds ", show len]

-- | Ensure that grow vector has at least given capacity possibly with reallocation.
ensure ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  Int ->
  m ()
ensure v cap = do
  current_cap <- capacity v
  unless (current_cap >= cap) $ do
    buffer <- readMutVar v.buffer
    grown <- MVector.grow buffer (cap - current_cap)
    writeMutVar v.buffer grown
{-# INLINEABLE ensure #-}

-- | Ensure that grow vector has enough space for additonal n elements.
-- We grow vector by 1.5 factor or by required elements count * 1.5.
ensureAppend ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  -- | Additional n elements
  Int ->
  m ()
ensureAppend vec i = do
  len <- length vec
  buf <- readMutVar vec.buffer
  let cap = MVector.length buf
  unless (cap >= len + i) $ do
    -- ugly as shit code would like to fix
    let newCap = ceiling $ max (growFactor * fromIntegral cap) (fromIntegral cap + growFactor * fromIntegral (len + i - cap))
    new_buf <- MVector.grow buf (newCap - cap)
    writeMutVar vec.buffer new_buf
  where
    growFactor :: Double
    growFactor = 1.5
{-# INLINEABLE ensureAppend #-}

-- | Read element from vector at given index.
read ::
  (HasCallStack, PrimMonad m) =>
  Vec (PrimState m) a ->
  -- | Index of element. Must be in [0 .. length) range
  Int ->
  m a
read vec i = do
  ensure_not_oob "Vec.read" i vec
  buf <- readMutVar vec.buffer
  MVector.unsafeRead buf i
{-# INLINEABLE read #-}

-- | Read element from vector at given index, without checking whether the index is inbounds
unsafeRead ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  -- | Index of element. Must be in [0 .. length) range
  Int ->
  m a
unsafeRead vec i = do
  buf <- readMutVar vec.buffer
  MVector.unsafeRead buf i
{-# INLINEABLE unsafeRead #-}

-- | Write down element in the vector at given index.
write ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  -- | Index of element. Must be in [0 .. length) range
  Int ->
  a ->
  m ()
write vec i value = do
  ensure_not_oob "Vec.write" i vec
  buf <- readMutVar vec.buffer
  MVector.unsafeWrite buf i value
{-# INLINEABLE write #-}

-- | Write down element in the vector at given index.
unsafeWrite ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  -- | Index of element. Must be in [0 .. length) range
  Int ->
  a ->
  m ()
unsafeWrite vec i value = do
  buf <- readMutVar vec.buffer
  MVector.unsafeWrite buf i value
{-# INLINEABLE unsafeWrite #-}

modify ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  Int ->
  (a -> a) ->
  m a
modify vec i f = do
  ensure_not_oob "Vec.modify" i vec
  old_val <- read vec i
  write vec i (f old_val)
  pure old_val
{-# INLINEABLE modify #-}

modify_ ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  Int ->
  (a -> a) ->
  m ()
modify_ vec i f = do
  ensure_not_oob "Vec.modify_" i vec
  old_val <- read vec i
  write vec i (f old_val)
{-# INLINEABLE modify_ #-}

modifyM ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  Int ->
  (a -> m a) ->
  m a
modifyM vec i f = do
  ensure_not_oob "Vec.modifyM" i vec
  old_val <- read vec i
  write vec i =<< f old_val
  pure old_val
{-# INLINEABLE modifyM #-}

modifyM_ ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  Int ->
  (a -> m a) ->
  m ()
modifyM_ vec i f = do
  ensure_not_oob "Vec.modifyM_" i vec
  old_val <- read vec i
  write vec i =<< f old_val
{-# INLINEABLE modifyM_ #-}

tap ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  Int ->
  (a -> m ()) ->
  m ()
tap vec i act = do
  ensure_not_oob "Vec.tap" i vec
  act =<< read vec i
{-# INLINE tap #-}

-- | O(1) amortized appending to vector
pushBack ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  a ->
  m ()
pushBack vec value = do
  ensureAppend vec 1
  unsafePushBack vec value
{-# INLINEABLE pushBack #-}

-- | O(1) amortized appending to vector. Doesn't reallocate vector, so
-- there must by capacity - length >= 1.
unsafePushBack ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  a ->
  m ()
unsafePushBack vec a = do
  len <- length vec
  buf <- readMutVar vec.buffer
  MVector.write buf len a
  writeMutVar vec.len (len + 1)
{-# INLINEABLE unsafePushBack #-}

swap ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  Int ->
  -- | index of element #1 to swap
  Int ->
  -- | index of element #2 to swap
  m ()
swap vec i j = do
  when (i /= j) $ do
    ensure_not_oob "Vec.swap" i vec
    ensure_not_oob "Vec.swap" j vec
    buffer <- readMutVar vec.buffer
    MVector.swap buffer i j

shrink ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  Int ->
  m ()
shrink vec amount = do
  old_len <- length vec
  writeMutVar vec.len (old_len - amount)

takeSwap ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  Int ->
  m a
takeSwap vec i = do
  value <- read vec i
  removeSwap vec i
  pure value

removeSwap ::
  (PrimMonad m) =>
  Vec (PrimState m) a ->
  -- | index of element to remove
  Int ->
  m ()
removeSwap vec i = do
  len <- length vec
  swap vec i (len - 1)
  shrink vec 1

clone :: (PrimMonad m) => Vec (PrimState m) a -> m (Vec (PrimState m) a)
clone vec = do
  list <- toList vec
  len <- length vec
  newVec <- new len
  for_ list $ pushBack newVec
  return newVec