Skip to content

Commit 13c268f

Browse files
committed
feat: Implement FPS testing functionality and enhance frame processing
- Added a new FPS test HTML page to visualize and measure frame rendering times. - Introduced `FpsTestRunner` class to manage FPS testing stages and results. - Enhanced `FrameProcessor` to support full frame requests and optimized tile merging. - Updated `InputRouter` to handle FPS packets and dispatch touch events. - Modified protocol definitions to include FPS packet parsing and building. - Improved error handling and logging for better debugging during input processing.
1 parent a60aa9c commit 13c268f

File tree

11 files changed

+662
-81
lines changed

11 files changed

+662
-81
lines changed

docker-compose.rwv.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ services:
88
DEFAULT_VIEWPORT_W: 480
99
DEFAULT_VIEWPORT_H: 480
1010
TILE_SIZE: 32
11-
FULLFRAME_TILE_COUNT: 4
11+
FULLFRAME_TILE_COUNT: 2
1212
FULLFRAME_AREA_THRESHOLD: 0.5
1313
FULLFRAME_EVERY: 50
14+
EVERY_NTH_FRAME: 1
15+
MIN_FRAME_INTERVAL_MS: 80
1416
JPEG_QUALITY: 85
1517
MAX_BYTES_PER_WS_MSG: 14336
1618
WS_PORT: 8081

dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ WORKDIR /app
2020

2121
COPY --from=prod-deps /app/node_modules ./node_modules
2222
COPY --from=build /app/dist ./dist
23+
COPY fps-test ./fps-test
2324

2425
EXPOSE 8080 8081 9221
2526
CMD ["node", "dist/index.js"]

fps-test/test1.html

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>FPS Test</title>
7+
<style>
8+
:root {
9+
--bg-card: #23242b;
10+
--bg-led: #7f7f79;
11+
--bg-button: #045694;
12+
--shadow-key: 0,0,0;
13+
--shadow-ambient: 0,0,0;
14+
15+
--elev-1: 0 1px 2px rgba(var(--shadow-key), .60),
16+
0 1px 3px rgba(var(--shadow-ambient), .38),
17+
0 0 0 1px rgba(255,255,255,.04);
18+
}
19+
body {
20+
padding: 0;
21+
margin: 0;
22+
background: #101215;
23+
color: #fff;
24+
font: 14px "Segoe UI", Arial, sans-serif;
25+
box-sizing: border-box;
26+
}
27+
.header {
28+
height: 40px;
29+
display: flex;
30+
gap:8px;
31+
margin-bottom: 16px;
32+
}
33+
.header .tab {
34+
display: flex;
35+
width: 40px;
36+
height: 40px;
37+
align-items: center;
38+
justify-content: center;
39+
}
40+
.header .tab:nth-child(2) {
41+
border-bottom: 2px solid #fff;
42+
}
43+
.chips {
44+
display:flex;
45+
align-items: center;
46+
justify-content: center;
47+
gap: 8px;
48+
}
49+
.chip {
50+
display: flex;
51+
height: 32px;
52+
border-radius: 16px;
53+
padding-right: 12px;
54+
background-color: var(--bg-card);
55+
box-shadow: var(--elev-1);
56+
}
57+
.chip .icon {
58+
display: inline-block;
59+
margin: 4px 4px 4px 8px;
60+
color: #e08a00;
61+
}
62+
.chip .value {
63+
display: inline-block;
64+
line-height:32px;
65+
}
66+
67+
.section-title {
68+
margin-bottom: 4px;
69+
margin-left: 8px;
70+
}
71+
72+
.buttons {
73+
display: flex;
74+
flex-wrap: wrap;
75+
gap: 8px;
76+
margin: 8px 4px;
77+
}
78+
.button {
79+
display: flex;
80+
flex-grow: 1;
81+
flex: 1 1 30%;
82+
height: 40px;
83+
margin-bottom: 8px;
84+
border-radius: 12px;
85+
transition: background-color .5s ease;
86+
background-color: var(--bg-card);
87+
box-shadow: var(--elev-1);
88+
overflow: hidden;
89+
}
90+
.button.on, .button:hover {
91+
transition: background-color .5s ease;
92+
background-color: var(--bg-button);
93+
}
94+
.button .icon {
95+
display: inline-block;
96+
margin: 8px 4px 8px 8px;
97+
color: #fff;
98+
z-index: 10;
99+
}
100+
.button .text {
101+
display: inline-block;
102+
line-height:40px;
103+
z-index: 10;
104+
}
105+
106+
.sliders {
107+
margin: 8px 4px;
108+
}
109+
.slider {
110+
position: relative;
111+
display: flex;
112+
height: 40px;
113+
margin-bottom: 8px;
114+
border-radius: 12px;
115+
background-color: var(--bg-card);
116+
box-shadow: var(--elev-1);
117+
overflow: hidden;
118+
}
119+
.slider .icon {
120+
display: inline-block;
121+
margin: 8px 4px 8px 8px;
122+
color: #e08a00;
123+
z-index: 10;
124+
}
125+
.slider .text {
126+
display: inline-block;
127+
line-height:40px;
128+
z-index: 10;
129+
}
130+
.slider .bar {
131+
position: absolute;
132+
height: 40px;
133+
left: 0;
134+
top: 0;
135+
background-color: var(--bg-led);
136+
z-index: 9;
137+
transition: width .5s ease;
138+
}
139+
140+
#result {
141+
display: none;
142+
position: absolute;
143+
left: 0;
144+
right: 0;
145+
top: 0;
146+
bottom: 0;
147+
padding-top: 64px;
148+
background: #000;
149+
z-index: 11;
150+
text-align: center;
151+
font-size: 32px;
152+
}
153+
</style>
154+
</head>
155+
<body>
156+
<div class="header">
157+
<div class="tab"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg></div>
158+
<div class="tab"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8z"/></svg></div>
159+
<div class="tab"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M11 21v-4.26c-.47.17-.97.26-1.5.26C7 17 5 15 5 12.5c0-1.27.5-2.41 1.36-3.23C6.13 8.73 6 8.13 6 7.5C6 5 8 3 10.5 3c1.56 0 2.94.8 3.75 2h.25a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5q-.75 0-1.5-.21V21z"/></svg></div>
160+
<div class="tab"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M19 6a1 1 0 0 0 1-1a1 1 0 0 0-1-1a1 1 0 0 0-1 1a1 1 0 0 0 1 1m0-4a3 3 0 0 1 3 3v6h-4V7H6v4H2V5a3 3 0 0 1 3-3zm-1 16.25c0 .38-.21.71-.53.88l-4.9 2.69q-.255.18-.57.18c-.21 0-.41-.06-.57-.18l-4.9-2.69a.99.99 0 0 1-.53-.88V13c0-.38.21-.71.53-.88l4.9-2.44c.16-.12.36-.18.57-.18q.315 0 .57.18l4.9 2.44c.32.17.53.5.53.88zm-6-6.6L9.04 13L12 14.6l2.96-1.6zm-4 6.01l3 1.63v-2.96l-3-1.62zm8 0v-2.95l-3 1.62v2.96z"/></svg></div>
161+
<div class="tab"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M4 1h16a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1m0 8h16a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1m0 8h16a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1M9 5h1V3H9zm0 8h1v-2H9zm0 8h1v-2H9zM5 3v2h2V3zm0 8v2h2v-2zm0 8v2h2v-2z"/></svg></div>
162+
</div>
163+
164+
<div class="chips">
165+
<div class="chip">
166+
<span class="icon">
167+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M7 2v11h3v9l7-12h-4l4-8z"/></svg>
168+
</span>
169+
<span class="value" id="power">60 W</span>
170+
</div>
171+
<div class="chip">
172+
<span class="icon">
173+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m3.55 19.09l1.41 1.41l1.8-1.79l-1.42-1.42M12 6c-3.31 0-6 2.69-6 6s2.69 6 6 6s6-2.69 6-6c0-3.32-2.69-6-6-6m8 7h3v-2h-3m-2.76 7.71l1.8 1.79l1.41-1.41l-1.79-1.8M20.45 5l-1.41-1.4l-1.8 1.79l1.42 1.42M13 1h-2v3h2M6.76 5.39L4.96 3.6L3.55 5l1.79 1.81zM1 13h3v-2H1m12 9h-2v3h2"/></svg>
174+
</span>
175+
<span class="value" id="power">27.2 °C</span>
176+
</div>
177+
</div>
178+
179+
<div class="section-title">Buttons</div>
180+
<div class="buttons">
181+
<div id="button0" class="button">
182+
<span class="icon">
183+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 11a1 1 0 0 0-1 1a1 1 0 0 0 1 1a1 1 0 0 0 1-1a1 1 0 0 0-1-1m.5-9c4.5 0 4.61 3.57 2.25 4.75c-.99.49-1.43 1.54-1.62 2.47c.48.2.9.51 1.22.91c3.7-2 7.68-1.21 7.68 2.37c0 4.5-3.57 4.6-4.75 2.23c-.5-.99-1.56-1.43-2.49-1.62c-.2.48-.51.89-.91 1.23c1.99 3.69 1.2 7.66-2.38 7.66c-4.5 0-4.59-3.58-2.23-4.76c.98-.49 1.42-1.53 1.62-2.45c-.49-.2-.92-.52-1.24-.92C5.96 15.85 2 15.07 2 11.5C2 7 5.56 6.89 6.74 9.26c.5.99 1.55 1.42 2.48 1.61c.19-.48.51-.9.92-1.22C8.15 5.96 8.94 2 12.5 2"/></svg>
184+
</span>
185+
<span class="text">Ventilation</span>
186+
</div>
187+
<div id="button1" class="button">
188+
<span class="icon">
189+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M18 6V4h2V2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14v-2h-4.03A4.97 4.97 0 0 0 18 16v-5H8v5c0 1.64.81 3.09 2.03 4H6V4h2v2c0 .55.45 1 1 1h8c.55 0 1-.45 1-1m-5 2c.55 0 1 .45 1 1s-.45 1-1 1s-1-.45-1-1s.45-1 1-1"/></svg>
190+
</span>
191+
<span class="text">Coffee Maker</span>
192+
</div>
193+
<div id="button2" class="button">
194+
<span class="icon">
195+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M19 19c0 1.11-.89 2-2 2H7a2 2 0 0 1-2-2v-7H3v-2h18v2h-2M8 1.5a3.35 3.35 0 0 0 0 6.7h1.53c.39 0 .76.1 1.08.3h2.02c-.58-1.05-1.77-1.75-3.1-1.75H8c-1 0-1.85-.98-1.85-2S7 3 8 3m4.85-1c0 1-.85 1.85-1.85 1.85v1.5c1.92 0 3.5 1.35 3.89 3.15h1.53a5.54 5.54 0 0 0-3.07-4.12c.62-.61 1-1.45 1-2.38Z"/></svg>
196+
</span>
197+
<span class="text">Multicooker</span>
198+
</div>
199+
<div id="button3" class="button">
200+
<span class="icon">
201+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M4.22 2A2.22 2.22 0 0 0 2 4.22v15.56C2 21 3 22 4.22 22h15.56A2.22 2.22 0 0 0 22 19.78V4.22C22 3 21 2 19.78 2zM11 4.07V6h2V4.07A8 8 0 0 1 20 12a8 8 0 0 1-7 7.93V18h-2v1.93A8 8 0 0 1 4 12a8 8 0 0 1 7-7.93M7.5 10.5A1.5 1.5 0 0 0 6 12c0 .83.66 1.5 1.5 1.5A1.5 1.5 0 0 0 9 12a1.5 1.5 0 0 0-1.5-1.5m9 0A1.5 1.5 0 0 0 15 12a1.5 1.5 0 0 0 1.5 1.5A1.5 1.5 0 0 0 18 12a1.5 1.5 0 0 0-1.5-1.5"/></svg>
202+
</span>
203+
<span class="text">TV</span>
204+
</div>
205+
<div id="button4" class="button">
206+
<span class="icon">
207+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M4.22 2A2.22 2.22 0 0 0 2 4.22v15.56C2 21 3 22 4.22 22h15.56A2.22 2.22 0 0 0 22 19.78V4.22C22 3 21 2 19.78 2zM11 4.07V6h2V4.07A8 8 0 0 1 20 12a8 8 0 0 1-7 7.93V18h-2v1.93A8 8 0 0 1 4 12a8 8 0 0 1 7-7.93M7.5 10.5A1.5 1.5 0 0 0 6 12c0 .83.66 1.5 1.5 1.5A1.5 1.5 0 0 0 9 12a1.5 1.5 0 0 0-1.5-1.5m9 0A1.5 1.5 0 0 0 15 12a1.5 1.5 0 0 0 1.5 1.5A1.5 1.5 0 0 0 18 12a1.5 1.5 0 0 0-1.5-1.5"/></svg>
208+
</span>
209+
<span class="text">Charger</span>
210+
</div>
211+
<div id="button5"class="button">
212+
<span class="icon">
213+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M4.22 2A2.22 2.22 0 0 0 2 4.22v15.56C2 21 3 22 4.22 22h15.56A2.22 2.22 0 0 0 22 19.78V4.22C22 3 21 2 19.78 2zM11 4.07V6h2V4.07A8 8 0 0 1 20 12a8 8 0 0 1-7 7.93V18h-2v1.93A8 8 0 0 1 4 12a8 8 0 0 1 7-7.93M7.5 10.5A1.5 1.5 0 0 0 6 12c0 .83.66 1.5 1.5 1.5A1.5 1.5 0 0 0 9 12a1.5 1.5 0 0 0-1.5-1.5m9 0A1.5 1.5 0 0 0 15 12a1.5 1.5 0 0 0 1.5 1.5A1.5 1.5 0 0 0 18 12a1.5 1.5 0 0 0-1.5-1.5"/></svg>
214+
</span>
215+
<span class="text">Outside</span>
216+
</div>
217+
</div>
218+
219+
<div class="section-title">Lights</div>
220+
<div class="sliders">
221+
<div class="slider">
222+
<div class="bar" id="bar0" style="width:5%"></div>
223+
<span class="icon">
224+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M2.95 3L2 6.91l17.34 4.34l.95-3.91zm3.14 3.89l-1.93-.48l.48-1.95l1.93.48zm3.85.97L8 7.38l.5-1.96l1.92.49zm3.86.96l-1.93-.48l.48-1.95l1.92.48zm3.85.97l-1.93-.48l.48-1.96l1.93.49zM4.66 12.75l-.95 3.91L21.05 21l.95-3.9zm3.14 3.9l-1.92-.49l.47-1.95l1.93.48zm3.85.96l-1.92-.48l.47-1.95l1.93.48zm3.85.97l-1.92-.49l.48-1.95l1.94.48zm3.86.96l-1.93-.48l.48-1.95l1.93.48zM6.25 12.11L11 10.2l6.75 1.69L13 13.8z"/></svg>
225+
</span>
226+
<span class="text">Living Room</span>
227+
</div>
228+
<div class="slider">
229+
<div class="bar" id="bar1" style="width:10%"></div>
230+
<span class="icon">
231+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M2.95 3L2 6.91l17.34 4.34l.95-3.91zm3.14 3.89l-1.93-.48l.48-1.95l1.93.48zm3.85.97L8 7.38l.5-1.96l1.92.49zm3.86.96l-1.93-.48l.48-1.95l1.92.48zm3.85.97l-1.93-.48l.48-1.96l1.93.49zM4.66 12.75l-.95 3.91L21.05 21l.95-3.9zm3.14 3.9l-1.92-.49l.47-1.95l1.93.48zm3.85.96l-1.92-.48l.47-1.95l1.93.48zm3.85.97l-1.92-.49l.48-1.95l1.94.48zm3.86.96l-1.93-.48l.48-1.95l1.93.48zM6.25 12.11L11 10.2l6.75 1.69L13 13.8z"/></svg>
232+
</span>
233+
<span class="text">Kitchen</span>
234+
</div>
235+
</div>
236+
237+
<div id="result"></div>
238+
<script>
239+
let iteration = 0;
240+
let power = 60;
241+
242+
let intervalIdChip = 0;
243+
let intervalIdButtons = 0;
244+
let intervalIdSliders = 0;
245+
let intervalIdBg = 0;
246+
247+
function start() {
248+
intervalIdChip = setInterval(() => animateChip(), 1_000);
249+
intervalIdButtons = setInterval(() => animateButtons(), 8_000);
250+
intervalIdSliders = setInterval(() => animateSliders(), 13_000);
251+
}
252+
253+
function animateChip() {
254+
if (iteration > 60) {
255+
clearInterval(intervalIdChip);
256+
clearInterval(intervalIdButtons);
257+
clearInterval(intervalIdSliders);
258+
259+
setTimeout(() => startBackgroundChange(), 10_000)
260+
return;
261+
}
262+
iteration++;
263+
264+
power += 42;
265+
document.getElementById('power').innerText = `${power} W`;
266+
}
267+
function animateButtons() {
268+
const id = iteration % 6;
269+
const button = document.getElementById(`button${id}`);
270+
if (button.classList.contains('on'))
271+
button.classList.remove('on');
272+
else
273+
button.classList.add('on');
274+
}
275+
function animateSliders() {
276+
const id = iteration % 2;
277+
const bar = document.getElementById(`bar${id}`);
278+
const value = (iteration + 66) % 101;
279+
bar.style.width = `${value}%`;
280+
}
281+
function startBackgroundChange() {
282+
iteration = 0;
283+
intervalIdBg = setInterval(() => changeBackground(), 3_000);
284+
}
285+
function changeBackground() {
286+
if (iteration > 20) {
287+
clearInterval(intervalIdBg);
288+
return;
289+
}
290+
iteration++;
291+
292+
document.body.style.backgroundColor = iteration % 2 ? '#000000' : '#101215';
293+
}
294+
295+
const params = new URLSearchParams(document.location.search);
296+
const partial = params.get('partial');
297+
const full = params.get('full');
298+
299+
if (partial || full) {
300+
const partialFps = Math.round(1000 / partial);
301+
const fullFps = Math.round(1000 / full);
302+
const result = document.getElementById('result');
303+
304+
result.innerText = `FPS
305+
Partial: ${partialFps} (${Math.round(partial)} ms)
306+
Full: ${fullFps} (${Math.round(full)} ms)`
307+
result.style.display = 'block';
308+
} else {
309+
setTimeout(() => start(), 5_000);
310+
}
311+
</script>
312+
</body>
313+
</html>
314+

0 commit comments

Comments
 (0)