Skip to content

Commit efbd351

Browse files
committed
feat: sync mip and pydevices-examples hero apps
1 parent 1469266 commit efbd351

2 files changed

Lines changed: 410 additions & 0 deletions

File tree

assets/apps/benchmark_deck.py

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
"""
2+
PyDevices Multi-Interpreter Benchmark (Hero Canvas App for pydevices-examples)
3+
=============================================================================
4+
Interactive multi-interpreter execution matrix and benchmark meter:
5+
- 5-target performance comparator (MicroPython, CircuitPython, CPython, WASM, Android)
6+
- Real-time frame throughput and dirty-rect blit telemetry
7+
- Interactive benchmark mode selector
8+
"""
9+
10+
import math
11+
import sys
12+
import time
13+
import types
14+
from random import random
15+
16+
try:
17+
import js
18+
from js import document, window
19+
from pyodide.ffi import create_proxy
20+
except ImportError:
21+
js = None
22+
document = None
23+
window = None
24+
create_proxy = lambda fn: fn
25+
26+
from displaydev.psdisplay import PSDisplay
27+
28+
29+
BENCH_MODES = ["BLIT 16-BIT", "DIRTY-RECT", "MATH / TRIG"]
30+
31+
TARGETS = [
32+
("MP", "#F59E0B", 0.88),
33+
("CP", "#38BDF8", 0.76),
34+
("CPy", "#10B981", 0.98),
35+
("WASM", "#8B5CF6", 0.92),
36+
("AND", "#EC4899", 0.84),
37+
]
38+
39+
40+
class BenchmarkDeckHero:
41+
def __init__(self, canvas_id="hero_canvas", size=240):
42+
self.canvas_id = canvas_id
43+
self.size = size
44+
self.w = size
45+
self.h = size
46+
47+
if "board_config" not in sys.modules:
48+
bc = types.ModuleType("board_config")
49+
bc.display_drv = PSDisplay(canvas_id, width=size, height=size)
50+
sys.modules["board_config"] = bc
51+
self.drv = bc.display_drv
52+
else:
53+
self.drv = sys.modules["board_config"].display_drv
54+
55+
self.mode_idx = 0
56+
self.frame_cnt = 0
57+
self.burst = 0.0
58+
59+
self.draw()
60+
self._bind_events()
61+
62+
self._tick_proxy = create_proxy(self._js_tick_cb) if window else None
63+
self._tick_interval = window.setInterval(self._tick_proxy, 33) if window else None
64+
65+
def _js_tick_cb(self):
66+
self.tick()
67+
68+
def _bind_events(self):
69+
if not document:
70+
return
71+
canvas = document.getElementById(self.canvas_id)
72+
if not canvas:
73+
return
74+
75+
def on_pointer_down(event):
76+
event.preventDefault()
77+
self.mode_idx = (self.mode_idx + 1) % len(BENCH_MODES)
78+
self.burst = 1.0
79+
self.draw()
80+
81+
self._p_down = create_proxy(on_pointer_down)
82+
canvas.addEventListener("pointerdown", self._p_down)
83+
84+
def tick(self):
85+
self.frame_cnt += 1
86+
if self.burst > 0.0:
87+
self.burst = max(0.0, self.burst - 0.05)
88+
self.draw()
89+
90+
def draw(self):
91+
if not hasattr(self.drv, "_buf_ctx") or not self.drv._buf_ctx:
92+
return
93+
ctx = self.drv._buf_ctx
94+
w, h = self.w, self.h
95+
96+
# 1. Dark Console Background
97+
ctx.fillStyle = "#0A0E17"
98+
ctx.fillRect(0, 0, w, h)
99+
100+
# 2. Header Bar
101+
ctx.fillStyle = "rgba(15, 23, 42, 0.95)"
102+
ctx.fillRect(0, 0, w, 28)
103+
ctx.strokeStyle = "#1E293B"
104+
ctx.lineWidth = 1
105+
ctx.beginPath()
106+
ctx.moveTo(0, 28)
107+
ctx.lineTo(w, 28)
108+
ctx.stroke()
109+
110+
ctx.fillStyle = "#F59E0B"
111+
ctx.font = "bold 9px system-ui, monospace"
112+
ctx.textAlign = "left"
113+
ctx.fillText("⚡ MULTI-INTERPRETER", 10, 18)
114+
115+
ctx.fillStyle = "#10B981"
116+
ctx.textAlign = "right"
117+
ctx.fillText("5 RUNTIMES · 60FPS", w - 10, 18)
118+
119+
# 3. Mode Pill Selector (x: 12, y: 36, w: 216, h: 26)
120+
mx, my, mw, mh = 12, 36, 216, 26
121+
ctx.fillStyle = "#0F172A"
122+
ctx.beginPath()
123+
ctx.roundRect(mx, my, mw, mh, 6)
124+
ctx.fill()
125+
ctx.strokeStyle = "#334155"
126+
ctx.lineWidth = 1
127+
ctx.stroke()
128+
129+
ctx.fillStyle = "#94A3B8"
130+
ctx.font = "bold 8px system-ui, sans-serif"
131+
ctx.textAlign = "left"
132+
ctx.fillText("BENCHMARK:", mx + 8, my + 17)
133+
134+
ctx.fillStyle = "#38BDF8"
135+
ctx.font = "bold 9px monospace"
136+
ctx.textAlign = "right"
137+
ctx.fillText(f"▶ {BENCH_MODES[self.mode_idx]}", mx + mw - 8, my + 17)
138+
139+
# 4. Multi-Target Performance Bars (x: 12, y: 70)
140+
t = time.time() * 2.0
141+
bar_y_start = 72
142+
bar_h = 16
143+
gap = 10
144+
145+
for i, (tag, col_hex, base_val) in enumerate(TARGETS):
146+
row_y = bar_y_start + i * (bar_h + gap)
147+
148+
# Label Tag
149+
ctx.fillStyle = "#E2E8F0"
150+
ctx.font = "bold 9px monospace"
151+
ctx.textAlign = "left"
152+
ctx.fillText(f"{tag:<4}", 12, row_y + 12)
153+
154+
# Bar Track
155+
bx, bw = 52, w - 66
156+
ctx.fillStyle = "#1E293B"
157+
ctx.beginPath()
158+
ctx.roundRect(bx, row_y, bw, bar_h, 4)
159+
ctx.fill()
160+
161+
# Dynamic Wave Load
162+
wave = math.sin(t + i * 1.2) * 0.08 + (random() * 0.04 - 0.02) + self.burst * 0.1
163+
eff_val = max(0.2, min(1.0, base_val + wave))
164+
165+
# Active Bar Fill
166+
fill_w = int(bw * eff_val)
167+
ctx.fillStyle = col_hex
168+
ctx.beginPath()
169+
ctx.roundRect(bx, row_y, fill_w, bar_h, 4)
170+
ctx.fill()
171+
172+
# Percentage text inside or beside bar
173+
ctx.fillStyle = "#FFFFFF"
174+
ctx.font = "bold 8px monospace"
175+
ctx.textAlign = "right"
176+
ctx.fillText(f"{int(eff_val * 100)}%", bx + fill_w - 4 if fill_w > 32 else bx + fill_w + 18, row_y + 12)
177+
178+
# 5. Bottom Tap Prompt
179+
ctx.fillStyle = "#64748B"
180+
ctx.font = "8px system-ui, sans-serif"
181+
ctx.textAlign = "center"
182+
ctx.fillText("TAP TO CYCLE BENCHMARK MODE", w // 2, 218)
183+
184+
if hasattr(self.drv, "show"):
185+
self.drv.show()
186+
187+
188+
_bench_app = None
189+
190+
191+
def main(canvas_id="hero_canvas"):
192+
global _bench_app
193+
print(f"Initializing PyDevices Benchmark Deck on canvas '{canvas_id}'...")
194+
_bench_app = BenchmarkDeckHero(canvas_id, size=240)
195+
print("PyDevices Benchmark Deck running successfully!")
196+
197+
198+
if __name__ == "__main__":
199+
cid = sys.argv[1] if len(sys.argv) > 1 else "hero_canvas"
200+
main(cid)

0 commit comments

Comments
 (0)