dsh-wallpaper_share 26.8.22-T
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 +674 -0
- package/README.md +114 -0
- package/README.zh-CN.md +251 -0
- package/cordis.patch.yml +6 -0
- package/lib/client.js +2283 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +3466 -0
- package/package.json +58 -0
- package/tools/scene-renderer/README.md +68 -0
- package/tools/scene-renderer/scene-renderer.mjs +268 -0
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-wallpaper_share",
|
|
3
|
+
"description": "Wallpaper Engine ↔ DeepSeek Harness wallpaper sync (display-only): the page background follows the current Wallpaper Engine wallpaper, with a wallpaper_share conversation-view tab (monitor selector, power toggle, focus mode, render-mode toggle, transparency/blur/shadow sliders)",
|
|
4
|
+
"version": "26.8.22-T",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"dsh": {
|
|
13
|
+
"bundle": {
|
|
14
|
+
"patch": "./cordis.patch.yml"
|
|
15
|
+
},
|
|
16
|
+
"client": {
|
|
17
|
+
"inject": [
|
|
18
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
19
|
+
"@deepseek-ai/dsh-client-ui-theme"
|
|
20
|
+
],
|
|
21
|
+
"platform": "web"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsdown",
|
|
26
|
+
"bundle": "tsdown",
|
|
27
|
+
"typecheck": "tsc --noEmit"
|
|
28
|
+
},
|
|
29
|
+
"license": "GPL-3.0",
|
|
30
|
+
"keywords": [
|
|
31
|
+
"dsh",
|
|
32
|
+
"deepseek-harness",
|
|
33
|
+
"wallpaper-engine",
|
|
34
|
+
"wallpaper",
|
|
35
|
+
"background",
|
|
36
|
+
"plugin"
|
|
37
|
+
],
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/YRN-playmaker/dsh-wallpaper_share.git"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"@types/react": "~18.3.1",
|
|
45
|
+
"tsdown": "^0.22.0",
|
|
46
|
+
"typescript": "^5.6.0"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"lib/index.js",
|
|
50
|
+
"lib/client.js",
|
|
51
|
+
"lib/client.js.map",
|
|
52
|
+
"cordis.patch.yml",
|
|
53
|
+
"README.md",
|
|
54
|
+
"README.zh-CN.md",
|
|
55
|
+
"LICENSE",
|
|
56
|
+
"tools/scene-renderer/"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# scene-renderer(参考 renderer / 协议契约)
|
|
2
|
+
|
|
3
|
+
`scene-renderer.mjs` 是 `dsh-wallpaper_share` 的 **参考 renderer**:它实现
|
|
4
|
+
`SceneAdapter` 约定的进程协议,用于端到端验证「scene.pkg → renderer 子进程 →
|
|
5
|
+
offscreen 帧 → Node 中继 → WebSocket → 浏览器 canvas」这条管线能稳定跑 30fps。
|
|
6
|
+
|
|
7
|
+
> ⚠️ **它不是真正的 Wallpaper Engine Scene renderer**。它输出的是按场景配色
|
|
8
|
+
> 生成的 RGBA 诊断动画,不是 `scene.pkg` 的真实渲染画面。真实 scene 渲染需要你
|
|
9
|
+
> 提供 renderer(见下文「如何接入真 renderer」)。
|
|
10
|
+
|
|
11
|
+
## 协议(与 `src/scene/SceneProtocol.ts` 一致)
|
|
12
|
+
|
|
13
|
+
**控制(stdin,换行分隔 JSON)**
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{"cmd":"load","scene":"C:\\...\\scene.pkg","assets":"C:\\...\\wallpaper_engine\\assets","width":1920,"height":1080,"fps":30,"quality":80}
|
|
17
|
+
{"cmd":"pause"}
|
|
18
|
+
{"cmd":"resume"}
|
|
19
|
+
{"cmd":"resize","width":2560,"height":1440}
|
|
20
|
+
{"cmd":"ping"}
|
|
21
|
+
{"cmd":"stop"}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**帧(stdout,二进制)**
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
[4B LE uint32 payloadLen][payload]
|
|
28
|
+
payload = [1B format][4B LE width][4B LE height][编码/像素字节]
|
|
29
|
+
format: 0=JPEG 1=WebP 2=RGBA 3=BGRA
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
本参考 renderer 使用 `format=2`(RGBA)。真 renderer 建议输出 `format=0`(JPEG)
|
|
33
|
+
或 `1`(WebP)以降低带宽。
|
|
34
|
+
|
|
35
|
+
**状态(stderr)**
|
|
36
|
+
|
|
37
|
+
- `[VERSION]reference-0.1.0` — 启动时自报版本
|
|
38
|
+
- `[STATUS]{"fps":29.8,"frame":893}` — 每秒心跳
|
|
39
|
+
- 其余行 = 人类日志
|
|
40
|
+
|
|
41
|
+
## 运行方式
|
|
42
|
+
|
|
43
|
+
`dsh-wallpaper_share` 会用它**当前宿主 node 进程**直接执行本脚本
|
|
44
|
+
(`process.execPath` + 本文件),无需额外安装。也可手动测试:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
node tools/scene-renderer/scene-renderer.mjs
|
|
48
|
+
# 然后粘贴一行 load 命令并回车
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 如何接入真 renderer
|
|
52
|
+
|
|
53
|
+
把 `CONFIG.sceneRendererPath` 指向一个实现了上述协议的独立可执行文件即可
|
|
54
|
+
(例如 WSL2 里封装的 linux-wallpaperengine 离屏渲染封装)。真 renderer 需要:
|
|
55
|
+
|
|
56
|
+
1. 读取 `scene`(scene.pkg)与 `assets`(WE engine assets 目录);
|
|
57
|
+
2. 离屏渲染(不弹窗)到 framebuffer;
|
|
58
|
+
3. 把每帧按上面格式写到 **stdout**;
|
|
59
|
+
4. 响应 `pause/resume/resize/stop/ping`;
|
|
60
|
+
5. stderr 输出 `[VERSION]` 与(可选)`[STATUS]` 心跳。
|
|
61
|
+
|
|
62
|
+
只要遵守协议,`SceneAdapter` / 帧传输 / 浏览器 canvas / fallback / diag 全部无需改动。
|
|
63
|
+
|
|
64
|
+
## 许可证
|
|
65
|
+
|
|
66
|
+
本参考 renderer 为 MIT,可随 `dsh-wallpaper_share` 一起发布。真 renderer 若是
|
|
67
|
+
GPL,请作为**独立组件**由用户自行提供(`CONFIG.sceneRendererPath`),不要复制其
|
|
68
|
+
源码进本仓库,以保持主项目 MIT。
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scene-renderer.mjs —— dsh-wallpaper_share 的「参考 renderer」。
|
|
4
|
+
*
|
|
5
|
+
* ⚠ 重要:这不是真正的 Wallpaper Engine Scene renderer。它是一个
|
|
6
|
+
* 协议参考实现 / 管线验证桩,用来端到端验证:
|
|
7
|
+
* spawn → stdin JSON 控制 → 二进制帧 stdout → Node 中继 → WebSocket
|
|
8
|
+
* → 浏览器 canvas → 30fps 稳定显示。
|
|
9
|
+
* 它输出的是「按场景配色生成的诊断动画」(RGBA),不是 scene.pkg 的
|
|
10
|
+
* 真实渲染画面。真实 scene 渲染由用户通过 CONFIG.sceneRendererPath
|
|
11
|
+
* 提供的 renderer(如 GPL 的 linux-wallpaperengine 离屏封装)完成。
|
|
12
|
+
*
|
|
13
|
+
* 协议(与 src/scene/SceneProtocol.ts 一致):
|
|
14
|
+
* stdin 换行分隔 JSON 命令:load/pause/resume/resize/stop/ping
|
|
15
|
+
* stdout 帧:`[4B LE payloadLen][1B format][4B LE width][4B LE height][像素/编码字节]`
|
|
16
|
+
* format 2 = RGBA(本参考 renderer 使用)
|
|
17
|
+
* stderr 日志 + `[VERSION]reference-0.1.0` + `[STATUS]{"fps":..,"frame":..}`
|
|
18
|
+
*/
|
|
19
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
20
|
+
import { dirname, join } from 'node:path'
|
|
21
|
+
|
|
22
|
+
const VERSION = 'reference-0.1.0'
|
|
23
|
+
const FORMAT_RGBA = 2
|
|
24
|
+
const STATUS_INTERVAL_MS = 1000
|
|
25
|
+
|
|
26
|
+
// ---- 状态 ----
|
|
27
|
+
let state = 'idle' // idle | running | paused
|
|
28
|
+
let scenePath = ''
|
|
29
|
+
let assetsPath = ''
|
|
30
|
+
let width = 640
|
|
31
|
+
let height = 360
|
|
32
|
+
let fps = 30
|
|
33
|
+
let frame = 0
|
|
34
|
+
let t0 = Date.now()
|
|
35
|
+
let framesThisSecond = 0
|
|
36
|
+
let fpsMeasured = 0
|
|
37
|
+
let colorA = [15, 16, 20]
|
|
38
|
+
let colorB = [46, 160, 67]
|
|
39
|
+
let gradient = null // 预计算背景渐变(rows)
|
|
40
|
+
|
|
41
|
+
// ---- 工具 ----
|
|
42
|
+
function log(line) {
|
|
43
|
+
process.stderr.write('[SceneRenderer] ' + line + '\n')
|
|
44
|
+
}
|
|
45
|
+
function status(obj) {
|
|
46
|
+
process.stderr.write('[STATUS]' + JSON.stringify(obj) + '\n')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sendFrame(rgba) {
|
|
50
|
+
const header = Buffer.alloc(9)
|
|
51
|
+
header[0] = FORMAT_RGBA
|
|
52
|
+
header.writeUInt32LE(width, 1)
|
|
53
|
+
header.writeUInt32LE(height, 5)
|
|
54
|
+
const len = header.length + rgba.length
|
|
55
|
+
const out = Buffer.alloc(4 + len)
|
|
56
|
+
out.writeUInt32LE(len, 0)
|
|
57
|
+
header.copy(out, 4)
|
|
58
|
+
rgba.copy(out, 4 + header.length)
|
|
59
|
+
process.stdout.write(out)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---- 场景读取(只做元数据,不做真实渲染) ----
|
|
63
|
+
function parseSchemeColor(v) {
|
|
64
|
+
try {
|
|
65
|
+
const parts = String(v).trim().split(/\s+/).map(Number)
|
|
66
|
+
if (parts.length < 3 || parts.some((n) => !Number.isFinite(n))) return null
|
|
67
|
+
const max = Math.max(...parts)
|
|
68
|
+
const scale = max > 1.5 ? 1 : 255
|
|
69
|
+
return parts.slice(0, 3).map((n) => Math.max(0, Math.min(255, Math.round(n * scale))))
|
|
70
|
+
} catch {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readSceneMeta(pkg) {
|
|
76
|
+
const dir = dirname(pkg)
|
|
77
|
+
const meta = { title: '', scheme: null, textureW: 0, textureH: 0, entries: 0 }
|
|
78
|
+
try {
|
|
79
|
+
const project = JSON.parse(readFileSync(join(dir, 'project.json'), 'utf8'))
|
|
80
|
+
meta.title = typeof project.title === 'string' ? project.title : ''
|
|
81
|
+
if (project.general?.properties?.schemecolor?.value !== undefined) {
|
|
82
|
+
meta.scheme = parseSchemeColor(project.general.properties.schemecolor.value)
|
|
83
|
+
}
|
|
84
|
+
} catch { /* project.json 不可用 */ }
|
|
85
|
+
try {
|
|
86
|
+
// 粗略扫描 scene.pkg 内最大 JPEG 尺寸(只读尺寸,不解码)
|
|
87
|
+
const b = readFileSync(pkg)
|
|
88
|
+
let pos = 0
|
|
89
|
+
while (pos < b.length - 9) {
|
|
90
|
+
if (b[pos] === 0xff && b[pos + 1] === 0xd8 && b[pos + 2] === 0xff) {
|
|
91
|
+
let scan = pos + 2
|
|
92
|
+
for (let g = 0; scan < b.length - 9 && g < 64; g++) {
|
|
93
|
+
if (b[scan] !== 0xff) { scan++; continue }
|
|
94
|
+
const marker = b[scan + 1]
|
|
95
|
+
if (marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd7)) { scan += 2; continue }
|
|
96
|
+
const len = b.readUInt16BE(scan + 2)
|
|
97
|
+
if (len < 2) break
|
|
98
|
+
if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
|
|
99
|
+
const h = b.readUInt16BE(scan + 5)
|
|
100
|
+
const w = b.readUInt16BE(scan + 7)
|
|
101
|
+
if (w * h > meta.textureW * meta.textureH) { meta.textureW = w; meta.textureH = h }
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
scan += 2 + len
|
|
105
|
+
}
|
|
106
|
+
const eoi = b.indexOf(Buffer.from([0xff, 0xd9]), scan)
|
|
107
|
+
pos = eoi >= 0 ? eoi + 1 : b.length
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
pos++
|
|
111
|
+
}
|
|
112
|
+
} catch { /* scene.pkg 不可读 */ }
|
|
113
|
+
return meta
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---- 诊断动画渲染 ----
|
|
117
|
+
function rebuildGradient() {
|
|
118
|
+
gradient = Buffer.alloc(height * 4)
|
|
119
|
+
for (let y = 0; y < height; y++) {
|
|
120
|
+
const k = y / Math.max(1, height - 1)
|
|
121
|
+
const r = Math.round(colorA[0] + (colorB[0] - colorA[0]) * k)
|
|
122
|
+
const g = Math.round(colorA[1] + (colorB[1] - colorA[1]) * k)
|
|
123
|
+
const b = Math.round(colorA[2] + (colorB[2] - colorA[2]) * k)
|
|
124
|
+
gradient.writeUInt8(r, y * 4)
|
|
125
|
+
gradient.writeUInt8(g, y * 4 + 1)
|
|
126
|
+
gradient.writeUInt8(b, y * 4 + 2)
|
|
127
|
+
gradient.writeUInt8(255, y * 4 + 3)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function renderFrame() {
|
|
132
|
+
if (gradient === null) rebuildGradient()
|
|
133
|
+
const buf = Buffer.alloc(width * height * 4)
|
|
134
|
+
// 背景:逐行渐变
|
|
135
|
+
for (let y = 0; y < height; y++) {
|
|
136
|
+
const src = y * 4
|
|
137
|
+
const dstStart = y * width * 4
|
|
138
|
+
for (let x = 0; x < width; x++) {
|
|
139
|
+
gradient.copy(buf, dstStart + x * 4, src, src + 4)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const t = frame / Math.max(1, fps)
|
|
143
|
+
// 移动的高亮横条(正弦上下扫)
|
|
144
|
+
const barY = Math.round(height / 2 + Math.sin(t * 1.3) * (height / 3))
|
|
145
|
+
for (let x = 0; x < width; x++) {
|
|
146
|
+
for (let dy = -3; dy <= 3; dy++) {
|
|
147
|
+
const y = barY + dy
|
|
148
|
+
if (y < 0 || y >= height) continue
|
|
149
|
+
const i = (y * width + x) * 4
|
|
150
|
+
buf[i] = Math.min(255, buf[i] + 90)
|
|
151
|
+
buf[i + 1] = Math.min(255, buf[i + 1] + 70)
|
|
152
|
+
buf[i + 2] = Math.min(255, buf[i + 2] + 50)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// 轨道圆点
|
|
156
|
+
const cx = width / 2 + Math.cos(t * 0.9) * (Math.min(width, height) * 0.3)
|
|
157
|
+
const cy = height / 2 + Math.sin(t * 0.7) * (Math.min(width, height) * 0.25)
|
|
158
|
+
const radius = Math.max(4, Math.round(Math.min(width, height) * 0.04))
|
|
159
|
+
for (let y = Math.max(0, Math.floor(cy - radius)); y < Math.min(height, Math.ceil(cy + radius)); y++) {
|
|
160
|
+
for (let x = Math.max(0, Math.floor(cx - radius)); x < Math.min(width, Math.ceil(cx + radius)); x++) {
|
|
161
|
+
const dx = x - cx
|
|
162
|
+
const dy = y - cy
|
|
163
|
+
if (dx * dx + dy * dy <= radius * radius) {
|
|
164
|
+
const i = (y * width + x) * 4
|
|
165
|
+
buf[i] = 255
|
|
166
|
+
buf[i + 1] = 255
|
|
167
|
+
buf[i + 2] = 255
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// 底部帧进度条
|
|
172
|
+
const progress = ((frame % (fps * 3)) / (fps * 3)) * width
|
|
173
|
+
for (let y = height - 6; y < height; y++) {
|
|
174
|
+
for (let x = 0; x < width; x++) {
|
|
175
|
+
const i = (y * width + x) * 4
|
|
176
|
+
if (x < progress) {
|
|
177
|
+
buf[i] = 255; buf[i + 1] = 255; buf[i + 2] = 255
|
|
178
|
+
} else {
|
|
179
|
+
buf[i] = 60; buf[i + 1] = 60; buf[i + 2] = 60
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return buf
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function tick() {
|
|
187
|
+
if (state !== 'running') return
|
|
188
|
+
const rgba = renderFrame()
|
|
189
|
+
sendFrame(rgba)
|
|
190
|
+
frame++
|
|
191
|
+
framesThisSecond++
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ---- 命令处理 ----
|
|
195
|
+
function handleLine(line) {
|
|
196
|
+
line = line.trim()
|
|
197
|
+
if (line === '') return
|
|
198
|
+
let cmd
|
|
199
|
+
try { cmd = JSON.parse(line) } catch { log('ignoring non-JSON stdin: ' + line); return }
|
|
200
|
+
switch (cmd.cmd) {
|
|
201
|
+
case 'load': {
|
|
202
|
+
scenePath = cmd.scene ?? ''
|
|
203
|
+
assetsPath = cmd.assets ?? ''
|
|
204
|
+
width = clampInt(cmd.width, 160, 1920, 640)
|
|
205
|
+
height = clampInt(cmd.height, 90, 1080, 360)
|
|
206
|
+
fps = clampInt(cmd.fps, 5, 60, 30)
|
|
207
|
+
const meta = readSceneMeta(scenePath)
|
|
208
|
+
if (meta.scheme !== null) { colorA = [Math.round(meta.scheme[0] * 0.25), Math.round(meta.scheme[1] * 0.25), Math.round(meta.scheme[2] * 0.25)]; colorB = meta.scheme }
|
|
209
|
+
gradient = null
|
|
210
|
+
frame = 0
|
|
211
|
+
log('Scene loaded: ' + (meta.title || scenePath) + ' (texture ' + meta.textureW + 'x' + meta.textureH + ', assets ' + (existsSync(join(assetsPath, 'shaders')) ? 'ok' : 'missing') + ')')
|
|
212
|
+
log('Streaming ' + width + 'x' + height + ' @' + fps + 'fps (RGBA diagnostic animation, NOT real scene render)')
|
|
213
|
+
state = 'running'
|
|
214
|
+
restartTick()
|
|
215
|
+
status({ fps: 0, frame: 0 })
|
|
216
|
+
break
|
|
217
|
+
}
|
|
218
|
+
case 'pause': state = 'paused'; log('paused'); break
|
|
219
|
+
case 'resume': state = 'running'; t0 = Date.now(); log('resumed'); break
|
|
220
|
+
case 'resize':
|
|
221
|
+
width = clampInt(cmd.width, 160, 3840, width)
|
|
222
|
+
height = clampInt(cmd.height, 90, 2160, height)
|
|
223
|
+
gradient = null
|
|
224
|
+
log('resized to ' + width + 'x' + height)
|
|
225
|
+
break
|
|
226
|
+
case 'ping': status({ fps: fpsMeasured, frame }); break
|
|
227
|
+
case 'stop': log('stopping'); state = 'idle'; setTimeout(() => process.exit(0), 20); break
|
|
228
|
+
default: log('unknown cmd: ' + (cmd.cmd ?? ''))
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function clampInt(v, min, max, dflt) {
|
|
233
|
+
const n = Math.round(Number(v))
|
|
234
|
+
if (!Number.isFinite(n)) return dflt
|
|
235
|
+
return Math.max(min, Math.min(max, n))
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ---- 启动 ----
|
|
239
|
+
process.stderr.write('[VERSION]' + VERSION + '\n')
|
|
240
|
+
log('reference scene renderer started (pid ' + process.pid + ')')
|
|
241
|
+
|
|
242
|
+
let tickTimer = null
|
|
243
|
+
function restartTick() {
|
|
244
|
+
if (tickTimer !== null) clearInterval(tickTimer)
|
|
245
|
+
tickTimer = setInterval(tick, 1000 / fps)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// stdin 换行缓冲
|
|
249
|
+
let stdinBuf = ''
|
|
250
|
+
process.stdin.on('data', (chunk) => {
|
|
251
|
+
stdinBuf += chunk.toString('utf8')
|
|
252
|
+
let nl = stdinBuf.indexOf('\n')
|
|
253
|
+
while (nl >= 0) {
|
|
254
|
+
handleLine(stdinBuf.slice(0, nl))
|
|
255
|
+
stdinBuf = stdinBuf.slice(nl + 1)
|
|
256
|
+
nl = stdinBuf.indexOf('\n')
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
process.stdin.on('end', () => process.exit(0))
|
|
260
|
+
process.on('SIGTERM', () => process.exit(0))
|
|
261
|
+
|
|
262
|
+
// 帧循环(load 后按实际 fps 重建)+ 每秒状态上报
|
|
263
|
+
restartTick()
|
|
264
|
+
setInterval(() => {
|
|
265
|
+
fpsMeasured = framesThisSecond
|
|
266
|
+
framesThisSecond = 0
|
|
267
|
+
if (state === 'running') status({ fps: fpsMeasured, frame })
|
|
268
|
+
}, STATUS_INTERVAL_MS)
|