mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
perf(ui): revised range-based fetching strategy
When the user scrolls in the gallery, we are alerted of the new range of visible images. Then we fetch those specific images. Previously, each change of range triggered a throttled function to fetch that range. The throttle timeout was 100ms. Now, each change of range appends that range to a list of ranges and triggers the throttled fetch. The timeout is increased to 500ms, but to compensate, each fetch handles all ranges that had been accumulated since the last fetch. The result is far fewer network requests, but each of them gets more images.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useAppStore } from 'app/store/storeHooks';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { ListRange } from 'react-virtuoso';
|
||||
import { imagesApi, useGetImageDTOsByNamesMutation } from 'services/api/endpoints/images';
|
||||
import { useThrottledCallback } from 'use-debounce';
|
||||
@@ -13,33 +13,20 @@ interface UseRangeBasedImageFetchingReturn {
|
||||
onRangeChanged: (range: ListRange) => void;
|
||||
}
|
||||
|
||||
const getUncachedNames = (imageNames: string[], cachedImageNames: string[], range: ListRange): string[] => {
|
||||
if (range.startIndex === range.endIndex) {
|
||||
// If the start and end indices are the same, no range to fetch
|
||||
return [];
|
||||
}
|
||||
const getUncachedNames = (imageNames: string[], cachedImageNames: string[], ranges: ListRange[]): string[] => {
|
||||
const uncachedNamesSet = new Set<string>();
|
||||
const cachedImageNamesSet = new Set(cachedImageNames);
|
||||
|
||||
if (imageNames.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const start = Math.max(0, range.startIndex);
|
||||
const end = Math.min(imageNames.length - 1, range.endIndex);
|
||||
|
||||
if (cachedImageNames.length === 0) {
|
||||
return imageNames.slice(start, end + 1);
|
||||
}
|
||||
|
||||
const uncachedNames: string[] = [];
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
const imageName = imageNames[i]!;
|
||||
if (!cachedImageNames.includes(imageName)) {
|
||||
uncachedNames.push(imageName);
|
||||
for (const range of ranges) {
|
||||
for (let i = range.startIndex; i <= range.endIndex; i++) {
|
||||
const n = imageNames[i]!;
|
||||
if (n && !cachedImageNamesSet.has(n)) {
|
||||
uncachedNamesSet.add(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uncachedNames;
|
||||
return Array.from(uncachedNamesSet);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -53,39 +40,33 @@ export const useRangeBasedImageFetching = ({
|
||||
}: UseRangeBasedImageFetchingArgs): UseRangeBasedImageFetchingReturn => {
|
||||
const store = useAppStore();
|
||||
const [getImageDTOsByNames] = useGetImageDTOsByNamesMutation();
|
||||
const lastRangeRef = useRef<ListRange | null>(null);
|
||||
const [pendingRanges, setPendingRanges] = useState<ListRange[]>([]);
|
||||
|
||||
const fetchImages = useCallback(
|
||||
(visibleRange: ListRange) => {
|
||||
(ranges: ListRange[], imageNames: string[]) => {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
const cachedImageNames = imagesApi.util.selectCachedArgsForQuery(store.getState(), 'getImageDTO');
|
||||
const uncachedNames = getUncachedNames(imageNames, cachedImageNames, visibleRange);
|
||||
const uncachedNames = getUncachedNames(imageNames, cachedImageNames, ranges);
|
||||
if (uncachedNames.length === 0) {
|
||||
return;
|
||||
}
|
||||
getImageDTOsByNames({ image_names: uncachedNames });
|
||||
lastRangeRef.current = visibleRange;
|
||||
setPendingRanges([]);
|
||||
},
|
||||
[enabled, getImageDTOsByNames, imageNames, store]
|
||||
[enabled, getImageDTOsByNames, store]
|
||||
);
|
||||
|
||||
const throttledFetchImages = useThrottledCallback(fetchImages, 100);
|
||||
const throttledFetchImages = useThrottledCallback(fetchImages, 500);
|
||||
|
||||
const onRangeChanged = useCallback(
|
||||
(range: ListRange) => {
|
||||
throttledFetchImages(range);
|
||||
},
|
||||
[throttledFetchImages]
|
||||
);
|
||||
const onRangeChanged = useCallback((range: ListRange) => {
|
||||
setPendingRanges((prev) => [...prev, range]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!lastRangeRef.current) {
|
||||
return;
|
||||
}
|
||||
throttledFetchImages(lastRangeRef.current);
|
||||
}, [imageNames, throttledFetchImages]);
|
||||
throttledFetchImages(pendingRanges, imageNames);
|
||||
}, [imageNames, pendingRanges, throttledFetchImages]);
|
||||
|
||||
return {
|
||||
onRangeChanged,
|
||||
|
||||
Reference in New Issue
Block a user