@3dsource/angular-unreal-module 0.0.146-dev.7 → 0.0.146-dev.9

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.
@@ -3589,8 +3589,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3589
3589
  args: [{ providedIn: 'root' }]
3590
3590
  }], ctorParameters: () => [] });
3591
3591
 
3592
- // How often to re-check `window.__stream.state` while a prefetch is connecting.
3592
+ // Legacy fallback only: poll interval for head-scripts that predate the
3593
+ // `onSettled` hook. The event-driven path (preferred) carries no such latency.
3593
3594
  const ADOPT_POLL_MS = 100;
3595
+ // Safety-net re-check for the event path — covers the (unexpected) case where
3596
+ // `onSettled` never fires. Coarse on purpose: it's a backstop, not the hot
3597
+ // path, so it adds no latency to a healthy adoption.
3598
+ const ADOPT_EVENT_SAFETY_MS = 1000;
3594
3599
  /**
3595
3600
  * Adopts a WebRTC connection prefetched by the `stream-prefetch.js` head-script
3596
3601
  * and parked on `window.__stream`, instead of running `connectToSignaling` from
@@ -3609,7 +3614,9 @@ class StreamAdoptionService {
3609
3614
  *
3610
3615
  * - nothing parked → emit `false` at once (caller connects normally).
3611
3616
  * - already settled (`ready`/`failed`/`adopted`) → adopt-if-ready at once.
3612
- * - still `connecting` → poll until the script settles it, then adopt-if-ready.
3617
+ * - still `connecting` → wait for the script's `onSettled` callback (no
3618
+ * polling latency) and adopt-if-ready the instant it fires; on older
3619
+ * scripts without the hook, fall back to a `timer` poll.
3613
3620
  *
3614
3621
  * Deliberately NO time budget: for DIRECT orchestration the prefetch holds
3615
3622
  * the socket open while the orchestrator provisions a machine, which can take
@@ -3639,12 +3646,48 @@ class StreamAdoptionService {
3639
3646
  return of(this.tryAdopt());
3640
3647
  }
3641
3648
  Logger.info('[Adoption] Prefetch in progress — waiting before connecting');
3649
+ // Preferred path: the script fires `onSettled` the moment it parks
3650
+ // `ready`/`failed`, so we adopt with zero polling lag.
3651
+ if (typeof parked.onSettled === 'function') {
3652
+ return this.awaitSettled$(parked);
3653
+ }
3654
+ // Legacy fallback: older head-script without the hook — poll its state.
3642
3655
  return timer(0, ADOPT_POLL_MS).pipe(map$1(() => window.__stream?.state ?? 'failed'),
3643
3656
  // Keep polling while still connecting; the `true` (inclusive) emits the
3644
3657
  // settling value (`ready`/`failed`) that ends the wait.
3645
3658
  takeWhile((state) => state === 'connecting', true), last(), map$1(() => this.tryAdopt()));
3646
3659
  });
3647
3660
  }
3661
+ /**
3662
+ * Event-driven wait: resolve the instant the script's `onSettled` callback
3663
+ * fires (ready/failed), then adopt-if-ready. Emits exactly once.
3664
+ *
3665
+ * A coarse {@link ADOPT_EVENT_SAFETY_MS} interval is a backstop only — if
3666
+ * `onSettled` somehow never fires it still settles, without adding latency to
3667
+ * the normal case. Teardown (route change → unsubscribe) sets `done`, so a
3668
+ * late callback after cancel can't trigger a stray adoption.
3669
+ */
3670
+ awaitSettled$(parked) {
3671
+ return new Observable((subscriber) => {
3672
+ let done = false;
3673
+ const finish = () => {
3674
+ if (done)
3675
+ return;
3676
+ done = true;
3677
+ subscriber.next(this.tryAdopt());
3678
+ subscriber.complete();
3679
+ };
3680
+ parked.onSettled?.(() => finish());
3681
+ const safety = setInterval(() => {
3682
+ if ((window.__stream?.state ?? 'failed') !== 'connecting')
3683
+ finish();
3684
+ }, ADOPT_EVENT_SAFETY_MS);
3685
+ return () => {
3686
+ done = true;
3687
+ clearInterval(safety);
3688
+ };
3689
+ });
3690
+ }
3648
3691
  /**
3649
3692
  * @returns `true` if a ready parked stream was adopted (caller must NOT
3650
3693
  * start a fresh connection), `false` otherwise.
@@ -6122,81 +6165,76 @@ class StreamStatusTelemetryService2 {
6122
6165
  /**
6123
6166
  * Emit the region / signaling / webrtc phases for an ADOPTED connection.
6124
6167
  *
6125
- * The prefetch script established the connection in parallel with page load,
6126
- * so its raw timeline (prefetch_start … ready) mostly predates this session —
6127
- * showing it verbatim would look identical to a non-prefetched connect. What
6128
- * matters is measured from the moment Angular reached the connect call
6129
- * (session start, == `lastMilestonePerf` here): the REAL wait until adoption,
6130
- * and how much the prefetch SAVED by finishing the handshake during page load.
6131
- *
6132
- * So the three phases fill exactly the `[session start → adopted]` window
6133
- * (split across signaling / webrtc by the prefetch's own sub-step ratio; the
6134
- * region was served from cache → ~0), and the savings figures ride in params:
6135
- * - `prefetchConnectMs` — full handshake the prefetch measured (the cost a
6136
- * cold, non-prefetched connect would pay here).
6137
- * - `prefetchSavedMs` — how much of it completed before Angular was ready.
6138
- * - `adoptionWaitMs` — the actual wait Angular experienced (= window).
6139
- * - `adoptionOverheadMs` — gap between prefetch-ready and adoption.
6168
+ * The prefetch script established the connection in PARALLEL with page load,
6169
+ * so the phases are positioned at their real (anchor-relative) spans — shifted
6170
+ * LEFT to overlap the HTML-loading / route-idle rows — rather than sequentially
6171
+ * after them. That's the whole point of the prefetch: the handshake already
6172
+ * happened while the page was loading. Each phase ships:
6173
+ * - `duration` — the real sub-step time the prefetch spent.
6174
+ * - `spanStartMs` — anchor-relative start, so the viewer left-shifts the bar.
6175
+ * - `adoptionAtMs` — where Angular took over (the next phase, Unreal
6176
+ * communication, resumes here on the sequential timeline).
6177
+ * - savings params: `prefetchConnectMs` (full handshake), `prefetchSavedMs`
6178
+ * (done before Angular was ready), `adoptionWaitMs` (real connect→adopted
6179
+ * wait), `adoptionOverheadMs` (prefetch-ready → adopted gap).
6140
6180
  */
6141
6181
  emitAdoptedConnection(timings) {
6142
6182
  const s = this.session;
6143
6183
  if (!s)
6144
6184
  return;
6145
6185
  const t = timings ?? {};
6146
- const start = t['prefetch_start'] ?? s.startedAtPerf;
6147
- const wsOpen = t['ws_open'] ?? start;
6148
- const ready = t['ready'] ?? t['dc_open'] ?? wsOpen;
6149
- // Real prefetch sub-durations — how the handshake actually broke down.
6150
- const sigRaw = Math.max(0, wsOpen - start);
6151
- const webRaw = Math.max(0, ready - wsOpen);
6152
- const connectMs = Math.round(sigRaw + webRaw);
6153
- // `lastMilestonePerf` is still pinned at session start (the connection
6154
- // closers are suppressed for adopted sessions), so this is the genuine
6155
- // connect → adopted wait the user lived through.
6156
6186
  const now = performance.now();
6157
- const waitMs = Math.max(0, Math.round(now - s.lastMilestonePerf));
6158
- // What running the handshake during page load bought us: the part already
6159
- // done by the time Angular reached the connect call.
6160
- const savedMs = Math.max(0, Math.min(connectMs, Math.round(s.startedAtPerf - start)));
6161
- const adoptionOverheadMs = Math.max(0, Math.round(now - ready));
6187
+ // Everything in anchor-relative ms — the same scale the viewer renders on
6188
+ // (and the prefetch marks share the page `performance` clock).
6189
+ const toMs = (perf) => Math.round(perf - s.anchorPerf);
6190
+ const startMs = toMs(t['prefetch_start'] ?? s.startedAtPerf);
6191
+ const wsOpenMs = Math.max(startMs, toMs(t['ws_open'] ?? t['prefetch_start'] ?? s.startedAtPerf));
6192
+ const readyMs = Math.max(wsOpenMs, toMs(t['ready'] ?? t['dc_open'] ?? t['ws_open'] ?? s.startedAtPerf));
6193
+ const adoptionAtMs = toMs(now);
6194
+ const sessionStartMs = toMs(s.startedAtPerf);
6195
+ const sigDur = wsOpenMs - startMs;
6196
+ const webDur = readyMs - wsOpenMs;
6197
+ const connectMs = readyMs - startMs;
6198
+ // Savings, anchored at the moment Angular reached the connect call.
6162
6199
  const savings = {
6163
6200
  prefetched: true,
6164
6201
  prefetchConnectMs: connectMs,
6165
- prefetchSavedMs: savedMs,
6166
- adoptionWaitMs: waitMs,
6167
- adoptionOverheadMs,
6202
+ // How much of the handshake finished before Angular was ready (the win).
6203
+ prefetchSavedMs: Math.max(0, Math.min(connectMs, sessionStartMs - startMs)),
6204
+ // The real wait Angular experienced (connect call → adopted).
6205
+ adoptionWaitMs: Math.max(0, adoptionAtMs - sessionStartMs),
6206
+ // Gap between the prefetch being ready and Angular adopting it.
6207
+ adoptionOverheadMs: Math.max(0, adoptionAtMs - readyMs),
6168
6208
  };
6169
- // Split the real wait across signaling / webrtc by the prefetch's own
6170
- // signaling:webrtc proportion.
6171
- const denom = sigRaw + webRaw;
6172
- const sigMs = denom > 0 ? Math.round((waitMs * sigRaw) / denom) : 0;
6173
- const webMs = Math.max(0, waitMs - sigMs);
6174
6209
  // Region (phaseNo 1) — served from the ping cache during prefetch (~0),
6175
- // but ship the samples so the phase still carries its data.
6210
+ // positioned at the prefetch start; ship the samples.
6176
6211
  const region = this.readRegionCache();
6177
- this.emitAdoptedPhase(0, 0, {
6212
+ this.emitAdoptedPhase(0, startMs, 0, adoptionAtMs, {
6178
6213
  region: region?.region_code ?? '',
6179
- pingsMs: region?.result.length
6180
- ? `${region.result.join(', ')} ms`
6181
- : null,
6182
- prefetched: true,
6183
- }, []);
6184
- // Signaling (phaseNo 2) — carries the streamer identity + savings summary.
6185
- this.emitAdoptedPhase(1, sigMs, {
6214
+ pingsMs: region?.result.length ? `${region.result.join(', ')} ms` : null,
6215
+ });
6216
+ // Signaling (phaseNo 2) — [prefetch_start → ws_open]; streamer identity +
6217
+ // savings; ws-connected marker sits at the phase's own end.
6218
+ this.emitAdoptedPhase(1, startMs, sigDur, adoptionAtMs, {
6186
6219
  ...this.adoptedSignalingParams(),
6187
6220
  ...savings,
6188
- prefetchSignalingMs: Math.round(sigRaw),
6189
- }, [{ id: 'signaling-ws-connected', time: Math.round(wsOpen) }]);
6190
- // WebRTC negotiation (phaseNo 3).
6191
- this.emitAdoptedPhase(2, webMs, {
6221
+ prefetchSignalingMs: sigDur,
6222
+ }, [{ id: 'signaling-ws-connected', time: wsOpenMs }]);
6223
+ // WebRTC negotiation (phaseNo 3) — [ws_open → ready].
6224
+ this.emitAdoptedPhase(2, wsOpenMs, webDur, adoptionAtMs, {
6192
6225
  iceCandidateCount: 0,
6193
- prefetched: true,
6194
- prefetchWebrtcMs: Math.round(webRaw),
6195
- }, []);
6226
+ prefetchWebrtcMs: webDur,
6227
+ });
6228
+ // Unreal communication chains from the adoption moment (real wall-clock).
6229
+ s.lastMilestonePerf = now;
6196
6230
  }
6197
- /** Emit one adopted phase with an explicit (prefetch-derived) duration and
6198
- * advance `lastMilestonePerf` synthetically so the next phase chains right. */
6199
- emitAdoptedPhase(bucketIdx, durationMs, extras, milestones) {
6231
+ /**
6232
+ * Emit one adopted phase positioned at its real `spanStartMs` (left-shifted
6233
+ * to overlap page load) with its real prefetch `duration`. `prefetched`,
6234
+ * `spanStartMs` and `adoptionAtMs` ride as top-level wire fields the viewer
6235
+ * uses to lay the bar out; the rest become display params.
6236
+ */
6237
+ emitAdoptedPhase(bucketIdx, spanStartMs, durationMs, adoptionAtMs, extras, milestones = []) {
6200
6238
  const s = this.session;
6201
6239
  if (!s)
6202
6240
  return;
@@ -6207,11 +6245,13 @@ class StreamStatusTelemetryService2 {
6207
6245
  id: bucket.id,
6208
6246
  color: bucket.color,
6209
6247
  duration: durationMs,
6248
+ prefetched: true,
6249
+ spanStartMs,
6250
+ adoptionAtMs,
6210
6251
  milestones,
6211
- params: toPhaseParams(extras),
6252
+ params: toPhaseParams({ ...extras, prefetched: true }),
6212
6253
  overlays: [],
6213
6254
  });
6214
- s.lastMilestonePerf += durationMs;
6215
6255
  }
6216
6256
  /** Streamer identity params for the adopted signaling phase — mirrors the
6217
6257
  * `ssinfo-received` closer's shape (client / instanceName / …ssData). */