fix(app): #348 — cursor switches ride a process-lifetime native cache; the per-flip Win32 handle leak is closed
Silk's per-mouse ICursor recreates the native Win32 cursor on every Image assignment; a per-frame cursor alternation (the pick cursor flickering between kinds while hovering an ANIMATED NPC — exactly the stand-at-a-vendor posture) allocated a fresh USER handle each flip until CreateCursor died with "Not enough memory" and took the render loop with it (vendor-gate.log, exit 82 — surfaced as one clean stack by #343's fix, as designed). GlfwCursorCache restores retail's own shape: each distinct MediaDescCursor is created ONCE for the process lifetime (glfwCreateCursor, rejected media cached as permanent misses) and switching is an O(1) zero-allocation glfwSetCursor. The AP-72 missing-art standard-cursor fallback rides the same cache (Arrow/Hand/Crosshair/IBeam; anything else keeps the Silk path). Graphical hosts attach after the native window exists; tests and windowless hosts keep the Silk path untouched. RetailCursorManager's dedup and PlanApplication logic are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c721830e71
commit
9d3df5f627
5 changed files with 245 additions and 1 deletions
|
|
@ -24,6 +24,33 @@ What does NOT go here:
|
|||
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
|
||||
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
|
||||
|
||||
## #348 — Render-loop death by Win32 cursor-handle exhaustion: Silk recreates the native cursor on every alternation
|
||||
|
||||
**Status:** FIX IN TREE (2026-08-08) pending the vendor-gate relaunch.
|
||||
**Evidence:** `vendor-gate.log` — `Silk.NET.GLFW.GlfwException: PlatformError:
|
||||
Win32: Failed to create cursor: Not enough memory` thrown from
|
||||
`RetailCursorManager.ApplyGlobal` inside `RenderFrameOrchestrator.Render`,
|
||||
exit 82 after ~minutes standing at a Holtburg vendor NPC. (The clean
|
||||
single-exception stack instead of masked shutdown noise is #343's fix
|
||||
working as designed.)
|
||||
|
||||
**Mechanism:** `RetailCursorManager`'s dedup only suppresses a STEADY
|
||||
cursor. Any per-frame alternation between two cursor states — the pick
|
||||
cursor flickering between kinds while hovering an ANIMATED NPC whose
|
||||
moving parts cross the cursor ray, exactly the "stand at a vendor"
|
||||
posture — reassigns `ICursor.Image` every flip, and Silk's GLFW backend
|
||||
creates a fresh native Win32 cursor per assignment without reusing the
|
||||
old ones. ~10,000 flips exhausts the USER-object quota and CreateCursor
|
||||
dies. Earlier same-day sessions (slope gates) never crashed because
|
||||
nobody hovers an animated NPC for minutes while moving.
|
||||
|
||||
**Fix (root cause):** `GlfwCursorCache` — one `glfwCreateCursor` per
|
||||
distinct cursor media for the process lifetime (retail's own shape: it
|
||||
loads each MediaDescCursor once), O(1) `glfwSetCursor` per switch,
|
||||
rejected media cached as permanent misses, disposal destroys all.
|
||||
`RetailCursorManager.AttachNativeWindow` opts in when a native GLFW
|
||||
window exists; tests and windowless hosts keep the Silk path.
|
||||
|
||||
## #32 — CLOSED 2026-08-07: local edge-slide fixed at `332045c7`, USER-PASSED on its first genuine live run
|
||||
|
||||
**"Yes works now."** — the user at the Rithwic cliff, on the first launch that
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue