@3dsource/angular-unreal-module 0.0.161 → 0.0.162
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/README.md
CHANGED
|
@@ -114,6 +114,15 @@ The `loadChildren()` boundary keeps the streaming engine out of the initial
|
|
|
114
114
|
bundle. Route-scoping `provideUnrealModule()` tears down its effects when the
|
|
115
115
|
route is left.
|
|
116
116
|
|
|
117
|
+
> `UNREAL_CONFIG` on a route's `providers` does **not** work: the engine
|
|
118
|
+
> services are `providedIn: 'root'`, so they are created by the root injector
|
|
119
|
+
> and read the token from there. A route-level value is invisible to them and
|
|
120
|
+
> `inject(UNREAL_CONFIG, { optional: true })` resolves to `null` — e.g. region
|
|
121
|
+
> pinging is skipped entirely (empty `regionsPingUrl`), the orchestration
|
|
122
|
+
> `requestStream` goes out without a region and the post-connection re-ping
|
|
123
|
+
> never runs. Importing only the token at the root does not pull the module into
|
|
124
|
+
> the initial bundle (the package is `sideEffects: false`).
|
|
125
|
+
|
|
117
126
|
### 3. Render the scene
|
|
118
127
|
|
|
119
128
|
```typescript
|
|
@@ -1538,6 +1538,16 @@ class RegionsPingService {
|
|
|
1538
1538
|
pingsMs: data.result,
|
|
1539
1539
|
}))), map$1((data) => data.region_code));
|
|
1540
1540
|
}
|
|
1541
|
+
// No regions URL and no cache: there is nothing to ping. Bail out loudly
|
|
1542
|
+
// instead of letting `HttpClient.get('')` request the current document
|
|
1543
|
+
// (which yields HTML, fails to parse and silently resolves an empty
|
|
1544
|
+
// region). The usual cause is `UNREAL_CONFIG` provided on a lazy route
|
|
1545
|
+
// rather than at the application root — this service is
|
|
1546
|
+
// `providedIn: 'root'`, so a route-level value never reaches it.
|
|
1547
|
+
if (!regionListUrl) {
|
|
1548
|
+
Logger.warn('RegionsPingService: regionsPingUrl is empty — skipping region discovery. Provide UNREAL_CONFIG at the APPLICATION ROOT (a route-level provider is invisible to root-scoped services).');
|
|
1549
|
+
return of(undefined).pipe(tap(() => this.store.dispatch(regionPingFetchEnd())), tap(() => this.store.dispatch(regionResolved({ region: '', pingsMs: [] }))));
|
|
1550
|
+
}
|
|
1541
1551
|
return this.getProviders(regionListUrl).pipe(tap(() => this.store.dispatch(regionPingFetchEnd())), switchMap((providers) => this.getPingResult(providers)), catchError(() => of(null)), tapLog('PingResult'),
|
|
1542
1552
|
// Warm the cache with the live result so the next connection (and any
|
|
1543
1553
|
// reload within the TTL) can skip pinging even if the prefetch script
|
|
@@ -1558,9 +1568,11 @@ class RegionsPingService {
|
|
|
1558
1568
|
* All access is guarded — `localStorage` can throw (private mode, disabled
|
|
1559
1569
|
* storage) or be absent (non-browser).
|
|
1560
1570
|
*
|
|
1561
|
-
* Public so adopted connections (which skip `getFastest`) can
|
|
1571
|
+
* Public so adopted connections (which skip `getFastest`) can still report a
|
|
1562
1572
|
* region-resolving telemetry phase from the same cached winner — see
|
|
1563
|
-
* `
|
|
1573
|
+
* `StreamStatusTelemetryService2.emitAdoptedConnection`. Returns `null` when
|
|
1574
|
+
* the region-ping prefetch never ran or its cache expired; that phase then
|
|
1575
|
+
* carries no region and no ping samples.
|
|
1564
1576
|
*/
|
|
1565
1577
|
readCachedWinner() {
|
|
1566
1578
|
try {
|
|
@@ -1606,6 +1618,10 @@ class RegionsPingService {
|
|
|
1606
1618
|
* connection handshake. Aborts with the injector via `destroyController`.
|
|
1607
1619
|
*/
|
|
1608
1620
|
refreshCacheInBackground(regionListUrl = this.unrealConfig?.regionsPingUrl || '') {
|
|
1621
|
+
if (!regionListUrl) {
|
|
1622
|
+
Logger.warn('RegionsPingService: regionsPingUrl is empty — skipping the background region re-ping.');
|
|
1623
|
+
return of(null);
|
|
1624
|
+
}
|
|
1609
1625
|
return defer(() => this.getProviders(regionListUrl)).pipe(switchMap((providers) => from(this.measureAccurate(providers.regions, providers.timeout ?? this.config.ping_timeout))), tap((winner) => {
|
|
1610
1626
|
if (winner) {
|
|
1611
1627
|
this.writeCachedWinner(winner.region_code, winner.result);
|
|
@@ -2198,9 +2214,18 @@ class SignallingService extends SubService {
|
|
|
2198
2214
|
},
|
|
2199
2215
|
}));
|
|
2200
2216
|
}
|
|
2217
|
+
/**
|
|
2218
|
+
* Status message for the connect branch. It does NOT dispatch
|
|
2219
|
+
* `setEstablishingConnection` — `UnrealEffects.connectToSignaling$` already
|
|
2220
|
+
* emits that once per logical attempt via `startWith(...)`, covering BOTH
|
|
2221
|
+
* branches (prefetch adoption and a fresh connect). Dispatching it here as
|
|
2222
|
+
* well fired the boundary twice on every cold start, and since
|
|
2223
|
+
* `StreamStatusTelemetryService2` starts a session on that boundary, the
|
|
2224
|
+
* first session was killed 2 ms in as `restart` — losing the only honest
|
|
2225
|
+
* page-load measurement and the prefetch adoption with it.
|
|
2226
|
+
*/
|
|
2201
2227
|
startEstablishingConnection() {
|
|
2202
2228
|
this.showStatusMessage(UnrealStatusMessage.STARTING_YOUR_SESSION);
|
|
2203
|
-
this.store.dispatch(setEstablishingConnection({ value: true }));
|
|
2204
2229
|
}
|
|
2205
2230
|
adaptUrlsToRegion(urlList, region) {
|
|
2206
2231
|
return urlList.map((url) => {
|
|
@@ -3688,11 +3713,12 @@ class StreamAdoptionService {
|
|
|
3688
3713
|
*
|
|
3689
3714
|
* Wrapped in `defer` so the `tryAdopt()` side effect (which dispatches
|
|
3690
3715
|
* `streamAdopted`) runs at SUBSCRIPTION time, not when the observable is
|
|
3691
|
-
* built. The effect prepends `startWith(setEstablishingConnection(true))
|
|
3692
|
-
*
|
|
3693
|
-
*
|
|
3694
|
-
* `
|
|
3695
|
-
* exist yet and is lost. `defer`
|
|
3716
|
+
* built. The effect prepends `startWith(setEstablishingConnection(true))` —
|
|
3717
|
+
* the single dispatch site for that boundary — which starts the telemetry
|
|
3718
|
+
* session and subscribes its phase catalog; that must happen BEFORE
|
|
3719
|
+
* `streamAdopted`, or `emitAdoptedConnection` fires into a session that does
|
|
3720
|
+
* not exist yet and the whole connection waterfall is lost. `defer`
|
|
3721
|
+
* guarantees that ordering.
|
|
3696
3722
|
*/
|
|
3697
3723
|
awaitAndAdopt$() {
|
|
3698
3724
|
return defer(() => {
|
|
@@ -5880,10 +5906,14 @@ class StreamStatusTelemetryService2 {
|
|
|
5880
5906
|
// CLOSER — the `regionResolved` action carries the raw ping
|
|
5881
5907
|
// samples (one per probe) from `RegionsPingService`; the
|
|
5882
5908
|
// catalog flattens them to a comma-separated string since
|
|
5883
|
-
// `toPhaseParams` only accepts primitives.
|
|
5884
|
-
//
|
|
5885
|
-
//
|
|
5886
|
-
//
|
|
5909
|
+
// `toPhaseParams` only accepts primitives.
|
|
5910
|
+
//
|
|
5911
|
+
// Adopted connections never run `getRegion`, so this closer never
|
|
5912
|
+
// fires for them (and is suppressed anyway via PREFETCHED_PHASE_IDS).
|
|
5913
|
+
// Their region phase comes from {@link emitAdoptedConnection}, which
|
|
5914
|
+
// reports `duration: 0`, no milestones, and reads the winner + samples
|
|
5915
|
+
// out of the ping cache — so it is EMPTY when the region-ping prefetch
|
|
5916
|
+
// never ran or its cache expired.
|
|
5887
5917
|
{
|
|
5888
5918
|
id: 'region-resolved',
|
|
5889
5919
|
event: this.actions$.pipe(ofType(regionResolved), take(1), map(({ region, pingsMs }) => ({
|
|
@@ -6053,12 +6083,17 @@ class StreamStatusTelemetryService2 {
|
|
|
6053
6083
|
// times per page load (the unreal-scene effect, `startStream`, the
|
|
6054
6084
|
// reconnect effect), but the connection layer collapses them into one
|
|
6055
6085
|
// attempt via `connectToSignaling$` (`exhaustMap` + `!cirrusConnected`
|
|
6056
|
-
// filter). `setEstablishingConnection(true)` is dispatched exactly
|
|
6057
|
-
//
|
|
6058
|
-
// downstream of
|
|
6059
|
-
//
|
|
6086
|
+
// filter). `setEstablishingConnection(true)` is dispatched exactly once
|
|
6087
|
+
// per real attempt, by that effect's `startWith(...)` — the SINGLE
|
|
6088
|
+
// dispatch site, downstream of the guard and ahead of both branches
|
|
6089
|
+
// (prefetch adoption / fresh connect). So binding here yields one session
|
|
6090
|
+
// per attempt instead of a burst of `restart` churn sessions. Genuine
|
|
6060
6091
|
// reconnects (e.g. DataChannelTimeout) re-enter the same path and
|
|
6061
6092
|
// correctly end the prior session as `restart` + start a new one.
|
|
6093
|
+
//
|
|
6094
|
+
// Do NOT add a second dispatch site: this binding turns every extra
|
|
6095
|
+
// `true` into `endSession('restart')` + a new session, which throws away
|
|
6096
|
+
// the first session's page-load measurement and its prefetch adoption.
|
|
6062
6097
|
this.actions$
|
|
6063
6098
|
.pipe(ofType(setEstablishingConnection), filter$1(({ value }) => value === true), takeUntil$1(this.destroy$))
|
|
6064
6099
|
.subscribe(() => this.startSession());
|