-
-
Notifications
You must be signed in to change notification settings - Fork 78
/
HatTokenMap.ts
154 lines (127 loc) · 4.54 KB
/
HatTokenMap.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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { HatStyleName } from "./constants";
import { Graph } from "../typings/Types";
import { IndividualHatMap, ReadOnlyHatMap } from "./IndividualHatMap";
import { HatAllocator } from "./HatAllocator";
import { hrtime } from "process";
import { abs } from "../util/bigint";
/**
* Maximum age for the pre-phrase snapshot before we consider it to be stale
*/
const PRE_PHRASE_SNAPSHOT_MAX_AGE_NS = BigInt(6e10); // 60 seconds
/**
* Maps from (hatStyle, character) pairs to tokens
*/
export default class HatTokenMap {
/**
* This is the active map the changes every time we reallocate hats. It is
* liable to change in the middle of a phrase.
*/
private activeMap: IndividualHatMap;
/**
* This is a snapshot of the hat map that remains stable over the course of a
* phrase. Ranges will be updated to account for changes to the document, but a
* hat with the same color and shape will refer to the same logical range.
*/
private prePhraseMapSnapshot?: IndividualHatMap;
private prePhraseMapsSnapshotTimestamp: bigint | null = null;
private lastSignalVersion: string | null = null;
private hatAllocator: HatAllocator;
constructor(private graph: Graph) {
graph.extensionContext.subscriptions.push(this);
this.activeMap = new IndividualHatMap(graph);
this.getActiveMap = this.getActiveMap.bind(this);
this.hatAllocator = new HatAllocator(graph, {
getActiveMap: this.getActiveMap,
});
}
init() {
return this.hatAllocator.addDecorations();
}
addDecorations() {
return this.hatAllocator.addDecorations();
}
static getKey(hatStyle: HatStyleName, character: string) {
return `${hatStyle}.${character}`;
}
static splitKey(key: string) {
let [hatStyle, character] = key.split(".");
if (character.length === 0) {
// If the character is `.` then it will appear as a zero length string
// due to the way the split on `.` works
character = ".";
}
return { hatStyle: hatStyle as HatStyleName, character };
}
private async getActiveMap() {
// NB: We need to take a snapshot of the hat map before we make any
// modifications if it is the beginning of the phrase
await this.maybeTakePrePhraseSnapshot();
return this.activeMap;
}
/**
* Returns a transient, read-only hat map for use during the course of a
* single command.
*
* Please do not hold onto this copy beyond the lifetime of a single command,
* because it will get stale.
* @param usePrePhraseSnapshot Whether to use pre-phrase snapshot
* @returns A readable snapshot of the map
*/
async getReadableMap(usePrePhraseSnapshot: boolean): Promise<ReadOnlyHatMap> {
// NB: Take a snapshot before we return the map if it is the beginning of
// the phrase so all commands will get the same map over the course of the
// phrase
await this.maybeTakePrePhraseSnapshot();
if (usePrePhraseSnapshot) {
if (this.lastSignalVersion == null) {
console.error(
"Pre phrase snapshot requested but no signal was present; please upgrade command client"
);
return this.activeMap;
}
if (this.prePhraseMapSnapshot == null) {
console.error(
"Navigation map pre-phrase snapshot requested, but no snapshot has been taken"
);
return this.activeMap;
}
if (
abs(hrtime.bigint() - this.prePhraseMapsSnapshotTimestamp!) >
PRE_PHRASE_SNAPSHOT_MAX_AGE_NS
) {
console.error(
"Navigation map pre-phrase snapshot requested, but snapshot is more than a minute old"
);
return this.activeMap;
}
return this.prePhraseMapSnapshot;
}
return this.activeMap;
}
public dispose() {
this.activeMap.dispose();
if (this.prePhraseMapSnapshot != null) {
this.prePhraseMapSnapshot.dispose();
}
}
private async maybeTakePrePhraseSnapshot() {
const phraseStartSignal = this.graph.commandServerApi?.signals?.prePhrase;
if (phraseStartSignal != null) {
const newSignalVersion = await phraseStartSignal.getVersion();
if (newSignalVersion !== this.lastSignalVersion) {
this.graph.debug.log("taking snapshot");
this.lastSignalVersion = newSignalVersion;
if (newSignalVersion != null) {
this.takePrePhraseSnapshot();
}
}
}
}
private takePrePhraseSnapshot() {
if (this.prePhraseMapSnapshot != null) {
this.prePhraseMapSnapshot.dispose();
}
this.prePhraseMapSnapshot = this.activeMap.clone();
this.prePhraseMapsSnapshotTimestamp = hrtime.bigint();
}
}