@empty-sekai/sekai-custom-profile-sdk 0.3.0
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/LICENSE +235 -0
- package/LICENSE-EXCEPTION +32 -0
- package/NOTICE +11 -0
- package/README.en.md +456 -0
- package/README.md +458 -0
- package/dist/allium_renderer_wasm.js +14 -0
- package/dist/allium_renderer_wasm.wasm +0 -0
- package/dist/authoring.d.ts +14 -0
- package/dist/authoring.js +34 -0
- package/dist/cache/glyphPersistentCache.d.ts +145 -0
- package/dist/cache/glyphPersistentCache.js +346 -0
- package/dist/cache/indexedDbGlyphRecordStore.d.ts +26 -0
- package/dist/cache/indexedDbGlyphRecordStore.js +214 -0
- package/dist/cache/sessionImageResourceCache.d.ts +39 -0
- package/dist/cache/sessionImageResourceCache.js +156 -0
- package/dist/emscripten.d.ts +11 -0
- package/dist/emscripten.js +1 -0
- package/dist/fontProvider.d.ts +36 -0
- package/dist/fontProvider.js +84 -0
- package/dist/fontSdfAtlas.d.ts +92 -0
- package/dist/fontSdfAtlas.js +296 -0
- package/dist/gpu/browserSemanticResources.d.ts +64 -0
- package/dist/gpu/browserSemanticResources.js +232 -0
- package/dist/gpu/generalTextRenderPlacement.d.ts +8 -0
- package/dist/gpu/generalTextRenderPlacement.js +131 -0
- package/dist/gpu/previewTransformTextureLayout.d.ts +1 -0
- package/dist/gpu/previewTransformTextureLayout.js +12 -0
- package/dist/gpu/semanticCommandGeometry.d.ts +21 -0
- package/dist/gpu/semanticCommandGeometry.js +226 -0
- package/dist/gpu/semanticCommandPlanner.d.ts +131 -0
- package/dist/gpu/semanticCommandPlanner.js +203 -0
- package/dist/gpu/semanticTextGlyphBridge.d.ts +3 -0
- package/dist/gpu/semanticTextGlyphBridge.js +22 -0
- package/dist/gpu/semanticWebglSceneRenderer.d.ts +70 -0
- package/dist/gpu/semanticWebglSceneRenderer.js +211 -0
- package/dist/gpu/slotRanges.d.ts +5 -0
- package/dist/gpu/slotRanges.js +14 -0
- package/dist/gpu/webglSdfAtlasTexture.d.ts +17 -0
- package/dist/gpu/webglSdfAtlasTexture.js +72 -0
- package/dist/gpu/webglSdfGlyphPipeline.d.ts +26 -0
- package/dist/gpu/webglSdfGlyphPipeline.js +267 -0
- package/dist/gpu/webglSemanticCommandExecutor.d.ts +79 -0
- package/dist/gpu/webglSemanticCommandExecutor.js +686 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +8 -0
- package/dist/interaction/numericTextRegions.d.ts +46 -0
- package/dist/interaction/numericTextRegions.js +44 -0
- package/dist/localizationProvider.d.ts +33 -0
- package/dist/localizationProvider.js +74 -0
- package/dist/originPrebuiltSdfAtlasPackage.d.ts +39 -0
- package/dist/originPrebuiltSdfAtlasPackage.js +316 -0
- package/dist/prebuiltSdfAtlas.d.ts +36 -0
- package/dist/prebuiltSdfAtlas.js +261 -0
- package/dist/protocol.d.ts +456 -0
- package/dist/protocol.js +1 -0
- package/dist/renderer.d.ts +218 -0
- package/dist/renderer.js +641 -0
- package/dist/resourceProvider.d.ts +24 -0
- package/dist/resourceProvider.js +41 -0
- package/dist/telemetry/rendererTelemetry.d.ts +158 -0
- package/dist/telemetry/rendererTelemetry.js +232 -0
- package/dist/third-party/freetype/FTL.txt +169 -0
- package/dist/types/atlas.d.ts +90 -0
- package/dist/types/atlas.js +1 -0
- package/dist/types/authoring.d.ts +81 -0
- package/dist/types/authoring.js +1 -0
- package/dist/types/core.d.ts +128 -0
- package/dist/types/core.js +1 -0
- package/dist/types/freeType.d.ts +54 -0
- package/dist/types/freeType.js +13 -0
- package/dist/types/glyph.d.ts +33 -0
- package/dist/types/glyph.js +1 -0
- package/dist/types/layout.d.ts +25 -0
- package/dist/types/layout.js +1 -0
- package/dist/types/text.d.ts +28 -0
- package/dist/types/text.js +1 -0
- package/dist/worker/glyphWorkScheduler.d.ts +29 -0
- package/dist/worker/glyphWorkScheduler.js +107 -0
- package/dist/worker-client.d.ts +114 -0
- package/dist/worker-client.js +413 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +708 -0
- package/package.json +46 -0
- package/src/atlas.rs +652 -0
- package/src/authoring.ts +42 -0
- package/src/authoring_runtime.rs +303 -0
- package/src/cache/glyphPersistentCache.ts +463 -0
- package/src/cache/indexedDbGlyphRecordStore.ts +238 -0
- package/src/cache/sessionImageResourceCache.ts +186 -0
- package/src/edt.rs +97 -0
- package/src/emscripten.ts +17 -0
- package/src/fontProvider.ts +127 -0
- package/src/fontSdfAtlas.ts +431 -0
- package/src/geometry.rs +149 -0
- package/src/glyph_plan.rs +226 -0
- package/src/gpu/browserSemanticResources.ts +294 -0
- package/src/gpu/generalTextRenderPlacement.ts +175 -0
- package/src/gpu/previewTransformTextureLayout.ts +12 -0
- package/src/gpu/semanticCommandGeometry.ts +260 -0
- package/src/gpu/semanticCommandPlanner.ts +280 -0
- package/src/gpu/semanticTextGlyphBridge.ts +35 -0
- package/src/gpu/semanticWebglSceneRenderer.ts +261 -0
- package/src/gpu/slotRanges.ts +15 -0
- package/src/gpu/webglSdfAtlasTexture.ts +69 -0
- package/src/gpu/webglSdfGlyphPipeline.ts +299 -0
- package/src/gpu/webglSemanticCommandExecutor.ts +739 -0
- package/src/index.ts +84 -0
- package/src/interaction/numericTextRegions.ts +80 -0
- package/src/layout.rs +1882 -0
- package/src/lib.rs +1443 -0
- package/src/localizationProvider.ts +113 -0
- package/src/main.rs +55 -0
- package/src/masterdata_runtime.rs +496 -0
- package/src/originPrebuiltSdfAtlasPackage.ts +403 -0
- package/src/prebuiltSdfAtlas.ts +306 -0
- package/src/protocol.ts +150 -0
- package/src/renderer.ts +792 -0
- package/src/resourceProvider.ts +69 -0
- package/src/scene.rs +665 -0
- package/src/telemetry/rendererTelemetry.ts +358 -0
- package/src/types/atlas.ts +80 -0
- package/src/types/authoring.ts +85 -0
- package/src/types/core.ts +110 -0
- package/src/types/freeType.ts +63 -0
- package/src/types/glyph.ts +33 -0
- package/src/types/layout.ts +28 -0
- package/src/types/text.ts +28 -0
- package/src/worker/glyphWorkScheduler.ts +130 -0
- package/src/worker-client.ts +484 -0
- package/src/worker.ts +895 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
export type TelemetryLevel = "off" | "summary" | "trace";
|
|
2
|
+
|
|
3
|
+
const MAX_TRACE_SAMPLES = 240;
|
|
4
|
+
|
|
5
|
+
export type RendererTelemetrySample = {
|
|
6
|
+
frameId: number;
|
|
7
|
+
gpuEpoch: number;
|
|
8
|
+
timingsMs: Record<string, number | null>;
|
|
9
|
+
workload: Record<string, number>;
|
|
10
|
+
recovery: { contextLosses: number; restoreMs: number | null; [key: string]: number | null };
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type TelemetryDistribution = {
|
|
15
|
+
p50: number;
|
|
16
|
+
p95: number;
|
|
17
|
+
max: number;
|
|
18
|
+
samples: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type RendererTelemetrySnapshot = {
|
|
22
|
+
schemaVersion: 1;
|
|
23
|
+
level: Exclude<TelemetryLevel, "off">;
|
|
24
|
+
last: RendererTelemetrySample;
|
|
25
|
+
summary: Record<string, TelemetryDistribution>;
|
|
26
|
+
recordedFrames: number;
|
|
27
|
+
droppedFrames: number;
|
|
28
|
+
samples?: RendererTelemetrySample[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export class RendererTelemetry {
|
|
32
|
+
private readonly level: TelemetryLevel;
|
|
33
|
+
private readonly maxSamples: number;
|
|
34
|
+
private readonly summaryEvery: number;
|
|
35
|
+
private readonly samples: RendererTelemetrySample[] = [];
|
|
36
|
+
private readonly timingValues = new Map<string, number[]>();
|
|
37
|
+
private last: RendererTelemetrySample | null = null;
|
|
38
|
+
private recordedFrames = 0;
|
|
39
|
+
private droppedFrames = 0;
|
|
40
|
+
private cachedSummary: Record<string, TelemetryDistribution> = {};
|
|
41
|
+
|
|
42
|
+
constructor(options: { level?: TelemetryLevel; maxSamples?: number; summaryEvery?: number } = {}) {
|
|
43
|
+
this.level = options.level ?? "summary";
|
|
44
|
+
const requestedSamples = options.maxSamples ?? MAX_TRACE_SAMPLES;
|
|
45
|
+
this.maxSamples = Number.isFinite(requestedSamples)
|
|
46
|
+
? Math.min(MAX_TRACE_SAMPLES, Math.max(1, Math.trunc(requestedSamples)))
|
|
47
|
+
: MAX_TRACE_SAMPLES;
|
|
48
|
+
this.summaryEvery = Math.max(1, options.summaryEvery ?? 30);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
record(sample: RendererTelemetrySample): void {
|
|
52
|
+
if (this.level === "off") return;
|
|
53
|
+
assertTelemetryPrivacy(sample);
|
|
54
|
+
const sanitized = structuredClone(sample);
|
|
55
|
+
this.last = sanitized;
|
|
56
|
+
this.recordedFrames += 1;
|
|
57
|
+
for (const [name, value] of Object.entries(sample.timingsMs)) {
|
|
58
|
+
if (value == null || !Number.isFinite(value)) continue;
|
|
59
|
+
const values = this.timingValues.get(name) ?? [];
|
|
60
|
+
values.push(value);
|
|
61
|
+
if (values.length > this.maxSamples) values.splice(0, values.length - this.maxSamples);
|
|
62
|
+
this.timingValues.set(name, values);
|
|
63
|
+
}
|
|
64
|
+
if (this.level === "trace") {
|
|
65
|
+
this.samples.push(sanitized);
|
|
66
|
+
if (this.samples.length > this.maxSamples) {
|
|
67
|
+
this.droppedFrames += this.samples.length - this.maxSamples;
|
|
68
|
+
this.samples.splice(0, this.samples.length - this.maxSamples);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (this.recordedFrames % this.summaryEvery === 0 || Object.keys(this.cachedSummary).length === 0) {
|
|
72
|
+
this.cachedSummary = summarize(this.timingValues);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
snapshot(): RendererTelemetrySnapshot | null {
|
|
77
|
+
if (this.level === "off" || !this.last) return null;
|
|
78
|
+
const snapshot: RendererTelemetrySnapshot = {
|
|
79
|
+
schemaVersion: 1,
|
|
80
|
+
level: this.level,
|
|
81
|
+
last: structuredClone(this.last),
|
|
82
|
+
summary: structuredClone(summarize(this.timingValues)),
|
|
83
|
+
recordedFrames: this.recordedFrames,
|
|
84
|
+
droppedFrames: this.droppedFrames,
|
|
85
|
+
};
|
|
86
|
+
if (this.level === "trace") snapshot.samples = structuredClone(this.samples);
|
|
87
|
+
assertTelemetryPrivacy(snapshot);
|
|
88
|
+
return snapshot;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
stats(): { level: TelemetryLevel; recordedFrames: number; retainedSamples: number; droppedFrames: number } {
|
|
92
|
+
return {
|
|
93
|
+
level: this.level,
|
|
94
|
+
recordedFrames: this.recordedFrames,
|
|
95
|
+
retainedSamples: this.samples.length,
|
|
96
|
+
droppedFrames: this.droppedFrames,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type RendererTelemetryOptions = {
|
|
102
|
+
level?: TelemetryLevel;
|
|
103
|
+
maxSamples?: number;
|
|
104
|
+
summaryEvery?: number;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type RendererAtlasSummary = {
|
|
108
|
+
backend: "edt" | "analytic";
|
|
109
|
+
pages: number;
|
|
110
|
+
glyphs: number;
|
|
111
|
+
missingGlyphs: number;
|
|
112
|
+
generation: {
|
|
113
|
+
glyphs: number;
|
|
114
|
+
pixels: number;
|
|
115
|
+
glyphMs: number;
|
|
116
|
+
faceLoadMs: number;
|
|
117
|
+
};
|
|
118
|
+
cache: {
|
|
119
|
+
hits: number;
|
|
120
|
+
misses: number;
|
|
121
|
+
generations: number;
|
|
122
|
+
bytes: number;
|
|
123
|
+
sessionHits: number;
|
|
124
|
+
persistentHits: number;
|
|
125
|
+
persistentMisses: number;
|
|
126
|
+
persistentWritesQueued: number;
|
|
127
|
+
pinnedPages: number;
|
|
128
|
+
pageEvictions: number;
|
|
129
|
+
} | null;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export type RendererGpuSummary = {
|
|
133
|
+
drawCalls: number;
|
|
134
|
+
geometryBuilds: number;
|
|
135
|
+
vertexBytes: number;
|
|
136
|
+
textureUploads: number;
|
|
137
|
+
textureBytes: number;
|
|
138
|
+
stateUploadBytes: number;
|
|
139
|
+
maskUploadBytes: number;
|
|
140
|
+
glyphGeometryBuilds: number;
|
|
141
|
+
isolationBegins: number;
|
|
142
|
+
isolationComposites: number;
|
|
143
|
+
isolationTargetAllocations: number;
|
|
144
|
+
isolationTextureBytes: number;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export type RendererUploadSummary = {
|
|
148
|
+
stateUploadBytes: number;
|
|
149
|
+
maskUploadBytes: number;
|
|
150
|
+
commandMaskUploadBytes: number;
|
|
151
|
+
commandStateUploadBytes: number;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export type RendererRestoreUploadSummary = {
|
|
155
|
+
atlasUploadBytes: number;
|
|
156
|
+
atlasUploadRects: number;
|
|
157
|
+
textureUploads: number;
|
|
158
|
+
textureBytes: number;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export type RendererRuntimeSnapshot = {
|
|
162
|
+
schemaVersion: 1;
|
|
163
|
+
state: "ready" | "context-lost" | "destroyed";
|
|
164
|
+
gpuEpoch: number;
|
|
165
|
+
frames: number;
|
|
166
|
+
bootstrap: {
|
|
167
|
+
textCommands: number;
|
|
168
|
+
glyphInstances: number;
|
|
169
|
+
atlasUploadBytes: number;
|
|
170
|
+
atlasUploadRects: number;
|
|
171
|
+
};
|
|
172
|
+
atlas: RendererAtlasSummary;
|
|
173
|
+
lastGpu: RendererGpuSummary | null;
|
|
174
|
+
updates: RendererUploadSummary;
|
|
175
|
+
recovery: {
|
|
176
|
+
contextLosses: number;
|
|
177
|
+
contextRestores: number;
|
|
178
|
+
restoreFailures: number;
|
|
179
|
+
lastRestoreMs: number | null;
|
|
180
|
+
lastRestoreUploads: RendererRestoreUploadSummary | null;
|
|
181
|
+
};
|
|
182
|
+
telemetry: RendererTelemetrySnapshot | null;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/** Tracks aggregate rendering health without retaining source text, stable ids,
|
|
186
|
+
* resource URLs, font names, or player data. */
|
|
187
|
+
export class RendererRuntimeTelemetry {
|
|
188
|
+
private readonly telemetry: RendererTelemetry;
|
|
189
|
+
private readonly atlas: RendererAtlasSummary;
|
|
190
|
+
private readonly bootstrap: RendererRuntimeSnapshot["bootstrap"];
|
|
191
|
+
private runtimeState: RendererRuntimeSnapshot["state"] = "ready";
|
|
192
|
+
private gpuEpoch = 1;
|
|
193
|
+
private frames = 0;
|
|
194
|
+
private lastGpu: RendererGpuSummary | null = null;
|
|
195
|
+
private updates: RendererUploadSummary = {
|
|
196
|
+
stateUploadBytes: 0,
|
|
197
|
+
maskUploadBytes: 0,
|
|
198
|
+
commandMaskUploadBytes: 0,
|
|
199
|
+
commandStateUploadBytes: 0,
|
|
200
|
+
};
|
|
201
|
+
private contextLosses = 0;
|
|
202
|
+
private contextRestores = 0;
|
|
203
|
+
private restoreFailures = 0;
|
|
204
|
+
private lastRestoreMs: number | null = null;
|
|
205
|
+
private lastRestoreUploads: RendererRestoreUploadSummary | null = null;
|
|
206
|
+
|
|
207
|
+
constructor(
|
|
208
|
+
options: RendererTelemetryOptions,
|
|
209
|
+
atlas: RendererAtlasSummary,
|
|
210
|
+
bootstrap: RendererRuntimeSnapshot["bootstrap"],
|
|
211
|
+
) {
|
|
212
|
+
this.telemetry = new RendererTelemetry(options);
|
|
213
|
+
this.atlas = structuredClone(atlas);
|
|
214
|
+
this.bootstrap = structuredClone(bootstrap);
|
|
215
|
+
assertTelemetryPrivacy(this.atlas);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
state(): RendererRuntimeSnapshot["state"] {
|
|
219
|
+
return this.runtimeState;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
recordDraw(metrics: RendererGpuSummary, submitCpuMs: number): void {
|
|
223
|
+
this.lastGpu = structuredClone(metrics);
|
|
224
|
+
this.frames += 1;
|
|
225
|
+
this.telemetry.record({
|
|
226
|
+
frameId: this.frames,
|
|
227
|
+
gpuEpoch: this.gpuEpoch,
|
|
228
|
+
timingsMs: {
|
|
229
|
+
gpuSubmitCpu: finiteOrNull(submitCpuMs),
|
|
230
|
+
gpuTime: null,
|
|
231
|
+
total: finiteOrNull(submitCpuMs),
|
|
232
|
+
},
|
|
233
|
+
workload: {
|
|
234
|
+
drawCalls: metrics.drawCalls,
|
|
235
|
+
vertexBytes: metrics.vertexBytes,
|
|
236
|
+
textureUploads: metrics.textureUploads,
|
|
237
|
+
textureBytes: metrics.textureBytes,
|
|
238
|
+
dirtyBytes: this.updates.stateUploadBytes
|
|
239
|
+
+ this.updates.maskUploadBytes
|
|
240
|
+
+ this.updates.commandMaskUploadBytes
|
|
241
|
+
+ this.updates.commandStateUploadBytes,
|
|
242
|
+
},
|
|
243
|
+
recovery: {
|
|
244
|
+
contextLosses: this.contextLosses,
|
|
245
|
+
contextRestores: this.contextRestores,
|
|
246
|
+
restoreFailures: this.restoreFailures,
|
|
247
|
+
restoreMs: this.lastRestoreMs,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
recordPatch(patch: Partial<RendererUploadSummary>): void {
|
|
253
|
+
for (const key of Object.keys(this.updates) as Array<keyof RendererUploadSummary>) {
|
|
254
|
+
this.updates[key] += patch[key] ?? 0;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
markContextLost(): void {
|
|
259
|
+
if (this.runtimeState === "destroyed" || this.runtimeState === "context-lost") return;
|
|
260
|
+
this.runtimeState = "context-lost";
|
|
261
|
+
this.contextLosses += 1;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
markContextRestored(restoreMs: number, uploads: RendererRestoreUploadSummary): void {
|
|
265
|
+
if (this.runtimeState === "destroyed") return;
|
|
266
|
+
this.runtimeState = "ready";
|
|
267
|
+
this.contextRestores += 1;
|
|
268
|
+
this.gpuEpoch += 1;
|
|
269
|
+
this.lastRestoreMs = finiteOrNull(restoreMs);
|
|
270
|
+
this.lastRestoreUploads = structuredClone(uploads);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
markRestoreFailed(restoreMs: number): void {
|
|
274
|
+
if (this.runtimeState === "destroyed") return;
|
|
275
|
+
this.runtimeState = "context-lost";
|
|
276
|
+
this.restoreFailures += 1;
|
|
277
|
+
this.lastRestoreMs = finiteOrNull(restoreMs);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
markDestroyed(): void {
|
|
281
|
+
this.runtimeState = "destroyed";
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
snapshot(): RendererRuntimeSnapshot {
|
|
285
|
+
const snapshot: RendererRuntimeSnapshot = {
|
|
286
|
+
schemaVersion: 1,
|
|
287
|
+
state: this.runtimeState,
|
|
288
|
+
gpuEpoch: this.gpuEpoch,
|
|
289
|
+
frames: this.frames,
|
|
290
|
+
bootstrap: structuredClone(this.bootstrap),
|
|
291
|
+
atlas: structuredClone(this.atlas),
|
|
292
|
+
lastGpu: structuredClone(this.lastGpu),
|
|
293
|
+
updates: structuredClone(this.updates),
|
|
294
|
+
recovery: {
|
|
295
|
+
contextLosses: this.contextLosses,
|
|
296
|
+
contextRestores: this.contextRestores,
|
|
297
|
+
restoreFailures: this.restoreFailures,
|
|
298
|
+
lastRestoreMs: this.lastRestoreMs,
|
|
299
|
+
lastRestoreUploads: structuredClone(this.lastRestoreUploads),
|
|
300
|
+
},
|
|
301
|
+
telemetry: this.telemetry.snapshot(),
|
|
302
|
+
};
|
|
303
|
+
assertTelemetryPrivacy(snapshot);
|
|
304
|
+
return snapshot;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function normalizeGpuTimerResult(nanoseconds: number | undefined, disjoint: boolean): number | null {
|
|
309
|
+
if (disjoint || nanoseconds == null || !Number.isFinite(nanoseconds) || nanoseconds < 0) return null;
|
|
310
|
+
return nanoseconds / 1_000_000;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const FORBIDDEN_FIELDS = new Set([
|
|
314
|
+
"rawtext",
|
|
315
|
+
"sourcecontent",
|
|
316
|
+
"userid",
|
|
317
|
+
"profileseq",
|
|
318
|
+
"documentdigest",
|
|
319
|
+
"fontpath",
|
|
320
|
+
"fonturl",
|
|
321
|
+
"character",
|
|
322
|
+
"scalar",
|
|
323
|
+
"cluster",
|
|
324
|
+
]);
|
|
325
|
+
|
|
326
|
+
export function assertTelemetryPrivacy(value: unknown): void {
|
|
327
|
+
visit(value, "telemetry");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function visit(value: unknown, path: string): void {
|
|
331
|
+
if (value == null || typeof value !== "object") return;
|
|
332
|
+
if (Array.isArray(value)) {
|
|
333
|
+
value.forEach((entry, index) => visit(entry, `${path}[${index}]`));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
|
|
337
|
+
const normalized = key.replace(/[_-]/g, "").toLowerCase();
|
|
338
|
+
if (FORBIDDEN_FIELDS.has(normalized)) throw new Error(`forbidden telemetry field ${path}.${key}`);
|
|
339
|
+
visit(child, `${path}.${key}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function summarize(values: ReadonlyMap<string, readonly number[]>): Record<string, TelemetryDistribution> {
|
|
344
|
+
return Object.fromEntries([...values.entries()].map(([name, source]) => {
|
|
345
|
+
const sorted = [...source].sort((a, b) => a - b);
|
|
346
|
+
const quantile = (q: number) => sorted[Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * q))] ?? 0;
|
|
347
|
+
return [name, {
|
|
348
|
+
p50: quantile(0.5),
|
|
349
|
+
p95: quantile(0.95),
|
|
350
|
+
max: quantile(1),
|
|
351
|
+
samples: sorted.length,
|
|
352
|
+
}];
|
|
353
|
+
}));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function finiteOrNull(value: number): number | null {
|
|
357
|
+
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
358
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { GlyphBatchRequest } from "../protocol.js";
|
|
2
|
+
import type { GlyphRasterIdentity } from "../cache/glyphPersistentCache.js";
|
|
3
|
+
|
|
4
|
+
export type GlyphRasterPlan = {
|
|
5
|
+
region: string;
|
|
6
|
+
family: string;
|
|
7
|
+
fontSourceHash: string;
|
|
8
|
+
schemaNamespace: string;
|
|
9
|
+
fontEngineFingerprint: string;
|
|
10
|
+
rasterContractId: string;
|
|
11
|
+
contractId: string;
|
|
12
|
+
backend: "edt" | "analytic";
|
|
13
|
+
supersample: number;
|
|
14
|
+
baseSize: number;
|
|
15
|
+
spread: number;
|
|
16
|
+
atlasWidth: number;
|
|
17
|
+
atlasHeight: number;
|
|
18
|
+
glyphs: Array<{ ch: string; glyphIndex: number; identity: GlyphRasterIdentity }>;
|
|
19
|
+
missing: string[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type AtlasPixelRect = { x: number; y: number; width: number; height: number };
|
|
23
|
+
|
|
24
|
+
export type AtlasPlacement = {
|
|
25
|
+
page: number;
|
|
26
|
+
pageEpoch: number;
|
|
27
|
+
pixelRect: AtlasPixelRect;
|
|
28
|
+
u0: number;
|
|
29
|
+
v0: number;
|
|
30
|
+
u1: number;
|
|
31
|
+
v1: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type AtlasGlyphRecord = {
|
|
35
|
+
key: string;
|
|
36
|
+
glyphIndex: number;
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
advance: number;
|
|
40
|
+
xOffset: number;
|
|
41
|
+
yOffset: number;
|
|
42
|
+
planeBearingX: number;
|
|
43
|
+
planeBearingY: number;
|
|
44
|
+
planeWidth: number;
|
|
45
|
+
planeHeight: number;
|
|
46
|
+
drawable: boolean;
|
|
47
|
+
pixels: Uint8Array;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type AtlasGenerateRequest = GlyphBatchRequest & {
|
|
51
|
+
glyphs: Array<{ key: string; char: string; glyphIndex: number }>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type AtlasStats = {
|
|
55
|
+
pages: number;
|
|
56
|
+
pinnedPages: number;
|
|
57
|
+
glyphs: number;
|
|
58
|
+
atlasBytes: number;
|
|
59
|
+
evictions: number;
|
|
60
|
+
pageAllocations: number;
|
|
61
|
+
hardBudgetBytes: number;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type AtlasResolveResult = {
|
|
65
|
+
leases: number[];
|
|
66
|
+
placements: Array<{ key: string; placement: AtlasPlacement }>;
|
|
67
|
+
missingKeys: string[];
|
|
68
|
+
generated: AtlasGlyphRecord[];
|
|
69
|
+
stats: AtlasStats;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type AtlasPageUpdate = {
|
|
73
|
+
page: number;
|
|
74
|
+
pageWidth: number;
|
|
75
|
+
pageEpoch: number;
|
|
76
|
+
revision: number;
|
|
77
|
+
fullUpload: boolean;
|
|
78
|
+
pixels: Uint8Array;
|
|
79
|
+
dirtyRects: Array<AtlasPixelRect & { pixels: Uint8Array }>;
|
|
80
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export type AuthoringElementId = number;
|
|
2
|
+
|
|
3
|
+
export const AUTHORING_CHECKPOINT_SCHEMA = "allium.renderer-authoring-checkpoint/v1" as const;
|
|
4
|
+
|
|
5
|
+
export type AuthoringCategory =
|
|
6
|
+
| "bondsHonors"
|
|
7
|
+
| "cardMembers"
|
|
8
|
+
| "collections"
|
|
9
|
+
| "generalBackgrounds"
|
|
10
|
+
| "generals"
|
|
11
|
+
| "honors"
|
|
12
|
+
| "others"
|
|
13
|
+
| "shapes"
|
|
14
|
+
| "stamps"
|
|
15
|
+
| "standMembers"
|
|
16
|
+
| "storyBackgrounds"
|
|
17
|
+
| "texts";
|
|
18
|
+
|
|
19
|
+
export type AuthoringCommand =
|
|
20
|
+
| { kind: "create"; page: number; category: AuthoringCategory; element: Record<string, unknown> }
|
|
21
|
+
| { kind: "duplicate"; id: AuthoringElementId }
|
|
22
|
+
| { kind: "delete"; id: AuthoringElementId }
|
|
23
|
+
| {
|
|
24
|
+
kind: "set_transform";
|
|
25
|
+
id: AuthoringElementId;
|
|
26
|
+
position?: [number, number, number] | null;
|
|
27
|
+
scale?: [number, number, number] | null;
|
|
28
|
+
rotation?: [number, number, number, number] | null;
|
|
29
|
+
}
|
|
30
|
+
| { kind: "set_lock"; id: AuthoringElementId; lock: boolean }
|
|
31
|
+
| { kind: "set_visible"; id: AuthoringElementId; visible: boolean }
|
|
32
|
+
| { kind: "set_parameters"; id: AuthoringElementId; values: Record<string, unknown> }
|
|
33
|
+
| { kind: "change_layer"; id: AuthoringElementId; layer: number };
|
|
34
|
+
|
|
35
|
+
export type AuthoringChangeKind = "inserted" | "updated" | "removed";
|
|
36
|
+
|
|
37
|
+
export type AuthoringElementChange = {
|
|
38
|
+
id: AuthoringElementId;
|
|
39
|
+
page: number;
|
|
40
|
+
category: AuthoringCategory;
|
|
41
|
+
kind: AuthoringChangeKind;
|
|
42
|
+
element: Record<string, unknown> | null;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type AuthoringSelection = {
|
|
46
|
+
id: AuthoringElementId;
|
|
47
|
+
page: number;
|
|
48
|
+
category: AuthoringCategory;
|
|
49
|
+
index: number;
|
|
50
|
+
element: Record<string, unknown>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type AuthoringDelta = {
|
|
54
|
+
revision: number;
|
|
55
|
+
changes: AuthoringElementChange[];
|
|
56
|
+
canUndo: boolean;
|
|
57
|
+
canRedo: boolean;
|
|
58
|
+
selectedId: AuthoringElementId | null;
|
|
59
|
+
selected: AuthoringSelection | null;
|
|
60
|
+
pageChanges: AuthoringPageChange[];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type AuthoringPageChangeKind = "inserted" | "removed" | "moved";
|
|
64
|
+
|
|
65
|
+
export type AuthoringPageChange = {
|
|
66
|
+
kind: AuthoringPageChangeKind;
|
|
67
|
+
page: number;
|
|
68
|
+
fromPage: number | null;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type GameProfileDocument = {
|
|
72
|
+
userCustomProfileCards: Array<Record<string, unknown>>;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** Opaque renderer-owned history payload. Callers may persist it but must not edit its internals. */
|
|
76
|
+
export type AuthoringCheckpoint = {
|
|
77
|
+
schema: typeof AUTHORING_CHECKPOINT_SCHEMA;
|
|
78
|
+
document: GameProfileDocument;
|
|
79
|
+
ids: unknown[];
|
|
80
|
+
nextId: number;
|
|
81
|
+
revision: number;
|
|
82
|
+
undo: unknown[];
|
|
83
|
+
redo: unknown[];
|
|
84
|
+
selectedId: AuthoringElementId | null;
|
|
85
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export type StableId = string;
|
|
2
|
+
|
|
3
|
+
export type CoreControlBinding =
|
|
4
|
+
| { kind: "tab_option"; control_id: StableId; value: string }
|
|
5
|
+
| { kind: "scroll_content"; control_id: StableId }
|
|
6
|
+
| { kind: "scroll_thumb"; control_id: StableId }
|
|
7
|
+
| { kind: "scroll_viewport"; control_id: StableId };
|
|
8
|
+
|
|
9
|
+
export type CoreLayerPatch = {
|
|
10
|
+
layer_id: StableId;
|
|
11
|
+
render_mask: boolean | null;
|
|
12
|
+
transform: { dx: number; dy: number } | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type CoreCommandPatch = {
|
|
16
|
+
command_id: StableId;
|
|
17
|
+
slot: number;
|
|
18
|
+
render_mask: boolean | null;
|
|
19
|
+
transform: { dx: number; dy: number } | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type CoreSceneDelta = {
|
|
23
|
+
schema_major: number;
|
|
24
|
+
schema_minor: number;
|
|
25
|
+
scene_id: StableId;
|
|
26
|
+
base_revision: number;
|
|
27
|
+
revision: number;
|
|
28
|
+
tick: number;
|
|
29
|
+
dirty: {
|
|
30
|
+
mask: boolean;
|
|
31
|
+
transform: boolean;
|
|
32
|
+
material: boolean;
|
|
33
|
+
layout: boolean;
|
|
34
|
+
command: boolean;
|
|
35
|
+
atlas: boolean;
|
|
36
|
+
control: boolean;
|
|
37
|
+
};
|
|
38
|
+
patches: CoreLayerPatch[];
|
|
39
|
+
command_patches: CoreCommandPatch[];
|
|
40
|
+
error?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type CoreInteractionRegion = {
|
|
44
|
+
id: StableId;
|
|
45
|
+
layer_id: StableId;
|
|
46
|
+
role: string;
|
|
47
|
+
bounds: { x: number; y: number; width: number; height: number };
|
|
48
|
+
quad: Array<[number, number]>;
|
|
49
|
+
matrix: [number, number, number, number, number, number];
|
|
50
|
+
hit_geometry: Array<[number, number]>;
|
|
51
|
+
clip: Array<[number, number]> | null;
|
|
52
|
+
control_bindings: CoreControlBinding[];
|
|
53
|
+
resolved_data: Record<string, unknown>;
|
|
54
|
+
capabilities: string[];
|
|
55
|
+
render_mask: boolean;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type CoreLayerTableEntry = {
|
|
59
|
+
layer_id: StableId;
|
|
60
|
+
parent_id: StableId | null;
|
|
61
|
+
slot: number;
|
|
62
|
+
subtree_start: number;
|
|
63
|
+
subtree_end: number;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type CoreSceneDump = {
|
|
67
|
+
schema: "allium.scene-dump";
|
|
68
|
+
schema_major: number;
|
|
69
|
+
schema_minor: number;
|
|
70
|
+
coordinate_space: "card-device-v1";
|
|
71
|
+
scene_id: StableId;
|
|
72
|
+
tick: number;
|
|
73
|
+
revisions: Record<string, number>;
|
|
74
|
+
layer_table: CoreLayerTableEntry[];
|
|
75
|
+
layer_tree: Array<[StableId, StableId | null]>;
|
|
76
|
+
layers: Array<Record<string, unknown>>;
|
|
77
|
+
interaction_regions: CoreInteractionRegion[];
|
|
78
|
+
component_controls: Array<Record<string, unknown>>;
|
|
79
|
+
command_states: Array<Record<string, unknown>>;
|
|
80
|
+
telemetry: Record<string, number>;
|
|
81
|
+
error?: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type CoreSceneCreateResponse = {
|
|
85
|
+
handle: number;
|
|
86
|
+
layer_bindings: Array<{ source_key: string; layer_id: StableId }>;
|
|
87
|
+
snapshot: {
|
|
88
|
+
schema_major: number;
|
|
89
|
+
schema_minor: number;
|
|
90
|
+
scene_id: StableId;
|
|
91
|
+
revisions: Record<string, number>;
|
|
92
|
+
layer_table: CoreLayerTableEntry[];
|
|
93
|
+
layer_sources: Array<{
|
|
94
|
+
id: StableId;
|
|
95
|
+
matrix: [number, number, number, number, number, number];
|
|
96
|
+
} & Record<string, unknown>>;
|
|
97
|
+
commands: Array<{
|
|
98
|
+
layer_id: StableId;
|
|
99
|
+
render_mask: boolean;
|
|
100
|
+
transform: { dx: number; dy: number };
|
|
101
|
+
command_start: number;
|
|
102
|
+
command_count: number;
|
|
103
|
+
}>;
|
|
104
|
+
semantic_commands: Array<Record<string, unknown>>;
|
|
105
|
+
interaction_regions: CoreInteractionRegion[];
|
|
106
|
+
component_controls: Array<Record<string, unknown>>;
|
|
107
|
+
command_states: Array<Record<string, unknown>>;
|
|
108
|
+
};
|
|
109
|
+
error?: string;
|
|
110
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type FreeTypeGlyph = {
|
|
2
|
+
key: string;
|
|
3
|
+
region: string;
|
|
4
|
+
family: string;
|
|
5
|
+
font_source_hash: string;
|
|
6
|
+
ch: string;
|
|
7
|
+
glyph_index: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
bearing_x: number;
|
|
11
|
+
bearing_y: number;
|
|
12
|
+
x_offset: number;
|
|
13
|
+
y_offset: number;
|
|
14
|
+
advance: number;
|
|
15
|
+
plane_bearing_x: number;
|
|
16
|
+
plane_bearing_y: number;
|
|
17
|
+
plane_width: number;
|
|
18
|
+
plane_height: number;
|
|
19
|
+
drawable: boolean;
|
|
20
|
+
pixels?: number[] | Uint8Array;
|
|
21
|
+
pixels_base64?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type FreeTypeGlyphMapBatch = {
|
|
25
|
+
region: string;
|
|
26
|
+
family: string;
|
|
27
|
+
font_source_hash: string;
|
|
28
|
+
glyphs: Array<{ ch: string; glyph_index: number }>;
|
|
29
|
+
missing: string[];
|
|
30
|
+
error?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type FreeTypeGlyphBatchPerf = {
|
|
34
|
+
total_ms: number;
|
|
35
|
+
face_load_ms: number;
|
|
36
|
+
glyph_total_ms: number;
|
|
37
|
+
glyph_count: number;
|
|
38
|
+
per_glyph_avg_ms: number;
|
|
39
|
+
total_pixel_count: number;
|
|
40
|
+
avg_pixels_per_glyph: number;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type FreeTypeGlyphBatch = {
|
|
44
|
+
region: string;
|
|
45
|
+
family: string;
|
|
46
|
+
font_source_hash: string;
|
|
47
|
+
base_size: number;
|
|
48
|
+
spread: number;
|
|
49
|
+
glyphs: FreeTypeGlyph[];
|
|
50
|
+
missing: string[];
|
|
51
|
+
perf?: FreeTypeGlyphBatchPerf;
|
|
52
|
+
error?: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function decodeFreeTypePixels(glyph: FreeTypeGlyph): Uint8Array {
|
|
56
|
+
if (glyph.pixels instanceof Uint8Array) return glyph.pixels;
|
|
57
|
+
if (Array.isArray(glyph.pixels)) return new Uint8Array(glyph.pixels);
|
|
58
|
+
if (!glyph.pixels_base64) return new Uint8Array(glyph.width * glyph.height);
|
|
59
|
+
const raw = atob(glyph.pixels_base64);
|
|
60
|
+
const pixels = new Uint8Array(raw.length);
|
|
61
|
+
for (let index = 0; index < raw.length; index += 1) pixels[index] = raw.charCodeAt(index);
|
|
62
|
+
return pixels;
|
|
63
|
+
}
|