@wierdbytes/pi-facelift 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +2 -1
- package/index.ts +28 -4
- package/package.json +1 -1
- package/working-time.test.ts +131 -1
- package/working-time.ts +31 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.6
|
|
4
|
+
|
|
5
|
+
- **Fix:** prompt-cache warm-ups no longer inflate `worked` time / deflate `tps` or start a phantom working timer while idle.
|
|
6
|
+
|
|
7
|
+
## 0.6.5
|
|
8
|
+
|
|
9
|
+
- **Fix:** pi crashing with "extension ctx is stale" when the session was replaced or reloaded while the working timer was ticking.
|
|
10
|
+
|
|
3
11
|
## 0.6.4
|
|
4
12
|
|
|
5
13
|
- **Fix:** declare `shiki` as a runtime dependency instead of a dev-only one.
|
package/README.md
CHANGED
|
@@ -49,7 +49,8 @@ tool output.
|
|
|
49
49
|
`⏱ worked 45.2s (total: 1m15s) · tps: 756`
|
|
50
50
|
|
|
51
51
|
- `worked` — model-only time (request dispatch → response end, so
|
|
52
|
-
time-to-first-token counts; tool execution
|
|
52
|
+
time-to-first-token counts; tool execution and pi's prompt-cache
|
|
53
|
+
warm-up requests are excluded).
|
|
53
54
|
- `total` — wall-clock time from your prompt to the final response,
|
|
54
55
|
including retries, tool calls, and all other overhead.
|
|
55
56
|
- `tps` — output tokens per second over the `worked` time. The live
|
package/index.ts
CHANGED
|
@@ -1180,11 +1180,24 @@ export default function piFaceliftExtension(pi: PiFaceliftApi, deps?: PiFacelift
|
|
|
1180
1180
|
};
|
|
1181
1181
|
|
|
1182
1182
|
const renderWorkingMessage = (ctx: SessionContextLike): void => {
|
|
1183
|
-
|
|
1184
|
-
|
|
1183
|
+
// The ticker captures `ctx`; after session replacement / reload any
|
|
1184
|
+
// access to it throws (stale ctx). Inside a timer that would be an
|
|
1185
|
+
// uncaughtException and kill pi, so swallow and stop ticking.
|
|
1186
|
+
try {
|
|
1187
|
+
if (ctx.hasUI) {
|
|
1188
|
+
ctx.ui.setWorkingMessage(workingMessageText(workingTracker.elapsedMs(), workingTracker.liveTps()));
|
|
1189
|
+
}
|
|
1190
|
+
} catch {
|
|
1191
|
+
stopWorkingTick();
|
|
1185
1192
|
}
|
|
1186
1193
|
};
|
|
1187
1194
|
|
|
1195
|
+
// Session replaced / reloaded / exiting mid-stream: message_end and
|
|
1196
|
+
// agent_settled may never arrive, so kill the ticker here.
|
|
1197
|
+
pi.on("session_shutdown", () => {
|
|
1198
|
+
stopWorkingTick();
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1188
1201
|
// `before_agent_start` fires right after the user submits the prompt,
|
|
1189
1202
|
// so it anchors the wall-clock `total` (retries + tools + overhead).
|
|
1190
1203
|
pi.on("before_agent_start", () => {
|
|
@@ -1198,11 +1211,22 @@ export default function piFaceliftExtension(pi: PiFaceliftApi, deps?: PiFacelift
|
|
|
1198
1211
|
workingTracker.ensureRun();
|
|
1199
1212
|
});
|
|
1200
1213
|
|
|
1214
|
+
// pi emits `turn_start` (awaited) right before every agent request, so it
|
|
1215
|
+
// arms the tracker. Cache-warmer replays also fire `before_provider_request`
|
|
1216
|
+
// but arrive outside this window (during tools or after settle) and never
|
|
1217
|
+
// get a `message_end` — unguarded they would hold a segment open forever.
|
|
1218
|
+
pi.on("turn_start", () => {
|
|
1219
|
+
if (!faceliftConfig.showWorkingTime) return;
|
|
1220
|
+
workingTracker.armRequest();
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1201
1223
|
pi.on("before_provider_request", (_event, ctx) => {
|
|
1202
1224
|
if (!faceliftConfig.showWorkingTime) return;
|
|
1203
1225
|
// Start the segment when the request is dispatched (not on the first
|
|
1204
|
-
// token), so time-to-first-token counts as working time too.
|
|
1205
|
-
|
|
1226
|
+
// token), so time-to-first-token counts as working time too. A `false`
|
|
1227
|
+
// means a cache warm-up (not armed) or a repeat payload hook for an
|
|
1228
|
+
// already-open request — leave the ticker alone.
|
|
1229
|
+
if (!workingTracker.beginSegment()) return;
|
|
1206
1230
|
stopWorkingTick();
|
|
1207
1231
|
renderWorkingMessage(ctx);
|
|
1208
1232
|
workingTick = setInterval(() => renderWorkingMessage(ctx), 1000);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wierdbytes/pi-facelift",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"description": "Cosmetic facelift for built-in pi tool output — open-right rounded frames, syntax-highlighted reads, status-aware bash summaries, find/grep.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
package/working-time.test.ts
CHANGED
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
* The tracker measures two metrics: `worked` (model streaming only,
|
|
5
5
|
* tools excluded) and `totalMs` (wall-clock from user prompt to settle,
|
|
6
6
|
* retries/tools/overhead included). The wiring drives it from pi
|
|
7
|
-
* lifecycle events: before_agent_start / agent_start /
|
|
7
|
+
* lifecycle events: before_agent_start / agent_start / turn_start /
|
|
8
8
|
* before_provider_request / message_end / agent_settled. The durable
|
|
9
9
|
* history line is produced via pi.appendEntry + registerEntryRenderer.
|
|
10
|
+
*
|
|
11
|
+
* `before_provider_request` also fires for pi's prompt-cache warm-ups,
|
|
12
|
+
* which never produce a `message_end`; only requests armed by `turn_start`
|
|
13
|
+
* may open a segment.
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
16
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
@@ -27,6 +31,7 @@ describe("WorkingTimeTracker", () => {
|
|
|
27
31
|
const t = new WorkingTimeTracker(() => now);
|
|
28
32
|
t.beginRun();
|
|
29
33
|
now = 2000;
|
|
34
|
+
t.armRequest();
|
|
30
35
|
t.beginSegment();
|
|
31
36
|
now = 4500;
|
|
32
37
|
t.endSegment();
|
|
@@ -40,11 +45,13 @@ describe("WorkingTimeTracker", () => {
|
|
|
40
45
|
let now = 0;
|
|
41
46
|
const t = new WorkingTimeTracker(() => now);
|
|
42
47
|
t.beginRun();
|
|
48
|
+
t.armRequest();
|
|
43
49
|
t.beginSegment();
|
|
44
50
|
now = 1000;
|
|
45
51
|
t.endSegment();
|
|
46
52
|
// tool + overhead from 1000..9000 counts toward total, not worked
|
|
47
53
|
now = 9000;
|
|
54
|
+
t.armRequest();
|
|
48
55
|
t.beginSegment();
|
|
49
56
|
now = 10000;
|
|
50
57
|
t.endSegment();
|
|
@@ -57,6 +64,7 @@ describe("WorkingTimeTracker", () => {
|
|
|
57
64
|
let now = 0;
|
|
58
65
|
const t = new WorkingTimeTracker(() => now);
|
|
59
66
|
t.beginRun();
|
|
67
|
+
t.armRequest();
|
|
60
68
|
t.beginSegment();
|
|
61
69
|
now = 500;
|
|
62
70
|
t.endSegment();
|
|
@@ -71,6 +79,7 @@ describe("WorkingTimeTracker", () => {
|
|
|
71
79
|
let now = 0;
|
|
72
80
|
const t = new WorkingTimeTracker(() => now);
|
|
73
81
|
t.beginRun();
|
|
82
|
+
t.armRequest();
|
|
74
83
|
t.beginSegment();
|
|
75
84
|
t.addDelta(4000); // ~1000 estimated tokens at 4 chars/token
|
|
76
85
|
now = 2000; // 2s of worked time
|
|
@@ -81,6 +90,7 @@ describe("WorkingTimeTracker", () => {
|
|
|
81
90
|
it("liveTps is 0 before any time elapses", () => {
|
|
82
91
|
const t = new WorkingTimeTracker(() => 0);
|
|
83
92
|
t.beginRun();
|
|
93
|
+
t.armRequest();
|
|
84
94
|
t.beginSegment();
|
|
85
95
|
t.addDelta(400);
|
|
86
96
|
expect(t.liveTps()).toBe(0);
|
|
@@ -90,6 +100,7 @@ describe("WorkingTimeTracker", () => {
|
|
|
90
100
|
let now = 0;
|
|
91
101
|
const t = new WorkingTimeTracker(() => now);
|
|
92
102
|
t.beginRun();
|
|
103
|
+
t.armRequest();
|
|
93
104
|
t.beginSegment();
|
|
94
105
|
now = 2000;
|
|
95
106
|
t.recordExactTokens(1500);
|
|
@@ -104,12 +115,14 @@ describe("WorkingTimeTracker", () => {
|
|
|
104
115
|
const t = new WorkingTimeTracker(() => now);
|
|
105
116
|
t.beginRun();
|
|
106
117
|
// first model segment: 0..1000
|
|
118
|
+
t.armRequest();
|
|
107
119
|
t.beginSegment();
|
|
108
120
|
now = 1000;
|
|
109
121
|
t.endSegment();
|
|
110
122
|
// "tool" runs from 1000..9000 — must NOT count
|
|
111
123
|
now = 9000;
|
|
112
124
|
// second model segment: 9000..10000
|
|
125
|
+
t.armRequest();
|
|
113
126
|
t.beginSegment();
|
|
114
127
|
now = 10000;
|
|
115
128
|
t.endSegment();
|
|
@@ -120,6 +133,7 @@ describe("WorkingTimeTracker", () => {
|
|
|
120
133
|
let now = 0;
|
|
121
134
|
const t = new WorkingTimeTracker(() => now);
|
|
122
135
|
t.beginRun();
|
|
136
|
+
t.armRequest();
|
|
123
137
|
t.beginSegment();
|
|
124
138
|
now = 3000;
|
|
125
139
|
expect(t.elapsedMs()).toBe(3000);
|
|
@@ -132,10 +146,63 @@ describe("WorkingTimeTracker", () => {
|
|
|
132
146
|
expect(t.settle()).toBeUndefined();
|
|
133
147
|
});
|
|
134
148
|
|
|
149
|
+
it("ignores an unarmed request (cache warm-up while idle)", () => {
|
|
150
|
+
let now = 0;
|
|
151
|
+
const t = new WorkingTimeTracker(() => now);
|
|
152
|
+
expect(t.beginSegment()).toBe(false);
|
|
153
|
+
now = 5000;
|
|
154
|
+
expect(t.isActive()).toBe(false);
|
|
155
|
+
expect(t.elapsedMs()).toBe(0);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("ignores a cache warm-up during tool execution", () => {
|
|
159
|
+
let now = 0;
|
|
160
|
+
const t = new WorkingTimeTracker(() => now);
|
|
161
|
+
t.beginRun();
|
|
162
|
+
t.armRequest();
|
|
163
|
+
expect(t.beginSegment()).toBe(true);
|
|
164
|
+
now = 1000;
|
|
165
|
+
t.endSegment();
|
|
166
|
+
// tool runs 1000..9000; the warmer replays the request at 4000
|
|
167
|
+
now = 4000;
|
|
168
|
+
expect(t.beginSegment()).toBe(false);
|
|
169
|
+
now = 9000;
|
|
170
|
+
t.armRequest();
|
|
171
|
+
expect(t.beginSegment()).toBe(true);
|
|
172
|
+
now = 10000;
|
|
173
|
+
t.endSegment();
|
|
174
|
+
expect(t.settle()?.ms).toBe(2000);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("keeps the first start when the request hook fires again mid-segment", () => {
|
|
178
|
+
let now = 0;
|
|
179
|
+
const t = new WorkingTimeTracker(() => now);
|
|
180
|
+
t.beginRun();
|
|
181
|
+
t.armRequest();
|
|
182
|
+
expect(t.beginSegment()).toBe(true);
|
|
183
|
+
now = 1000;
|
|
184
|
+
expect(t.beginSegment()).toBe(false); // e.g. a provider re-sending the payload
|
|
185
|
+
now = 3000;
|
|
186
|
+
t.endSegment();
|
|
187
|
+
expect(t.settle()?.ms).toBe(3000);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("disarms on settle and on beginRun", () => {
|
|
191
|
+
const t = new WorkingTimeTracker(() => 0);
|
|
192
|
+
t.beginRun();
|
|
193
|
+
t.armRequest();
|
|
194
|
+
t.settle();
|
|
195
|
+
expect(t.beginSegment()).toBe(false);
|
|
196
|
+
t.armRequest();
|
|
197
|
+
t.beginRun();
|
|
198
|
+
expect(t.beginSegment()).toBe(false);
|
|
199
|
+
});
|
|
200
|
+
|
|
135
201
|
it("resets state after settle", () => {
|
|
136
202
|
let now = 0;
|
|
137
203
|
const t = new WorkingTimeTracker(() => now);
|
|
138
204
|
t.beginRun();
|
|
205
|
+
t.armRequest();
|
|
139
206
|
t.beginSegment();
|
|
140
207
|
now = 1000;
|
|
141
208
|
t.endSegment();
|
|
@@ -264,11 +331,13 @@ describe("working-time wiring", () => {
|
|
|
264
331
|
fire("before_agent_start", {}); // anchors the total clock
|
|
265
332
|
vi.advanceTimersByTime(500); // pre-loop overhead
|
|
266
333
|
fire("agent_start", {});
|
|
334
|
+
fire("turn_start", {});
|
|
267
335
|
fire("before_provider_request", {});
|
|
268
336
|
vi.advanceTimersByTime(5000);
|
|
269
337
|
fire("message_end", asst);
|
|
270
338
|
// tool gap — counts toward total, NOT worked
|
|
271
339
|
vi.advanceTimersByTime(3000);
|
|
340
|
+
fire("turn_start", {});
|
|
272
341
|
fire("before_provider_request", {});
|
|
273
342
|
vi.advanceTimersByTime(2000);
|
|
274
343
|
fire("message_end", asst);
|
|
@@ -287,6 +356,7 @@ describe("working-time wiring", () => {
|
|
|
287
356
|
const { fire, entries, ctx } = loadExtension();
|
|
288
357
|
fire("before_agent_start", {});
|
|
289
358
|
fire("agent_start", {});
|
|
359
|
+
fire("turn_start", {});
|
|
290
360
|
fire("before_provider_request", {});
|
|
291
361
|
// Stream ~4000 chars => ~1000 estimated tokens over 2s of worked time.
|
|
292
362
|
fire("message_update", {
|
|
@@ -311,6 +381,7 @@ describe("working-time wiring", () => {
|
|
|
311
381
|
const { fire, entries } = loadExtension();
|
|
312
382
|
fire("before_agent_start", {});
|
|
313
383
|
fire("agent_start", {});
|
|
384
|
+
fire("turn_start", {});
|
|
314
385
|
fire("before_provider_request", {});
|
|
315
386
|
vi.advanceTimersByTime(1000);
|
|
316
387
|
fire("message_end", user); // stray non-assistant end — ignored
|
|
@@ -327,6 +398,7 @@ describe("working-time wiring", () => {
|
|
|
327
398
|
const { fire, ctx } = loadExtension();
|
|
328
399
|
fire("before_agent_start", {});
|
|
329
400
|
fire("agent_start", {});
|
|
401
|
+
fire("turn_start", {});
|
|
330
402
|
fire("before_provider_request", {});
|
|
331
403
|
vi.advanceTimersByTime(2000);
|
|
332
404
|
const calls = ctx.ui.setWorkingMessage.mock.calls.map((c) => c[0]);
|
|
@@ -340,6 +412,64 @@ describe("working-time wiring", () => {
|
|
|
340
412
|
expect(last?.[0]).toBeUndefined();
|
|
341
413
|
});
|
|
342
414
|
|
|
415
|
+
it("ignores a cache warm-up during tool execution (no segment, no ticker)", () => {
|
|
416
|
+
vi.useFakeTimers();
|
|
417
|
+
const { fire, entries, ctx } = loadExtension();
|
|
418
|
+
fire("before_agent_start", {});
|
|
419
|
+
fire("agent_start", {});
|
|
420
|
+
fire("turn_start", {});
|
|
421
|
+
fire("before_provider_request", {});
|
|
422
|
+
vi.advanceTimersByTime(1000);
|
|
423
|
+
fire("message_end", asst);
|
|
424
|
+
// tool executes; the cache warmer replays the request mid-tool
|
|
425
|
+
vi.advanceTimersByTime(2000);
|
|
426
|
+
const callsBefore = ctx.ui.setWorkingMessage.mock.calls.length;
|
|
427
|
+
fire("before_provider_request", {});
|
|
428
|
+
vi.advanceTimersByTime(5000);
|
|
429
|
+
expect(ctx.ui.setWorkingMessage.mock.calls.length).toBe(callsBefore);
|
|
430
|
+
fire("turn_start", {});
|
|
431
|
+
fire("before_provider_request", {});
|
|
432
|
+
vi.advanceTimersByTime(1000);
|
|
433
|
+
fire("message_end", asst);
|
|
434
|
+
fire("agent_settled", {});
|
|
435
|
+
vi.useRealTimers();
|
|
436
|
+
expect(entries).toHaveLength(1);
|
|
437
|
+
const data = entries[0].data as WorkingTimeEntry;
|
|
438
|
+
expect(data.ms).toBe(2000); // the 7s tool gap stays excluded
|
|
439
|
+
expect(data.totalMs).toBe(9000);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("ignores a cache warm-up after settle (idle warming): no phantom run or ticker", () => {
|
|
443
|
+
vi.useFakeTimers();
|
|
444
|
+
const { fire, entries, ctx } = loadExtension();
|
|
445
|
+
fire("before_agent_start", {});
|
|
446
|
+
fire("agent_start", {});
|
|
447
|
+
fire("turn_start", {});
|
|
448
|
+
fire("before_provider_request", {});
|
|
449
|
+
vi.advanceTimersByTime(1000);
|
|
450
|
+
fire("message_end", asst);
|
|
451
|
+
fire("agent_settled", {});
|
|
452
|
+
ctx.ui.setWorkingMessage.mockClear();
|
|
453
|
+
// idle warm-up minutes later
|
|
454
|
+
vi.advanceTimersByTime(270_000);
|
|
455
|
+
fire("before_provider_request", {});
|
|
456
|
+
vi.advanceTimersByTime(5000);
|
|
457
|
+
expect(ctx.ui.setWorkingMessage).not.toHaveBeenCalled();
|
|
458
|
+
// the next real run is measured from its own prompt
|
|
459
|
+
fire("before_agent_start", {});
|
|
460
|
+
fire("agent_start", {});
|
|
461
|
+
fire("turn_start", {});
|
|
462
|
+
fire("before_provider_request", {});
|
|
463
|
+
vi.advanceTimersByTime(1500);
|
|
464
|
+
fire("message_end", asst);
|
|
465
|
+
fire("agent_settled", {});
|
|
466
|
+
vi.useRealTimers();
|
|
467
|
+
expect(entries).toHaveLength(2);
|
|
468
|
+
const data = entries[1].data as WorkingTimeEntry;
|
|
469
|
+
expect(data.ms).toBe(1500);
|
|
470
|
+
expect(data.totalMs).toBe(1500);
|
|
471
|
+
});
|
|
472
|
+
|
|
343
473
|
it("registers an entry renderer that produces a dim worked+total line", () => {
|
|
344
474
|
const { entryRenderers } = loadExtension();
|
|
345
475
|
const renderer = entryRenderers.get(WORKING_TIME_ENTRY);
|
package/working-time.ts
CHANGED
|
@@ -61,6 +61,7 @@ export interface WorkingTimeEntry {
|
|
|
61
61
|
* Lifecycle (driven by pi events):
|
|
62
62
|
* before_agent_start → beginRun() (total clock starts here)
|
|
63
63
|
* agent_start → ensureRun() (fallback if before_agent_start skipped)
|
|
64
|
+
* turn_start → armRequest() (an agent request is coming)
|
|
64
65
|
* before_provider_request → beginSegment() + start the live ticker
|
|
65
66
|
* message_update (asst) → addDelta() (feed the live tps estimate)
|
|
66
67
|
* message_end (asst) → recordExactTokens() + endSegment() + stop ticker
|
|
@@ -68,6 +69,15 @@ export interface WorkingTimeEntry {
|
|
|
68
69
|
*
|
|
69
70
|
* Retries / auto-compaction fire their own agent_start but pi only emits
|
|
70
71
|
* `agent_settled` when it truly stops, so a run is measured end-to-end.
|
|
72
|
+
*
|
|
73
|
+
* `before_provider_request` is not exclusive to agent turns: pi's prompt
|
|
74
|
+
* cache warmer replays the last request (`maxTokens: 1`) with the same
|
|
75
|
+
* options, so the hook fires for warm-ups too — mid-run while tools execute,
|
|
76
|
+
* and after the run settles in `idle` warming mode. Those requests never
|
|
77
|
+
* produce a `message_end`, so an unguarded segment would stay open and
|
|
78
|
+
* swallow tool time (or start a phantom run while idle). Segments therefore
|
|
79
|
+
* open only while armed: from `turn_start` (pi emits one before every agent
|
|
80
|
+
* request) until the assistant `message_end`.
|
|
71
81
|
*/
|
|
72
82
|
export class WorkingTimeTracker {
|
|
73
83
|
private readonly now: () => number;
|
|
@@ -76,6 +86,7 @@ export class WorkingTimeTracker {
|
|
|
76
86
|
private runStart: number | undefined;
|
|
77
87
|
private runEstChars = 0;
|
|
78
88
|
private runExactTokens = 0;
|
|
89
|
+
private requestArmed = false;
|
|
79
90
|
|
|
80
91
|
constructor(now: () => number = () => Date.now()) {
|
|
81
92
|
this.now = now;
|
|
@@ -88,6 +99,7 @@ export class WorkingTimeTracker {
|
|
|
88
99
|
this.runStart = this.now();
|
|
89
100
|
this.runEstChars = 0;
|
|
90
101
|
this.runExactTokens = 0;
|
|
102
|
+
this.requestArmed = false;
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
/** Start a run only if one isn't already in progress (idempotent). */
|
|
@@ -95,14 +107,29 @@ export class WorkingTimeTracker {
|
|
|
95
107
|
if (this.runStart === undefined) this.beginRun();
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
/**
|
|
99
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Mark that the agent is about to issue a real request (pi `turn_start`).
|
|
112
|
+
* Only an armed tracker lets `beginSegment` open a segment.
|
|
113
|
+
*/
|
|
114
|
+
armRequest(): void {
|
|
100
115
|
this.ensureRun();
|
|
101
|
-
|
|
116
|
+
this.requestArmed = true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Begin timing an assistant streaming segment. Ignored unless armed (see
|
|
121
|
+
* `armRequest`) or when a segment is already open. Returns `true` only
|
|
122
|
+
* when a new segment was opened.
|
|
123
|
+
*/
|
|
124
|
+
beginSegment(): boolean {
|
|
125
|
+
if (!this.requestArmed || this.segmentStart !== undefined) return false;
|
|
126
|
+
this.segmentStart = this.now();
|
|
127
|
+
return true;
|
|
102
128
|
}
|
|
103
129
|
|
|
104
|
-
/** Close the current segment, folding its duration into the total. */
|
|
130
|
+
/** Close the current segment, folding its duration into the total, and disarm. */
|
|
105
131
|
endSegment(): void {
|
|
132
|
+
this.requestArmed = false;
|
|
106
133
|
if (this.segmentStart !== undefined) {
|
|
107
134
|
this.accumulatorMs += Math.max(0, this.now() - this.segmentStart);
|
|
108
135
|
this.segmentStart = undefined;
|