fix(display): publish coherent pointer and drawable samples #1428
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1428
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/pointer-coordinate-snapshot"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Pointer geometry and pending input cross from the window thread to the renderer/game threads. Independent coordinate, size and press fields can describe different events during movement or resize. Publish and consume one coherent sample instead.
Windows and SDL use a short locked mailbox. A press retains its event position and geometry until consumed, wheel fractions accumulate, and reset clears pending input. Windows game menus have a separate mailbox so they cannot consume Insert-menu input. Windows button events use signed client coordinates; captured movement/timer paths return before OS queries, and leave tracking is armed once per enter. SDL window queries stay on its main thread.
Merged main
d224dc0680ad418b1ab111555c7d19cc483dcfdcin8a6f1e5a0a18b1542265ce0ad1c2b994862c2f56, retaining the earlier fixes in09685de. The only merge conflict wastests/run_all.py: the upstream runner is byte-identical, withRUN_ALL_ARGSdeclared in this PR's test. All seven pointer production/header/fixture files are byte-identical to the previous reviewed head. Main's API13 and ultrawide-menu callback are preserved.Validation: pointer, screensaver and Windows startup checks pass. Full local suite: 278 passed / 3 failed / 20 skipped, versus 277 / 3 / 20 on this exact main. Failure names match: Windows disk-usage extended-path fixture, symlink privilege and MSVC headers. The expanded fixture covers independent mailboxes, event/poll divergence, resize, hidden-cursor wheel, capture/focus transitions and concurrent consumers. Prior four behavioral mutation results apply to the unchanged pointer source/fixture. No native game run of this merged head is claimed; integrated input/menu runtime still needs validation.
Fresh exact-head source, Windows Builder and AppImage workflows are being requested. Earlier greens belong to their recorded heads; the full local suite is not entirely green.
Looks good. The mailbox is correct: every read and write of the shared sample goes through one SRW lock (pthread rwlock on the POSIX shim), so no torn samples and no atomics/ordering subtleties to worry about. Presses OR together until a take, whole wheel notches are consumed and the remainder kept, and reset leaves the dimensions alone. On SDL, pointer_update runs on the main thread after every event and once per loop pass (4 ms wait), and RESIZED publishes before rx_resize, so the drawable-size callback is never more than a tick stale. The Windows and SDL paths match in behavior, the test is registered in tests/run_all.py with {cxx}, and the screensaver test stubs line up with the new wndproc calls. No settings option ids are touched.
The nits below are non-blocking. I can't see upstream CI on this head (approval-gated), so I'm relying on the fork runs you linked; the source-regressions run was still in progress in the description.
@ -268,6 +305,7 @@ static LRESULT CALLBACK wndproc(HWND w, UINT msg, WPARAM wp, LPARAM lp) {if (game_menu_open) menus_mouse(w, menu_open, 0);Nit: this now does TrackMouseEvent plus pointer_update (3x GetAsyncKeyState, WindowFromPoint, GetCursorPos, lock) on every WM_MOUSEMOVE, including while the mouse is captured for the camera, where the result is always inactive. A cheap early-out when mouse_captured (still clearing the sample once) would keep the hot path as light as before. TrackMouseEvent used to run only on the first move after leaving, too.
@ -45,24 +48,32 @@ void hos_mouse_give_back(int32_t dx, int32_t dy) {}Nit: a menu click now takes its position from GetMouseState at pump time rather than from the event's x/y, so if the cursor moved within the same event batch the press can land at the later position. Probably invisible at a 4 ms poll, but worth knowing.
@ -268,6 +305,7 @@ static LRESULT CALLBACK wndproc(HWND w, UINT msg, WPARAM wp, LPARAM lp) {if (game_menu_open) menus_mouse(w, menu_open, 0);Addressed in e51cb58. Captured WM_MOUSEMOVE and periodic refresh now return before OS sampling or mailbox publication. The capture transition clears the sample once and cancels existing leave tracking; uncaptured moves arm TrackMouseEvent only when needed, retry on failure, and rearm after WM_MOUSELEAVE.
The fixture exercises the actual capture transition and window-message cases, including 100 captured moves plus periodic refresh with zero OS queries, and tracking/cancellation/retry assertions. The candidate passes and the corresponding compiled behavioral mutations fail. Exact-head fork source, Windows builder and Linux AppImage runs are all green; the description now links those runs.
@ -45,24 +48,32 @@ void hos_mouse_give_back(int32_t dx, int32_t dy) {}Addressed in e51cb58. SDL button-down now publishes the event's x/y. The mailbox also latches the first pending press position and its drawable geometry, so the normal post-event refresh cannot replace them with a later cursor position or resize before consumption. Press bits still OR together; the drawable-size callback continues to expose the latest dimensions, and the following pointer read returns the latest cursor sample.
The actual SDL button-event path is covered by an event/poll divergence regression with fractional 2x coordinates and an intervening resize, plus coalesced presses and focus-loss clearing. Both polling the click position and overwriting the pending point are detected by compiled mutation controls. Windows LLVM and Linux GCC fixtures pass, the changed Linux C units compile, and all three exact-head fork CIs are green. No additional native game or physical Linux-window test is claimed for this follow-up.
Both earlier nits are fixed, thanks. Captured WM_MOUSEMOVE and the timer refresh now return before any OS query or lock (mouse_capture clears the mailbox on entry and cancels leave tracking), and TrackMouseEvent is armed once per enter. On SDL the click uses the event's x/y, and the latched
presskeeps that position and its geometry until the renderer takes it. The publish/take logic still looks right to me: every read and write of the sample, the press latch and the wheel happens under the one SRW lock, so there are no torn samples and no ordering to reason about. drawable_size reads under the shared lock, and RESIZED publishes before rx_resize. Upstream CI is green on this head.What's blocking now is the merge with main. #1500 (mouse in the game's own menus) landed today and
host/display.cconflicts in 4 hunks. The other files merge cleanly. The textual conflict is the easy part. The harder part is that #1500 is built on theptr_x/ptr_y/ptr_w/ptr_h/ptr_inglobals this PR removes:hos_mouse_game_pointer(called on the game thread from oot_menu.c, Windows only) readsptr_x/ptr_y/ptr_w/ptr_h/ptr_indirectly. That's the same unsynchronized multi-field read this PR fixes for the Insert menu, so please don't bring the volatiles back for it. It should read the mailbox. It can't usehos_pointer_take, though, because that would consume the Insert menu's press and wheel. I see two options: a non-consuming shared-lock peek ofpointplusactive, or (cleaner, I think) a secondHosPointerfor the game menus. With the second mailbox,game_menu_presspublishes its press there and the game thread takes it, so game-menu clicks also get the event position and the press latch instead of a separategm_pressedpaired with whatever the latest position happens to be.game_menu_pressandmenus_mouse(gm_mx = ptr_x) write or readptr_x/ptr_y. In WM_MOUSEMOVE, the "moved since last time" check (gm_mx/gm_my,gm_mouse_used) can use the lp coordinates in locals. Theif (mouse_captured) return 0;early-out is fine there, since the game menus release capture.gm_wheel, thenplay_wheel), with only the Insert branch going throughpointer_update(w, 0, delta).menus_mousestill clearsptr_pressed/ptr_wheelon every transition. Map that tohos_pointer_resetpluspointer_update, asmenu_mousedoes here.tests/test_display_pointer.pypastes the WM_MOUSEMOVE case into the fixture. After the merge that body referencesgame_menu_open,menu_open,gm_mx/gm_myandgm_mouse_used, so the fixture'swinnamespace will need stubs for them. screensaver_test.c merges cleanly, and itspointer_updates == 5timer check still holds with #1500's WM_TIMER additions.The Linux side isn't affected: #1500 didn't touch display_sdl.c, and the game-menu mouse in oot_menu.c is
_WIN32only.The code at this head is good. I'll approve once it's merged with main and the game-menu path goes through the mailbox, so there's one coherent sample source on Windows rather than two.
@ -55,3 +57,3 @@static int game_menu_open;static volatile LONG64 game_menu_seen_ns;static volatile LONG gm_pressed, gm_wheel;static HosPointer game_pointer = HOS_POINTER_INIT;After merging main, #1500's game-menu reader (
hos_mouse_game_pointer, game thread) needs a coherent source too. I'd suggest a secondHosPointerfor the game menus, or at least a shared-lock peek of this one, rather than restoring theptr_*volatiles. Details in the review body.Nit, the Windows twin of the SDL point: the click position now comes from GetCursorPos when the message is processed, not from the button-down's lp. lp is exact client coordinates for the click, so it could be fed through the same way
pointer_publish_atdoes on SDL. It's very unlikely to be visible at message-pump latency, so non-blocking.Addressed both findings in
09685de31e. This merges main snapshot2877284a55and preservesacf9daeand the earlier review fixes.The game-menu reader now takes its own HosPointer, with coherent geometry, a latched press and fractional wheel. It makes no Windows queries and cannot consume Insert input. Both Windows button helpers publish the message's signed client coordinates. Movement detection uses local message coordinates; menu transitions/capture/focus clear pending state. Wheel routing keeps Insert priority, then the game menu (including while its cursor is hidden), then play_wheel.
Validation: the expanded Windows LLVM pointer fixture and 18-case screensaver fixture pass; the actual display.c compiles with C11 and -Wall -Wextra -Werror. The fixture exercises cross-thread game consumption, independent mailboxes, event/poll divergence plus resize, hidden-cursor wheel, transitions/capture/focus and the real wheel case. Four compiled behavioral mutations fail the intended assertions. The full local suite reports 254 passed / 6 failed / 22 skipped; all six failures reproduce on untouched main
2877284(listed in the updated description).Fork CI for this exact head is pending: source regressions, Windows builder, Linux AppImage. No new native game run or full integrated game link is claimed; native menu/input integration still needs a coordinated test. Earlier runtime evidence remains tied to its recorded hashes.
Heads-up: this conflicts with main now (a lot landed today). Could you merge main into the branch (or rebase) and resolve the conflicts? Thanks!
Merged main
d224dc0680ad418b1ab111555c7d19cc483dcfdcinto this branch at8a6f1e5a0a18b1542265ce0ad1c2b994862c2f56. The only conflict was the old runner argument table; the new upstream runner is preserved byte-for-byte, and the pointer test declares its own arguments. The reviewed pointer production and fixture blobs are unchanged, including the separate game-menu mailbox and Windows event-coordinate fixes. Main’s API13/ultrawide callback is retained.Three focused checks pass. Full local suite:278/3/20 versus277/3/20 on the exact base, with identical failure names; the description records their limits. Fresh exact-head fork checks were requested:
Native integration of this merged revision remains untested; no earlier-head runtime or CI result is presented as validation of this head.
Thanks for the merge and the game-menu rework. Everything from the last round is addressed:
HosPointer(game_pointer).hos_mouse_game_pointertakes from it on the game thread, so it gets a coherent sample, the latched press position and the fractional wheel, without querying the window and without touching the Insert menu's press or wheel.pointer_publish_atgates the routing: Insert gets press and wheel only whilemenu_open, and the game mailbox is active only forgame_menu_open && !menu_open. Theptr_*volatiles andgm_pressed/gm_wheelare gone.pointer_update, where Insert wins, then the game menu (hidden cursor included, return 2). Otherwise it falls back toplay_wheelwithin the 200 ms window.menus_mouseresets both mailboxes and republishes on every transition. The WM_MOUSEMOVE "moved" check uses the lp coordinates in locals, and the open-transition snapshot ofgm_mx/gm_myreads the cursor directly.menu_press,game_menu_press) publish the message's own client coordinates, so the nit is fixed too.hos_mouse_game_pointer,game_menu_press,menus_mouseand the real wheel case, with stubs for the #1500 state. It covers cross-thread game consumption next to an Insert sample, hidden-cursor wheel, the three-way wheel routing and stale input across transitions.test_display_pointer.pydeclaresRUN_ALL_ARGSand doesn't touch run_all.py.One small behaviour difference, not blocking: main counted game-menu wheel notches even when the cursor was outside the client area. Now they're dropped because the mailbox goes inactive. That seems right to me.
Upstream CI on
8a6f1e5is still pending as I write this. If it shows the known main failures (test_recomplauncher_json, test_lift_cleanup_error, nx2/test_draw_parameters, nx2/test_nup_out_of_space), those aren't from this PR. As you said, native game-menu input on this merged revision hasn't been tested yet, so a quick in-game check of the pause/file-select menus on Windows before merging would be good.