Skip to content

Commit 62e5b9d

Browse files
docs(ui): add comments for recent perf optimizations
1 parent 65eabde commit 62e5b9d

File tree

10 files changed

+27
-0
lines changed

10 files changed

+27
-0
lines changed

invokeai/frontend/web/src/app/components/App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const App = ({ config = DEFAULT_CONFIG, studioInitAction }: Props) => {
9595

9696
export default memo(App);
9797

98+
// Running these hooks in a separate component ensures we do not inadvertently rerender the entire app when they change.
9899
const HookIsolator = memo(
99100
({ config, studioInitAction }: { config: PartialAppConfig; studioInitAction?: StudioInitAction }) => {
100101
const language = useAppSelector(selectLanguage);

invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
178178
);
179179
}, [imageDTO, element, store, dndId]);
180180

181+
// Perf optimization:
182+
// The gallery image component can be heavy and re-render often. We want to track hovering state without causing
183+
// unnecessary re-renders. To do this, we use a local atom - which has a stable reference - in the image component -
184+
// and then pass the atom to the hover icons component, which subscribes to the atom and re-renders when the atom
185+
// changes.
181186
const $isHovered = useMemo(() => atom(false), []);
182187

183188
const onMouseOver = useCallback(() => {

invokeai/frontend/web/src/features/nodes/components/flow/AddNodeCmdk/AddNodeCmdk.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ export const AddNodeCmdk = memo(() => {
146146
const [searchTerm, setSearchTerm] = useState('');
147147
const addNode = useAddNode();
148148
const tab = useAppSelector(selectActiveTab);
149+
// Filtering the list is expensive - debounce the search term to avoid stutters
149150
const [debouncedSearchTerm] = useDebounce(searchTerm, 300);
150151
const isOpen = useStore($addNodeCmdk);
151152
const open = useCallback(() => {

invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ export const InputFieldRenderer = memo(({ nodeId, fieldName, settings }: Props)
123123
const field = useInputFieldInstance(nodeId, fieldName);
124124
const template = useInputFieldTemplate(nodeId, fieldName);
125125

126+
// When deciding which component to render, first we check the type of the template, which is more efficient than the
127+
// instance type check. The instance type check uses zod and is slower.
128+
126129
if (isStringFieldCollectionInputTemplate(template)) {
127130
if (!isStringFieldCollectionInputInstance(field)) {
128131
return null;

invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ type NodeWrapperProps = PropsWithChildren & {
1919
width?: ChakraProps['w'];
2020
};
2121

22+
// Animations are disabled as a performance optimization - they can cause massive slowdowns in large workflows - even
23+
// when the animations are GPU-accelerated CSS.
24+
2225
const containerSx: SystemStyleObject = {
2326
h: 'full',
2427
position: 'relative',

invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDataTab.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const selector = createSelector(selectNodesSlice, (nodes) => selectLastSelectedN
1212
const InspectorDataTab = () => {
1313
const { t } = useTranslation();
1414
const lastSelectedNodeData = useAppSelector(selector);
15+
// This is debounced to prevent re-rendering the whole component when the user changes the node's values quickly
1516
const [debouncedLastSelectedNodeData] = useDebounce(lastSelectedNodeData, 300);
1617

1718
if (!debouncedLastSelectedNodeData) {

invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/NodeTemplateGate.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { useNodeTemplateSafe } from 'features/nodes/hooks/useNodeTemplate';
22
import type { PropsWithChildren, ReactNode } from 'react';
33
import { memo } from 'react';
44

5+
// This component is used to gate the rendering of a component based on the existence of a template. It makes it
6+
// easier to handle cases where we are missing a node template in the inspector.
7+
58
export const TemplateGate = memo(
69
({ nodeId, fallback, children }: PropsWithChildren<{ nodeId: string; fallback: ReactNode }>) => {
710
const template = useNodeTemplateSafe(nodeId);

invokeai/frontend/web/src/features/nodes/hooks/useInputFieldIsInvalid.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ export const useInputFieldIsInvalid = (nodeId: string, fieldName: string) => {
5050

5151
// Else special handling for individual field types
5252

53+
// Check the template type first - it's the most efficient. If that passes, check the instance type, which uses
54+
// zod and therefore is slower.
55+
5356
if (isImageFieldCollectionInputTemplate(template) && isImageFieldCollectionInputInstance(field)) {
5457
if (validateImageFieldCollectionValue(field.value, template).length > 0) {
5558
return true;

invokeai/frontend/web/src/features/nodes/store/util/validateConnection.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ const getTargetEqualityPredicate =
3535
return e.target === c.target && e.targetHandle === c.targetHandle;
3636
};
3737

38+
/**
39+
* Validates a connection between two fields
40+
* @returns A translation key for an error if the connection is invalid, otherwise null
41+
*/
3842
export const validateConnection: ValidateConnectionFunc = (
3943
c,
4044
nodes,

invokeai/frontend/web/src/features/queue/store/readiness.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ import { assert } from 'tsafe';
6767
*
6868
* For example, the canvas tab needs to check the status of the canvas manager before enqueuing, while the workflows
6969
* tab needs to check the status of the nodes and their connections.
70+
*
71+
* A global store that contains the reasons why the app is not ready to enqueue generations. State changes are debounced
72+
* to reduce the number of times we run the fairly involved readiness checks.
7073
*/
7174

7275
const LAYER_TYPE_TO_TKEY = {

0 commit comments

Comments
 (0)