Skip to content

Commit 8c02307

Browse files
committed
use shallow comparison and some refactoring
1 parent c0fa4c2 commit 8c02307

File tree

6 files changed

+114
-49
lines changed

6 files changed

+114
-49
lines changed

chartlets.js/packages/lib/src/actions/handleHostStoreChange.ts

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { ContributionState } from "@/types/state/contribution";
1212
import type { HostStore } from "@/types/state/host";
1313
import { store } from "@/store";
1414
import { shallowEqualArrays } from "@/utils/compare";
15+
import memoize from "fast-memoize";
1516

1617
/**
1718
* A reference to a property of an input of a callback of a contribution.
@@ -62,42 +63,60 @@ export function getCallbackRequests(
6263
contributionsRecord: Record<string, ContributionState[]>,
6364
hostStore: HostStore,
6465
): (CallbackRequest | undefined)[] {
65-
const { configuration, lastInputValues } = store.getState();
66-
const { logging } = configuration;
67-
return propertyRefs.map((propertyRef) => {
68-
const contributions = contributionsRecord[propertyRef.contribPoint];
69-
const contribution = contributions[propertyRef.contribIndex];
70-
const callback = contribution.callbacks![propertyRef.callbackIndex];
71-
const inputValues = getInputValues(
72-
callback.inputs!,
73-
contribution,
66+
const { lastCallbackInputValues } = store.getState();
67+
return propertyRefs.map((propertyRef) =>
68+
getCallbackRequest(
69+
propertyRef,
70+
lastCallbackInputValues,
71+
contributionsRecord,
7472
hostStore,
75-
);
76-
const propRefId = propertyRef.id;
77-
if (
78-
lastInputValues?.[propRefId] &&
79-
shallowEqualArrays(lastInputValues?.[propRefId], inputValues)
80-
) {
81-
// Skip adding the inputValues if memoized values are returned.
82-
if (logging?.enabled) {
83-
console.groupCollapsed("Skipping callback request");
84-
console.log("inputValues", inputValues);
85-
console.groupEnd();
86-
}
87-
return;
88-
}
89-
if (lastInputValues) {
90-
lastInputValues[propRefId] = inputValues;
91-
store.setState({
92-
lastInputValues: { ...lastInputValues },
93-
});
94-
} else {
95-
store.setState({ lastInputValues: { [propRefId]: inputValues } });
96-
}
97-
return { ...propertyRef, inputValues };
98-
});
73+
),
74+
);
9975
}
10076

77+
const getCallbackRequest = (
78+
propertyRef: PropertyRef,
79+
lastCallbackInputValues: Record<string, unknown[]> | undefined,
80+
contributionsRecord: Record<string, ContributionState[]>,
81+
hostStore: HostStore,
82+
) => {
83+
const contributions = contributionsRecord[propertyRef.contribPoint];
84+
const contribution = contributions[propertyRef.contribIndex];
85+
const callback = contribution.callbacks![propertyRef.callbackIndex];
86+
const _inputValues = getInputValues(
87+
callback.inputs!,
88+
contribution,
89+
hostStore,
90+
);
91+
92+
// This is a dummy function created to memoize the _inputValues values
93+
const _getInputValues = (_inputValues: unknown[]): unknown[] => {
94+
return _inputValues;
95+
};
96+
const memoizedInputValues = memoize(_getInputValues);
97+
const inputValues = memoizedInputValues(_inputValues);
98+
99+
const propRefId = propertyRef.id;
100+
if (lastCallbackInputValues) {
101+
const lastInputValues = lastCallbackInputValues[propRefId];
102+
if (lastInputValues && shallowEqualArrays(lastInputValues, inputValues)) {
103+
// We no longer log, as the situation is quite common
104+
// Enable error logging for debugging only:
105+
// console.groupCollapsed("Skipping callback request");
106+
// console.debug("inputValues", inputValues);
107+
// console.groupEnd();
108+
return undefined;
109+
}
110+
lastCallbackInputValues[propRefId] = inputValues;
111+
store.setState({
112+
lastCallbackInputValues: { ...lastCallbackInputValues },
113+
});
114+
} else {
115+
store.setState({ lastCallbackInputValues: { [propRefId]: inputValues } });
116+
}
117+
return { ...propertyRef, inputValues };
118+
};
119+
101120
// TODO: use a memoized selector to get hostStorePropertyRefs
102121
// Note that this will only be effective and once we split the
103122
// static contribution infos and dynamic contribution states.

chartlets.js/packages/lib/src/actions/handleHostStoreChanges.test.tsx

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ describe("handleHostStoreChange", () => {
2121
listeners.push(_l);
2222
},
2323
};
24-
let lastInputValues: Record<string, unknown[]> = {};
24+
let lastCallbackInputValues: Record<string, unknown[]> = {};
2525

2626
beforeEach(() => {
2727
listeners = [];
2828
hostState = {};
29-
lastInputValues = {};
29+
lastCallbackInputValues = {};
3030
});
3131

3232
it("should do nothing without host store", () => {
@@ -86,13 +86,40 @@ describe("handleHostStoreChange", () => {
8686
},
8787
},
8888
},
89+
contributionsRecord: {
90+
panel: [
91+
{
92+
name: "p0",
93+
container: { title: "Panel A" },
94+
extension: "e0",
95+
componentResult: {},
96+
initialState: {},
97+
callbacks: [
98+
{
99+
function: {
100+
name: "callback",
101+
parameters: [],
102+
return: {},
103+
},
104+
inputs: [{ id: "@app", property: "variableName" }],
105+
outputs: [{ id: "select", property: "value" }],
106+
},
107+
],
108+
},
109+
],
110+
},
89111
});
90112
hostStore.set("variableName", "CHL");
91113
handleHostStoreChange();
114+
115+
// calling it second time for coverage. No state change changes the
116+
// control flow
117+
handleHostStoreChange();
118+
// TODO: Update this test to assert the generated callback request
92119
});
93120

94121
it("should memoize second call with same arguments", () => {
95-
const extensions = [{ name: "e0", version: "0", contributes: ["panels"] }];
122+
const extensions = [{ name: "ext", version: "0", contributes: ["panels"] }];
96123
store.setState({
97124
configuration: { hostStore, logging: { enabled: false } },
98125
extensions,
@@ -122,7 +149,6 @@ describe("handleHostStoreChange", () => {
122149
return: {},
123150
},
124151
inputs: [{ id: "@app", property: "variableName" }],
125-
outputs: [{ id: "select", property: "value" }],
126152
},
127153
],
128154
initialState: {},
@@ -131,9 +157,8 @@ describe("handleHostStoreChange", () => {
131157
},
132158
},
133159
},
134-
lastInputValues: lastInputValues,
160+
lastCallbackInputValues: lastCallbackInputValues,
135161
});
136-
hostStore.set("variableName", "CHL");
137162
const propertyRefs: PropertyRef[] = [
138163
{
139164
id: "panel-0-0-0",
@@ -165,7 +190,11 @@ describe("handleHostStoreChange", () => {
165190
},
166191
],
167192
};
168-
const result = getCallbackRequests(
193+
194+
hostStore.set("variableName", "CHL");
195+
196+
// first call -> should create callback request
197+
let result = getCallbackRequests(
169198
propertyRefs,
170199
contributionsRecord,
171200
hostStore,
@@ -174,5 +203,21 @@ describe("handleHostStoreChange", () => {
174203
...propertyRefs[0],
175204
inputValues: ["CHL"],
176205
});
206+
207+
// second call -> memoized -> should not create callback request
208+
result = getCallbackRequests(propertyRefs, contributionsRecord, hostStore);
209+
expect(result).toEqual([undefined]);
210+
211+
// Third call - change state -> should create callback request
212+
hostStore.set("variableName", "TMP");
213+
result = getCallbackRequests(propertyRefs, contributionsRecord, hostStore);
214+
expect(result[0]).toEqual({
215+
...propertyRefs[0],
216+
inputValues: ["TMP"],
217+
});
218+
219+
// fourth call -> memoized -> should not invoke callback
220+
result = getCallbackRequests(propertyRefs, contributionsRecord, hostStore);
221+
expect(result).toEqual([undefined]);
177222
});
178223
});

chartlets.js/packages/lib/src/actions/helpers/getInputValues.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,8 @@ import {
1313
import { formatObjPath, getValue, type ObjPathLike } from "@/utils/objPath";
1414
import { isObject } from "@/utils/isObject";
1515
import type { HostStore } from "@/types/state/host";
16-
import memoize from "fast-memoize";
1716

18-
export const getInputValues = memoize(_getInputValues);
19-
20-
export function _getInputValues(
17+
export function getInputValues(
2118
inputs: Input[],
2219
contributionState: ContributionState,
2320
hostStore?: HostStore,

chartlets.js/packages/lib/src/store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ export const store = create<StoreState>(() => ({
77
extensions: [],
88
contributionsResult: {},
99
contributionsRecord: {},
10-
lastInputValues: {},
10+
lastCallbackInputValues: {},
1111
}));

chartlets.js/packages/lib/src/types/state/store.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface StoreState {
2727
themeMode?: ThemeMode;
2828
/**
2929
* Store last input values for callback requests to avoid invoking them if
30-
* there are no changes
30+
* there are no state changes
3131
* */
32-
lastInputValues?: Record<string, unknown[]>;
32+
lastCallbackInputValues?: Record<string, unknown[]>;
3333
}

chartlets.js/packages/lib/src/utils/compare.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ export function shallowEqualArrays(
22
arr1?: unknown[],
33
arr2?: unknown[],
44
): boolean {
5-
if (!arr1 || !arr2) return false;
6-
if (arr1.length !== arr2.length) return false;
5+
if (!arr1 || !arr2) {
6+
return false;
7+
}
8+
if (arr1.length !== arr2.length) {
9+
return false;
10+
}
711
return arr1.every((val, index) => val === arr2[index]);
812
}

0 commit comments

Comments
 (0)