/
PathTracker.ts
101 lines (90 loc) · 3.26 KB
/
PathTracker.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import { getOrCreate } from '../../utils/getOrCreate';
import type { Entity } from '../Entity';
export const UnknownKey = Symbol('Unknown Key');
export const UnknownNonAccessorKey = Symbol('Unknown Non-Accessor Key');
export const UnknownInteger = Symbol('Unknown Integer');
export type ObjectPathKey =
| string
| typeof UnknownKey
| typeof UnknownNonAccessorKey
| typeof UnknownInteger;
export type ObjectPath = ObjectPathKey[];
export const EMPTY_PATH: ObjectPath = [];
export const UNKNOWN_PATH: ObjectPath = [UnknownKey];
// For deoptimizations, this means we are modifying an unknown property but did
// not lose track of the object or are creating a setter/getter;
// For assignment effects it means we do not check for setter/getter effects
// but only if something is mutated that is included, which is relevant for
// Object.defineProperty
export const UNKNOWN_NON_ACCESSOR_PATH: ObjectPath = [UnknownNonAccessorKey];
export const UNKNOWN_INTEGER_PATH: ObjectPath = [UnknownInteger];
const EntitiesKey = Symbol('Entities');
interface EntityPaths {
[pathSegment: string]: EntityPaths;
[EntitiesKey]: Set<Entity>;
[UnknownInteger]?: EntityPaths;
[UnknownKey]?: EntityPaths;
[UnknownNonAccessorKey]?: EntityPaths;
}
export class PathTracker {
private entityPaths: EntityPaths = Object.create(null, {
[EntitiesKey]: { value: new Set<Entity>() }
});
trackEntityAtPathAndGetIfTracked(path: ObjectPath, entity: Entity): boolean {
const trackedEntities = this.getEntities(path);
if (trackedEntities.has(entity)) return true;
trackedEntities.add(entity);
return false;
}
withTrackedEntityAtPath<T>(
path: ObjectPath,
entity: Entity,
onUntracked: () => T,
returnIfTracked: T
): T {
const trackedEntities = this.getEntities(path);
if (trackedEntities.has(entity)) return returnIfTracked;
trackedEntities.add(entity);
const result = onUntracked();
trackedEntities.delete(entity);
return result;
}
private getEntities(path: ObjectPath): Set<Entity> {
let currentPaths = this.entityPaths;
for (const pathSegment of path) {
currentPaths = currentPaths[pathSegment] =
currentPaths[pathSegment] ||
Object.create(null, { [EntitiesKey]: { value: new Set<Entity>() } });
}
return currentPaths[EntitiesKey];
}
}
export const SHARED_RECURSION_TRACKER = new PathTracker();
interface DiscriminatedEntityPaths {
[pathSegment: string]: DiscriminatedEntityPaths;
[EntitiesKey]: Map<unknown, Set<Entity>>;
[UnknownInteger]?: DiscriminatedEntityPaths;
[UnknownKey]?: DiscriminatedEntityPaths;
[UnknownNonAccessorKey]?: DiscriminatedEntityPaths;
}
export class DiscriminatedPathTracker {
private entityPaths: DiscriminatedEntityPaths = Object.create(null, {
[EntitiesKey]: { value: new Map<unknown, Set<Entity>>() }
});
trackEntityAtPathAndGetIfTracked(
path: ObjectPath,
discriminator: unknown,
entity: Entity
): boolean {
let currentPaths = this.entityPaths;
for (const pathSegment of path) {
currentPaths = currentPaths[pathSegment] =
currentPaths[pathSegment] ||
Object.create(null, { [EntitiesKey]: { value: new Map<unknown, Set<Entity>>() } });
}
const trackedEntities = getOrCreate(currentPaths[EntitiesKey], discriminator, () => new Set());
if (trackedEntities.has(entity)) return true;
trackedEntities.add(entity);
return false;
}
}