@satinhq/kit 0.1.0-beta.1 → 0.1.0-beta.3
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/NOTICE +2 -2
- package/README.md +1 -1
- package/dist/acknowledgements/inventory.json +5 -5
- package/dist/actions/RowActions.d.ts +2 -2
- package/dist/actions/RowActions.js +2 -2
- package/dist/app/deployment.js +8 -2
- package/dist/app/installCheck.js +4 -3
- package/dist/app/launchWatchdog.js +6 -1
- package/dist/controls/civilDate.d.ts +1 -1
- package/dist/controls/civilDate.js +1 -1
- package/dist/docs/components/file-importer.md +1 -1
- package/dist/docs/components/paste-button.md +1 -1
- package/dist/docs/components/photos-picker.md +1 -1
- package/dist/docs/components/share-link.md +1 -1
- package/dist/docs/limitations.md +1 -1
- package/dist/docs/push.md +3 -3
- package/dist/docs/reference.md +1870 -0
- package/dist/glass/GlassMaterialRoot.d.ts +3 -1
- package/dist/glass/GlassMaterialRoot.js +2 -2
- package/dist/glass/MaterialEffectRoot.js +5 -1
- package/dist/glass/errors.d.ts +3 -1
- package/dist/glass/errors.js +49 -3
- package/dist/glass/fallback/canvasOwnership.d.ts +7 -0
- package/dist/glass/fallback/canvasOwnership.js +23 -0
- package/dist/glass/fallback/material.js +3 -2
- package/dist/glass/fallback/regions.d.ts +1 -0
- package/dist/glass/fallback/regions.js +9 -12
- package/dist/glass/fallback/runtime.d.ts +2 -0
- package/dist/glass/fallback/runtime.js +15 -4
- package/dist/glass/lens/controlLift.d.ts +1 -1
- package/dist/glass/lens/controlLift.js +1 -1
- package/dist/glass/lens/interactivePress.d.ts +2 -2
- package/dist/glass/lens/interactivePress.js +2 -2
- package/dist/glass/lens/menu-opening-native-fits.json +1 -1
- package/dist/glass/lens/menuContextFit.json +1 -1
- package/dist/glass/lens/menuDropFit.json +1 -1
- package/dist/glass/lens/menuField.d.ts +2 -2
- package/dist/glass/lens/menuField.js +3 -3
- package/dist/glass/lens/menuGlyphDropFit.json +1 -1
- package/dist/glass/lens/menuMorphFit.json +1 -1
- package/dist/glass/lens/menuMorphPhoneFit.json +1 -1
- package/dist/glass/lens/tabAccessoryMotion.d.ts +1 -1
- package/dist/glass/lens/tabSearchMember.d.ts +1 -1
- package/dist/glass/lens/tabSearchMember.js +1 -1
- package/dist/glass/menuPresenter.js +1 -1
- package/dist/glass/regions.d.ts +1 -1
- package/dist/glass/regions.js +1 -1
- package/dist/glass/shapeTrack.d.ts +1 -1
- package/dist/glass/submenuMotion.d.ts +1 -1
- package/dist/glass/submenuMotion.js +1 -1
- package/dist/glass/webgpu/client.d.ts +3 -0
- package/dist/glass/webgpu/client.js +57 -9
- package/dist/glass/webgpu/entry.worker.js +3 -6
- package/dist/glass/webgpu/errorCode.js +3 -2
- package/dist/glass/webgpu/frameClock.d.ts +6 -2
- package/dist/glass/webgpu/frameClock.js +9 -2
- package/dist/glass/webgpu/frameProbe.d.ts +1 -1
- package/dist/glass/webgpu/frameProbe.js +3 -3
- package/dist/glass/webgpu/layers.d.ts +5 -1
- package/dist/glass/webgpu/layers.js +61 -6
- package/dist/glass/webgpu/minimize.js +20 -2
- package/dist/glass/webgpu/production.js +13 -1
- package/dist/glass/webgpu/raster.d.ts +18 -0
- package/dist/glass/webgpu/raster.js +105 -15
- package/dist/glass/webgpu/rasterMaterial.d.ts +4 -0
- package/dist/glass/webgpu/rasterMaterial.js +17 -1
- package/dist/glass/webgpu/regionScene.d.ts +4 -1
- package/dist/glass/webgpu/regionScene.js +30 -1
- package/dist/glass/webgpu/renderer.js +23 -7
- package/dist/glass/webgpu/sourcePixels.d.ts +4 -4
- package/dist/glass/webgpu/sourcePixels.js +48 -8
- package/dist/keyboard/adapter.js +2 -2
- package/dist/keyboard/avoidance.d.ts +1 -1
- package/dist/keyboard/avoidance.js +1 -1
- package/dist/keyboard/motion.d.ts +1 -1
- package/dist/keyboard/motion.js +1 -1
- package/dist/list-form/ListForm.d.ts +2 -2
- package/dist/list-form/ListForm.js +1 -1
- package/dist/list-form/ListItem.js +1 -1
- package/dist/list-form/RowLabel.d.ts +1 -1
- package/dist/list-form/RowLabel.js +1 -1
- package/dist/list-form/Slider.js +1 -1
- package/dist/list-form/editMotion.js +1 -1
- package/dist/list-form/editing.d.ts +5 -5
- package/dist/list-form/editing.js +1 -1
- package/dist/motion/animationValue.d.ts +2 -2
- package/dist/motion/animationValue.js +9 -10
- package/dist/motion/matchedGeometry.d.ts +4 -4
- package/dist/motion/matchedGeometry.js +7 -8
- package/dist/motion/springTrack.d.ts +1 -2
- package/dist/motion/springTrack.js +10 -5
- package/dist/navigation/ScreenContainer.js +1 -1
- package/dist/navigation/chromeCurves.js +1 -1
- package/dist/navigation/types.d.ts +1 -1
- package/dist/navigation/zoomGeometry.js +1 -1
- package/dist/navigation/zoomMotion.d.ts +1 -1
- package/dist/navigation/zoomMotion.js +2 -2
- package/dist/navigation/zoomTransition.js +3 -4
- package/dist/presentation/Presentation.d.ts +2 -2
- package/dist/presentation/Presentation.js +1 -1
- package/dist/presentation/presentationSource.js +1 -1
- package/dist/public/controls.d.ts +4 -4
- package/dist/public/controls.js +7 -7
- package/dist/public/glass.d.ts +3 -4
- package/dist/public/glass.js +1 -2
- package/dist/public/navigationTransition.d.ts +1 -1
- package/dist/public/navigationTransition.js +1 -1
- package/dist/public/scroll.d.ts +1 -1
- package/dist/public/scroll.js +1 -1
- package/dist/public/toolbar.js +1 -1
- package/dist/root/SatinRoot.js +9 -5
- package/dist/safe-area/SafeAreaInset.js +3 -3
- package/dist/safe-area/modifiers.js +2 -2
- package/dist/safe-area/useSafeAreaMetrics.js +2 -2
- package/dist/scroll/ScrollSurface.d.ts +5 -5
- package/dist/scroll/ScrollSurface.js +1 -1
- package/dist/scroll/position.js +1 -1
- package/dist/scroll/screenGeometry.d.ts +1 -1
- package/dist/scroll/screenGeometry.js +1 -1
- package/dist/search/SearchDrawer.js +1 -1
- package/dist/search/searchMotion.d.ts +1 -1
- package/dist/search/searchMotion.js +1 -1
- package/dist/search/useSearchPresentation.js +1 -1
- package/dist/shapes/corners.js +1 -1
- package/dist/styles.css +55 -34
- package/dist/sw/renderServiceWorker.js +12 -3
- package/dist/text-input/TextEditor.js +7 -3
- package/package.json +4 -4
- package/dist/docs/swiftui-reference.md +0 -1895
|
@@ -30,6 +30,7 @@ export class WebGPUClient {
|
|
|
30
30
|
}
|
|
31
31
|
adapterDeadline;
|
|
32
32
|
failed;
|
|
33
|
+
startingTransport = false;
|
|
33
34
|
frames = new PageFrameHost();
|
|
34
35
|
webgpu = false;
|
|
35
36
|
currentAppearance = () => readDarkAppearance() ? 'dark' : 'light';
|
|
@@ -68,7 +69,26 @@ export class WebGPUClient {
|
|
|
68
69
|
})), motionPreparationEnabled);
|
|
69
70
|
glassTiming('client-start');
|
|
70
71
|
try {
|
|
71
|
-
|
|
72
|
+
for (let attempt = 0;; attempt++) {
|
|
73
|
+
this.startingTransport = true;
|
|
74
|
+
try {
|
|
75
|
+
await this.initialize(canvas, resolution, worker, webgpu, appearance, execution);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const code = glassFailureCode('renderer initialization', error);
|
|
80
|
+
const retryable = this.startingTransport &&
|
|
81
|
+
['worker-startup', 'readiness-timeout', 'device-request'].includes(code);
|
|
82
|
+
if (!retryable || attempt === 2)
|
|
83
|
+
throw error;
|
|
84
|
+
this.close();
|
|
85
|
+
this.failed = undefined;
|
|
86
|
+
await new Promise(resolve => setTimeout(resolve, 250 * 2 ** attempt));
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
this.startingTransport = false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
72
92
|
}
|
|
73
93
|
catch (error) {
|
|
74
94
|
reportGlassError(glassFailureCode('renderer initialization', error), error, 'renderer initialization');
|
|
@@ -153,7 +173,7 @@ export class WebGPUClient {
|
|
|
153
173
|
return;
|
|
154
174
|
}
|
|
155
175
|
if (glassError) {
|
|
156
|
-
reportGlassError(glassError.code, glassError.message, glassError.stage, glassError.reason);
|
|
176
|
+
reportGlassError(glassError.code, glassError.message, glassError.stage, glassError.reason, this.startingTransport ? { recovering: true } : undefined);
|
|
157
177
|
return;
|
|
158
178
|
}
|
|
159
179
|
const request = this.pending.get(id);
|
|
@@ -174,12 +194,18 @@ export class WebGPUClient {
|
|
|
174
194
|
};
|
|
175
195
|
}
|
|
176
196
|
else {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
197
|
+
let transport;
|
|
198
|
+
try {
|
|
199
|
+
transport = new Worker(new URL('./entry.worker.js', import.meta.url), {
|
|
200
|
+
type: 'module',
|
|
201
|
+
name: 'satin-glass-webgpu',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
throw new Error(`Worker startup: ${String(error)}`, { cause: error });
|
|
206
|
+
}
|
|
181
207
|
transport.onmessage = receive;
|
|
182
|
-
transport.onerror = event => this.failTransport(new Error(event.message || 'Glass worker failed'), 'worker startup/runtime');
|
|
208
|
+
transport.onerror = event => this.failTransport(new Error(`Worker startup/runtime: ${event.message || 'Glass worker failed'}`), 'worker startup/runtime');
|
|
183
209
|
transport.onmessageerror = () => this.failTransport(Error('Glass worker message could not be deserialized'), 'worker transport');
|
|
184
210
|
this.transport = {
|
|
185
211
|
postMessage: (data, transfer = []) => transport.postMessage(data, transfer),
|
|
@@ -205,7 +231,7 @@ export class WebGPUClient {
|
|
|
205
231
|
}
|
|
206
232
|
catch (error) {
|
|
207
233
|
this.failed = error instanceof Error ? error : Error(String(error));
|
|
208
|
-
|
|
234
|
+
// init owns the bounded startup retry and terminal report.
|
|
209
235
|
this.transport?.terminate();
|
|
210
236
|
for (const pending of this.pending.values())
|
|
211
237
|
pending.reject(this.failed);
|
|
@@ -216,6 +242,7 @@ export class WebGPUClient {
|
|
|
216
242
|
clearTimeout(timeout);
|
|
217
243
|
}
|
|
218
244
|
}
|
|
245
|
+
this.startingTransport = false;
|
|
219
246
|
const offscreen = this.local ? canvas : canvas.transferControlToOffscreen();
|
|
220
247
|
await this.request({
|
|
221
248
|
type: 'init',
|
|
@@ -233,7 +260,8 @@ export class WebGPUClient {
|
|
|
233
260
|
failTransport(error, stage) {
|
|
234
261
|
this.adapterDeadline?.cancel();
|
|
235
262
|
this.failed = error;
|
|
236
|
-
|
|
263
|
+
if (!this.startingTransport)
|
|
264
|
+
reportGlassError(glassFailureCode(stage, error), error, stage);
|
|
237
265
|
for (const pending of this.pending.values())
|
|
238
266
|
pending.reject(error);
|
|
239
267
|
this.pending.clear();
|
|
@@ -324,6 +352,26 @@ export class WebGPUClient {
|
|
|
324
352
|
this.bindLifecycle();
|
|
325
353
|
this.visibility();
|
|
326
354
|
}
|
|
355
|
+
/** Dispose a failed transport before constructing its replacement. */
|
|
356
|
+
close() {
|
|
357
|
+
this.frames.cancel();
|
|
358
|
+
this.adapterDeadline?.cancel();
|
|
359
|
+
this.adapterDeadline = undefined;
|
|
360
|
+
document.removeEventListener('visibilitychange', this.visibility, true);
|
|
361
|
+
window.removeEventListener('pagehide', this.visibility, true);
|
|
362
|
+
window.removeEventListener('pageshow', this.visibility);
|
|
363
|
+
window.removeEventListener('focus', this.focus);
|
|
364
|
+
document.removeEventListener('visibilitychange', this.adapterVisibility, true);
|
|
365
|
+
window.removeEventListener('pagehide', this.adapterVisibility, true);
|
|
366
|
+
window.removeEventListener('pageshow', this.adapterVisibility);
|
|
367
|
+
window.removeEventListener('focus', this.adapterVisibility);
|
|
368
|
+
this.transport?.terminate();
|
|
369
|
+
this.transport = undefined;
|
|
370
|
+
this.local = undefined;
|
|
371
|
+
for (const pending of this.pending.values())
|
|
372
|
+
pending.reject(Error('Glass transport retired'));
|
|
373
|
+
this.pending.clear();
|
|
374
|
+
}
|
|
327
375
|
park() {
|
|
328
376
|
this.frames.cancel();
|
|
329
377
|
document.removeEventListener('visibilitychange', this.visibility, true);
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { workerAnimationFrameClock } from './frameClock.js';
|
|
2
2
|
import { glassFailureCode } from './errorCode.js';
|
|
3
3
|
import { createWebGPURenderer } from './renderer.js';
|
|
4
4
|
import { glassTiming, setGlassTimingSink } from './profile.js';
|
|
5
5
|
setGlassTimingSink(event => globalThis.postMessage({ glassProfile: event }));
|
|
6
6
|
glassTiming('worker-start');
|
|
7
|
-
const clock =
|
|
7
|
+
const clock = workerAnimationFrameClock;
|
|
8
8
|
const renderer = createWebGPURenderer(message => globalThis.postMessage(message), clock);
|
|
9
9
|
globalThis.onmessage = event => {
|
|
10
|
-
|
|
11
|
-
clock.receive(event.data.request);
|
|
12
|
-
else
|
|
13
|
-
renderer.receive(event);
|
|
10
|
+
renderer.receive(event);
|
|
14
11
|
};
|
|
15
12
|
globalThis.addEventListener('error', event => globalThis.postMessage({
|
|
16
13
|
glassError: {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/** Classification retains the raw message; it never chooses a replacement. */
|
|
2
2
|
export function glassFailureCode(stage, cause) {
|
|
3
3
|
const message = cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause), text = stage + ' ' + message;
|
|
4
|
+
// Preserve the failed transport owner when a worker reports its own device loss.
|
|
5
|
+
if (/worker.*(?:startup|runtime|transport|rejection)/i.test(text))
|
|
6
|
+
return 'worker-startup';
|
|
4
7
|
if (/device\.lost|device.?lost/i.test(text))
|
|
5
8
|
return 'device-lost';
|
|
6
9
|
if (/out.?of.?memory/i.test(text))
|
|
@@ -19,8 +22,6 @@ export function glassFailureCode(stage, cause) {
|
|
|
19
22
|
return 'context-configure';
|
|
20
23
|
if (/readiness|2\.5 seconds|2500 ms/i.test(text))
|
|
21
24
|
return 'readiness-timeout';
|
|
22
|
-
if (/worker.*(?:startup|runtime|transport|rejection)/i.test(text))
|
|
23
|
-
return 'worker-startup';
|
|
24
25
|
if (/unsupported|invalid glass|Unknown.*(?:layer|region)|exceeds.*supported/i.test(text))
|
|
25
26
|
return 'unsupported-configuration';
|
|
26
27
|
return 'presentation';
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import type { Envelope } from '../worker/protocol.js';
|
|
2
2
|
/** Epoch milliseconds from the document's animation frame, shared with WAAPI. */
|
|
3
3
|
export interface FrameClock {
|
|
4
|
+
readonly kind?: 'page-raf' | 'worker-raf';
|
|
4
5
|
request(callback: (at: number) => void): number;
|
|
5
6
|
cancel(token: number): void;
|
|
6
7
|
}
|
|
7
8
|
export declare const documentFrameClock: FrameClock;
|
|
9
|
+
/** A retained worker timeline can sample without a page callback or message round trip.
|
|
10
|
+
* Both realms use epoch milliseconds, so the same WAAPI track remains the time authority. */
|
|
11
|
+
export declare const workerAnimationFrameClock: FrameClock;
|
|
8
12
|
type FrameRequest = {
|
|
9
13
|
glassFrame: {
|
|
10
14
|
token: number;
|
|
11
15
|
cancel?: boolean;
|
|
12
16
|
};
|
|
13
17
|
};
|
|
14
|
-
/**
|
|
15
|
-
*
|
|
18
|
+
/** Page-linked worker clock retained for diagnostic comparisons. Its round trip
|
|
19
|
+
* also inherits page stalls; the independent worker renderer does not use it. */
|
|
16
20
|
export declare class WorkerFrameClock implements FrameClock {
|
|
17
21
|
private sequence;
|
|
18
22
|
private pending?;
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
export const documentFrameClock = {
|
|
2
|
+
kind: 'page-raf',
|
|
2
3
|
request: callback => requestAnimationFrame(timestamp => callback(performance.timeOrigin + timestamp)),
|
|
3
4
|
cancel: token => cancelAnimationFrame(token),
|
|
4
5
|
};
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
6
|
+
/** A retained worker timeline can sample without a page callback or message round trip.
|
|
7
|
+
* Both realms use epoch milliseconds, so the same WAAPI track remains the time authority. */
|
|
8
|
+
export const workerAnimationFrameClock = {
|
|
9
|
+
...documentFrameClock,
|
|
10
|
+
kind: 'worker-raf',
|
|
11
|
+
};
|
|
12
|
+
/** Page-linked worker clock retained for diagnostic comparisons. Its round trip
|
|
13
|
+
* also inherits page stalls; the independent worker renderer does not use it. */
|
|
7
14
|
export class WorkerFrameClock {
|
|
8
15
|
sequence = 0;
|
|
9
16
|
pending;
|
|
@@ -44,7 +44,7 @@ export declare class FrameProbe {
|
|
|
44
44
|
private post;
|
|
45
45
|
constructor(post: (summary: FrameSummary[]) => void);
|
|
46
46
|
get timestampQuery(): boolean;
|
|
47
|
-
useDevice(device: GPUDevice | undefined): void;
|
|
47
|
+
useDevice(device: GPUDevice | undefined, gpuTiming?: boolean): void;
|
|
48
48
|
/** Wraps one frame's encoding and submission. Returns the frame's sample: its completion and
|
|
49
49
|
* GPU times are filled in when they arrive. */
|
|
50
50
|
frame(kind: FrameKind, interval: number | null, restart: boolean, encode: (e: GPUCommandEncoder) => void, canSubmit?: () => boolean): FrameProbeSample | undefined;
|
|
@@ -78,12 +78,12 @@ export class FrameProbe {
|
|
|
78
78
|
this.post = post;
|
|
79
79
|
}
|
|
80
80
|
get timestampQuery() {
|
|
81
|
-
return !!this.
|
|
81
|
+
return !!this.timer;
|
|
82
82
|
}
|
|
83
|
-
useDevice(device) {
|
|
83
|
+
useDevice(device, gpuTiming = true) {
|
|
84
84
|
this.device = device;
|
|
85
85
|
this.timer = undefined;
|
|
86
|
-
if (device?.features.has('timestamp-query'))
|
|
86
|
+
if (gpuTiming && device?.features.has('timestamp-query'))
|
|
87
87
|
try {
|
|
88
88
|
this.timer = new GPUFrameTimer(device);
|
|
89
89
|
}
|
|
@@ -22,7 +22,9 @@ export declare class LayerDevice {
|
|
|
22
22
|
private stopped;
|
|
23
23
|
private adapterPending;
|
|
24
24
|
private recovering;
|
|
25
|
-
private
|
|
25
|
+
private recoveryBlockedUntil;
|
|
26
|
+
private recoveryTimer?;
|
|
27
|
+
private recoveryFailures;
|
|
26
28
|
private forcedNullAdapters;
|
|
27
29
|
private lastLoss?;
|
|
28
30
|
readonly stats: {
|
|
@@ -43,6 +45,8 @@ export declare class LayerDevice {
|
|
|
43
45
|
stop(): void;
|
|
44
46
|
/** Foreground signals wake the single acquisition already in flight. */
|
|
45
47
|
retryAdapter(): Promise<GPUDevice> | undefined;
|
|
48
|
+
private scheduleRecovery;
|
|
49
|
+
private waitUntilVisible;
|
|
46
50
|
forceNullAdaptersForTest(attempts: number): void;
|
|
47
51
|
private requestAdapter;
|
|
48
52
|
private acquire;
|
|
@@ -34,7 +34,9 @@ export class LayerDevice {
|
|
|
34
34
|
stopped = false;
|
|
35
35
|
adapterPending = false;
|
|
36
36
|
recovering = false;
|
|
37
|
-
|
|
37
|
+
recoveryBlockedUntil = 0;
|
|
38
|
+
recoveryTimer;
|
|
39
|
+
recoveryFailures = 0;
|
|
38
40
|
forcedNullAdapters = 0;
|
|
39
41
|
lastLoss;
|
|
40
42
|
stats = {
|
|
@@ -57,8 +59,10 @@ export class LayerDevice {
|
|
|
57
59
|
async ready() {
|
|
58
60
|
if (this.device)
|
|
59
61
|
return this.device;
|
|
60
|
-
if (this.
|
|
61
|
-
|
|
62
|
+
if (Date.now() < this.recoveryBlockedUntil) {
|
|
63
|
+
this.scheduleRecovery();
|
|
64
|
+
throw Error('Glass device recovery is cooling down');
|
|
65
|
+
}
|
|
62
66
|
if (this.starting)
|
|
63
67
|
return this.starting;
|
|
64
68
|
this.starting = this.acquire();
|
|
@@ -79,8 +83,13 @@ export class LayerDevice {
|
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
failedDevice?.destroy();
|
|
82
|
-
if (!this.stopped)
|
|
86
|
+
if (!this.stopped) {
|
|
83
87
|
this.reportError(this.recovering ? 'device recreation' : 'device acquisition', error);
|
|
88
|
+
if (this.recovering) {
|
|
89
|
+
this.recoveryFailures++;
|
|
90
|
+
this.scheduleRecovery();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
84
93
|
throw error;
|
|
85
94
|
}
|
|
86
95
|
finally {
|
|
@@ -91,21 +100,53 @@ export class LayerDevice {
|
|
|
91
100
|
setAdapterVisible(visible) {
|
|
92
101
|
this.adapterVisible = visible;
|
|
93
102
|
this.retryWake?.();
|
|
103
|
+
if (!visible) {
|
|
104
|
+
clearTimeout(this.recoveryTimer);
|
|
105
|
+
this.recoveryTimer = undefined;
|
|
106
|
+
}
|
|
107
|
+
else if (this.recovering && !this.starting && !this.device)
|
|
108
|
+
this.scheduleRecovery();
|
|
94
109
|
}
|
|
95
110
|
stop() {
|
|
96
111
|
this.stopped = true;
|
|
112
|
+
clearTimeout(this.recoveryTimer);
|
|
97
113
|
this.retryWake?.();
|
|
98
114
|
}
|
|
99
115
|
/** Foreground signals wake the single acquisition already in flight. */
|
|
100
116
|
retryAdapter() {
|
|
101
|
-
if (this.device || this.
|
|
117
|
+
if (this.device || this.stopped || !this.adapterVisible)
|
|
118
|
+
return;
|
|
119
|
+
if (Date.now() < this.recoveryBlockedUntil) {
|
|
120
|
+
this.scheduleRecovery();
|
|
102
121
|
return;
|
|
122
|
+
}
|
|
103
123
|
if (this.starting) {
|
|
104
124
|
this.retryWake?.();
|
|
105
125
|
return;
|
|
106
126
|
}
|
|
107
127
|
return this.ready();
|
|
108
128
|
}
|
|
129
|
+
scheduleRecovery() {
|
|
130
|
+
if (this.stopped || !this.adapterVisible || this.recoveryTimer)
|
|
131
|
+
return;
|
|
132
|
+
const delay = Math.max(this.recoveryBlockedUntil - Date.now(), Math.min(1000 * 2 ** this.recoveryFailures, 30_000));
|
|
133
|
+
this.recoveryTimer = setTimeout(() => {
|
|
134
|
+
this.recoveryTimer = undefined;
|
|
135
|
+
void this.retryAdapter()?.catch(() => {
|
|
136
|
+
/* ready records and schedules the next attempt. */
|
|
137
|
+
});
|
|
138
|
+
}, delay);
|
|
139
|
+
}
|
|
140
|
+
async waitUntilVisible() {
|
|
141
|
+
while (!this.adapterVisible && !this.stopped) {
|
|
142
|
+
await new Promise(resolve => {
|
|
143
|
+
this.retryWake = resolve;
|
|
144
|
+
});
|
|
145
|
+
this.retryWake = undefined;
|
|
146
|
+
}
|
|
147
|
+
if (this.stopped)
|
|
148
|
+
throw Error('Glass renderer stopped during adapter acquisition');
|
|
149
|
+
}
|
|
109
150
|
forceNullAdaptersForTest(attempts) {
|
|
110
151
|
if (!Number.isInteger(attempts) || attempts < 0 || attempts > 100)
|
|
111
152
|
throw Error('Null-adapter attempts must be an integer between 0 and 100');
|
|
@@ -167,6 +208,15 @@ export class LayerDevice {
|
|
|
167
208
|
})).catch(error => {
|
|
168
209
|
throw Error(`requestDevice rejected: ${String(error)}`);
|
|
169
210
|
});
|
|
211
|
+
// requestDevice may resolve after a quick re-background. Do not rebuild or
|
|
212
|
+
// announce readiness against a suspended presentation generation.
|
|
213
|
+
try {
|
|
214
|
+
await this.waitUntilVisible();
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
device.destroy();
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
170
220
|
this.device = device;
|
|
171
221
|
this.faults.length = 0;
|
|
172
222
|
registerGPUReporter(device, (stage, error) => {
|
|
@@ -207,11 +257,13 @@ export class LayerDevice {
|
|
|
207
257
|
}
|
|
208
258
|
if (invalidationError) {
|
|
209
259
|
this.reportError('device recreation', invalidationError);
|
|
260
|
+
this.scheduleRecovery();
|
|
210
261
|
return;
|
|
211
262
|
}
|
|
212
263
|
// WebKit can report destroyed for an unexpected loss as well.
|
|
213
264
|
if (this.lossTimes.length > 3) {
|
|
214
|
-
this.
|
|
265
|
+
this.recoveryBlockedUntil = this.lossTimes[0] + 60_000;
|
|
266
|
+
this.scheduleRecovery();
|
|
215
267
|
this.reportError('device recreation', 'Glass device repeatedly lost during recovery');
|
|
216
268
|
return;
|
|
217
269
|
}
|
|
@@ -227,6 +279,9 @@ export class LayerDevice {
|
|
|
227
279
|
for (const listener of this.listeners)
|
|
228
280
|
listener(device);
|
|
229
281
|
this.stats.restores++;
|
|
282
|
+
this.recoveryFailures = 0;
|
|
283
|
+
clearTimeout(this.recoveryTimer);
|
|
284
|
+
this.recoveryTimer = undefined;
|
|
230
285
|
this.recovering = false;
|
|
231
286
|
if (lagTraceEnabled())
|
|
232
287
|
glassTiming('device-restored');
|
|
@@ -374,8 +374,26 @@ export class WorkerMinimize {
|
|
|
374
374
|
},
|
|
375
375
|
};
|
|
376
376
|
}
|
|
377
|
-
if (atRest(f))
|
|
378
|
-
|
|
377
|
+
if (atRest(f)) {
|
|
378
|
+
// A retargeted track includes its pre-onset frames. They must use the current
|
|
379
|
+
// slot too; returning the captured spec preserves the old expanded width.
|
|
380
|
+
return {
|
|
381
|
+
...spec,
|
|
382
|
+
frameWidth: geometry.dockWidth,
|
|
383
|
+
frameHeight: geometry.dockHeight,
|
|
384
|
+
shape: {
|
|
385
|
+
...spec.shape,
|
|
386
|
+
first: {
|
|
387
|
+
...spec.shape.first,
|
|
388
|
+
centerX: geometry.dockWidth / 2,
|
|
389
|
+
centerY: geometry.dockHeight / 2,
|
|
390
|
+
halfWidth: geometry.dockWidth / 2,
|
|
391
|
+
halfHeight: geometry.dockHeight / 2,
|
|
392
|
+
radius: geometry.dockRadius,
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
379
397
|
const g = this.options.geometry, p = minimizePlacement(f.progress, g.dockWidth, g.dockHeight, g.tabBarMinimizedOffsetX), height = g.dockHeight + (48 - g.dockHeight) * f.heightProgress, y = 7 * f.heightProgress, pill = spec.tabSelection?.capsule;
|
|
380
398
|
const bar = {
|
|
381
399
|
centerX: p.x + p.width / 2,
|
|
@@ -304,6 +304,12 @@ export class ProductionGlassRuntime {
|
|
|
304
304
|
static async create(options) {
|
|
305
305
|
if (this.shared) {
|
|
306
306
|
const runtime = await this.shared;
|
|
307
|
+
if (runtime.client.status().failed) {
|
|
308
|
+
runtime.teardown();
|
|
309
|
+
runtime.client.close();
|
|
310
|
+
this.shared = undefined;
|
|
311
|
+
return this.create(options);
|
|
312
|
+
}
|
|
307
313
|
runtime.rebind(options);
|
|
308
314
|
await runtime.prepare();
|
|
309
315
|
return runtime;
|
|
@@ -317,7 +323,9 @@ export class ProductionGlassRuntime {
|
|
|
317
323
|
catch (error) {
|
|
318
324
|
// A rejected create never reaches the React owner's cleanup. Retire its
|
|
319
325
|
// listeners and captures here before a release app mounts CSS material.
|
|
320
|
-
runtime.
|
|
326
|
+
runtime.teardown();
|
|
327
|
+
runtime.client.close();
|
|
328
|
+
this.shared = undefined;
|
|
321
329
|
throw error;
|
|
322
330
|
}
|
|
323
331
|
})();
|
|
@@ -377,6 +385,10 @@ export class ProductionGlassRuntime {
|
|
|
377
385
|
element.style.cssText =
|
|
378
386
|
'position:absolute;inset:0;overflow:visible;clip-path:none;transform:none';
|
|
379
387
|
this.canvas.style.cssText = `position:absolute;left:0;top:0;width:${geometry.width}px;height:${geometry.height}px;pointer-events:none`;
|
|
388
|
+
if (!this.client.documentOwned) {
|
|
389
|
+
this.canvas.style.objectFit = 'contain';
|
|
390
|
+
this.canvas.style.objectPosition = 'left bottom';
|
|
391
|
+
}
|
|
380
392
|
this.canvas.dataset.satinWebgpu = 'production';
|
|
381
393
|
// Chrome canvases start as one pixel; each preparation places them on the band their surfaces
|
|
382
394
|
// reach. A remount (geometry change, rebind) keeps the band the worker is drawing for until the
|
|
@@ -37,6 +37,19 @@ export declare class WebGPURaster {
|
|
|
37
37
|
private overlayPipeline;
|
|
38
38
|
private presentPipeline;
|
|
39
39
|
private format;
|
|
40
|
+
private presentationRoot?;
|
|
41
|
+
private presentationOrigin?;
|
|
42
|
+
private presentationMembers;
|
|
43
|
+
private presentationSequence?;
|
|
44
|
+
private collectingPresentation;
|
|
45
|
+
/** Member samples encode their materials first. The root then presents the whole frame once. */
|
|
46
|
+
withSharedPresentation(draw: () => void): void;
|
|
47
|
+
/** Retain independent material sampling, but store overlapping fixed chrome once. */
|
|
48
|
+
joinPresentation(root: WebGPURaster, origin: {
|
|
49
|
+
x: number;
|
|
50
|
+
y: number;
|
|
51
|
+
}): void;
|
|
52
|
+
leavePresentation(): void;
|
|
40
53
|
private lost;
|
|
41
54
|
private suspended;
|
|
42
55
|
private covered;
|
|
@@ -172,8 +185,10 @@ export declare class WebGPURaster {
|
|
|
172
185
|
* region presenter's layer already is its sample rectangle and keeps layer space. */
|
|
173
186
|
private localResult;
|
|
174
187
|
/** Device-pixel offset of this layer's canvas within its layer. */
|
|
188
|
+
private presentationViewport;
|
|
175
189
|
private canvasOffset;
|
|
176
190
|
private canvasSize;
|
|
191
|
+
private resizeCanvas;
|
|
177
192
|
/** Size the shared layer canvas to a band of its layer (the element is placed there by
|
|
178
193
|
* the page): a lift overlay presents a strip, not a second full-screen canvas. */
|
|
179
194
|
setCanvasViewport(viewport: readonly [number, number, number, number] | undefined): void;
|
|
@@ -201,6 +216,9 @@ export declare class WebGPURaster {
|
|
|
201
216
|
* Returns false, drawing nothing, when the canvas already shows exactly this (see
|
|
202
217
|
* `presentedLayers`): the same results at the same places, or nothing at all. */
|
|
203
218
|
static presentLayer(encoder: GPUCommandEncoder, rasters: readonly WebGPURaster[]): boolean;
|
|
219
|
+
private presentationOffset;
|
|
220
|
+
private groupPresentUniform;
|
|
221
|
+
private groupPresentScissor;
|
|
204
222
|
private presentDraw;
|
|
205
223
|
/** Scroll-attached glass is part of the scrolling content, and the navigation bar's scroll-edge
|
|
206
224
|
* effect is painted over that content (CSS, a layer above the scroll container). Its own
|