@starcried/starlite-inochi2d 0.1.0-alpha.2 → 0.1.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/README.en.md +41 -0
- package/README.md +5 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/controller.d.ts.map +1 -1
- package/dist/controller.js +110 -11
- package/dist/controller.js.map +1 -1
- package/dist/dialogue.d.ts +1 -1
- package/dist/dialogue.d.ts.map +1 -1
- package/dist/dialogue.js +110 -9
- package/dist/dialogue.js.map +1 -1
- package/dist/hit-mask.js +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/locale.d.ts +2 -0
- package/dist/locale.d.ts.map +1 -0
- package/dist/locale.js +16 -0
- package/dist/locale.js.map +1 -0
- package/dist/performance.d.ts +2 -0
- package/dist/performance.d.ts.map +1 -0
- package/dist/performance.js +114 -0
- package/dist/performance.js.map +1 -0
- package/dist/quick-settings.d.ts +2 -0
- package/dist/quick-settings.d.ts.map +1 -0
- package/dist/quick-settings.js +541 -0
- package/dist/quick-settings.js.map +1 -0
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
package/dist/dialogue.js
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
// ../core/dist/dialogue.js
|
|
2
|
+
function resolveDialogueContext(input) {
|
|
3
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
4
|
+
throw new Error("dialogue context must be an object");
|
|
5
|
+
const value = input;
|
|
6
|
+
if (Object.keys(value).some((key) => !["id", "lines", "animation", "triggers", "mode", "order"].includes(key)))
|
|
7
|
+
throw new Error("dialogue context has an unknown field");
|
|
8
|
+
const text = (item, maximum, field) => {
|
|
9
|
+
if (typeof item !== "string" || !item.trim() || new TextEncoder().encode(item.trim()).byteLength > maximum)
|
|
10
|
+
throw new Error(`dialogue context ${field} is invalid`);
|
|
11
|
+
return item.trim();
|
|
12
|
+
};
|
|
13
|
+
const id = text(value.id, 2048, "id");
|
|
14
|
+
const lines = resolveDialogueSentences({ lines: value.lines }, [], "dialogue context").lines;
|
|
15
|
+
if (!Array.isArray(value.triggers) || !value.triggers.length || value.triggers.length > 2 || new Set(value.triggers).size !== value.triggers.length || value.triggers.some((trigger) => trigger !== "click" && trigger !== "idle"))
|
|
16
|
+
throw new Error("dialogue context triggers are invalid");
|
|
17
|
+
if (value.mode !== "replace" && value.mode !== "merge")
|
|
18
|
+
throw new Error("dialogue context mode is invalid");
|
|
19
|
+
if (value.order !== "sequential" && value.order !== "random")
|
|
20
|
+
throw new Error("dialogue context order is invalid");
|
|
21
|
+
return {
|
|
22
|
+
id,
|
|
23
|
+
lines,
|
|
24
|
+
...value.animation !== void 0 ? { animation: text(value.animation, 1024, "animation") } : {},
|
|
25
|
+
triggers: ["click", "idle"].filter((trigger) => value.triggers.includes(trigger)),
|
|
26
|
+
mode: value.mode,
|
|
27
|
+
order: value.order
|
|
28
|
+
};
|
|
29
|
+
}
|
|
2
30
|
function resolveDialogueSentences(input, animations, field) {
|
|
3
31
|
const resolve = (value, path) => {
|
|
4
32
|
if (!Array.isArray(value) || value.length < 1 || value.length > 128) {
|
|
@@ -40,6 +68,10 @@ var DialogueScheduler = class {
|
|
|
40
68
|
#started = false;
|
|
41
69
|
#enterDueAt;
|
|
42
70
|
#nextIdleAt;
|
|
71
|
+
#context = null;
|
|
72
|
+
#contextSignature = "null";
|
|
73
|
+
#contextEntries = [];
|
|
74
|
+
#contextCursor = 0;
|
|
43
75
|
constructor(config, random) {
|
|
44
76
|
this.#config = config;
|
|
45
77
|
this.#random = random;
|
|
@@ -54,6 +86,25 @@ var DialogueScheduler = class {
|
|
|
54
86
|
}
|
|
55
87
|
this.#scheduleIdle(timestampMs);
|
|
56
88
|
}
|
|
89
|
+
/** Preserve the model and first entrance while replacing host-owned text. */
|
|
90
|
+
setContext(input, timestampMs) {
|
|
91
|
+
if (!Number.isFinite(timestampMs))
|
|
92
|
+
throw new Error("dialogue context timestamp is invalid");
|
|
93
|
+
const context = input === null ? null : resolveDialogueContext(input);
|
|
94
|
+
const signature = JSON.stringify(context);
|
|
95
|
+
if (signature === this.#contextSignature)
|
|
96
|
+
return false;
|
|
97
|
+
this.#context = context;
|
|
98
|
+
this.#contextSignature = signature;
|
|
99
|
+
this.#contextCursor = 0;
|
|
100
|
+
this.#contextEntries = context ? [
|
|
101
|
+
...context.lines.map((line) => ({ line, ...context.animation !== void 0 ? { animation: context.animation } : {}, triggers: context.triggers })),
|
|
102
|
+
...context.mode === "merge" ? context.triggers.flatMap((scene) => this.#entries(scene)) : []
|
|
103
|
+
] : [];
|
|
104
|
+
if (this.#started)
|
|
105
|
+
this.#scheduleIdle(timestampMs);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
57
108
|
poll(timestampMs, foregroundActive) {
|
|
58
109
|
if (!this.#started || foregroundActive) {
|
|
59
110
|
return void 0;
|
|
@@ -64,8 +115,8 @@ var DialogueScheduler = class {
|
|
|
64
115
|
this.#scheduleIdle(timestampMs + action.durationMs);
|
|
65
116
|
return action;
|
|
66
117
|
}
|
|
67
|
-
if (this.#config.idle && this.#nextIdleAt !== void 0 && timestampMs >= this.#nextIdleAt) {
|
|
68
|
-
const action = this.#action("idle", this.#config.idle);
|
|
118
|
+
if ((this.#config.idle || this.#context?.triggers.includes("idle")) && this.#nextIdleAt !== void 0 && timestampMs >= this.#nextIdleAt) {
|
|
119
|
+
const action = this.#contextAction("idle") ?? this.#action("idle", this.#config.idle);
|
|
69
120
|
this.#scheduleIdle(timestampMs + action.durationMs);
|
|
70
121
|
return action;
|
|
71
122
|
}
|
|
@@ -73,10 +124,10 @@ var DialogueScheduler = class {
|
|
|
73
124
|
}
|
|
74
125
|
triggerClick(timestampMs) {
|
|
75
126
|
this.deferIdle(timestampMs);
|
|
76
|
-
if (!this.#config.click) {
|
|
127
|
+
if (!this.#config.click && !this.#context?.triggers.includes("click")) {
|
|
77
128
|
return void 0;
|
|
78
129
|
}
|
|
79
|
-
const action = this.#action("click", this.#config.click);
|
|
130
|
+
const action = this.#contextAction("click") ?? this.#action("click", this.#config.click);
|
|
80
131
|
this.#scheduleIdle(timestampMs + action.durationMs);
|
|
81
132
|
return action;
|
|
82
133
|
}
|
|
@@ -86,6 +137,39 @@ var DialogueScheduler = class {
|
|
|
86
137
|
this.#scheduleIdle(timestampMs);
|
|
87
138
|
}
|
|
88
139
|
}
|
|
140
|
+
#entries(scene) {
|
|
141
|
+
const config = this.#config[scene];
|
|
142
|
+
if (!config)
|
|
143
|
+
return [];
|
|
144
|
+
const animations = config.animations?.length ? config.animations : config.animation ? [config.animation] : [];
|
|
145
|
+
if (config.linesByAnimation)
|
|
146
|
+
return animations.flatMap((animation) => (config.linesByAnimation[animation] ?? []).map((line) => ({ line, animation, durationMs: config.durationMs, triggers: [scene] })));
|
|
147
|
+
return (animations.length ? animations : [void 0]).flatMap((animation) => (config.lines ?? []).map((line) => ({ line, ...animation !== void 0 ? { animation } : {}, durationMs: config.durationMs, triggers: [scene] })));
|
|
148
|
+
}
|
|
149
|
+
#contextAction(scene) {
|
|
150
|
+
const context = this.#context;
|
|
151
|
+
if (!context?.triggers.includes(scene))
|
|
152
|
+
return void 0;
|
|
153
|
+
let entry;
|
|
154
|
+
if (context.order === "random") {
|
|
155
|
+
const eligible = this.#contextEntries.filter((item) => item.triggers.includes(scene));
|
|
156
|
+
entry = eligible[Math.floor(this.#unitRandom() * eligible.length)];
|
|
157
|
+
} else {
|
|
158
|
+
for (let offset = 0; offset < this.#contextEntries.length; offset++) {
|
|
159
|
+
const index = (this.#contextCursor + offset) % this.#contextEntries.length;
|
|
160
|
+
const item = this.#contextEntries[index];
|
|
161
|
+
if (!item.triggers.includes(scene))
|
|
162
|
+
continue;
|
|
163
|
+
entry = item;
|
|
164
|
+
this.#contextCursor = (index + 1) % this.#contextEntries.length;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (!entry)
|
|
169
|
+
return void 0;
|
|
170
|
+
const config = this.#config[scene];
|
|
171
|
+
return { scene, line: entry.line, ...entry.animation !== void 0 ? { animation: entry.animation } : {}, durationMs: entry.durationMs ?? (config ? config.durationMs : 4e3) };
|
|
172
|
+
}
|
|
89
173
|
#action(scene, config) {
|
|
90
174
|
const chooseAnimation = () => config.animations?.length ? config.animations[Math.floor(this.#unitRandom() * config.animations.length)] : config.animation;
|
|
91
175
|
const animation = config.linesByAnimation ? chooseAnimation() : void 0;
|
|
@@ -105,11 +189,11 @@ var DialogueScheduler = class {
|
|
|
105
189
|
return;
|
|
106
190
|
}
|
|
107
191
|
const idle = this.#config.idle;
|
|
108
|
-
if (!idle) {
|
|
192
|
+
if (!idle && !this.#context?.triggers.includes("idle")) {
|
|
109
193
|
this.#nextIdleAt = void 0;
|
|
110
194
|
return;
|
|
111
195
|
}
|
|
112
|
-
const interval = idle.intervalMinMs + (idle.intervalMaxMs - idle.intervalMinMs) * this.#unitRandom();
|
|
196
|
+
const interval = (idle ? idle.intervalMinMs : 3e4) + ((idle ? idle.intervalMaxMs : 6e4) - (idle ? idle.intervalMinMs : 3e4)) * this.#unitRandom();
|
|
113
197
|
this.#nextIdleAt = timestampMs + interval;
|
|
114
198
|
}
|
|
115
199
|
#unitRandom() {
|
|
@@ -125,10 +209,12 @@ var DialogueScheduler = class {
|
|
|
125
209
|
var DialogueBubble = class {
|
|
126
210
|
element;
|
|
127
211
|
#host;
|
|
212
|
+
#anchor;
|
|
128
213
|
#until = 0;
|
|
129
214
|
#timeout;
|
|
130
|
-
constructor(host) {
|
|
215
|
+
constructor(host, anchor) {
|
|
131
216
|
this.#host = host;
|
|
217
|
+
this.#anchor = anchor;
|
|
132
218
|
this.element = document.createElement("div");
|
|
133
219
|
this.element.className = "model-widget__bubble";
|
|
134
220
|
this.element.hidden = true;
|
|
@@ -170,8 +256,20 @@ var DialogueBubble = class {
|
|
|
170
256
|
return;
|
|
171
257
|
const host = this.#host.getBoundingClientRect(), bubble = this.element.getBoundingClientRect();
|
|
172
258
|
const width = Math.max(1, document.documentElement.clientWidth || window.innerWidth);
|
|
173
|
-
const
|
|
174
|
-
|
|
259
|
+
const candidate = this.#anchor?.();
|
|
260
|
+
const anchor = candidate && Number.isFinite(candidate.x) && Number.isFinite(candidate.y) ? candidate : void 0;
|
|
261
|
+
const margin = 12, centered = host.left + (anchor?.x ?? host.width / 2) - bubble.width / 2;
|
|
262
|
+
const above = host.top + (anchor?.y ?? 0) >= bubble.height + margin * 2;
|
|
263
|
+
this.element.dataset.modelPlacement = above ? "above" : "below";
|
|
264
|
+
if (anchor) {
|
|
265
|
+
const height = Math.max(1, document.documentElement.clientHeight || window.innerHeight);
|
|
266
|
+
const preferredTop = above ? host.top + anchor.y - margin - bubble.height : host.top + host.height + margin;
|
|
267
|
+
const top = Math.max(margin, Math.min(Math.max(margin, height - bubble.height - margin), preferredTop));
|
|
268
|
+
Object.assign(this.element.style, { left: `${anchor.x}px`, top: `${top - host.top}px`, bottom: "auto" });
|
|
269
|
+
} else {
|
|
270
|
+
for (const property of ["left", "top", "bottom"])
|
|
271
|
+
this.element.style.removeProperty(property);
|
|
272
|
+
}
|
|
175
273
|
const left = Math.max(margin, Math.min(Math.max(margin, width - bubble.width - margin), centered));
|
|
176
274
|
this.element.style.setProperty("--model-bubble-shift-x", `${Math.round(left - centered)}px`);
|
|
177
275
|
}
|
|
@@ -186,6 +284,8 @@ function observePassThroughClicks(surface, onClick) {
|
|
|
186
284
|
let down;
|
|
187
285
|
let ready;
|
|
188
286
|
const inside = (event) => {
|
|
287
|
+
if (event.composedPath().some((target) => target instanceof Element && target.matches("[data-starlite-quick-settings],[data-starlite-widget-restore]")))
|
|
288
|
+
return false;
|
|
189
289
|
const rect = surface.getBoundingClientRect();
|
|
190
290
|
return rect.width > 0 && rect.height > 0 && event.clientX >= rect.left && event.clientX < rect.right && event.clientY >= rect.top && event.clientY < rect.bottom;
|
|
191
291
|
};
|
|
@@ -242,5 +342,6 @@ export {
|
|
|
242
342
|
DialogueBubble,
|
|
243
343
|
DialogueScheduler,
|
|
244
344
|
observePassThroughClicks,
|
|
345
|
+
resolveDialogueContext,
|
|
245
346
|
resolveDialogueSentences
|
|
246
347
|
};
|
package/dist/dialogue.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialogue.js","sourceRoot":"","sources":["../src/dialogue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,wBAAwB,EAAE,wBAAwB,
|
|
1
|
+
{"version":3,"file":"dialogue.js","sourceRoot":"","sources":["../src/dialogue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,wBAAwB,EAA6C,MAAM,0BAA0B,CAAC"}
|
package/dist/hit-mask.js
CHANGED
|
@@ -18,8 +18,8 @@ var RenderedHitMask = class {
|
|
|
18
18
|
if (!listener)
|
|
19
19
|
this.#surface = void 0;
|
|
20
20
|
}
|
|
21
|
-
capture(canvas, timestamp) {
|
|
22
|
-
if (!this.#listener || timestamp - this.#lastTime < 50)
|
|
21
|
+
capture(canvas, timestamp, force = false) {
|
|
22
|
+
if (!this.#listener || !force && timestamp - this.#lastTime < 50)
|
|
23
23
|
return;
|
|
24
24
|
this.#lastTime = timestamp;
|
|
25
25
|
const rect = canvas.getBoundingClientRect();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
export { resolveLocale, type StarLiteLocale } from "./locale.js";
|
|
1
2
|
export { validateInochi2DWidgetConfig } from "./config.js";
|
|
3
|
+
export { QuickSettingsMenu, type QuickSettingsOptions, type QuickSettingItem } from "./quick-settings.js";
|
|
2
4
|
export type { AmbientAnimationConfig, AnimationClip, AnimationInterpolation, AnimationKeyframe, AnimationLane, AnimationPlaybackMode, AnimationPlaybackOptions, AnimationPlaybackResult, DialogueConfig, DialogueSceneConfig, DialogueSceneName, EnterDialogueSceneConfig, IdleDialogueSceneConfig, Inochi2dWasmRuntime, ModelAnimationInfo, ParameterAxis, ParameterBinding, ParameterIds, ParameterTarget, ResolvedParameterBindings, ResolvedParameterTarget, ResolvedWidgetConfig, RuntimeFactory, WidgetConfig, WidgetController, WidgetStatus, } from "./types.js";
|
|
3
5
|
/** Mount an Inochi2D widget and resolve once it is running or in fallback state. */
|
|
4
6
|
export declare function mountInochi2DWidget(element: HTMLElement, config: import("./types.js").WidgetConfig, options?: import("@starcried/starlite-core").MountOptions): Promise<import("./types.js").WidgetController>;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAIjE,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE1G,YAAY,EACV,sBAAsB,EACtB,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,aAAa,EACb,qBAAqB,EACrB,wBAAwB,EACxB,uBAAuB,EACvB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,yBAAyB,EACzB,uBAAuB,EACvB,oBAAoB,EACpB,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,oFAAoF;AACpF,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,OAAO,YAAY,EAAE,YAAY,EACzC,OAAO,GAAE,OAAO,0BAA0B,EAAE,YAAiB,GAC5D,OAAO,CAAC,OAAO,YAAY,EAAE,gBAAgB,CAAC,CAEhD;AAED,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,4BAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
export { resolveLocale } from "./locale.js";
|
|
1
2
|
import { mountWithDependencies } from "./controller.js";
|
|
2
3
|
import { createDefaultDependencies } from "./dependencies.js";
|
|
3
4
|
export { validateInochi2DWidgetConfig } from "./config.js";
|
|
5
|
+
export { QuickSettingsMenu } from "./quick-settings.js";
|
|
4
6
|
/** Mount an Inochi2D widget and resolve once it is running or in fallback state. */
|
|
5
7
|
export async function mountInochi2DWidget(element, config, options = {}) {
|
|
6
8
|
return mountWithDependencies(element, config, createDefaultDependencies(), options.signal);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AA+
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAuB,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAoD,MAAM,qBAAqB,CAAC;AA+B1G,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAoB,EACpB,MAAyC,EACzC,OAAO,GAAoD,EAAE;IAE7D,OAAO,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,yBAAyB,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;AAC7F,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,MAAM,gBAAgB,GAAG,mBAAmB,CAAC"}
|
package/dist/locale.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../src/locale.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/locale.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// ../core/dist/locale.js
|
|
2
|
+
function resolveLocale(language) {
|
|
3
|
+
const value = Array.isArray(language) ? language[0] : language;
|
|
4
|
+
if (typeof value !== "string" || !value.trim())
|
|
5
|
+
return "zh-CN";
|
|
6
|
+
return /^zh(?:[-_]|$)/i.test(value.trim()) ? "zh-CN" : "en";
|
|
7
|
+
}
|
|
8
|
+
function validateLocale(value) {
|
|
9
|
+
if (value !== void 0 && value !== "zh-CN" && value !== "en") {
|
|
10
|
+
throw new Error("locale must be zh-CN or en");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
resolveLocale,
|
|
15
|
+
validateLocale
|
|
16
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale.js","sourceRoot":"","sources":["../src/locale.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAuB,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"performance.d.ts","sourceRoot":"","sources":["../src/performance.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// ../core/dist/performance.js
|
|
2
|
+
function validatePerformanceConfig(value) {
|
|
3
|
+
if (value === void 0)
|
|
4
|
+
return;
|
|
5
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => key !== "show") || "show" in value && value.show !== void 0 && typeof value.show !== "boolean") {
|
|
6
|
+
throw new Error("performance must be an object with an optional boolean show");
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
var FrameSamples = class {
|
|
10
|
+
#values;
|
|
11
|
+
#next = 0;
|
|
12
|
+
#size = 0;
|
|
13
|
+
constructor(capacity = 240) {
|
|
14
|
+
if (!Number.isSafeInteger(capacity) || capacity < 1)
|
|
15
|
+
throw new Error("invalid sample capacity");
|
|
16
|
+
this.#values = new Float64Array(capacity);
|
|
17
|
+
}
|
|
18
|
+
add(value) {
|
|
19
|
+
if (!Number.isFinite(value) || value < 0)
|
|
20
|
+
return;
|
|
21
|
+
this.#values[this.#next] = value;
|
|
22
|
+
this.#next = (this.#next + 1) % this.#values.length;
|
|
23
|
+
this.#size = Math.min(this.#size + 1, this.#values.length);
|
|
24
|
+
}
|
|
25
|
+
reset() {
|
|
26
|
+
this.#next = 0;
|
|
27
|
+
this.#size = 0;
|
|
28
|
+
}
|
|
29
|
+
values() {
|
|
30
|
+
return Array.from({ length: this.#size }, (_, i) => this.#values[(this.#next - this.#size + i + this.#values.length) % this.#values.length]);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var PerformanceOverlay = class {
|
|
34
|
+
element;
|
|
35
|
+
#intervals = new FrameSamples();
|
|
36
|
+
#cpu = new FrameSamples();
|
|
37
|
+
#previous;
|
|
38
|
+
#lastPaint;
|
|
39
|
+
#status = "loading";
|
|
40
|
+
#destroyed = false;
|
|
41
|
+
constructor(host) {
|
|
42
|
+
this.element = document.createElement("output");
|
|
43
|
+
this.element.className = "model-widget__performance";
|
|
44
|
+
this.element.setAttribute("data-model-performance", "");
|
|
45
|
+
this.element.setAttribute("aria-label", "StarLite model performance");
|
|
46
|
+
this.element.setAttribute("aria-live", "off");
|
|
47
|
+
this.element.title = "Rendered FPS and frame-interval p95; CPU p95 is the wall time of model update and WebGL submission, including any driver waits, not measured GPU execution or CPU utilization. The latest 240 frames are sampled. Pausing resets measurements.";
|
|
48
|
+
Object.assign(this.element.style, {
|
|
49
|
+
position: "absolute",
|
|
50
|
+
left: "8px",
|
|
51
|
+
bottom: "8px",
|
|
52
|
+
zIndex: "2",
|
|
53
|
+
maxWidth: "calc(100% - 16px)",
|
|
54
|
+
boxSizing: "border-box",
|
|
55
|
+
padding: "6px 8px",
|
|
56
|
+
borderRadius: "6px",
|
|
57
|
+
background: "rgba(15, 23, 42, .86)",
|
|
58
|
+
color: "#f1f5f9",
|
|
59
|
+
font: "11px/1.5 ui-monospace, SFMono-Regular, Consolas, monospace",
|
|
60
|
+
whiteSpace: "pre-line",
|
|
61
|
+
overflowWrap: "anywhere",
|
|
62
|
+
pointerEvents: "none",
|
|
63
|
+
userSelect: "none",
|
|
64
|
+
textAlign: "left"
|
|
65
|
+
});
|
|
66
|
+
host.append(this.element);
|
|
67
|
+
this.#paintStatus();
|
|
68
|
+
}
|
|
69
|
+
setStatus(status) {
|
|
70
|
+
if (this.#destroyed || status === this.#status)
|
|
71
|
+
return;
|
|
72
|
+
this.#status = status;
|
|
73
|
+
this.#intervals.reset();
|
|
74
|
+
this.#cpu.reset();
|
|
75
|
+
this.#previous = void 0;
|
|
76
|
+
this.#lastPaint = void 0;
|
|
77
|
+
this.#paintStatus();
|
|
78
|
+
}
|
|
79
|
+
recordFrame(timestampMs, cpuMs, details = {}) {
|
|
80
|
+
if (this.#destroyed || this.#status !== "running" || !Number.isFinite(timestampMs))
|
|
81
|
+
return;
|
|
82
|
+
if (this.#previous !== void 0 && timestampMs > this.#previous)
|
|
83
|
+
this.#intervals.add(timestampMs - this.#previous);
|
|
84
|
+
this.#previous = timestampMs;
|
|
85
|
+
this.#cpu.add(cpuMs);
|
|
86
|
+
if (this.#lastPaint !== void 0 && timestampMs - this.#lastPaint < 500)
|
|
87
|
+
return;
|
|
88
|
+
this.#lastPaint = timestampMs;
|
|
89
|
+
const intervals = this.#intervals.values(), cpu = this.#cpu.values();
|
|
90
|
+
const sum = intervals.reduce((total, value) => total + value, 0);
|
|
91
|
+
const fps = sum > 0 ? `${(1e3 * intervals.length / sum).toFixed(1)} FPS` : "FPS warming up";
|
|
92
|
+
const p95 = (values) => values.length ? values.sort((a, b) => a - b)[Math.ceil(values.length * 0.95) - 1].toFixed(1) : "\u2014";
|
|
93
|
+
const lines = [fps, `Frame p95 ${p95(intervals)} ms`, `CPU p95 ${p95(cpu)} ms`];
|
|
94
|
+
if (Number.isFinite(details.calls) && Number.isFinite(details.triangles)) {
|
|
95
|
+
lines.push(`${details.calls} calls \xB7 ${details.triangles.toLocaleString("en-US")} tris`);
|
|
96
|
+
}
|
|
97
|
+
this.element.textContent = lines.join("\n");
|
|
98
|
+
this.element.dataset.modelPerformanceStatus = this.#status;
|
|
99
|
+
}
|
|
100
|
+
destroy() {
|
|
101
|
+
if (this.#destroyed)
|
|
102
|
+
return;
|
|
103
|
+
this.#destroyed = true;
|
|
104
|
+
this.element.remove();
|
|
105
|
+
}
|
|
106
|
+
#paintStatus() {
|
|
107
|
+
this.element.textContent = `StarLite \xB7 ${this.#status}`;
|
|
108
|
+
this.element.dataset.modelPerformanceStatus = this.#status;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
export {
|
|
112
|
+
PerformanceOverlay,
|
|
113
|
+
validatePerformanceConfig
|
|
114
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"performance.js","sourceRoot":"","sources":["../src/performance.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,OAAO,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quick-settings.d.ts","sourceRoot":"","sources":["../src/quick-settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAC"}
|