packages feed

tcod-haskell-0.1.0.0: src/Game/TCOD/List.hs

module Game.TCOD.List(
    TCODList(..)
  , listNew
  , listAllocate
  , listDuplicated
  , listDelete
  , listPush
  , listPushPtr
  , listPop
  , listPopPtr
  , listPeek
  , listPeekPtr
  , listAddAll
  , listGet
  , listGetPtr
  , listSet
  , listSetPtr
  , listBeginPtr
  , listEndPtr
  , listReverse
  , listRemoveIterator
  , listRemoveIteratorFast
  , listRemovePtr
  , listRemove
  , listRemoveFastPtr
  , listRemoveFast
  , listContainsPtr
  , listContains
  , listClear
  , listClearAndDelete
  , listSize
  , listInsertBeforePtr
  , listInsertBefore
  , listIsEmpty
  -- * Haskell conversion
  , listToList
  , listFromList
  , listToVectorUnsafe
  , listToVector
  ) where

import Foreign
import Foreign.C
import GHC.Generics
import Data.Vector.Storable (Vector)

import qualified Data.Vector.Storable as V

import Game.TCOD.Context as C

context tcodContext
include "list.h"

-- | This is a fast, lightweight and generic container, that provides array,
-- list and stack paradigms.
newtype TCODList a = TCODList { unTCODList :: Ptr () }
  deriving (Eq, Ord, Show, Generic)

-- | Creating a list
listNew :: IO (TCODList a)
listNew = TCODList <$> [C.exp| void * { TCOD_list_new() } |]

-- | You can also create an empty list and pre-allocate memory for elements.
-- Use this if you know the list size and want the memory to fit it perfectly.
listAllocate :: Int -- ^ Number of elements
  -> IO (TCODList a)
listAllocate n = TCODList <$> [C.exp| void* {TCOD_list_allocate($(int n'))} |]
  where n' = fromIntegral n

-- | You can create a list by duplicating an existing list.
listDuplicated :: TCODList a -> IO (TCODList a)
listDuplicated (TCODList l) = TCODList <$> [C.exp| void* {TCOD_list_duplicate($(void* l))}|]

-- | You can delete a list, freeing any allocated resources. Note that deleting
-- the list does not delete it's elements. You have to use clearAndDelete before
-- deleting the list if you want to destroy the elements too.
listDelete :: TCODList a -> IO ()
listDelete (TCODList l) = [C.exp| void {TCOD_list_delete($(void* l))} |]

-- | You can push an element on the stack (append it to the end of the list)
listPush :: Storable a => TCODList a -> a -> IO ()
listPush l v = listPushPtr l =<< new v

-- | You can push an element on the stack (append it to the end of the list)
listPushPtr :: TCODList a -> Ptr a -> IO ()
listPushPtr (TCODList l) vptr = do
  let vptr' = castPtr vptr
  [C.exp| void { TCOD_list_push($(void* l), $(void* vptr'))} |]

-- | You can pop an element from the stack (remove the last element of the list).
listPop :: Storable a => TCODList a -> IO (Maybe a)
listPop l = do
  ptr <- listPopPtr l
  if ptr == nullPtr then pure Nothing else Just <$> peek ptr

-- | You can pop an element from the stack (remove the last element of the list).
listPopPtr :: TCODList a -> IO (Ptr a)
listPopPtr (TCODList l) = castPtr <$> [C.exp| void* { TCOD_list_pop($(void* l))} |]

-- | You can read the last element of the stack without removing it :
listPeek :: Storable a => TCODList a -> IO (Maybe a)
listPeek l = do
  ptr <- listPeekPtr l
  if ptr == nullPtr then pure Nothing else Just <$> peek ptr

-- | You can read the last element of the stack without removing it :
listPeekPtr :: TCODList a -> IO (Ptr a)
listPeekPtr (TCODList l) = castPtr <$> [C.exp| void* { TCOD_list_peek($(void* l))} |]

-- | You can concatenate two lists. Every element of l2 will be added to current
-- list
listAddAll :: TCODList a -> TCODList a -> IO ()
listAddAll (TCODList l1) (TCODList l2) = [C.exp| void { TCOD_list_add_all($(void* l1), $(void* l2))} |]

-- | You can retrieve a value with get.
listGet :: Storable a => TCODList a -> Int -> IO (Maybe a)
listGet l n = do
  ptr <- listGetPtr l n
  if ptr == nullPtr then pure Nothing else Just <$> peek ptr

-- | You can retrieve a value with get.
listGetPtr :: TCODList a -> Int -> IO (Ptr a)
listGetPtr (TCODList l) n = do
  let n' = fromIntegral n
  castPtr <$> [C.exp| void* {TCOD_list_get($(void* l), $(int n'))} |]

-- | You can assign a value with set. If needed, the array will allocate new elements up to idx.
listSet :: Storable a => TCODList a -> a -> Int -> IO ()
listSet l v n = do
  vptr <- new v
  listSetPtr l vptr n

-- | You can assign a value with set. If needed, the array will allocate new elements up to idx.
listSetPtr :: TCODList a -> Ptr a -> Int -> IO ()
listSetPtr (TCODList l) vptr n = do
  let vptr' = castPtr vptr
      n' = fromIntegral n
  [C.exp| void { TCOD_list_set($(void* l), $(void* vptr'), $(int n')) } |]

-- | You can iterate through the elements of the list using an iterator.
-- begin() returns the address of the first element of the list. You go to the
-- next element using the increment operator ++. When the iterator's value is
-- equal to end(), you've gone through all the elements.
--
-- Warning ! You cannot insert elements in the list while iterating through it.
-- Inserting elements can result in reallocation of the list and your iterator
-- will not longer be valid.
listBeginPtr :: TCODList a -> IO (Ptr (Ptr a))
listBeginPtr (TCODList l) = castPtr <$> [C.exp| void** { TCOD_list_begin($(void* l)) } |]

-- | You can iterate through the elements of the list using an iterator.
-- begin() returns the address of the first element of the list. You go to the
-- next element using the increment operator ++. When the iterator's value is
-- equal to end(), you've gone through all the elements.
--
-- Warning ! You cannot insert elements in the list while iterating through it.
-- Inserting elements can result in reallocation of the list and your iterator
-- will not longer be valid.
listEndPtr :: TCODList a -> IO (Ptr (Ptr a))
listEndPtr (TCODList l) = castPtr <$> [C.exp| void** { TCOD_list_end($(void* l)) } |]

-- | This function reverses the order of the elements in the list.
listReverse :: TCODList a -> IO ()
listReverse (TCODList l) = [C.exp| void { TCOD_list_reverse($(void* l)) } |]

-- | You can remove an element from the list while iterating. The element at
-- the iterator position will be removed. The function returns the new iterator.
-- The _fast versions replace the element to remove with the last element of the
-- list. They're faster, but do not preserve the list order.
listRemoveIterator :: TCODList a -> Ptr (Ptr a) -> IO (Ptr (Ptr a))
listRemoveIterator (TCODList l) ptr = do
  let ptr' = castPtr ptr
  castPtr <$> [C.exp| void** {TCOD_list_remove_iterator($(void* l), $(void** ptr'))} |]

-- | You can remove an element from the list while iterating. The element at
-- the iterator position will be removed. The function returns the new iterator.
-- The _fast versions replace the element to remove with the last element of the
-- list. They're faster, but do not preserve the list order.
listRemoveIteratorFast :: TCODList a -> Ptr (Ptr a) -> IO (Ptr (Ptr a))
listRemoveIteratorFast (TCODList l) ptr = do
  let ptr' = castPtr ptr
  castPtr <$> [C.exp| void** {TCOD_list_remove_iterator_fast($(void* l), $(void** ptr'))} |]

-- | Removing an element from the list
listRemovePtr :: TCODList a -> Ptr a -> IO ()
listRemovePtr (TCODList l) vptr = do
  let vptr' = castPtr vptr
  [C.exp| void { TCOD_list_remove($(void* l), $(void* vptr')) } |]

-- | Removing an element from the list
listRemove :: Storable a => TCODList a -> a -> IO ()
listRemove l v = do
  vptr <- new v
  listRemovePtr l vptr

-- | The _fast versions replace the element to remove with the last element of
-- the list. They're faster, but do not preserve the list order.
listRemoveFastPtr :: TCODList a -> Ptr a -> IO ()
listRemoveFastPtr (TCODList l) vptr = do
  let vptr' = castPtr vptr
  [C.exp| void { TCOD_list_remove_fast($(void* l), $(void* vptr')) } |]

-- | The _fast versions replace the element to remove with the last element of
-- the list. They're faster, but do not preserve the list order.
listRemoveFast :: Storable a => TCODList a -> a -> IO ()
listRemoveFast l v = do
  vptr <- new v
  listRemoveFastPtr l vptr

-- | Checking if an element is in the list
listContainsPtr :: TCODList a -> Ptr a -> IO Bool
listContainsPtr (TCODList l) vptr = do
  let vptr' = castPtr vptr
  toBool <$> [C.exp| int {(int)TCOD_list_contains($(void* l), $(void* vptr'))}|]

-- | Checking if an element is in the list
listContains :: Storable a => TCODList a -> a -> IO Bool
listContains l a = with a $ listContainsPtr l

-- | Emptying a list
listClear :: TCODList a -> IO ()
listClear (TCODList l) = [C.exp| void {TCOD_list_clear($(void* l))} |]

-- | For lists containing pointers, you can clear the list and delete
-- (or free for C) the elements :
listClearAndDelete :: TCODList a -> IO ()
listClearAndDelete (TCODList l) = [C.exp| void {TCOD_list_clear_and_delete($(void* l))} |]

-- | Getting the list size
listSize :: TCODList a -> IO Int
listSize (TCODList l) = fromIntegral <$> [C.exp| int {TCOD_list_size($(void* l))} |]

-- | Insert an element in the list
listInsertBeforePtr :: TCODList a -> Ptr a -> Int -> IO (Ptr (Ptr a))
listInsertBeforePtr (TCODList l) vptr n = do
  let vptr' = castPtr vptr
      n' = fromIntegral n
  castPtr <$> [C.exp| void** { TCOD_list_insert_before($(void* l), $(void* vptr'), $(int n')) } |]

-- | Insert an element in the list
listInsertBefore :: Storable a => TCODList a -> a -> Int -> IO (Ptr (Ptr a))
listInsertBefore l v n = do
  vptr <- new v
  listInsertBeforePtr l vptr n

-- | Checking if a list is empty
listIsEmpty :: TCODList a -> IO Bool
listIsEmpty (TCODList l) = toBool <$> [C.exp| int { (int)TCOD_list_is_empty($(void* l))}|]

-- | Load to Haskell list O(N)
listToList :: forall a . Storable a => TCODList a -> IO [a]
listToList l = do
  begPtr <- listBeginPtr l
  endPtr <- listEndPtr l
  go [] begPtr endPtr
  where
    go acc begPtr endPtr = if begPtr == endPtr then pure acc
      else do
        v <- peek =<< peek endPtr
        let bytes = sizeOf (undefined :: a)
        go (v : acc) begPtr (plusPtr endPtr (negate bytes))

-- | Unload Haskell list to TCOD container O(N)
listFromList :: forall a f . (Foldable f, Storable a) => f a -> IO (TCODList a)
listFromList xs = do
  l <- listAllocate (length xs)
  mapM_ (listPush l) xs
  pure l

-- | Convert TCOD list to Haskell storable vector. O(1)
--
-- Note: The vector reuses memory of the list and valid until the TCOD list
-- is not deleted or reallocated (due mutation).
listToVectorUnsafe :: forall a . Storable a => TCODList a -> IO (Vector a)
listToVectorUnsafe l = do
  begPtr <- listBeginPtr l
  endPtr <- listEndPtr l
  if begPtr == nullPtr then pure mempty else do
    ptr <- newForeignPtr_ =<< peek begPtr
    pure $ V.unsafeFromForeignPtr0 ptr (minusPtr endPtr begPtr)

-- | Convert TCOD list to Haskell storable vector. O(N)
--
-- Note: The vector content is copied into haskell heap.
listToVector :: forall a . Storable a => TCODList a -> IO (Vector a)
listToVector = fmap V.convert . listToVectorUnsafe