Skip to content

Commit e309652

Browse files
author
oatkiller
committed
memoize many selectors
1 parent 105e3a6 commit e309652

File tree

3 files changed

+97
-76
lines changed

3 files changed

+97
-76
lines changed

x-pack/plugins/security_solution/public/resolver/store/camera/selectors.ts

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ export const scale: (state: CameraState) => (time: number) => Vector2 = createSe
164164
scalingConstants.maximum
165165
);
166166

167-
return (time) => {
167+
// memoizing this so the vector returned will be the same object reference if called with the same `time`.
168+
return defaultMemoize((time) => {
168169
/**
169170
* If the animation has completed, return the `scaleNotCountingAnimation`, as
170171
* the animation always completes with the scale set back at starting value.
@@ -247,12 +248,13 @@ export const scale: (state: CameraState) => (time: number) => Vector2 = createSe
247248
*/
248249
return [lerpedScale, lerpedScale];
249250
}
250-
};
251+
});
251252
} else {
252253
/**
253254
* The scale should be the same in both axes.
255+
* Memoizing this so the vector returned will be the same object reference every time.
254256
*/
255-
return () => [scaleNotCountingAnimation, scaleNotCountingAnimation];
257+
return defaultMemoize(() => [scaleNotCountingAnimation, scaleNotCountingAnimation]);
256258
}
257259

258260
/**
@@ -277,22 +279,26 @@ export const clippingPlanes: (
277279
) => (time: number) => ClippingPlanes = createSelector(
278280
(state) => state.rasterSize,
279281
scale,
280-
(rasterSize, scaleAtTime) => (time: number) => {
281-
const [scaleX, scaleY] = scaleAtTime(time);
282-
const renderWidth = rasterSize[0];
283-
const renderHeight = rasterSize[1];
284-
const clippingPlaneRight = renderWidth / 2 / scaleX;
285-
const clippingPlaneTop = renderHeight / 2 / scaleY;
286-
287-
return {
288-
renderWidth,
289-
renderHeight,
290-
clippingPlaneRight,
291-
clippingPlaneTop,
292-
clippingPlaneLeft: -clippingPlaneRight,
293-
clippingPlaneBottom: -clippingPlaneTop,
294-
};
295-
}
282+
(rasterSize, scaleAtTime) =>
283+
/**
284+
* memoizing this for object reference equality.
285+
*/
286+
defaultMemoize((time: number) => {
287+
const [scaleX, scaleY] = scaleAtTime(time);
288+
const renderWidth = rasterSize[0];
289+
const renderHeight = rasterSize[1];
290+
const clippingPlaneRight = renderWidth / 2 / scaleX;
291+
const clippingPlaneTop = renderHeight / 2 / scaleY;
292+
293+
return {
294+
renderWidth,
295+
renderHeight,
296+
clippingPlaneRight,
297+
clippingPlaneTop,
298+
clippingPlaneLeft: -clippingPlaneRight,
299+
clippingPlaneBottom: -clippingPlaneTop,
300+
};
301+
})
296302
);
297303

298304
/**
@@ -323,7 +329,10 @@ export const translation: (state: CameraState) => (time: number) => Vector2 = cr
323329
scale,
324330
(state) => state.animation,
325331
(panning, translationNotCountingCurrentPanning, scaleAtTime, animation) => {
326-
return (time: number) => {
332+
/**
333+
* Memoizing this for object reference equality.
334+
*/
335+
return defaultMemoize((time: number) => {
327336
const [scaleX, scaleY] = scaleAtTime(time);
328337
if (animation !== undefined && animationIsActive(animation, time)) {
329338
return vector2.lerp(
@@ -343,7 +352,7 @@ export const translation: (state: CameraState) => (time: number) => Vector2 = cr
343352
} else {
344353
return translationNotCountingCurrentPanning;
345354
}
346-
};
355+
});
347356
}
348357
);
349358

@@ -357,7 +366,10 @@ export const inverseProjectionMatrix: (
357366
clippingPlanes,
358367
translation,
359368
(clippingPlanesAtTime, translationAtTime) => {
360-
return (time: number) => {
369+
/**
370+
* Memoizing this for object reference equality (and reduced memory churn.)
371+
*/
372+
return defaultMemoize((time: number) => {
361373
const {
362374
renderWidth,
363375
renderHeight,
@@ -404,7 +416,7 @@ export const inverseProjectionMatrix: (
404416
translateForCamera,
405417
multiply(scaleToClippingPlaneDimensions, multiply(invertY, screenToNDC))
406418
);
407-
};
419+
});
408420
}
409421
);
410422

@@ -415,7 +427,8 @@ export const viewableBoundingBox: (state: CameraState) => (time: number) => AABB
415427
clippingPlanes,
416428
inverseProjectionMatrix,
417429
(clippingPlanesAtTime, matrixAtTime) => {
418-
return (time: number) => {
430+
// memoizing this so the AABB returned will be the same object reference if called with the same `time`.
431+
return defaultMemoize((time: number) => {
419432
const { renderWidth, renderHeight } = clippingPlanesAtTime(time);
420433
const matrix = matrixAtTime(time);
421434
const bottomLeftCorner: Vector2 = [0, renderHeight];
@@ -424,7 +437,7 @@ export const viewableBoundingBox: (state: CameraState) => (time: number) => AABB
424437
minimum: vector2.applyMatrix3(bottomLeftCorner, matrix),
425438
maximum: vector2.applyMatrix3(topRightCorner, matrix),
426439
};
427-
};
440+
});
428441
}
429442
);
430443

@@ -436,6 +449,8 @@ export const projectionMatrix: (state: CameraState) => (time: number) => Matrix3
436449
clippingPlanes,
437450
translation,
438451
(clippingPlanesAtTime, translationAtTime) => {
452+
// memoizing this so the matrix returned will be the same object reference if called with the same `time`.
453+
// this should also save on some memory allocation
439454
return defaultMemoize((time: number) => {
440455
const {
441456
renderWidth,

x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
*/
66

77
import rbush from 'rbush';
8-
import { createSelector, defaultMemoize } from 'reselect';
8+
import {
9+
createSelector,
10+
defaultMemoize as defaultMemoizeWithOriginalType,
11+
defaultMemoize,
12+
} from 'reselect';
913
import {
1014
DataState,
1115
Vector2,
@@ -133,7 +137,9 @@ export function relatedEventsByEntityId(data: DataState): Map<string, ResolverRe
133137
* Returns a function that returns a function (when supplied with an entity id for a node)
134138
* that returns related events for a node that match an event.category (when supplied with the category)
135139
*/
136-
export const relatedEventsByCategory = createSelector(
140+
export const relatedEventsByCategory: (
141+
state: DataState
142+
) => (entityID: string) => (ecsCategory: string) => ResolverEvent[] = createSelector(
137143
relatedEventsByEntityId,
138144
function provideGettersByCategory(
139145
/* eslint-disable no-shadow */
@@ -248,7 +254,7 @@ export const relatedEventInfoByEntityId: (
248254
});
249255
};
250256

251-
const matchingEventsForCategory = defaultMemoize(unmemoizedMatchingEventsForCategory);
257+
const matchingEventsForCategory = unmemoizedMatchingEventsForCategory;
252258

253259
/**
254260
* The number of events that occurred before the API limit was reached.
@@ -426,45 +432,44 @@ const spatiallyIndexedLayout: (state: DataState) => rbush<IndexedEntity> = creat
426432
}
427433
);
428434

435+
/**
436+
* Returns nodes and edge lines that could be visible in the `query`.
437+
*/
429438
export const nodesAndEdgelines: (
430439
state: DataState
431-
) => (query: AABB) => VisibleEntites = createSelector(spatiallyIndexedLayout, function (tree) {
432-
// memoize the results of this call to avoid unnecessarily rerunning
433-
let lastBoundingBox: AABB | null = null;
434-
let currentlyVisible: VisibleEntites = {
435-
processNodePositions: new Map<ResolverEvent, Vector2>(),
436-
connectingEdgeLineSegments: [],
437-
};
438-
return (boundingBox: AABB) => {
439-
if (lastBoundingBox !== null && isEqual(lastBoundingBox, boundingBox)) {
440-
return currentlyVisible;
441-
} else {
442-
const {
443-
minimum: [minX, minY],
444-
maximum: [maxX, maxY],
445-
} = boundingBox;
446-
const entities = tree.search({
447-
minX,
448-
minY,
449-
maxX,
450-
maxY,
451-
});
452-
const visibleProcessNodePositions = new Map<ResolverEvent, Vector2>(
453-
entities
454-
.filter((entity): entity is IndexedProcessNode => entity.type === 'processNode')
455-
.map((node) => [node.entity, node.position])
456-
);
457-
const connectingEdgeLineSegments = entities
458-
.filter((entity): entity is IndexedEdgeLineSegment => entity.type === 'edgeLine')
459-
.map((node) => node.entity);
460-
currentlyVisible = {
461-
processNodePositions: visibleProcessNodePositions,
462-
connectingEdgeLineSegments,
463-
};
464-
lastBoundingBox = boundingBox;
465-
return currentlyVisible;
466-
}
467-
};
440+
) => (
441+
/**
442+
* An axis aligned bounding box (in world corrdinates) to search in. Any entities that might collide with this box will be returned.
443+
*/
444+
query: AABB
445+
) => VisibleEntites = createSelector(spatiallyIndexedLayout, function (tree) {
446+
/**
447+
* Memoized for performance and object reference equality.
448+
*/
449+
return defaultMemoize((boundingBox: AABB) => {
450+
const {
451+
minimum: [minX, minY],
452+
maximum: [maxX, maxY],
453+
} = boundingBox;
454+
const entities = tree.search({
455+
minX,
456+
minY,
457+
maxX,
458+
maxY,
459+
});
460+
const visibleProcessNodePositions = new Map<ResolverEvent, Vector2>(
461+
entities
462+
.filter((entity): entity is IndexedProcessNode => entity.type === 'processNode')
463+
.map((node) => [node.entity, node.position])
464+
);
465+
const connectingEdgeLineSegments = entities
466+
.filter((entity): entity is IndexedEdgeLineSegment => entity.type === 'edgeLine')
467+
.map((node) => node.entity);
468+
return {
469+
processNodePositions: visibleProcessNodePositions,
470+
connectingEdgeLineSegments,
471+
};
472+
});
468473
});
469474

470475
/**

x-pack/plugins/security_solution/public/resolver/store/selectors.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -212,17 +212,6 @@ export const graphableProcesses = composeSelectors(
212212
dataSelectors.graphableProcesses
213213
);
214214

215-
/**
216-
* Calls the `secondSelector` with the result of the `selector`. Use this when re-exporting a
217-
* concern-specific selector. `selector` should return the concern-specific state.
218-
*/
219-
function composeSelectors<OuterState, InnerState, ReturnValue>(
220-
selector: (state: OuterState) => InnerState,
221-
secondSelector: (state: InnerState) => ReturnValue
222-
): (state: OuterState) => ReturnValue {
223-
return (state) => secondSelector(selector(state));
224-
}
225-
226215
const boundingBox = composeSelectors(cameraStateSelector, cameraSelectors.viewableBoundingBox);
227216

228217
const nodesAndEdgelines = composeSelectors(dataStateSelector, dataSelectors.nodesAndEdgelines);
@@ -246,6 +235,7 @@ export const visibleNodesAndEdgeLines = createSelector(nodesAndEdgelines, boundi
246235
boundingBox
247236
/* eslint-enable no-shadow */
248237
) {
238+
// `boundingBox` and `nodesAndEdgelines` are each memoized.
249239
return (time: number) => nodesAndEdgelines(boundingBox(time));
250240
});
251241

@@ -287,3 +277,14 @@ export const ariaFlowtoNodeID: (
287277
});
288278
}
289279
);
280+
281+
/**
282+
* Calls the `secondSelector` with the result of the `selector`. Use this when re-exporting a
283+
* concern-specific selector. `selector` should return the concern-specific state.
284+
*/
285+
function composeSelectors<OuterState, InnerState, ReturnValue>(
286+
selector: (state: OuterState) => InnerState,
287+
secondSelector: (state: InnerState) => ReturnValue
288+
): (state: OuterState) => ReturnValue {
289+
return (state) => secondSelector(selector(state));
290+
}

0 commit comments

Comments
 (0)