Skip to content

Commit 3db69af

Browse files
refactor(ui): generalize stage event handlers
Create intermediary nanostores for values required by the event handlers. This allows the event handlers to be purely imperative, with no reactivity: instead of recreating/setting the handlers when a dependent piece of state changes, we use nanostores' imperative API to access dependent state. For example, some handlers depend on brush size. If we used the standard declarative `useSelector` API, we'd need to recreate the event handler callback each time the brush size changed. This can be costly. An intermediate `$brushSize` nanostore is set in a `useLayoutEffect()`, which responds to changes to the redux store. Then, in the event handler, we use the imperative API to access the brush size: `$brushSize.get()`. This change allows the event handler logic to be shared with the pending canvas v2, and also more easily tested. It's a noticeable perf improvement, too, especially when changing brush size.
1 parent 1823e44 commit 3db69af

File tree

5 files changed

+342
-273
lines changed

5 files changed

+342
-273
lines changed

invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,38 @@ import { createSelector } from '@reduxjs/toolkit';
44
import { logger } from 'app/logging/logger';
55
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
66
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
7-
import { useMouseEvents } from 'features/controlLayers/hooks/mouseEventHooks';
87
import {
8+
$brushSize,
9+
$brushSpacingPx,
10+
$isDrawing,
11+
$lastAddedPoint,
912
$lastCursorPos,
1013
$lastMouseDownPos,
14+
$selectedLayerId,
15+
$selectedLayerType,
16+
$shouldInvertBrushSizeScrollDirection,
1117
$tool,
18+
BRUSH_SPACING_PCT,
19+
brushSizeChanged,
1220
isRegionalGuidanceLayer,
1321
layerBboxChanged,
1422
layerTranslated,
23+
MAX_BRUSH_SPACING_PX,
24+
MIN_BRUSH_SPACING_PX,
25+
rgLayerLineAdded,
26+
rgLayerPointsAdded,
27+
rgLayerRectAdded,
1528
selectControlLayersSlice,
1629
} from 'features/controlLayers/store/controlLayersSlice';
17-
import { debouncedRenderers, renderers as normalRenderers } from 'features/controlLayers/util/renderers';
30+
import type { AddLineArg, AddPointToLineArg, AddRectArg } from 'features/controlLayers/store/types';
31+
import {
32+
debouncedRenderers,
33+
renderers as normalRenderers,
34+
setStageEventHandlers,
35+
} from 'features/controlLayers/util/renderers';
1836
import Konva from 'konva';
1937
import type { IRect } from 'konva/lib/types';
38+
import { clamp } from 'lodash-es';
2039
import { memo, useCallback, useLayoutEffect, useMemo, useState } from 'react';
2140
import { getImageDTO } from 'services/api/endpoints/images';
2241
import { useDevicePixelRatio } from 'use-device-pixel-ratio';
@@ -48,7 +67,6 @@ const useStageRenderer = (
4867
const dispatch = useAppDispatch();
4968
const state = useAppSelector((s) => s.controlLayers.present);
5069
const tool = useStore($tool);
51-
const mouseEventHandlers = useMouseEvents();
5270
const lastCursorPos = useStore($lastCursorPos);
5371
const lastMouseDownPos = useStore($lastMouseDownPos);
5472
const selectedLayerIdColor = useAppSelector(selectSelectedLayerColor);
@@ -57,6 +75,26 @@ const useStageRenderer = (
5775
const layerCount = useMemo(() => state.layers.length, [state.layers]);
5876
const renderers = useMemo(() => (asPreview ? debouncedRenderers : normalRenderers), [asPreview]);
5977
const dpr = useDevicePixelRatio({ round: false });
78+
const shouldInvertBrushSizeScrollDirection = useAppSelector((s) => s.canvas.shouldInvertBrushSizeScrollDirection);
79+
const brushSpacingPx = useMemo(
80+
() => clamp(state.brushSize / BRUSH_SPACING_PCT, MIN_BRUSH_SPACING_PX, MAX_BRUSH_SPACING_PX),
81+
[state.brushSize]
82+
);
83+
84+
useLayoutEffect(() => {
85+
$brushSize.set(state.brushSize);
86+
$brushSpacingPx.set(brushSpacingPx);
87+
$selectedLayerId.set(state.selectedLayerId);
88+
$selectedLayerType.set(selectedLayerType);
89+
$shouldInvertBrushSizeScrollDirection.set(shouldInvertBrushSizeScrollDirection);
90+
}, [
91+
brushSpacingPx,
92+
selectedLayerIdColor,
93+
selectedLayerType,
94+
shouldInvertBrushSizeScrollDirection,
95+
state.brushSize,
96+
state.selectedLayerId,
97+
]);
6098

6199
const onLayerPosChanged = useCallback(
62100
(layerId: string, x: number, y: number) => {
@@ -72,6 +110,31 @@ const useStageRenderer = (
72110
[dispatch]
73111
);
74112

113+
const onRGLayerLineAdded = useCallback(
114+
(arg: AddLineArg) => {
115+
dispatch(rgLayerLineAdded(arg));
116+
},
117+
[dispatch]
118+
);
119+
const onRGLayerPointAddedToLine = useCallback(
120+
(arg: AddPointToLineArg) => {
121+
dispatch(rgLayerPointsAdded(arg));
122+
},
123+
[dispatch]
124+
);
125+
const onRGLayerRectAdded = useCallback(
126+
(arg: AddRectArg) => {
127+
dispatch(rgLayerRectAdded(arg));
128+
},
129+
[dispatch]
130+
);
131+
const onBrushSizeChanged = useCallback(
132+
(size: number) => {
133+
dispatch(brushSizeChanged(size));
134+
},
135+
[dispatch]
136+
);
137+
75138
useLayoutEffect(() => {
76139
log.trace('Initializing stage');
77140
if (!container) {
@@ -89,21 +152,29 @@ const useStageRenderer = (
89152
if (asPreview) {
90153
return;
91154
}
92-
stage.on('mousedown', mouseEventHandlers.onMouseDown);
93-
stage.on('mouseup', mouseEventHandlers.onMouseUp);
94-
stage.on('mousemove', mouseEventHandlers.onMouseMove);
95-
stage.on('mouseleave', mouseEventHandlers.onMouseLeave);
96-
stage.on('wheel', mouseEventHandlers.onMouseWheel);
155+
const cleanup = setStageEventHandlers({
156+
stage,
157+
$tool,
158+
$isDrawing,
159+
$lastMouseDownPos,
160+
$lastCursorPos,
161+
$lastAddedPoint,
162+
$brushSize,
163+
$brushSpacingPx,
164+
$selectedLayerId,
165+
$selectedLayerType,
166+
$shouldInvertBrushSizeScrollDirection,
167+
onRGLayerLineAdded,
168+
onRGLayerPointAddedToLine,
169+
onRGLayerRectAdded,
170+
onBrushSizeChanged,
171+
});
97172

98173
return () => {
99-
log.trace('Cleaning up stage listeners');
100-
stage.off('mousedown', mouseEventHandlers.onMouseDown);
101-
stage.off('mouseup', mouseEventHandlers.onMouseUp);
102-
stage.off('mousemove', mouseEventHandlers.onMouseMove);
103-
stage.off('mouseleave', mouseEventHandlers.onMouseLeave);
104-
stage.off('wheel', mouseEventHandlers.onMouseWheel);
174+
log.trace('Removing stage listeners');
175+
cleanup();
105176
};
106-
}, [stage, asPreview, mouseEventHandlers]);
177+
}, [asPreview, onBrushSizeChanged, onRGLayerLineAdded, onRGLayerPointAddedToLine, onRGLayerRectAdded, stage]);
107178

108179
useLayoutEffect(() => {
109180
log.trace('Updating stage dimensions');

invokeai/frontend/web/src/features/controlLayers/hooks/mouseEventHooks.ts

Lines changed: 0 additions & 233 deletions
This file was deleted.

0 commit comments

Comments
 (0)