diff options
author | Sam T <pxqr.sta@gmail.com> | 2013-05-05 05:01:28 +0400 |
---|---|---|
committer | Sam T <pxqr.sta@gmail.com> | 2013-05-05 05:01:28 +0400 |
commit | 7f63b5554fcf31d9b71c79fa9d0ee66d2ef95c94 (patch) | |
tree | 008058dc9bfe4b6f75cf1add4f493235a7ee8ed1 /src/Network/BitTorrent/PeerWire | |
parent | f4122eec550671a646310106224ee6523ea8e369 (diff) |
~ Move bitfields to Data.
Diffstat (limited to 'src/Network/BitTorrent/PeerWire')
-rw-r--r-- | src/Network/BitTorrent/PeerWire/Bitfield.hs | 344 | ||||
-rw-r--r-- | src/Network/BitTorrent/PeerWire/Message.hs | 2 | ||||
-rw-r--r-- | src/Network/BitTorrent/PeerWire/Selection.hs | 3 |
3 files changed, 3 insertions, 346 deletions
diff --git a/src/Network/BitTorrent/PeerWire/Bitfield.hs b/src/Network/BitTorrent/PeerWire/Bitfield.hs deleted file mode 100644 index 03273899..00000000 --- a/src/Network/BitTorrent/PeerWire/Bitfield.hs +++ /dev/null | |||
@@ -1,344 +0,0 @@ | |||
1 | -- | | ||
2 | -- Copyright : (c) Sam T. 2013 | ||
3 | -- License : MIT | ||
4 | -- Maintainer : pxqr.sta@gmail.com | ||
5 | -- Stability : experimental | ||
6 | -- Portability : portable | ||
7 | -- | ||
8 | -- | ||
9 | -- This module provides Bitfield datatype used to represent sets of | ||
10 | -- piece indexes any peer have. All associated operations should be | ||
11 | -- defined here as well. | ||
12 | -- | ||
13 | {-# LANGUAGE BangPatterns #-} | ||
14 | {-# LANGUAGE RankNTypes #-} | ||
15 | module Network.BitTorrent.PeerWire.Bitfield | ||
16 | -- TODO: move to Data.Bitfield | ||
17 | ( Bitfield(..) | ||
18 | |||
19 | -- * Construction | ||
20 | , empty, full | ||
21 | , toList | ||
22 | , fromByteString, toByteString | ||
23 | |||
24 | -- * Query | ||
25 | , haveCount, completeness | ||
26 | , findMin, findMax | ||
27 | , union, intersection, difference, combine | ||
28 | , frequencies | ||
29 | |||
30 | -- * Serialization | ||
31 | , getBitfield, putBitfield | ||
32 | , bitfieldByteCount, bitfieldBitCount | ||
33 | |||
34 | |||
35 | , aligned, alignLow, alignedZip | ||
36 | ) where | ||
37 | |||
38 | import Control.Applicative hiding (empty) | ||
39 | import Data.Array.Unboxed | ||
40 | import Data.Bits | ||
41 | import Data.ByteString (ByteString) | ||
42 | import qualified Data.ByteString as B | ||
43 | import qualified Data.ByteString.Internal as B | ||
44 | import Data.List as L hiding (union) | ||
45 | import Data.Maybe | ||
46 | import Data.Serialize | ||
47 | import Data.Word | ||
48 | |||
49 | import Foreign | ||
50 | |||
51 | --import Network.BitTorrent.PeerWire.Block | ||
52 | import Data.Torrent | ||
53 | |||
54 | -- one good idea is to aggregate frequently used stats in reducer | ||
55 | -- it should give a big boost | ||
56 | newtype Bitfield = MkBitfield { | ||
57 | bfBits :: ByteString | ||
58 | -- , bfSize :: Int | ||
59 | } deriving (Show, Eq, Ord) | ||
60 | |||
61 | |||
62 | empty :: Int -> Bitfield | ||
63 | empty n = MkBitfield $ B.replicate (sizeInBase n 8) 0 | ||
64 | {-# INLINE empty #-} | ||
65 | |||
66 | full :: Int -> Bitfield | ||
67 | full n = MkBitfield $ B.replicate (sizeInBase n 8) (complement 0) | ||
68 | {-# INLINE full #-} | ||
69 | |||
70 | toList :: Bitfield -> [Bool] | ||
71 | toList (MkBitfield bs) = concatMap unpkg (B.unpack bs) | ||
72 | where | ||
73 | unpkg :: Word8 -> [Bool] | ||
74 | unpkg byte = L.map (testBit byte) [0..bitSize (undefined :: Word8) - 1] | ||
75 | {-# INLINE toList #-} | ||
76 | |||
77 | fromByteString :: ByteString -> Bitfield | ||
78 | fromByteString = MkBitfield | ||
79 | {-# INLINE fromByteString #-} | ||
80 | |||
81 | toByteString :: Bitfield -> ByteString | ||
82 | toByteString = bfBits | ||
83 | {-# INLINE toByteString #-} | ||
84 | |||
85 | getBitfield :: Int -> Get Bitfield | ||
86 | getBitfield n = MkBitfield <$> getBytes n | ||
87 | {-# INLINE getBitfield #-} | ||
88 | |||
89 | putBitfield :: Bitfield -> Put | ||
90 | putBitfield = putByteString . bfBits | ||
91 | {-# INLINE putBitfield #-} | ||
92 | |||
93 | bitfieldByteCount :: Bitfield -> Int | ||
94 | bitfieldByteCount = B.length . bfBits | ||
95 | {-# INLINE bitfieldByteCount #-} | ||
96 | |||
97 | -- WARN | ||
98 | -- TODO | ||
99 | bitfieldBitCount :: Bitfield -> Int | ||
100 | bitfieldBitCount bf = bitSize (undefined :: Word8) * bitfieldByteCount bf | ||
101 | {-# INLINE bitfieldBitCount #-} | ||
102 | |||
103 | align :: Storable a => Ptr a -> (Ptr a, Int) | ||
104 | align p = tie (alignPtr p) undefined | ||
105 | where | ||
106 | tie :: Storable a => (Int -> Ptr a) -> a -> (Ptr a, Int) | ||
107 | tie f a = (f (alignment a), (alignment a)) | ||
108 | |||
109 | alignLow :: Ptr Word8 -> Ptr Word | ||
110 | alignLow ptr = | ||
111 | let alg = alignment (undefined :: Word) | ||
112 | aptr = alignPtr (castPtr ptr) alg :: Ptr Word | ||
113 | in | ||
114 | if ptr == castPtr aptr | ||
115 | then aptr | ||
116 | else castPtr ((castPtr aptr :: Ptr Word8) `advancePtr` negate alg) | ||
117 | |||
118 | isAlignedBy :: Storable a => Ptr a -> Int -> Bool | ||
119 | isAlignedBy ptr alg = alignPtr ptr alg == ptr | ||
120 | |||
121 | type Mem a = (Ptr a, Int) | ||
122 | |||
123 | aligned :: Storable a => Mem Word8 -> (Mem Word8, Mem a, Mem Word8) | ||
124 | aligned (ptr, len) = | ||
125 | let lowPtr = ptr | ||
126 | lowLen = midPtr `minusPtr` ptr | ||
127 | midOff = lowLen | ||
128 | (midPtr, alg) = align (castPtr ptr) | ||
129 | midLen = alg * div (len - midOff) alg | ||
130 | midLenA = midLen `div` alg | ||
131 | hghOff = midOff + midLen | ||
132 | hghPtr = ptr `advancePtr` hghOff | ||
133 | hghLen = len - hghOff | ||
134 | in | ||
135 | ((lowPtr, lowLen), (midPtr, midLenA), (hghPtr, hghLen)) | ||
136 | where | ||
137 | {-# INLINE aligned #-} | ||
138 | |||
139 | type Mem3 a = (Ptr a, Ptr a, Ptr a, Int) | ||
140 | |||
141 | emptyMem3 :: Mem3 a | ||
142 | emptyMem3 = (nullPtr, nullPtr, nullPtr, 0) | ||
143 | |||
144 | -- assume resulting memory is aligned | ||
145 | alignedZip :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> Int | ||
146 | -> (Mem3 Word, Mem3 Word8) | ||
147 | alignedZip aptr bptr cptr size = | ||
148 | let alg = alignment (undefined :: Word) in | ||
149 | if (aptr `isAlignedBy` alg) && (bptr `isAlignedBy` alg) | ||
150 | then | ||
151 | let asize = alignLow (nullPtr `plusPtr` size) `minusPtr` nullPtr | ||
152 | in | ||
153 | ( (castPtr aptr, castPtr bptr, castPtr cptr, asize `div` alg) | ||
154 | , ( aptr `advancePtr` asize | ||
155 | , bptr `advancePtr` asize | ||
156 | , cptr `advancePtr` asize | ||
157 | , (size - asize) | ||
158 | ) | ||
159 | ) | ||
160 | else (emptyMem3, (aptr, bptr, cptr, size)) | ||
161 | |||
162 | -- force specialization | ||
163 | zipWithBS :: (Word -> Word -> Word) | ||
164 | -> (Word8 -> Word8 -> Word8) | ||
165 | -> ByteString -> ByteString -> ByteString | ||
166 | zipWithBS f g a b = | ||
167 | let (afptr, aoff, asize) = B.toForeignPtr a | ||
168 | (bfptr, boff, bsize) = B.toForeignPtr b | ||
169 | size = min asize bsize in | ||
170 | B.unsafeCreate size $ \rptr -> do | ||
171 | withForeignPtr afptr $ \_aptr -> do | ||
172 | withForeignPtr bfptr $ \_bptr -> do | ||
173 | let aptr = _aptr `advancePtr` aoff | ||
174 | let bptr = _bptr `advancePtr` boff | ||
175 | |||
176 | let (mid, hgh) = alignedZip aptr bptr rptr size | ||
177 | zipWords mid | ||
178 | zipBytes hgh | ||
179 | where | ||
180 | zipBytes :: (Ptr Word8, Ptr Word8, Ptr Word8, Int) -> IO () | ||
181 | zipBytes (aptr, bptr, rptr, n) = go 0 | ||
182 | where | ||
183 | go :: Int -> IO () | ||
184 | go i | i < n = do -- TODO unfold | ||
185 | av <- peekElemOff aptr i | ||
186 | bv <- peekElemOff bptr i | ||
187 | pokeElemOff rptr i (g av bv) | ||
188 | go (succ i) | ||
189 | | otherwise = return () | ||
190 | |||
191 | zipWords :: (Ptr Word, Ptr Word, Ptr Word, Int) -> IO () | ||
192 | zipWords (aptr, bptr, rptr, n) = go 0 | ||
193 | where | ||
194 | go :: Int -> IO () | ||
195 | go i | i < n = do -- TODO unfold | ||
196 | av <- peekElemOff aptr i | ||
197 | bv <- peekElemOff bptr i | ||
198 | pokeElemOff rptr i (f av bv) | ||
199 | go (succ i) | ||
200 | | otherwise = return () | ||
201 | |||
202 | |||
203 | |||
204 | zipWithBF :: (forall a. Bits a => a -> a -> a) -> Bitfield -> Bitfield -> Bitfield | ||
205 | zipWithBF f a b = MkBitfield $ zipWithBS f f (bfBits a) (bfBits b) | ||
206 | {-# INLINE zipWithBF #-} | ||
207 | |||
208 | findSet :: ByteString -> Maybe Int | ||
209 | findSet b = | ||
210 | let (fptr, off, len) = B.toForeignPtr b in | ||
211 | B.inlinePerformIO $ withForeignPtr fptr $ \_ptr -> do | ||
212 | let ptr = _ptr `advancePtr` off | ||
213 | |||
214 | let (low, mid, hgh) = aligned (ptr, len) | ||
215 | let lowOff = fst low `minusPtr` ptr | ||
216 | let midOff = fst mid `minusPtr` ptr | ||
217 | let hghOff = fst hgh `minusPtr` ptr | ||
218 | |||
219 | let resL = (lowOff +) <$> goFind low | ||
220 | let resM = (midOff +) <$> goFind (mid :: Mem Word) -- tune size here | ||
221 | -- TODO: with Word8 | ||
222 | -- bytestring findIndex works 2 | ||
223 | -- times faster. | ||
224 | let resH = (hghOff +) <$> goFind hgh | ||
225 | |||
226 | let res = resL <|> resM <|> resH | ||
227 | |||
228 | -- computation of res should not escape withForeignPtr | ||
229 | case res of | ||
230 | Nothing -> return () | ||
231 | Just _ -> return () | ||
232 | |||
233 | return res | ||
234 | |||
235 | where | ||
236 | goFind :: (Storable a, Eq a, Num a) => Mem a -> Maybe Int | ||
237 | goFind (ptr, n) = go 0 | ||
238 | where | ||
239 | go :: Int -> Maybe Int | ||
240 | go i | i < n = | ||
241 | let v = B.inlinePerformIO (peekElemOff ptr i) in | ||
242 | if v /= 0 | ||
243 | then Just i | ||
244 | else go (succ i) | ||
245 | | otherwise = Nothing | ||
246 | |||
247 | foldBS :: (Word8 -> Int -> Int) -> (Word -> Int -> Int) -> Int -> ByteString -> Int | ||
248 | foldBS f g acc b = | ||
249 | let (fptr, off, len) = B.toForeignPtr b in | ||
250 | B.inlinePerformIO $ withForeignPtr fptr $ \_ptr -> do | ||
251 | let ptr = _ptr `advancePtr` off | ||
252 | |||
253 | let (low, mid, hgh) = aligned (ptr, len) | ||
254 | let resL = goFold low acc | ||
255 | let resM = goFoldW (mid :: Mem Word) resL | ||
256 | let resH = goFold hgh resM | ||
257 | |||
258 | -- computation of res should not escape withForeignPtr | ||
259 | case resH of | ||
260 | 0 -> return () | ||
261 | _ -> return () | ||
262 | |||
263 | return resH | ||
264 | |||
265 | where | ||
266 | goFold :: Mem Word8 -> Int -> Int | ||
267 | goFold (ptr, n) = go 0 | ||
268 | where | ||
269 | go :: Int -> Int -> Int | ||
270 | go i !a | ||
271 | | i < n = | ||
272 | let v = B.inlinePerformIO (peekElemOff ptr i) | ||
273 | in go (succ i) (f v a) | ||
274 | | otherwise = a | ||
275 | |||
276 | goFoldW :: Mem Word -> Int -> Int | ||
277 | goFoldW (ptr, n) = go 0 | ||
278 | where | ||
279 | go :: Int -> Int -> Int | ||
280 | go i !a | ||
281 | | i < n = | ||
282 | let v = B.inlinePerformIO (peekElemOff ptr i) | ||
283 | in go (succ i) (g v a) | ||
284 | | otherwise = a | ||
285 | |||
286 | union :: Bitfield -> Bitfield -> Bitfield | ||
287 | union = zipWithBF (.|.) | ||
288 | {-# INLINE union #-} | ||
289 | |||
290 | intersection :: Bitfield -> Bitfield -> Bitfield | ||
291 | intersection = zipWithBF (.&.) | ||
292 | {-# INLINE intersection #-} | ||
293 | |||
294 | difference :: Bitfield -> Bitfield -> Bitfield | ||
295 | difference = zipWithBF diffWord8 | ||
296 | where | ||
297 | diffWord8 :: Bits a => a -> a -> a | ||
298 | diffWord8 a b = a .&. (a `xor` b) | ||
299 | {-# INLINE diffWord8 #-} | ||
300 | {-# INLINE difference #-} | ||
301 | |||
302 | combine :: [Bitfield] -> Maybe Bitfield | ||
303 | combine [] = Nothing | ||
304 | combine as = return $ foldr1 intersection as | ||
305 | |||
306 | haveCount :: Bitfield -> Int | ||
307 | haveCount (MkBitfield b) = foldBS f f 0 b | ||
308 | where | ||
309 | f byte count = popCount byte + count | ||
310 | |||
311 | completeness :: Bitfield -> (Int, Int) | ||
312 | completeness bf = (haveCount bf, bitfieldBitCount bf) | ||
313 | |||
314 | -- | Get min index of piece that the peer have. | ||
315 | findMin :: Bitfield -> Maybe Int | ||
316 | findMin (MkBitfield b) = do | ||
317 | byteIx <- findSet b | ||
318 | bitIx <- findMinWord8 (B.index b byteIx) | ||
319 | return $ byteIx * bitSize (undefined :: Word8) + bitIx | ||
320 | where | ||
321 | -- TODO: bit tricks | ||
322 | findMinWord8 :: Word8 -> Maybe Int | ||
323 | findMinWord8 byte = L.find (testBit byte) [0..bitSize (undefined :: Word8) - 1] | ||
324 | {-# INLINE findMinWord8 #-} | ||
325 | {-# INLINE findMin #-} | ||
326 | |||
327 | |||
328 | findMax :: Bitfield -> Maybe Int | ||
329 | findMax (MkBitfield b) = do | ||
330 | -- TODO avoid reverse | ||
331 | byteIx <- (pred (B.length b) -) <$> findSet (B.reverse b) | ||
332 | bitIx <- findMaxWord8 (B.index b byteIx) | ||
333 | return $ byteIx * bitSize (undefined :: Word8) + bitIx | ||
334 | where | ||
335 | -- TODO: bit tricks | ||
336 | findMaxWord8 :: Word8 -> Maybe Int | ||
337 | findMaxWord8 byte = L.find (testBit byte) | ||
338 | (reverse [0 :: Int .. | ||
339 | bitSize (undefined :: Word8) - 1]) | ||
340 | |||
341 | {-# INLINE findMax #-} | ||
342 | |||
343 | frequencies :: [Bitfield] -> [Int] | ||
344 | frequencies xs = foldr1 (zipWith (+)) $ map (map fromEnum . toList) xs | ||
diff --git a/src/Network/BitTorrent/PeerWire/Message.hs b/src/Network/BitTorrent/PeerWire/Message.hs index cc771966..39102eed 100644 --- a/src/Network/BitTorrent/PeerWire/Message.hs +++ b/src/Network/BitTorrent/PeerWire/Message.hs | |||
@@ -10,7 +10,7 @@ import qualified Data.ByteString as B | |||
10 | import Data.Serialize | 10 | import Data.Serialize |
11 | 11 | ||
12 | import Network.BitTorrent.PeerWire.Block | 12 | import Network.BitTorrent.PeerWire.Block |
13 | import Network.BitTorrent.PeerWire.Bitfield | 13 | import Data.Bitfield |
14 | 14 | ||
15 | import Data.Array | 15 | import Data.Array |
16 | 16 | ||
diff --git a/src/Network/BitTorrent/PeerWire/Selection.hs b/src/Network/BitTorrent/PeerWire/Selection.hs index 2e17317e..83ab8311 100644 --- a/src/Network/BitTorrent/PeerWire/Selection.hs +++ b/src/Network/BitTorrent/PeerWire/Selection.hs | |||
@@ -29,9 +29,10 @@ module Network.BitTorrent.PeerWire.Selection | |||
29 | , autoSelector | 29 | , autoSelector |
30 | ) where | 30 | ) where |
31 | 31 | ||
32 | import Data.Bitfield | ||
32 | import Network.BitTorrent.PeerWire.Block | 33 | import Network.BitTorrent.PeerWire.Block |
33 | import Network.BitTorrent.PeerWire.Message | 34 | import Network.BitTorrent.PeerWire.Message |
34 | import Network.BitTorrent.PeerWire.Bitfield | 35 | |
35 | 36 | ||
36 | 37 | ||
37 | type Selector = Bitfield -- ^ Indices of client "have" pieces. | 38 | type Selector = Bitfield -- ^ Indices of client "have" pieces. |