opencode-extended-sidebar 0.2.90 → 0.2.91
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/.oesignore +17 -17
- package/CHANGELOG.md +96 -95
- package/LICENSE +21 -21
- package/README.md +150 -235
- package/oes.json +13 -11
- package/package.json +100 -100
- package/scripts/install-git-hooks.mjs +29 -29
- package/src/pware.oc.core/constants/index.ts +18 -18
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -29
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +15 -13
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -241
- package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -55
- package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -65
- package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -58
- package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -63
- package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -34
- package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -74
- package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -69
- package/src/pware.oc.core/git/index.ts +7 -7
- package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -237
- package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -87
- package/src/pware.oc.core/index.ts +19 -18
- package/src/pware.oc.core/pware.oc.core.bus.ts +42 -42
- package/src/pware.oc.core/pware.oc.core.cache.ts +48 -48
- package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -97
- package/src/pware.oc.core/pware.oc.core.debug.ts +280 -280
- package/src/pware.oc.core/pware.oc.core.events.ts +79 -79
- package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -135
- package/src/pware.oc.core/pware.oc.core.layout.ts +234 -234
- package/src/pware.oc.core/pware.oc.core.oes.ts +99 -89
- package/src/pware.oc.core/pware.oc.core.paths.ts +240 -240
- package/src/pware.oc.core/pware.oc.core.preview.ts +74 -74
- package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -726
- package/src/pware.oc.core/pware.oc.core.runtimeConfig.ts +41 -0
- package/src/pware.oc.core/pware.oc.core.sqlite.ts +164 -175
- package/src/pware.oc.core/pware.oc.core.sqliteGateway.ts +89 -0
- package/src/pware.oc.core/pware.oc.core.status.ts +214 -214
- package/src/pware.oc.core/pware.oc.core.timing.ts +63 -63
- package/src/pware.oc.core/pware.oc.core.width.ts +215 -215
- package/src/pware.oc.omo/constants/index.ts +15 -15
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -18
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -49
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -29
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -11
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -60
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -49
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -25
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -38
- package/src/pware.oc.omo/index.ts +7 -7
- package/src/pware.oc.omo/resolver/index.ts +19 -19
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -179
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -116
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -34
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -503
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -40
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -269
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -155
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +363 -361
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -11
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -14
- package/src/pware.oc.opencode/constants/index.ts +10 -10
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -15
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -18
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -45
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -58
- package/src/pware.oc.opencode/index.ts +8 -8
- package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -182
- package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -399
- package/src/pware.oc.opencode/resolver/index.ts +223 -170
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +150 -162
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +149 -185
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +248 -211
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +22 -26
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +224 -236
- package/src/pware.oc.perf/index.ts +14 -14
- package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -15
- package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -284
- package/src/pware.oc.perf/pware.oc.perf.reader.ts +962 -927
- package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -88
- package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -118
- package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -79
- package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -71
- package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -214
- package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -179
- package/src/pware.oc.perf/pware.oc.perf.view.tsx +463 -462
- package/src/pware.oc.runtime/index.ts +13 -13
- package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -142
- package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +87 -79
- package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +282 -253
- package/src/pware.oc.runtime/pware.oc.runtime.omoRead.ts +37 -0
- package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +83 -95
- package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +130 -129
- package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -105
- package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +44 -43
- package/src/pware.oc.runtime/resolver/index.ts +268 -224
- package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -146
- package/src/pware.oc.ui/index.ts +12 -12
- package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +381 -339
- package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -144
- package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -111
- package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -82
- package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -843
- package/src/pware.oc.ui/pware.oc.ui.sections.tsx +552 -526
- package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1718 -1629
- package/src/pware.oc.ui.tsx +98 -98
|
@@ -1,179 +1,179 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin self-cost: event/scan/tick latency + renderer FPS. Pure accumulators, safe on any host.
|
|
3
|
-
*/
|
|
4
|
-
import {
|
|
5
|
-
SELF_PHASE_EVENT,
|
|
6
|
-
SELF_PHASE_SCAN,
|
|
7
|
-
SELF_PHASE_TICK,
|
|
8
|
-
type SelfPhase,
|
|
9
|
-
} from "../pware.oc.core/constants/pware.oc.core.constants.phase.js"
|
|
10
|
-
import { debugActive, profileActive } from "../pware.oc.core/pware.oc.core.debug.js"
|
|
11
|
-
|
|
12
|
-
export type { SelfPhase }
|
|
13
|
-
|
|
14
|
-
export type SelfBucket = { n: number; sum: number; max: number }
|
|
15
|
-
|
|
16
|
-
export type SelfStats = {
|
|
17
|
-
[SELF_PHASE_EVENT]: SelfBucket
|
|
18
|
-
[SELF_PHASE_SCAN]: SelfBucket
|
|
19
|
-
[SELF_PHASE_TICK]: SelfBucket
|
|
20
|
-
/** Last known renderer FPS, null until first read. */
|
|
21
|
-
fps: number | null
|
|
22
|
-
/** Last known average frame time in ms, null until first read. */
|
|
23
|
-
frameMs: number | null
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const PHASES: SelfPhase[] = [SELF_PHASE_EVENT, SELF_PHASE_SCAN, SELF_PHASE_TICK]
|
|
27
|
-
|
|
28
|
-
const buckets: Record<SelfPhase, SelfBucket> = {
|
|
29
|
-
[SELF_PHASE_EVENT]: { n: 0, sum: 0, max: 0 },
|
|
30
|
-
[SELF_PHASE_SCAN]: { n: 0, sum: 0, max: 0 },
|
|
31
|
-
[SELF_PHASE_TICK]: { n: 0, sum: 0, max: 0 },
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let fps: number | null = null
|
|
35
|
-
let frameMs: number | null = null
|
|
36
|
-
|
|
37
|
-
/** Test override for the diagnostic gate — null delegates to the env-driven loggers. */
|
|
38
|
-
let selfDiagForced: boolean | null = null
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* True while a debug or profile logger is active. Self-cost instrumentation is
|
|
42
|
-
* purely diagnostic, so production (neither env var set) pays nothing for it.
|
|
43
|
-
*/
|
|
44
|
-
export function selfDiagActive(): boolean {
|
|
45
|
-
return selfDiagForced ?? (debugActive() || profileActive())
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Test hook — force the gate on/off without touching process.env. */
|
|
49
|
-
export function setSelfDiagForced(on: boolean | null): void {
|
|
50
|
-
selfDiagForced = on
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Measures fn with performance.now(), accumulates into bucket `label`, returns fn's result.
|
|
55
|
-
* When neither the debug nor the profile logger is active the fn runs bare — no clock read,
|
|
56
|
-
* no accumulator writes. Throws propagate.
|
|
57
|
-
*/
|
|
58
|
-
export function selfTime<T>(label: SelfPhase, fn: () => T): T {
|
|
59
|
-
if (!selfDiagActive()) return fn()
|
|
60
|
-
const start = performance.now()
|
|
61
|
-
try {
|
|
62
|
-
return fn()
|
|
63
|
-
} finally {
|
|
64
|
-
const elapsed = performance.now() - start
|
|
65
|
-
const b = buckets[label]
|
|
66
|
-
b.n += 1
|
|
67
|
-
b.sum += elapsed
|
|
68
|
-
if (elapsed > b.max) b.max = elapsed
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Shallow copy of current stats (never hand out the internal object). */
|
|
73
|
-
export function readSelfStats(): SelfStats {
|
|
74
|
-
return {
|
|
75
|
-
event: { ...buckets.event },
|
|
76
|
-
scan: { ...buckets.scan },
|
|
77
|
-
tick: { ...buckets.tick },
|
|
78
|
-
fps,
|
|
79
|
-
frameMs,
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Zero all buckets and fps/frameMs. Test helper + used on remount. */
|
|
84
|
-
export function resetSelfStats(): void {
|
|
85
|
-
for (const phase of PHASES) {
|
|
86
|
-
buckets[phase].n = 0
|
|
87
|
-
buckets[phase].sum = 0
|
|
88
|
-
buckets[phase].max = 0
|
|
89
|
-
}
|
|
90
|
-
fps = null
|
|
91
|
-
frameMs = null
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Record the latest renderer read. fps/frameMs null clears. */
|
|
95
|
-
export function setSelfFps(nextFps: number | null, nextFrameMs: number | null): void {
|
|
96
|
-
fps = nextFps
|
|
97
|
-
frameMs = nextFrameMs
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function finitePositive(v: unknown): v is number {
|
|
101
|
-
return typeof v === "number" && Number.isFinite(v) && v > 0
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
type RendererLike = {
|
|
105
|
-
getStats?: unknown
|
|
106
|
-
getNativeStats?: unknown
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Null-safe renderer stats read. Accepts any object (the host's CliRenderer).
|
|
111
|
-
* Tries getStats() first (has .fps), falls back to getNativeStats().averageFrameTime,
|
|
112
|
-
* returns { fps, frameMs } where both are numbers|null. Never throws — any missing
|
|
113
|
-
* method / exception yields { fps: null, frameMs: null }.
|
|
114
|
-
*/
|
|
115
|
-
export function readRendererFps(renderer: unknown): { fps: number | null; frameMs: number | null } {
|
|
116
|
-
let outFps: number | null = null
|
|
117
|
-
let outFrameMs: number | null = null
|
|
118
|
-
if (renderer === null || typeof renderer !== "object") return { fps: outFps, frameMs: outFrameMs }
|
|
119
|
-
|
|
120
|
-
const r = renderer as RendererLike
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
if (typeof r.getStats === "function") {
|
|
124
|
-
const stats: unknown = r.getStats.call(renderer)
|
|
125
|
-
if (stats !== null && typeof stats === "object") {
|
|
126
|
-
const raw = (stats as { fps?: unknown }).fps
|
|
127
|
-
if (finitePositive(raw)) outFps = Math.round(raw)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
} catch {
|
|
131
|
-
// getStats threw — leave outFps null and fall back below
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (outFps === null) {
|
|
135
|
-
try {
|
|
136
|
-
if (typeof r.getNativeStats === "function") {
|
|
137
|
-
const stats: unknown = r.getNativeStats.call(renderer)
|
|
138
|
-
if (stats !== null && typeof stats === "object") {
|
|
139
|
-
const avg = (stats as { averageFrameTime?: unknown }).averageFrameTime
|
|
140
|
-
if (finitePositive(avg)) outFrameMs = Math.round(avg * 10) / 10
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
} catch {
|
|
144
|
-
// getNativeStats threw — leave outFrameMs null
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return { fps: outFps, frameMs: outFrameMs }
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Round ms to 1 decimal for sub-second self timings (the reader's formatDuration is coarse). */
|
|
152
|
-
function ms1(x: number): string {
|
|
153
|
-
return String(Math.round(x * 10) / 10)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function phasePart(b: SelfBucket, suffix: string): string | null {
|
|
157
|
-
if (b.n === 0) return null
|
|
158
|
-
return `${ms1(b.sum / b.n)}ms/${suffix}`
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Pure line text for the status row, e.g. "self 0.4ms/ev · 1.2ms/sc · 59fps".
|
|
163
|
-
*/
|
|
164
|
-
export function formatSelfLine(stats: SelfStats): string {
|
|
165
|
-
const parts: string[] = []
|
|
166
|
-
const ev = phasePart(stats.event, "ev")
|
|
167
|
-
const sc = phasePart(stats.scan, "sc")
|
|
168
|
-
const tk = phasePart(stats.tick, "tk")
|
|
169
|
-
if (ev) parts.push(ev)
|
|
170
|
-
if (sc) parts.push(sc)
|
|
171
|
-
if (tk) parts.push(tk)
|
|
172
|
-
if (stats.fps != null) {
|
|
173
|
-
parts.push(`${stats.fps}fps`)
|
|
174
|
-
} else if (stats.frameMs != null) {
|
|
175
|
-
parts.push(`${String(Math.round(stats.frameMs))}ms/f`)
|
|
176
|
-
}
|
|
177
|
-
if (parts.length === 0) return "self —"
|
|
178
|
-
return `self ${parts.join(" · ")}`
|
|
179
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Plugin self-cost: event/scan/tick latency + renderer FPS. Pure accumulators, safe on any host.
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
SELF_PHASE_EVENT,
|
|
6
|
+
SELF_PHASE_SCAN,
|
|
7
|
+
SELF_PHASE_TICK,
|
|
8
|
+
type SelfPhase,
|
|
9
|
+
} from "../pware.oc.core/constants/pware.oc.core.constants.phase.js"
|
|
10
|
+
import { debugActive, profileActive } from "../pware.oc.core/pware.oc.core.debug.js"
|
|
11
|
+
|
|
12
|
+
export type { SelfPhase }
|
|
13
|
+
|
|
14
|
+
export type SelfBucket = { n: number; sum: number; max: number }
|
|
15
|
+
|
|
16
|
+
export type SelfStats = {
|
|
17
|
+
[SELF_PHASE_EVENT]: SelfBucket
|
|
18
|
+
[SELF_PHASE_SCAN]: SelfBucket
|
|
19
|
+
[SELF_PHASE_TICK]: SelfBucket
|
|
20
|
+
/** Last known renderer FPS, null until first read. */
|
|
21
|
+
fps: number | null
|
|
22
|
+
/** Last known average frame time in ms, null until first read. */
|
|
23
|
+
frameMs: number | null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const PHASES: SelfPhase[] = [SELF_PHASE_EVENT, SELF_PHASE_SCAN, SELF_PHASE_TICK]
|
|
27
|
+
|
|
28
|
+
const buckets: Record<SelfPhase, SelfBucket> = {
|
|
29
|
+
[SELF_PHASE_EVENT]: { n: 0, sum: 0, max: 0 },
|
|
30
|
+
[SELF_PHASE_SCAN]: { n: 0, sum: 0, max: 0 },
|
|
31
|
+
[SELF_PHASE_TICK]: { n: 0, sum: 0, max: 0 },
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let fps: number | null = null
|
|
35
|
+
let frameMs: number | null = null
|
|
36
|
+
|
|
37
|
+
/** Test override for the diagnostic gate — null delegates to the env-driven loggers. */
|
|
38
|
+
let selfDiagForced: boolean | null = null
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* True while a debug or profile logger is active. Self-cost instrumentation is
|
|
42
|
+
* purely diagnostic, so production (neither env var set) pays nothing for it.
|
|
43
|
+
*/
|
|
44
|
+
export function selfDiagActive(): boolean {
|
|
45
|
+
return selfDiagForced ?? (debugActive() || profileActive())
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Test hook — force the gate on/off without touching process.env. */
|
|
49
|
+
export function setSelfDiagForced(on: boolean | null): void {
|
|
50
|
+
selfDiagForced = on
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Measures fn with performance.now(), accumulates into bucket `label`, returns fn's result.
|
|
55
|
+
* When neither the debug nor the profile logger is active the fn runs bare — no clock read,
|
|
56
|
+
* no accumulator writes. Throws propagate.
|
|
57
|
+
*/
|
|
58
|
+
export function selfTime<T>(label: SelfPhase, fn: () => T): T {
|
|
59
|
+
if (!selfDiagActive()) return fn()
|
|
60
|
+
const start = performance.now()
|
|
61
|
+
try {
|
|
62
|
+
return fn()
|
|
63
|
+
} finally {
|
|
64
|
+
const elapsed = performance.now() - start
|
|
65
|
+
const b = buckets[label]
|
|
66
|
+
b.n += 1
|
|
67
|
+
b.sum += elapsed
|
|
68
|
+
if (elapsed > b.max) b.max = elapsed
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Shallow copy of current stats (never hand out the internal object). */
|
|
73
|
+
export function readSelfStats(): SelfStats {
|
|
74
|
+
return {
|
|
75
|
+
event: { ...buckets.event },
|
|
76
|
+
scan: { ...buckets.scan },
|
|
77
|
+
tick: { ...buckets.tick },
|
|
78
|
+
fps,
|
|
79
|
+
frameMs,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Zero all buckets and fps/frameMs. Test helper + used on remount. */
|
|
84
|
+
export function resetSelfStats(): void {
|
|
85
|
+
for (const phase of PHASES) {
|
|
86
|
+
buckets[phase].n = 0
|
|
87
|
+
buckets[phase].sum = 0
|
|
88
|
+
buckets[phase].max = 0
|
|
89
|
+
}
|
|
90
|
+
fps = null
|
|
91
|
+
frameMs = null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Record the latest renderer read. fps/frameMs null clears. */
|
|
95
|
+
export function setSelfFps(nextFps: number | null, nextFrameMs: number | null): void {
|
|
96
|
+
fps = nextFps
|
|
97
|
+
frameMs = nextFrameMs
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function finitePositive(v: unknown): v is number {
|
|
101
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type RendererLike = {
|
|
105
|
+
getStats?: unknown
|
|
106
|
+
getNativeStats?: unknown
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Null-safe renderer stats read. Accepts any object (the host's CliRenderer).
|
|
111
|
+
* Tries getStats() first (has .fps), falls back to getNativeStats().averageFrameTime,
|
|
112
|
+
* returns { fps, frameMs } where both are numbers|null. Never throws — any missing
|
|
113
|
+
* method / exception yields { fps: null, frameMs: null }.
|
|
114
|
+
*/
|
|
115
|
+
export function readRendererFps(renderer: unknown): { fps: number | null; frameMs: number | null } {
|
|
116
|
+
let outFps: number | null = null
|
|
117
|
+
let outFrameMs: number | null = null
|
|
118
|
+
if (renderer === null || typeof renderer !== "object") return { fps: outFps, frameMs: outFrameMs }
|
|
119
|
+
|
|
120
|
+
const r = renderer as RendererLike
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
if (typeof r.getStats === "function") {
|
|
124
|
+
const stats: unknown = r.getStats.call(renderer)
|
|
125
|
+
if (stats !== null && typeof stats === "object") {
|
|
126
|
+
const raw = (stats as { fps?: unknown }).fps
|
|
127
|
+
if (finitePositive(raw)) outFps = Math.round(raw)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// getStats threw — leave outFps null and fall back below
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (outFps === null) {
|
|
135
|
+
try {
|
|
136
|
+
if (typeof r.getNativeStats === "function") {
|
|
137
|
+
const stats: unknown = r.getNativeStats.call(renderer)
|
|
138
|
+
if (stats !== null && typeof stats === "object") {
|
|
139
|
+
const avg = (stats as { averageFrameTime?: unknown }).averageFrameTime
|
|
140
|
+
if (finitePositive(avg)) outFrameMs = Math.round(avg * 10) / 10
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// getNativeStats threw — leave outFrameMs null
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { fps: outFps, frameMs: outFrameMs }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Round ms to 1 decimal for sub-second self timings (the reader's formatDuration is coarse). */
|
|
152
|
+
function ms1(x: number): string {
|
|
153
|
+
return String(Math.round(x * 10) / 10)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function phasePart(b: SelfBucket, suffix: string): string | null {
|
|
157
|
+
if (b.n === 0) return null
|
|
158
|
+
return `${ms1(b.sum / b.n)}ms/${suffix}`
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Pure line text for the status row, e.g. "self 0.4ms/ev · 1.2ms/sc · 59fps".
|
|
163
|
+
*/
|
|
164
|
+
export function formatSelfLine(stats: SelfStats): string {
|
|
165
|
+
const parts: string[] = []
|
|
166
|
+
const ev = phasePart(stats.event, "ev")
|
|
167
|
+
const sc = phasePart(stats.scan, "sc")
|
|
168
|
+
const tk = phasePart(stats.tick, "tk")
|
|
169
|
+
if (ev) parts.push(ev)
|
|
170
|
+
if (sc) parts.push(sc)
|
|
171
|
+
if (tk) parts.push(tk)
|
|
172
|
+
if (stats.fps != null) {
|
|
173
|
+
parts.push(`${stats.fps}fps`)
|
|
174
|
+
} else if (stats.frameMs != null) {
|
|
175
|
+
parts.push(`${String(Math.round(stats.frameMs))}ms/f`)
|
|
176
|
+
}
|
|
177
|
+
if (parts.length === 0) return "self —"
|
|
178
|
+
return `self ${parts.join(" · ")}`
|
|
179
|
+
}
|