require :: Handle -> Set AssetId -> IO (Either Error (Map AssetId Result))
require h assetIds = do
    -- Mark assets as dirty if they are not comleted yet.
    forM_ assetIds $ \dep -> do
        buildIfNecessary h dep

    -- Wait for the dependencies to have completed building.
    atomically $ do
        s <- readTVar (state h)

        -- All dependencies which are relevant.
        let allDependencies = M.filterWithKey (\aId _ -> S.member aId assetIds) (assets s)

        -- The dependencies which are completed and for which we have a result.
        let completedDependencies = flip M.mapMaybe allDependencies $ \ars -> case (arsState ars, arsResult ars) of
                (Completed _, Just res) -> Just res
                _                       -> Nothing

        -- A more accurate check would be 'assetIds == M.keysSet completedDependencies'.
        -- Though comparing the length is probably faster.
        if length completedDependencies == length assetIds
            then return $ Right $ completedDependencies
            else if any (isFailed . arsState) (M.elems allDependencies)
                then return $ Left DependencyFailed
                else retry
require :: Handle -> Set AssetId -> IO (Either Error (Map AssetId Result))
require h assetIds = do
    -- Mark assets as dirty if they are not comleted yet.
    forM_ assetIds $ \dep -> do
        buildIfNecessary h dep

    -- Wait for the dependencies to have completed building.
    atomically $ do
        s <- readTVar (state h)

        -- All dependencies which are relevant.
        let allDependencies = M.filterWithKey (\aId _ -> S.member aId assetIds) (assets s)

        -- The dependencies which are completed and for which we have a result.
        let completedDependencies = flip M.mapMaybe allDependencies $ \ars -> case (arsState ars, arsResult ars) of
                (Completed _, Just res) -> Just res
                _                       -> Nothing

        -- A more accurate check would be 'assetIds == M.keysSet completedDependencies'.
        -- Though comparing the length is probably faster.
        if length completedDependencies == length assetIds
            then return $ Right $ completedDependencies
            else if any (isFailed . arsState) (M.elems allDependencies)
                then return $ Left DependencyFailed
                else retry
require h assetIds = do
    -- Mark assets as dirty if they are not comleted yet.
    forM_ assetIds $ \dep -> do
        buildIfNecessary h dep

    -- Wait for the dependencies to have completed building.
    atomically $ do
        s <- readTVar (state h)

        -- All dependencies which are relevant.
        let allDependencies = M.filterWithKey (\aId _ -> S.member aId assetIds) (assets s)

        -- The dependencies which are completed and for which we have a result.
        let completedDependencies = flip M.mapMaybe allDependencies $ \ars -> case (arsState ars, arsResult ars) of
                (Completed _, Just res) -> Just res
                _                       -> Nothing

        -- A more accurate check would be 'assetIds == M.keysSet completedDependencies'.
        -- Though comparing the length is probably faster.
        if length completedDependencies == length assetIds
            then return $ Right $ completedDependencies
            else if any (isFailed . arsState) (M.elems allDependencies)
                then return $ Left DependencyFailed
                else retry