Spaces:
Sleeping
Sleeping
Fix black tiles on connect; 64px tiles, byte-based delta choice
Browse files
app.py
CHANGED
|
@@ -51,7 +51,12 @@ FB = int(os.environ.get("NEURALOS_FB", "1024")) # framebuffer edge
|
|
| 51 |
TASKBAR_H = 40
|
| 52 |
TITLEBAR_H = 26
|
| 53 |
CHROME_BORDER = 3
|
| 54 |
-
TILE =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
# auto | mock | cpu | cuda
|
| 57 |
ENGINE_MODE = os.environ.get("NEURALOS_ENGINE", "auto").lower()
|
|
@@ -1058,10 +1063,12 @@ class Compositor:
|
|
| 1058 |
self.metrics["idle"] += 1
|
| 1059 |
self.metrics["tiles"] = 0
|
| 1060 |
return ("none", None, None)
|
| 1061 |
-
#
|
| 1062 |
-
#
|
| 1063 |
-
|
| 1064 |
-
|
|
|
|
|
|
|
| 1065 |
if full:
|
| 1066 |
frame = self.stack.composite()
|
| 1067 |
payload = self._encode(frame)
|
|
@@ -1153,18 +1160,51 @@ def refinement_worker():
|
|
| 1153 |
print("[refine]", e)
|
| 1154 |
|
| 1155 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1156 |
def broadcast_dirty(clients):
|
| 1157 |
with STATE_LOCK:
|
| 1158 |
for (text, ttl) in list(KERNEL.overlays):
|
| 1159 |
if ttl < time.time():
|
| 1160 |
KERNEL.overlays.remove((text, ttl))
|
| 1161 |
result = COMPOSITOR.render()
|
| 1162 |
-
stats =
|
| 1163 |
-
stats["cache"] = {"hits": STORE.hits, "misses": STORE.misses}
|
| 1164 |
-
stats["engine"] = get_engine().stats()
|
| 1165 |
-
stats["windows"] = len(KERNEL.windows)
|
| 1166 |
-
stats["menu"] = KERNEL.menu_open
|
| 1167 |
-
stats["buffer"] = KERNEL.text_buffer
|
| 1168 |
kind, payload, blob = result
|
| 1169 |
if kind == "none":
|
| 1170 |
return result
|
|
@@ -1327,9 +1367,12 @@ try:
|
|
| 1327 |
def kernel_ws(ws):
|
| 1328 |
CLIENTS.add(ws)
|
| 1329 |
try:
|
| 1330 |
-
get_engine()
|
| 1331 |
send_log(ws, "Kernel attached")
|
| 1332 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1333 |
while True:
|
| 1334 |
data = ws.receive()
|
| 1335 |
if not data:
|
|
|
|
| 51 |
TASKBAR_H = 40
|
| 52 |
TITLEBAR_H = 26
|
| 53 |
CHROME_BORDER = 3
|
| 54 |
+
TILE = 64 # dirty-tile granularity (16x16 grid)
|
| 55 |
+
# Full-vs-patch decision constants (measured, quality 82): every patch pays a
|
| 56 |
+
# fresh WebP header, so many small tiles can cost more than one full frame. We
|
| 57 |
+
# therefore compare estimated shipped BYTES, not tile count.
|
| 58 |
+
PATCH_OVERHEAD = 700 # bytes of per-image WebP overhead
|
| 59 |
+
PX_RATE = 0.019 # encoded bytes per pixel for UI content
|
| 60 |
|
| 61 |
# auto | mock | cpu | cuda
|
| 62 |
ENGINE_MODE = os.environ.get("NEURALOS_ENGINE", "auto").lower()
|
|
|
|
| 1063 |
self.metrics["idle"] += 1
|
| 1064 |
self.metrics["tiles"] = 0
|
| 1065 |
return ("none", None, None)
|
| 1066 |
+
# A full frame is one image; a patch set is N images, each with its own
|
| 1067 |
+
# WebP header. Ship whichever is estimated to cost fewer bytes.
|
| 1068 |
+
dirty_px = sum(w * h for (_, _, w, h) in rects)
|
| 1069 |
+
est_patch = PATCH_OVERHEAD * len(rects) + PX_RATE * dirty_px
|
| 1070 |
+
est_full = PATCH_OVERHEAD + PX_RATE * FB * FB
|
| 1071 |
+
full = self.last is None or est_patch >= 0.9 * est_full
|
| 1072 |
if full:
|
| 1073 |
frame = self.stack.composite()
|
| 1074 |
payload = self._encode(frame)
|
|
|
|
| 1160 |
print("[refine]", e)
|
| 1161 |
|
| 1162 |
|
| 1163 |
+
def _frame_stats():
|
| 1164 |
+
"""Metrics + kernel state shipped in every frame header.
|
| 1165 |
+
|
| 1166 |
+
Deliberately does NOT call get_engine(): during a cold boot that blocks on
|
| 1167 |
+
model load, and the first frame must not wait behind it."""
|
| 1168 |
+
stats = dict(COMPOSITOR.metrics)
|
| 1169 |
+
stats["cache"] = {"hits": STORE.hits, "misses": STORE.misses}
|
| 1170 |
+
stats["engine"] = ENGINE.stats() if ENGINE is not None else \
|
| 1171 |
+
{"engine": "loading", "device": "-", "lcm": False}
|
| 1172 |
+
stats["windows"] = len(KERNEL.windows)
|
| 1173 |
+
stats["menu"] = KERNEL.menu_open
|
| 1174 |
+
stats["buffer"] = KERNEL.text_buffer
|
| 1175 |
+
return stats
|
| 1176 |
+
|
| 1177 |
+
|
| 1178 |
+
def send_full_to(ws):
|
| 1179 |
+
"""Push a complete frame to ONE just-connected client.
|
| 1180 |
+
|
| 1181 |
+
The compositor streams a single delta stream shared by every client, so a
|
| 1182 |
+
client that joins after the first frame would otherwise only ever receive
|
| 1183 |
+
the tiles the cursor/caret happens to dirty -- most of its canvas stays
|
| 1184 |
+
blank ("black tiles until mouseover"). Each new socket therefore starts
|
| 1185 |
+
from a full snapshot. Chrome is procedural, so this is cheap and never
|
| 1186 |
+
touches the model."""
|
| 1187 |
+
with STATE_LOCK:
|
| 1188 |
+
COMPOSITOR.build() # layers must exist before compositing
|
| 1189 |
+
COMPOSITOR.stack.take_dirty_rects() # we are shipping the whole state
|
| 1190 |
+
frame = COMPOSITOR.stack.composite()
|
| 1191 |
+
blob = COMPOSITOR._encode(frame)
|
| 1192 |
+
COMPOSITOR.last = frame
|
| 1193 |
+
COMPOSITOR.metrics["full"] += 1
|
| 1194 |
+
COMPOSITOR.metrics["bytes"] = len(blob)
|
| 1195 |
+
COMPOSITOR.metrics["px"] = FB * FB
|
| 1196 |
+
COMPOSITOR.metrics["tiles"] = 0
|
| 1197 |
+
stats = _frame_stats()
|
| 1198 |
+
ws.send(pack(T_FULL, {"w": FB, "h": FB, **stats}, blob))
|
| 1199 |
+
|
| 1200 |
+
|
| 1201 |
def broadcast_dirty(clients):
|
| 1202 |
with STATE_LOCK:
|
| 1203 |
for (text, ttl) in list(KERNEL.overlays):
|
| 1204 |
if ttl < time.time():
|
| 1205 |
KERNEL.overlays.remove((text, ttl))
|
| 1206 |
result = COMPOSITOR.render()
|
| 1207 |
+
stats = _frame_stats()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1208 |
kind, payload, blob = result
|
| 1209 |
if kind == "none":
|
| 1210 |
return result
|
|
|
|
| 1367 |
def kernel_ws(ws):
|
| 1368 |
CLIENTS.add(ws)
|
| 1369 |
try:
|
|
|
|
| 1370 |
send_log(ws, "Kernel attached")
|
| 1371 |
+
# Paint the whole desktop for this client FIRST: chrome is
|
| 1372 |
+
# procedural, so the first frame need not wait behind engine load.
|
| 1373 |
+
send_full_to(ws)
|
| 1374 |
+
get_engine()
|
| 1375 |
+
broadcast_dirty(CLIENTS) # then stream deltas as usual
|
| 1376 |
while True:
|
| 1377 |
data = ws.receive()
|
| 1378 |
if not data:
|