77{-# LANGUAGE ScopedTypeVariables #-}
88{-# LANGUAGE TupleSections #-}
99{-# LANGUAGE TypeFamilies #-}
10+ {-# LANGUAGE ViewPatterns #-}
1011
1112module Development.IDE.Graph.Internal.Database where
1213
1314import Control.Concurrent.Async
1415import Control.Concurrent.Extra
1516import Control.Exception
1617import Control.Monad
18+ import Control.Monad.Trans.Class (lift )
1719import Control.Monad.Trans.Reader
20+ import qualified Control.Monad.Trans.State.Strict as State
1821import Data.Dynamic
1922import Data.Either
23+ import Data.Foldable (traverse_ )
2024import Data.IORef.Extra
25+ import Data.IntSet (IntSet )
26+ import qualified Data.IntSet as Set
2127import Data.Maybe
2228import Data.Tuple.Extra
2329import qualified Development.IDE.Graph.Internal.Ids as Ids
@@ -36,17 +42,31 @@ newDatabase databaseExtra databaseRules = do
3642 databaseLock <- newLock
3743 databaseIds <- newIORef Intern. empty
3844 databaseValues <- Ids. empty
45+ databaseReverseDeps <- Ids. empty
46+ databaseReverseDepsLock <- newLock
3947 pure Database {.. }
4048
41- -- | Increment the step and mark all ids dirty
42- incDatabase :: Database -> IO ()
43- incDatabase db = do
44- modifyIORef' (databaseStep db) $ \ ( Step i) -> Step $ i + 1
49+ -- | Increment the step and mark dirty
50+ incDatabase :: Database -> Maybe [ Key ] -> IO ()
51+ -- all keys are dirty
52+ incDatabase db Nothing =
4553 withLock (databaseLock db) $
46- Ids. forMutate (databaseValues db) $ second $ \ case
54+ Ids. forMutate (databaseValues db) $ \ _ -> second $ \ case
4755 Clean x -> Dirty (Just x)
4856 Dirty x -> Dirty x
4957 Running _ x -> Dirty x
58+ -- only some keys are dirty
59+ incDatabase db (Just kk) = do
60+ modifyIORef' (databaseStep db) $ \ (Step i) -> Step $ i + 1
61+ intern <- readIORef (databaseIds db)
62+ let dirtyIds = mapMaybe (`Intern.lookup` intern) kk
63+ transitiveDirtyIds <- transitiveDirtySet db dirtyIds
64+ withLock (databaseLock db) $
65+ Ids. forMutate (databaseValues db) $ \ i -> \ case
66+ (k, Running _ x) -> (k, Dirty x)
67+ (k, Clean x) | i `Set.member` transitiveDirtyIds ->
68+ (k, Dirty (Just x))
69+ other -> other
5070
5171
5272-- | Unwrap and build a list of keys in parallel
@@ -116,17 +136,17 @@ builder db@Database{..} keys = do
116136cleanupAsync :: IORef [Async a ] -> IO ()
117137cleanupAsync ref = mapConcurrently_ uninterruptibleCancel =<< readIORef ref
118138
119-
120139-- | Check if we need to run the database.
121140check :: Database -> Key -> Id -> Maybe Result -> IO Result
122141check db key id result@ (Just me@ Result {resultDeps= Just deps}) = do
123- res <- builder db $ map Left deps
124- let dirty = any (\ (_,dep) -> resultBuilt me < resultChanged dep) res
125- let mode = if dirty then Shake. RunDependenciesChanged else Shake. RunDependenciesSame
142+ mode <- do
143+ res <- builder db (map Left deps)
144+ let dirty = any (\ (_,dep) -> resultBuilt me < resultChanged dep) res
145+ return $ if dirty then Shake. RunDependenciesChanged else Shake. RunDependenciesSame
146+ -- If I am not dirty then none of my dependencies are, so they must be unchanged
126147 spawn db key id mode result
127148check db key id result = spawn db key id Shake. RunDependenciesChanged result
128149
129-
130150-- | Spawn a new computation to run the action.
131151spawn :: Database -> Key -> Id -> Shake. RunMode -> Maybe Result -> IO Result
132152spawn db@ Database {.. } key id mode result = do
@@ -137,10 +157,11 @@ spawn db@Database{..} key id mode result = do
137157 deps <- readIORef deps
138158 let changed = if runChanged == Shake. ChangedRecomputeDiff then built else maybe built resultChanged result
139159 -- only update the deps when the rule ran with changes
140- let actual_deps = if runChanged /= Shake. ChangedNothing then deps else previousDeps
160+ let actualDeps = if runChanged /= Shake. ChangedNothing then deps else previousDeps
141161 previousDeps= resultDeps =<< result
142- let res = Result runValue built changed actual_deps runStore
143- withLock databaseLock $
162+ let res = Result runValue built changed actualDeps runStore
163+ withLock databaseLock $ do
164+ updateReverseDeps id db (fromMaybe [] previousDeps) (fromMaybe [] actualDeps)
144165 Ids. insert databaseValues id (key, Clean res)
145166 pure res
146167
@@ -152,3 +173,41 @@ splitIO act = do
152173 let act2 = Box <$> act
153174 let res = unsafePerformIO act2
154175 (void $ evaluate res, fromBox res)
176+
177+ --------------------------------------------------------------------------------
178+ -- Reverse dependencies
179+
180+ -- | Update the reverse dependencies of an Id
181+ updateReverseDeps
182+ :: Id -- ^ Id
183+ -> Database
184+ -> [Id ] -- ^ Previous direct dependencies of Id
185+ -> [Id ] -- ^ Current direct dependencies of Id
186+ -> IO ()
187+ updateReverseDeps myId db prev new = do
188+ forM_ prev $ doOne (Set. delete $ idToInt myId)
189+ forM_ new $ doOne (Set. insert $ idToInt myId)
190+ where
191+ doOne f id = withLock (databaseReverseDepsLock db) $ do
192+ rdeps <- getReverseDependencies db id
193+ Ids. insert (databaseReverseDeps db) id (f $ fromMaybe mempty rdeps)
194+
195+ idToInt :: Id -> Int
196+ idToInt = id
197+
198+ getReverseDependencies :: Database -> Id -> IO (Maybe (IntSet ))
199+ getReverseDependencies db = Ids. lookup (databaseReverseDeps db)
200+
201+ transitiveDirtySet :: Foldable t => Database -> t Id -> IO IntSet
202+ transitiveDirtySet database = flip State. execStateT Set. empty . traverse_ loop
203+ where
204+ loop (idToInt -> x) = do
205+ seen <- State. get
206+ if x `Set.member` seen then pure () else do
207+ State. put (Set. insert x seen)
208+ next <- lift $ getReverseDependencies database x
209+ traverse_ loop (maybe mempty Set. toList next)
210+
211+
212+ idFromInt :: Set. Key -> Id
213+ idFromInt = id
0 commit comments