@@ -1,13 +1,26 @@
1+{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE DeriveFunctor #-}
3{-# LANGUAGE DeriveFunctor #-}
2{-# LANGUAGE DeriveFoldable #-}
4{-# LANGUAGE DeriveFoldable #-}
3{-# LANGUAGE DeriveTraversable #-}
5{-# LANGUAGE DeriveTraversable #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7+{-# LANGUAGE LambdaCase #-}
8+{-# LANGUAGE ScopedTypeVariables #-}
5{-# LANGUAGE TypeFamilies #-}
9{-# LANGUAGE TypeFamilies #-}
12+{-# LANGUAGE ForeignFunctionInterface #-}
13+{-# LANGUAGE JavaScriptFFI #-}
14+{-# LANGUAGE MagicHash #-}
15+{-# LANGUAGE UnliftedFFITypes #-}
6module Data.FastMutableIntMap
17module Data.FastMutableIntMap
7 ( FastMutableIntMap
18 ( FastMutableIntMap
9 , newEmpty
20 , newEmpty
12 , getFrozenAndClear
25 , getFrozenAndClear
@@ -27,56 +40,180 @@ module Data.FastMutableIntMap
27--TODO: Fast patch type
40--TODO: Fast patch type
29import Prelude hiding (lookup)
42import Prelude hiding (lookup)
31import Data.IntMap.Strict (IntMap)
44import Data.IntMap.Strict (IntMap)
32import qualified Data.IntMap.Strict as IntMap
45import qualified Data.IntMap.Strict as IntMap
33import Data.Maybe
46import Data.Maybe
34import Reflex.Patch.Class
47import Reflex.Patch.Class
35−import Data.Foldable (traverse_)
36import Control.Monad.IO.Class
48import Control.Monad.IO.Class
51+import Control.Lens (imapM_, iforM_, iforM)
55+import JavaScript.Array (JSArray)
56+import qualified JavaScript.Array as JSArray
57+import GHCJS.Prim (JSVal (..))
58+import GHC.Exts (ByteArray#)
59+import GHC.Prim (unsafeCoerce#)
62+import Data.Foldable (traverse_)
65+newtype FastMutableIntMap a =
67+ FastMutableIntMap JSVal
38−newtype FastMutableIntMap a = FastMutableIntMap (IORef (IntMap a))
69+ FastMutableIntMap (IORef (IntMap a))
40new :: IntMap a -> IO (FastMutableIntMap a)
72new :: IntMap a -> IO (FastMutableIntMap a)
76+ imapM_ (insert result) m
41new m = FastMutableIntMap <$> newIORef m
79new m = FastMutableIntMap <$> newIORef m
83+foreign import javascript unsafe "var a = []; a.__ghcjsArray = true; $r = { f: { n: 'FastMutableIntMap', size: 2 }, d1: a, d2: { d1: 0 }, m: 0 }" newEmpty :: IO (FastMutableIntMap a)
43newEmpty :: IO (FastMutableIntMap a)
85newEmpty :: IO (FastMutableIntMap a)
44newEmpty = FastMutableIntMap <$> newIORef IntMap.empty
86newEmpty = FastMutableIntMap <$> newIORef IntMap.empty
46−insert :: FastMutableIntMap a -> Int -> a -> IO ()
89+insert :: forall a. FastMutableIntMap a -> Int -> a -> IO ()
91+insert m k !v = insert_ m k ((unsafeCoerce# :: a -> ByteArray#) v)
92+foreign import javascript unsafe "if($1.d1[$2 & 0x7FFFFFFF] === undefined) { ++$1.d2.d1; } $1.d1[$2 & 0x7FFFFFFF] = $3;" insert_ :: FastMutableIntMap a -> Int -> ByteArray# -> IO ()
47insert (FastMutableIntMap r) k v = modifyIORef' r $ IntMap.insert k v
94insert (FastMutableIntMap r) k v = modifyIORef' r $ IntMap.insert k v
97+insertLookup :: forall a. FastMutableIntMap a -> Int -> a -> IO (Maybe a)
99+insertLookup m k !newVal = do
100+ oldVal <- insertLookup_ m k ((unsafeCoerce# :: a -> ByteArray#) newVal)
101+ return $ elemFromJSVal oldVal
102+foreign import javascript unsafe "$r = $1.d1[$2 & 0x7FFFFFFF]; if($r === undefined) { ++$1.d2.d1; } $1.d1[$2 & 0x7FFFFFFF] = $3;" insertLookup_ :: FastMutableIntMap a -> Int -> ByteArray# -> IO JSVal
104+insertLookup (FastMutableIntMap r) k v = do
105+ oldMap <- readIORef r
106+ let (oldVal, newMap) = IntMap.updateLookupWithKey (\_ _ -> Just v) k oldMap
107+ writeIORef r $! newMap
111+deleteLookup :: FastMutableIntMap a -> Int -> IO (Maybe a)
113+deleteLookup m k = do
114+ v <- deleteLookup_ m k
115+ return $ elemFromJSVal v
116+foreign import javascript unsafe "$r = $1.d1[$2 & 0x7FFFFFFF]; if($r !== undefined) { --$1.d2.d1; } delete $1.d1[$2 & 0x7FFFFFFF];" deleteLookup_ :: FastMutableIntMap a -> Int -> IO JSVal
118+deleteLookup (FastMutableIntMap r) k = do
119+ oldMap <- readIORef r
120+ let (oldVal, newMap) = IntMap.updateLookupWithKey (\_ _ -> Nothing) k oldMap
121+ writeIORef r $! newMap
49−lookup :: FastMutableIntMap a -> Int -> IO (Maybe a)
125+lookup :: forall a. FastMutableIntMap a -> Int -> IO (Maybe a)
129+ return $ elemFromJSVal v
130+foreign import javascript unsafe "$r = $1.d1[$2 & 0x7FFFFFFF];" lookup_ :: FastMutableIntMap a -> Int -> IO JS
132+--TODO: Find out whether it's possible for Haskell objects to be represented as 'undefined'; if so, we need to do something slightly better to represent missing values
134+elemFromJSVal :: JSVal -> Maybe a
135+elemFromJSVal v = case isUndefined v of
137+ False -> Just $ (unsafeCoerce# :: ByteArray# -> a) (case v of JSVal raw -> raw)
50lookup (FastMutableIntMap r) k = IntMap.lookup k <$> readIORef r
140lookup (FastMutableIntMap r) k = IntMap.lookup k <$> readIORef r
143+--TODO: Do something faster than trying every one
144+{-# INLINE forIntersectionWithImmutable_ #-}
52forIntersectionWithImmutable_ :: MonadIO m => FastMutableIntMap a -> IntMap b -> (a -> b -> m ()) -> m ()
145forIntersectionWithImmutable_ :: MonadIO m => FastMutableIntMap a -> IntMap b -> (a -> b -> m ()) -> m ()
147+forIntersectionWithImmutable_ ma mb f = iforM_ mb $ \k b -> do
148+ liftIO (lookup ma k) >>= \case
149+ Nothing -> return ()
53forIntersectionWithImmutable_ (FastMutableIntMap r) b f = do
152forIntersectionWithImmutable_ (FastMutableIntMap r) b f = do
54 a <- liftIO $ readIORef r
153 a <- liftIO $ readIORef r
55 traverse_ (uncurry f) $ IntMap.intersectionWith (,) a b
154 traverse_ (uncurry f) $ IntMap.intersectionWith (,) a b
57for_ :: MonadIO m => FastMutableIntMap a -> (a -> m ()) -> m ()
158for_ :: MonadIO m => FastMutableIntMap a -> (a -> m ()) -> m ()
161+ raw <- liftIO $ packFastMutableIntMap m --TODO: Don't pack the keys, since we don't need them
162+ forM_ (JSArray.toList raw) $ \p -> do
163+ (_ :: Int, JSVal v) <- liftIO $ fromJSValUnchecked p
164+ f $ (unsafeCoerce# :: ByteArray# -> a) v
58for_ (FastMutableIntMap r) f = do
166for_ (FastMutableIntMap r) f = do
59 a <- liftIO $ readIORef r
167 a <- liftIO $ readIORef r
60 traverse_ f a
168 traverse_ f a
172+foreign import javascript unsafe "$r = $1.d2.d1 === 0;" isEmpty :: FastMutableIntMap a -> IO Bool
62isEmpty :: FastMutableIntMap a -> IO Bool
174isEmpty :: FastMutableIntMap a -> IO Bool
63isEmpty (FastMutableIntMap r) = IntMap.null <$> readIORef r
175isEmpty (FastMutableIntMap r) = IntMap.null <$> readIORef r
179+foreign import javascript unsafe "$r = $1.d2.d1;" size :: FastMutableIntMap a -> IO Int
65size :: FastMutableIntMap a -> IO Int
181size :: FastMutableIntMap a -> IO Int
66size (FastMutableIntMap r) = IntMap.size <$> readIORef r
182size (FastMutableIntMap r) = IntMap.size <$> readIORef r
68-- | Make an immutable snapshot of the datastructure and clear it
185-- | Make an immutable snapshot of the datastructure and clear it
69getFrozenAndClear :: FastMutableIntMap a -> IO (IntMap a)
186getFrozenAndClear :: FastMutableIntMap a -> IO (IntMap a)
188+getFrozenAndClear m = do
189+ raw <- packFastMutableIntMap m
191+ pairs <- forM (JSArray.toList raw) $ \p -> do
192+ (k, JSVal v) <- fromJSValUnchecked p
193+ return (k, (unsafeCoerce# :: ByteArray# -> a) v)
194+ return $ IntMap.fromDistinctAscList pairs
195+-- | Make a copy of a FastMutableIntMap's contents, as an array of key/value pairs
196+foreign import javascript unsafe "$r = new Array($1.d2.d1); $r.__ghcjsArray = true; var i = 0; for(var k in $1.d1) { if(k !== '__ghcjsArray') { var p = [k, $1.d1[k]]; p.__ghcjsArray = true; $r[i++] = p; } } $r.sort(function(a, b) { return a[0] - b[0]; });" packFastMutableIntMap :: FastMutableIntMap a -> IO JSArray
197+foreign import javascript unsafe "$1.d1 = []; $1.d1.__ghcjsArray = true; $1.d2.d1 = 0;" clear :: FastMutableIntMap a -> IO ()
198+--NOTE: We would like to use Array.forEach here, but, at least on Chrome 59, forEach appears to be linear in the maximum key in the array. Since we sometimes use very large indexes, this is unusable. for(... in ...), on the other hand, appears to actually operate sparsely - but this requires us to sort the array in a second step.
70getFrozenAndClear (FastMutableIntMap r) = do
200getFrozenAndClear (FastMutableIntMap r) = do
71 result <- readIORef r
201 result <- readIORef r
72 writeIORef r IntMap.empty
202 writeIORef r IntMap.empty
73 return result
203 return result
75applyPatch :: FastMutableIntMap a -> PatchIntMap a -> IO (IntMap a)
206applyPatch :: FastMutableIntMap a -> PatchIntMap a -> IO (IntMap a)
208+applyPatch f (PatchIntMap m) = fmap (IntMap.mapMaybe id) $ iforM m $ \k mv -> case mv of
209+ Nothing -> deleteLookup f k
210+ Just v -> insertLookup f k v
76applyPatch (FastMutableIntMap r) p@(PatchIntMap m) = do
212applyPatch (FastMutableIntMap r) p@(PatchIntMap m) = do
77 v <- readIORef r
213 v <- readIORef r
78 writeIORef r $! applyAlways p v
214 writeIORef r $! applyAlways p v
79 return $ IntMap.intersection v m
215 return $ IntMap.intersection v m
81newtype PatchIntMap a = PatchIntMap (IntMap (Maybe a)) deriving (Functor, Foldable, Traversable, Monoid)
218newtype PatchIntMap a = PatchIntMap (IntMap (Maybe a)) deriving (Functor, Foldable, Traversable, Monoid)