Skip to content

Commit 6ce5804

Browse files
committed
refactor: update Layer and LayersService for better performance and consistency
- Moved from useEffect to useLayoutEffect in useLayer for immediate updates. - Replaced window.devicePixelRatio with globalThis.devicePixelRatio for broader compatibility. - Enhanced updateSize method in Layer to ensure proper rendering. - Changed rootSize in LayersService to use signals for reactive updates. - Switched debounce to throttle in handleRootResize for improved performance.
1 parent b489a52 commit 6ce5804

File tree

8 files changed

+72
-55
lines changed

8 files changed

+72
-55
lines changed

.storybook/preview.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import React, { StrictMode } from "react";
44
import './styles/global.css';
55

66
const preview: Preview = {
7-
// decorators: [
8-
// (Story) => (
9-
// <StrictMode>
10-
// <Story />
11-
// </StrictMode>
12-
// ),
13-
// ],
7+
decorators: [
8+
(Story) => (
9+
<StrictMode>
10+
<Story />
11+
</StrictMode>
12+
),
13+
],
1414
};
1515

1616
export default preview;

src/graphConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export const initGraphConstants: TGraphConstants = {
133133
system: {
134134
GRID_SIZE: 16,
135135
/* @deprecated this config is not used anymore, Layers checks devicePixelRatio internally */
136-
PIXEL_RATIO: typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1,
136+
PIXEL_RATIO: typeof globalThis !== "undefined" ? globalThis.devicePixelRatio || 1 : 1,
137137
USABLE_RECT_GAP: 400,
138138
CAMERA_VIEWPORT_TRESHOLD: 0.5,
139139
},

src/plugins/minimap/layer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,16 @@ export class MiniMapLayer extends Layer<MiniMapLayerProps, MiniMapLayerContext>
9292
this.onCanvasEvent("mousedown", this.handleMouseDownEvent);
9393
}
9494
}
95+
this.onSignal(this.props.graph.hitTest.$usableRect, () => {
96+
this.onBlockUpdated();
97+
this.calculateViewPortCoords();
98+
this.rerenderMapContent();
99+
});
95100

96101
super.afterInit();
97102
}
98103

99-
public updateSize(): void {
104+
protected updateCanvasSize(): void {
100105
this.rerenderMapContent();
101106
}
102107

src/react-components/hooks/useLayer.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from "react";
1+
import { useDeferredValue, useLayoutEffect, useState } from "react";
22

33
import isEqual from "lodash/isEqual";
44

@@ -36,11 +36,16 @@ export function useLayer<T extends Constructor<Layer> = Constructor<Layer>>(
3636
: never
3737
) {
3838
const [layer, setLayer] = useState<InstanceType<T> | null>(null);
39+
const deferredLayer = useDeferredValue(layer);
3940

40-
useEffect(() => {
41+
useLayoutEffect(() => {
42+
// setLayer will apply the next state not immediately,
43+
// so we have to store link to layer instance in useLayoutEffect
44+
// in order to detach that layer from graph in case of fast re-run of effect
4145
const layerInstance = graph ? graph.addLayer(layerCtor, props) : null;
4246
setLayer(layerInstance);
4347
return () => {
48+
// detach layer from graph
4449
if (layerInstance) {
4550
graph?.detachLayer(layerInstance);
4651
}
@@ -49,11 +54,11 @@ export function useLayer<T extends Constructor<Layer> = Constructor<Layer>>(
4954

5055
const prevProps = usePrevious(props);
5156

52-
useEffect(() => {
53-
if (layer && (!prevProps || !isEqual(prevProps, props))) {
54-
layer.setProps(props);
57+
useLayoutEffect(() => {
58+
if (deferredLayer && (!prevProps || !isEqual(prevProps, props))) {
59+
deferredLayer.setProps(props);
5560
}
56-
}, [layer, props, prevProps]);
61+
}, [deferredLayer, props, prevProps]);
5762

5863
return layer;
5964
}

src/services/HitTest.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export interface IHitBox extends HitBoxData {
3434
export class HitTest extends Emitter {
3535
private tree = new RBush<HitBox>(9);
3636

37-
protected $usableRect = signal<TRect>({ x: 0, y: 0, width: 0, height: 0 });
37+
public readonly $usableRect = signal<TRect>({ x: 0, y: 0, width: 0, height: 0 });
3838

3939
// Single queue replaces all complex state tracking
4040
protected queue = new Map<HitBox, HitBoxData | null>();
@@ -76,7 +76,7 @@ export class HitTest extends Emitter {
7676
},
7777
{
7878
priority: ESchedulerPriority.LOWEST,
79-
frameTimeout: 250,
79+
frameTimeout: 100,
8080
}
8181
);
8282

@@ -91,8 +91,10 @@ export class HitTest extends Emitter {
9191
}
9292

9393
public clear() {
94+
this.processQueue.cancel();
9495
this.queue.clear();
9596
this.tree.clear();
97+
this.updateUsableRect();
9698
}
9799

98100
public add(item: HitBox) {
@@ -121,11 +123,15 @@ export class HitTest extends Emitter {
121123

122124
protected updateUsableRect() {
123125
const rect = this.tree.toJSON();
126+
if (rect.length === 0) {
127+
this.$usableRect.value = { x: 0, y: 0, width: 0, height: 0 };
128+
return;
129+
}
124130
this.$usableRect.value = {
125-
x: rect.minX,
126-
y: rect.minY,
127-
width: rect.maxX - rect.minX,
128-
height: rect.maxY - rect.minY,
131+
x: Number.isFinite(rect.minX) ? rect.minX : 0,
132+
y: Number.isFinite(rect.minY) ? rect.minY : 0,
133+
width: Number.isFinite(rect.maxX) ? rect.maxX - rect.minX : 0,
134+
height: Number.isFinite(rect.maxY) ? rect.maxY - rect.minY : 0,
129135
};
130136
}
131137

src/services/Layer.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,12 @@ export class Layer<
202202
this.init();
203203
}
204204

205-
public updateSize(width: number, height: number) {
206-
if (this.canvas) {
207-
const dpr = this.props.canvas.respectPixelRatio === false ? 1 : this.context.graph.layers.getDPR();
208-
this.canvas.width = width * dpr;
209-
this.canvas.height = height * dpr;
210-
}
211-
}
205+
protected sizeTouched = false;
206+
207+
public updateSize = () => {
208+
this.sizeTouched = true;
209+
this.performRender();
210+
};
212211

213212
/**
214213
* Called after initialization and when the layer is reattached.
@@ -250,6 +249,7 @@ export class Layer<
250249
this.html.style.transform = `matrix(${camera.scale}, 0, 0, ${camera.scale}, ${camera.x}, ${camera.y})`;
251250
});
252251
}
252+
this.onSignal(this.props.graph.layers.rootSize, this.updateSize);
253253
}
254254

255255
protected init() {
@@ -370,7 +370,17 @@ export class Layer<
370370
ctx.setTransform(scale * dpr, 0, 0, scale * dpr, x * dpr, y * dpr);
371371
}
372372

373+
protected updateCanvasSize() {
374+
const { width, height, dpr } = this.context.graph.layers.getRootSize();
375+
this.canvas.width = width * dpr;
376+
this.canvas.height = height * dpr;
377+
}
378+
373379
public resetTransform() {
380+
if (this.sizeTouched) {
381+
this.sizeTouched = false;
382+
this.updateCanvasSize();
383+
}
374384
const cameraState = this.props.canvas?.transformByCameraPosition ? this.context.camera.getCameraState() : null;
375385
// Reset transform and clear the canvas
376386
this.context.ctx.setTransform(1, 0, 0, 1, 0, 0);

src/services/LayersService.ts

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
import { signal } from "@preact/signals-core";
2+
13
import { ESchedulerPriority } from "../lib";
24
import { Component } from "../lib/Component";
35
import { Emitter } from "../utils/Emitter";
4-
import { debounce } from "../utils/functions";
6+
import { throttle } from "../utils/functions";
57

68
import { Layer } from "./Layer";
79

810
export class Layers extends Emitter {
911
private attached = false;
1012

11-
private rootSize: { width: number; height: number } = { width: 0, height: 0 };
13+
public readonly rootSize = signal({ width: 0, height: 0, dpr: globalThis.devicePixelRatio || 1 });
1214

1315
protected layers: Set<Layer> = new Set();
1416

@@ -17,7 +19,7 @@ export class Layers extends Emitter {
1719
}
1820

1921
public getDPR() {
20-
return devicePixelRatio;
22+
return globalThis.devicePixelRatio || 1;
2123
}
2224

2325
public createLayer<T extends Constructor<Layer> = Constructor<Layer>>(
@@ -30,8 +32,6 @@ export class Layers extends Emitter {
3032
}) as InstanceType<T>;
3133
this.layers.add(layer);
3234
if (this.attached) {
33-
const { width, height } = this.rootSize;
34-
layer.updateSize(width, height);
3535
layer.attachLayer(this.$root);
3636
}
3737
return layer;
@@ -43,7 +43,7 @@ export class Layers extends Emitter {
4343
}
4444

4545
public getRootSize() {
46-
return this.rootSize;
46+
return this.rootSize.value;
4747
}
4848

4949
public getLayers(): Layer[] {
@@ -97,13 +97,13 @@ export class Layers extends Emitter {
9797
this.handleRootResize();
9898
});
9999

100-
protected handleRootResize = debounce(
100+
protected handleRootResize = throttle(
101101
() => {
102102
this.updateSize();
103103
},
104104
{
105-
priority: ESchedulerPriority.HIGH,
106-
frameTimeout: 16,
105+
priority: ESchedulerPriority.LOWEST,
106+
frameInterval: 1,
107107
}
108108
);
109109

@@ -120,23 +120,14 @@ export class Layers extends Emitter {
120120
}
121121

122122
public updateSize = () => {
123-
if (!this.rootSize) {
124-
return;
125-
}
126-
this.updateRootSize();
127-
128-
const { width, height } = this.rootSize;
129-
this.layers.forEach((layer) => {
130-
layer.updateSize(width, height);
131-
});
132-
this.emit("update-size", this.rootSize);
133-
};
134-
135-
private updateRootSize = () => {
136123
if (!this.$root) {
137124
return;
138125
}
139-
this.rootSize.width = this.$root.clientWidth;
140-
this.rootSize.height = this.$root.clientHeight;
126+
this.rootSize.value = {
127+
width: this.$root.clientWidth,
128+
height: this.$root.clientHeight,
129+
dpr: this.getDPR(),
130+
};
131+
this.emit("update-size", this.getRootSize());
141132
};
142133
}

src/utils/functions/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,9 @@ function isTrackpadDetector() {
173173
let isTrackpadDetected = false;
174174
let cleanStateTimer = setTimeout(() => {}, 0);
175175

176-
return (e: WheelEvent) => {
177-
const normalizedDeltaY = e.deltaY * devicePixelRatio;
178-
const normalizedDeltaX = e.deltaX * devicePixelRatio;
176+
return (e: WheelEvent, dpr: number = globalThis.devicePixelRatio || 1) => {
177+
const normalizedDeltaY = e.deltaY * dpr;
178+
const normalizedDeltaX = e.deltaX * dpr;
179179
// deltaX in the trackpad scroll usually is not zero.
180180
if (normalizedDeltaX) {
181181
isTrackpadDetected = true;

0 commit comments

Comments
 (0)