perf(ui): debounce invoke readiness calculations

This commit is contained in:
psychedelicious
2025-02-16 08:53:07 +10:00
parent abbb3609c8
commit a0cdcdef57
4 changed files with 141 additions and 251 deletions

View File

@@ -1,22 +1,15 @@
import type { TooltipProps } from '@invoke-ai/ui-library';
import { Divider, Flex, ListItem, Text, Tooltip, UnorderedList } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { $true } from 'app/store/nanostores/util';
import { useAppSelector } from 'app/store/storeHooks';
import { useCanvasManagerSafe } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { selectSendToCanvas } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectIterations } from 'features/controlLayers/store/paramsSlice';
import { selectDynamicPromptsIsLoading } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
import { $templates } from 'features/nodes/store/nodesSlice';
import type { Reason } from 'features/queue/store/readiness';
import {
buildSelectIsReadyToEnqueueCanvasTab,
buildSelectIsReadyToEnqueueUpscaleTab,
buildSelectIsReadyToEnqueueWorkflowsTab,
buildSelectReasonsWhyCannotEnqueueCanvasTab,
buildSelectReasonsWhyCannotEnqueueUpscaleTab,
buildSelectReasonsWhyCannotEnqueueWorkflowsTab,
$isReadyToEnqueue,
$reasonsWhyCannotEnqueue,
selectPromptsCount,
selectWorkflowsBatchSize,
} from 'features/queue/store/readiness';
@@ -26,7 +19,6 @@ import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { enqueueMutationFixedCacheKeyOptions, useEnqueueBatchMutation } from 'services/api/endpoints/queue';
import { useBoardName } from 'services/api/hooks/useBoardName';
import { $isConnected } from 'services/events/stores';
type Props = TooltipProps & {
prepend?: boolean;
@@ -60,56 +52,8 @@ const TooltipContent = memo(({ prepend = false }: { prepend?: boolean }) => {
TooltipContent.displayName = 'TooltipContent';
const CanvasTabTooltipContent = memo(({ prepend = false }: { prepend?: boolean }) => {
const isConnected = useStore($isConnected);
const canvasManager = useCanvasManagerSafe();
const canvasIsFiltering = useStore(canvasManager?.stateApi.$isFiltering ?? $true);
const canvasIsTransforming = useStore(canvasManager?.stateApi.$isTransforming ?? $true);
const canvasIsRasterizing = useStore(canvasManager?.stateApi.$isRasterizing ?? $true);
const canvasIsSelectingObject = useStore(canvasManager?.stateApi.$isSegmenting ?? $true);
const canvasIsCompositing = useStore(canvasManager?.compositor.$isBusy ?? $true);
const selectIsReady = useMemo(
() =>
buildSelectIsReadyToEnqueueCanvasTab({
isConnected,
canvasIsFiltering,
canvasIsTransforming,
canvasIsRasterizing,
canvasIsSelectingObject,
canvasIsCompositing,
}),
[
isConnected,
canvasIsCompositing,
canvasIsFiltering,
canvasIsRasterizing,
canvasIsSelectingObject,
canvasIsTransforming,
]
);
const selectReasons = useMemo(
() =>
buildSelectReasonsWhyCannotEnqueueCanvasTab({
isConnected,
canvasIsFiltering,
canvasIsTransforming,
canvasIsRasterizing,
canvasIsSelectingObject,
canvasIsCompositing,
}),
[
isConnected,
canvasIsCompositing,
canvasIsFiltering,
canvasIsRasterizing,
canvasIsSelectingObject,
canvasIsTransforming,
]
);
const isReady = useAppSelector(selectIsReady);
const reasons = useAppSelector(selectReasons);
const isReady = useStore($isReadyToEnqueue);
const reasons = useStore($reasonsWhyCannotEnqueue);
return (
<Flex flexDir="column" gap={1}>
@@ -129,13 +73,8 @@ const CanvasTabTooltipContent = memo(({ prepend = false }: { prepend?: boolean }
CanvasTabTooltipContent.displayName = 'CanvasTabTooltipContent';
const UpscaleTabTooltipContent = memo(({ prepend = false }: { prepend?: boolean }) => {
const isConnected = useStore($isConnected);
const selectIsReady = useMemo(() => buildSelectIsReadyToEnqueueUpscaleTab({ isConnected }), [isConnected]);
const selectReasons = useMemo(() => buildSelectReasonsWhyCannotEnqueueUpscaleTab({ isConnected }), [isConnected]);
const isReady = useAppSelector(selectIsReady);
const reasons = useAppSelector(selectReasons);
const isReady = useStore($isReadyToEnqueue);
const reasons = useStore($reasonsWhyCannotEnqueue);
return (
<Flex flexDir="column" gap={1}>
@@ -153,20 +92,8 @@ const UpscaleTabTooltipContent = memo(({ prepend = false }: { prepend?: boolean
UpscaleTabTooltipContent.displayName = 'UpscaleTabTooltipContent';
const WorkflowsTabTooltipContent = memo(({ prepend = false }: { prepend?: boolean }) => {
const isConnected = useStore($isConnected);
const templates = useStore($templates);
const selectIsReady = useMemo(
() => buildSelectIsReadyToEnqueueWorkflowsTab({ isConnected, templates }),
[isConnected, templates]
);
const selectReasons = useMemo(
() => buildSelectReasonsWhyCannotEnqueueWorkflowsTab({ isConnected, templates }),
[isConnected, templates]
);
const isReady = useAppSelector(selectIsReady);
const reasons = useAppSelector(selectReasons);
const isReady = useStore($isReadyToEnqueue);
const reasons = useStore($reasonsWhyCannotEnqueue);
return (
<Flex flexDir="column" gap={1}>

View File

@@ -1,61 +1,15 @@
import { useStore } from '@nanostores/react';
import { enqueueRequested } from 'app/store/actions';
import { $true } from 'app/store/nanostores/util';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useCanvasManagerSafe } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { $templates } from 'features/nodes/store/nodesSlice';
import {
buildSelectIsReadyToEnqueueCanvasTab,
buildSelectIsReadyToEnqueueUpscaleTab,
buildSelectIsReadyToEnqueueWorkflowsTab,
} from 'features/queue/store/readiness';
import { $isReadyToEnqueue } from 'features/queue/store/readiness';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import { enqueueMutationFixedCacheKeyOptions, useEnqueueBatchMutation } from 'services/api/endpoints/queue';
import { $isConnected } from 'services/events/stores';
export const useInvoke = () => {
const dispatch = useAppDispatch();
const tabName = useAppSelector(selectActiveTab);
const isConnected = useStore($isConnected);
const canvasManager = useCanvasManagerSafe();
const canvasIsFiltering = useStore(canvasManager?.stateApi.$isFiltering ?? $true);
const canvasIsTransforming = useStore(canvasManager?.stateApi.$isTransforming ?? $true);
const canvasIsRasterizing = useStore(canvasManager?.stateApi.$isRasterizing ?? $true);
const canvasIsSelectingObject = useStore(canvasManager?.stateApi.$isSegmenting ?? $true);
const canvasIsCompositing = useStore(canvasManager?.compositor.$isBusy ?? $true);
const templates = useStore($templates);
const selectIsReady = useMemo(() => {
if (tabName === 'canvas') {
return buildSelectIsReadyToEnqueueCanvasTab({
isConnected,
canvasIsFiltering,
canvasIsTransforming,
canvasIsRasterizing,
canvasIsSelectingObject,
canvasIsCompositing,
});
}
if (tabName === 'upscaling') {
return buildSelectIsReadyToEnqueueUpscaleTab({ isConnected });
}
if (tabName === 'workflows') {
return buildSelectIsReadyToEnqueueWorkflowsTab({ isConnected, templates });
}
return () => false;
}, [
tabName,
isConnected,
canvasIsFiltering,
canvasIsTransforming,
canvasIsRasterizing,
canvasIsSelectingObject,
canvasIsCompositing,
templates,
]);
const isReady = useAppSelector(selectIsReady);
const isReady = useStore($isReadyToEnqueue);
const [_, { isLoading }] = useEnqueueBatchMutation(enqueueMutationFixedCacheKeyOptions);
const queueBack = useCallback(() => {

View File

@@ -1,7 +1,12 @@
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import { getConnectedEdges } from '@xyflow/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { $true } from 'app/store/nanostores/util';
import { useAppSelector } from 'app/store/storeHooks';
import type { AppConfig } from 'app/types/invokeai';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { useCanvasManagerSafe } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import type { ParamsState } from 'features/controlLayers/store/paramsSlice';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
@@ -16,6 +21,7 @@ import {
import type { DynamicPromptsState } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import { $templates } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import type { NodesState, Templates } from 'features/nodes/store/types';
import type { WorkflowSettingsState } from 'features/nodes/store/workflowSettingsSlice';
@@ -46,8 +52,13 @@ import { isBatchNode, isExecutableNode, isInvocationNode } from 'features/nodes/
import type { UpscaleState } from 'features/parameters/store/upscaleSlice';
import { selectUpscaleSlice } from 'features/parameters/store/upscaleSlice';
import { selectConfigSlice } from 'features/system/store/configSlice';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
import type { TabName } from 'features/ui/store/uiTypes';
import i18n from 'i18next';
import { forEach, groupBy, upperFirst } from 'lodash-es';
import { debounce, forEach, groupBy, upperFirst } from 'lodash-es';
import { atom, computed } from 'nanostores';
import { useEffect } from 'react';
import { $isConnected } from 'services/events/stores';
import { assert } from 'tsafe';
/**
@@ -68,6 +79,122 @@ const LAYER_TYPE_TO_TKEY = {
export type Reason = { prefix?: string; content: string };
export const $reasonsWhyCannotEnqueue = atom<Reason[]>([]);
export const $isReadyToEnqueue = computed($reasonsWhyCannotEnqueue, (reasons) => reasons.length === 0);
const debouncedUpdateReasons = debounce(
(
tab: TabName,
isConnected: boolean,
canvas: CanvasState,
params: ParamsState,
dynamicPrompts: DynamicPromptsState,
canvasIsFiltering: boolean,
canvasIsTransforming: boolean,
canvasIsRasterizing: boolean,
canvasIsCompositing: boolean,
canvasIsSelectingObject: boolean,
nodes: NodesState,
workflowSettings: WorkflowSettingsState,
templates: Templates,
upscale: UpscaleState,
config: AppConfig
) => {
if (tab === 'canvas') {
$reasonsWhyCannotEnqueue.set(
getReasonsWhyCannotEnqueueCanvasTab({
isConnected,
canvas,
params,
dynamicPrompts,
canvasIsFiltering,
canvasIsTransforming,
canvasIsRasterizing,
canvasIsCompositing,
canvasIsSelectingObject,
})
);
} else if (tab === 'workflows') {
$reasonsWhyCannotEnqueue.set(
getReasonsWhyCannotEnqueueWorkflowsTab({
isConnected,
nodes,
workflowSettings,
templates,
})
);
} else if (tab === 'upscaling') {
$reasonsWhyCannotEnqueue.set(
getReasonsWhyCannotEnqueueUpscaleTab({
isConnected,
upscale,
config,
params,
})
);
} else {
$reasonsWhyCannotEnqueue.set([]);
}
},
300
);
export const useReadinessWatcher = () => {
useAssertSingleton('useReadinessWatcher');
const canvasManager = useCanvasManagerSafe();
const tab = useAppSelector(selectActiveTab);
const canvas = useAppSelector(selectCanvasSlice);
const params = useAppSelector(selectParamsSlice);
const dynamicPrompts = useAppSelector(selectDynamicPromptsSlice);
const nodes = useAppSelector(selectNodesSlice);
const workflowSettings = useAppSelector(selectWorkflowSettingsSlice);
const upscale = useAppSelector(selectUpscaleSlice);
const config = useAppSelector(selectConfigSlice);
const templates = useStore($templates);
const isConnected = useStore($isConnected);
const canvasIsFiltering = useStore(canvasManager?.stateApi.$isFiltering ?? $true);
const canvasIsTransforming = useStore(canvasManager?.stateApi.$isTransforming ?? $true);
const canvasIsRasterizing = useStore(canvasManager?.stateApi.$isRasterizing ?? $true);
const canvasIsSelectingObject = useStore(canvasManager?.stateApi.$isSegmenting ?? $true);
const canvasIsCompositing = useStore(canvasManager?.compositor.$isBusy ?? $true);
useEffect(() => {
debouncedUpdateReasons(
tab,
isConnected,
canvas,
params,
dynamicPrompts,
canvasIsFiltering,
canvasIsTransforming,
canvasIsRasterizing,
canvasIsCompositing,
canvasIsSelectingObject,
nodes,
workflowSettings,
templates,
upscale,
config
);
}, [
canvas,
canvasIsCompositing,
canvasIsFiltering,
canvasIsRasterizing,
canvasIsSelectingObject,
canvasIsTransforming,
config,
dynamicPrompts,
isConnected,
nodes,
params,
tab,
templates,
upscale,
workflowSettings,
]);
};
const disconnectedReason = (t: typeof i18n.t) => ({ content: t('parameters.invoke.systemDisconnected') });
export const resolveBatchValue = (batchNode: InvocationNode, nodes: InvocationNode[], edges: AnyEdge[]) => {
@@ -485,126 +612,6 @@ const getReasonsWhyCannotEnqueueCanvasTab = (arg: {
return reasons;
};
export const buildSelectReasonsWhyCannotEnqueueCanvasTab = (arg: {
isConnected: boolean;
canvasIsFiltering: boolean;
canvasIsTransforming: boolean;
canvasIsRasterizing: boolean;
canvasIsCompositing: boolean;
canvasIsSelectingObject: boolean;
}) => {
const {
isConnected,
canvasIsFiltering,
canvasIsTransforming,
canvasIsRasterizing,
canvasIsCompositing,
canvasIsSelectingObject,
} = arg;
return createSelector(
selectCanvasSlice,
selectParamsSlice,
selectDynamicPromptsSlice,
(canvas, params, dynamicPrompts) =>
getReasonsWhyCannotEnqueueCanvasTab({
isConnected,
canvas,
params,
dynamicPrompts,
canvasIsFiltering,
canvasIsTransforming,
canvasIsRasterizing,
canvasIsCompositing,
canvasIsSelectingObject,
})
);
};
export const buildSelectIsReadyToEnqueueCanvasTab = (arg: {
isConnected: boolean;
canvasIsFiltering: boolean;
canvasIsTransforming: boolean;
canvasIsRasterizing: boolean;
canvasIsCompositing: boolean;
canvasIsSelectingObject: boolean;
}) => {
const {
isConnected,
canvasIsFiltering,
canvasIsTransforming,
canvasIsRasterizing,
canvasIsCompositing,
canvasIsSelectingObject,
} = arg;
return createSelector(
selectCanvasSlice,
selectParamsSlice,
selectDynamicPromptsSlice,
(canvas, params, dynamicPrompts) =>
getReasonsWhyCannotEnqueueCanvasTab({
isConnected,
canvas,
params,
dynamicPrompts,
canvasIsFiltering,
canvasIsTransforming,
canvasIsRasterizing,
canvasIsCompositing,
canvasIsSelectingObject,
}).length === 0
);
};
export const buildSelectReasonsWhyCannotEnqueueUpscaleTab = (arg: { isConnected: boolean }) => {
const { isConnected } = arg;
return createSelector(selectUpscaleSlice, selectConfigSlice, selectParamsSlice, (upscale, config, params) =>
getReasonsWhyCannotEnqueueUpscaleTab({ isConnected, upscale, config, params })
);
};
export const buildSelectIsReadyToEnqueueUpscaleTab = (arg: { isConnected: boolean }) => {
const { isConnected } = arg;
return createSelector(
selectUpscaleSlice,
selectConfigSlice,
selectParamsSlice,
(upscale, config, params) =>
getReasonsWhyCannotEnqueueUpscaleTab({ isConnected, upscale, config, params }).length === 0
);
};
export const buildSelectReasonsWhyCannotEnqueueWorkflowsTab = (arg: { isConnected: boolean; templates: Templates }) => {
const { isConnected, templates } = arg;
return createSelector(selectNodesSlice, selectWorkflowSettingsSlice, (nodes, workflowSettings) =>
getReasonsWhyCannotEnqueueWorkflowsTab({
isConnected,
nodes,
workflowSettings,
templates,
})
);
};
export const buildSelectIsReadyToEnqueueWorkflowsTab = (arg: { isConnected: boolean; templates: Templates }) => {
const { isConnected, templates } = arg;
return createSelector(
selectNodesSlice,
selectWorkflowSettingsSlice,
(nodes, workflowSettings) =>
getReasonsWhyCannotEnqueueWorkflowsTab({
isConnected,
nodes,
workflowSettings,
templates,
}).length === 0
);
};
export const selectPromptsCount = createSelector(
selectParamsSlice,
selectDynamicPromptsSlice,