2015-04-26 2 views
1

Я пытаюсь создать базовый 2D-движок с haskell и привязками SDL1.2 (для удовольствия, я просто изучаю). В идеале мир должен быть образован процедурно, кусок куска, позволяющий бесплатно исследовать.Haskell Data.Vector, огромная утечка памяти

Сейчас мой кусок состоит из 200 * 200 плиток, которые я представляю, используя тип:

Mat [Tile] = Vec.Vector (Vec.Vector [Tile])

и эти функции:

fromMat :: [[a]] -> Mat a 
fromMat xs = Vec.fromList [Vec.fromList xs' | xs' <- xs] 

(§) :: Mat a -> (Int, Int) -> a 
v § (r, c) = (v Vec.! r) Vec.! c 

Я использую циклический список плитки в чтобы обеспечить анимацию спрайтов, а затем динамическое поведение.

Каждый кадр игрового цикла программа считывает часть вектора, относящуюся к текущей позиции камеры, отображает соответствующие фрагменты и возвращает новый вектор, в котором каждый из этих циклических списков заменен хвостом.

Вот код отвечает за это:

applyTileMat :: Chunk -> SDL.Surface -> SDL.Surface -> IO Chunk 
applyTileMat ch src dest = 
    let m = chLand $! ch 
     (x,y) = chPos ch 
     wid = Vec.length (m Vec.! 0) - 1 
     hei = (Vec.length m) - 1 
     (canW,canH) = canvasSize ch in 

    do sequence $ [ applyTile (head (m § (i,j))) (32*(j-x), 32*(i-y)) src dest | i <- [y..(y+canH)], j <- [x..(x+canW)]] 
    m' <-sequence $ [sequence [(return $! tail (m § (i,j))) | j <- [0..wid]] | i <- [0..hei]] --weird :P 
    return ch { chLand = fromMat m' } 

первая последовательность делает дисплей часть, вторая возвращает новый вектор т».

Сначала я использовал следующее понимание, чтобы получить т»

let !m' = [id $! [(tail $! (m § (i,j))) | j <- [0..wid]] | i <- [0..hei]] 

но делать это приводит все большее использование памяти. Я думаю, что это связано с ленивой оценкой, предотвращающей сбор данных надлежащим образом, но я действительно не понимаю, почему.

В этом конкретном случае это действительно не так, поскольку я должен смотреть на весь вектор. Но я не знаю, как это сделать, если бы я хотел только «обновить» часть моего блока каждый кадр, тем самым создав новый фрагмент с только частью данных из предыдущего.

Я, вероятно, не использую Data.Vector так, как он предназначен, но это самая простая структура данных, которую я нашел с произвольным доступом O (n).

Весь код есть: https://github.com/eniac314/wizzard/blob/master/tiler.hs

+0

снято в темноте, но вы проверили то же самое с данными. Vector.Unboxed? – hasufell

+0

@hasufell OP хранит '[Tile]' -s в векторе, который не может быть распакован. –

+0

Да, я подумал об этом, но, как сказал Андраш, кажется, что я могу хранить только базовые типы с незанятыми векторами. –

ответ

3

Проблема заключается в том, что на самом деле векторы ленивы в элементах. Во-первых, давайте посмотрим, почему ваш пример не работает.

let !m' = [id $! [(tail $! (m § (i,j))) | j <- [0..wid]] | i <- [0..hei]] 

The bang pattern in !m не много. Все ! делает, что переменная является конструктором или лямбдой, а не функцией. Здесь !m можно определить как [], так и (:) без оценки каких-либо элементов. Аналогично, įd $! -s не заставляют никаких реальных элементов внутренних списков.

return ch { chLand = fromMat m' } 

fromMat является следующим виновником. fromMat не заставляет внутренние векторы, а также не заставляет элементы. В результате ссылки на старые векторы бесконечно падают в трясины.

Часто правильное решение должно импортировать Control.DeepSeq и использовать force или $!! для полной оценки векторов. К сожалению, мы не можем этого сделать из-за циклических списков (попытки заставить один результат в бесконечном цикле).

Что нам действительно нужно, это функция, которая объединяет все элементы вектора к слабой головной нормальной форме:

whnfElements :: Vector a -> Vector a 
whnfElements v = V.foldl' (flip seq)() v `seq` v 

Мы можем использовать эту функцию, чтобы определить строгий map для векторов:

vmap' :: (a -> b) -> Vector a -> Vector b 
vmap' f = whnfElements . V.map f 

Теперь обновление становится следующим:

+0

Блестящий, вот и все! Но что произойдет, если мне нужно будет лишь взглянуть на небольшую часть текущего вектора, чтобы построить следующую? возникла бы необходимость сократить все élements до WHNF заставлять меня пройти весь вектор? –

+0

Если вы используете векторы, тогда весь вектор должен быть скопирован на каждом обновлении. Таким образом, все элементы не добавляют лишних накладных расходов, и их нельзя пропускать, потому что пропускание их для любого отдельного элемента приводит к утечке памяти. –

+0

Если вы хотите эффективного обновления, вам нужно использовать что-то еще, кроме векторов. 'Data.IntMap' или' Data.HashMap', скорее всего, являются самыми быстрыми для этого, но обратите внимание, что они имеют значительные накладные расходы пространства-времени. Возможно, вы могли бы попробовать «Карты», содержащие векторные блоки некоторого размера (16-32); с тем, что вы получаете гораздо лучшую эффективность пространства. Вы должны скопировать по крайней мере один цельный блок при каждом обновлении, но это ограниченная и, возможно, допустимая сумма, в отличие от векторов. –