Skip to content

Commit ad7dbff

Browse files
committed
introduce OrderedSet
to optimize comparisons and insertion into the set-indexed map
1 parent 4ff4d05 commit ad7dbff

File tree

5 files changed

+137
-35
lines changed

5 files changed

+137
-35
lines changed

src/execution/collectFields.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { AccumulatorMap } from '../jsutils/AccumulatorMap.js';
2-
import { getBySet } from '../jsutils/getBySet.js';
32
import { invariant } from '../jsutils/invariant.js';
4-
import { isSameSet } from '../jsutils/isSameSet.js';
53
import type { ObjMap } from '../jsutils/ObjMap.js';
4+
import type { ReadonlyOrderedSet } from '../jsutils/OrderedSet.js';
5+
import { OrderedSet } from '../jsutils/OrderedSet.js';
66

77
import type {
88
FieldNode,
@@ -33,11 +33,13 @@ export interface DeferUsage {
3333
ancestors: ReadonlyArray<Target>;
3434
}
3535

36-
export const NON_DEFERRED_TARGET_SET: TargetSet = new Set<Target>([undefined]);
36+
export const NON_DEFERRED_TARGET_SET = new OrderedSet<Target>([
37+
undefined,
38+
]).freeze();
3739

3840
export type Target = DeferUsage | undefined;
39-
export type TargetSet = ReadonlySet<Target>;
40-
export type DeferUsageSet = ReadonlySet<DeferUsage>;
41+
export type TargetSet = ReadonlyOrderedSet<Target>;
42+
export type DeferUsageSet = ReadonlyOrderedSet<DeferUsage>;
4143

4244
export interface FieldDetails {
4345
node: FieldNode;
@@ -430,13 +432,13 @@ function getTargetSetDetails(
430432
}
431433
}
432434

433-
const maskingTargets: TargetSet = new Set<Target>(maskingTargetList);
434-
if (isSameSet(maskingTargets, parentTargets)) {
435+
const maskingTargets = new OrderedSet(maskingTargetList).freeze();
436+
if (maskingTargets === parentTargets) {
435437
parentTargetKeys.add(responseKey);
436438
continue;
437439
}
438440

439-
let targetSetDetails = getBySet(targetSetDetailsMap, maskingTargets);
441+
let targetSetDetails = targetSetDetailsMap.get(maskingTargets);
440442
if (targetSetDetails === undefined) {
441443
targetSetDetails = {
442444
keys: new Set(),

src/jsutils/OrderedSet.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
const setContainingUndefined = new Set([undefined]);
2+
const setsContainingOneItem = new WeakMap<object, Set<object | undefined>>();
3+
const setsAppendedByUndefined = new WeakMap<
4+
ReadonlySet<object | undefined>,
5+
Set<object | undefined>
6+
>();
7+
const setsAppendedByDefined = new WeakMap<
8+
ReadonlySet<object | undefined>,
9+
WeakMap<object, Set<object | undefined>>
10+
>();
11+
12+
function createOrderedSet<T extends object | undefined>(
13+
item: T,
14+
): ReadonlySet<T | undefined> {
15+
if (item === undefined) {
16+
return setContainingUndefined;
17+
}
18+
19+
let set = setsContainingOneItem.get(item);
20+
if (set === undefined) {
21+
set = new Set([item]);
22+
set.add(item);
23+
setsContainingOneItem.set(item, set);
24+
}
25+
return set as ReadonlyOrderedSet<T | undefined>;
26+
}
27+
28+
function appendToOrderedSet<T extends object | undefined>(
29+
set: ReadonlySet<T | undefined>,
30+
item: T | undefined,
31+
): ReadonlySet<T | undefined> {
32+
if (set.has(item)) {
33+
return set;
34+
}
35+
36+
if (item === undefined) {
37+
let appendedSet = setsAppendedByUndefined.get(set);
38+
if (appendedSet === undefined) {
39+
appendedSet = new Set(set);
40+
appendedSet.add(undefined);
41+
setsAppendedByUndefined.set(set, appendedSet);
42+
}
43+
return appendedSet as ReadonlySet<T | undefined>;
44+
}
45+
46+
let appendedSets = setsAppendedByDefined.get(set);
47+
if (appendedSets === undefined) {
48+
appendedSets = new WeakMap();
49+
setsAppendedByDefined.set(set, appendedSets);
50+
const appendedSet = new Set(set);
51+
appendedSet.add(item);
52+
appendedSets.set(item, appendedSet);
53+
return appendedSet as ReadonlySet<T | undefined>;
54+
}
55+
56+
let appendedSet: Set<object | undefined> | undefined = appendedSets.get(item);
57+
if (appendedSet === undefined) {
58+
appendedSet = new Set<object | undefined>(set);
59+
appendedSet.add(item);
60+
appendedSets.set(item, appendedSet);
61+
}
62+
63+
return appendedSet as ReadonlySet<T | undefined>;
64+
}
65+
66+
export type ReadonlyOrderedSet<T> = ReadonlySet<T>;
67+
68+
const emptySet = new Set();
69+
70+
/**
71+
* A set that when frozen can be directly compared for equality.
72+
*
73+
* Sets are limited to JSON serializable values.
74+
*
75+
* @internal
76+
*/
77+
export class OrderedSet<T extends object | undefined> {
78+
_set: ReadonlySet<T | undefined> = emptySet as ReadonlySet<T>;
79+
constructor(items: Iterable<T>) {
80+
for (const item of items) {
81+
if (this._set === emptySet) {
82+
this._set = createOrderedSet(item);
83+
continue;
84+
}
85+
86+
this._set = appendToOrderedSet(this._set, item);
87+
}
88+
}
89+
90+
freeze(): ReadonlyOrderedSet<T> {
91+
return this._set as ReadonlyOrderedSet<T>;
92+
}
93+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import { OrderedSet } from '../OrderedSet.js';
5+
6+
describe('OrderedSet', () => {
7+
it('empty sets are equal', () => {
8+
const orderedSetA = new OrderedSet([]).freeze();
9+
const orderedSetB = new OrderedSet([]).freeze();
10+
11+
expect(orderedSetA).to.equal(orderedSetB);
12+
});
13+
14+
it('sets with members in different orders or numbers are equal', () => {
15+
const a = { a: 'a' };
16+
const b = { b: 'b' };
17+
const c = { c: 'c' };
18+
const orderedSetA = new OrderedSet([a, b, c, a, undefined]).freeze();
19+
const orderedSetB = new OrderedSet([undefined, b, a, b, c]).freeze();
20+
21+
expect(orderedSetA).to.not.equal(orderedSetB);
22+
});
23+
24+
it('sets with members in different orders or numbers are equal', () => {
25+
const a = { a: 'a' };
26+
const b = { b: 'b' };
27+
const c = { c: 'c' };
28+
const d = { c: 'd' };
29+
const orderedSetA = new OrderedSet([a, b, c, a, undefined]).freeze();
30+
const orderedSetB = new OrderedSet([undefined, b, a, b, d]).freeze();
31+
32+
expect(orderedSetA).to.not.equal(orderedSetB);
33+
});
34+
});

src/jsutils/getBySet.ts

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

src/jsutils/isSameSet.ts

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

0 commit comments

Comments
 (0)