feat(plugins): generic platform state + world-event re-probe (install-order epic) - #1808
Merged
Merged
Conversation
…R-020 append-only) Adds enum xrt_plugin_platform_state (UNKNOWN/READY/PLATFORM_ABSENT/ PLATFORM_NOT_RUNNING/NO_DISPLAY/INCOMPATIBLE), struct xrt_plugin_platform_status (state + flags + 128-byte UTF-8 hint + reserved tail), XRT_PLUGIN_PLATFORM_FLAG_FALLBACK, and an optional get_platform_state slot appended at the END of xrt_plugin_iface, gated by struct_size. No XRT_PLUGIN_API_VERSION_CURRENT bump; feature macro XRT_PLUGIN_HAS_PLATFORM_STATE. The slot takes no instance so it is callable before probe() (a plug-in that declines can still say why): load -> negotiate -> get_platform_state -> probe. sim-display implements it (always READY + FALLBACK flag). plugin-discovery.md documents the call sequence, the ~100 ms non-blocking probe rule, and the no-live-swap re-probe rule. Part of #1803 (#1804). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… no live swap, complete adoption Loader (target_plugin_loader.c), all three platform paths: - per-registered-plug-in records (target_plugin_get_status): load outcome (ACTIVE / LOADED / DECLINED / BINARY_MISSING / DEPENDENCY_MISSING / LOAD_FAILED / ABI_MISMATCH / ...) + the platform state and hint the plug-in reports through get_platform_state, queried after negotiate and BEFORE probe; the active plug-in's state is re-queried live. - orphan registrations (Binary missing) and err=126 "a library the plug-in imports is missing" are told apart; a repeated identical failure logs at INFO after the first WARN (re-probes no longer spam). - no live swap: target_plugin_refresh_active returns the active plug-in untouched unless it is the FALLBACK (XRT_PLUGIN_PLATFORM_FLAG_FALLBACK, or the runtime's own sim-display id for a plug-in too old to report). Service: - world-event re-probe worker in the IPC server, fed by WM_DISPLAYCHANGE and WM_DEVICECHANGE(DBT_DEVNODES_CHANGED) on the hidden session window, a RegNotifyChangeKeyValue waiter on the DisplayProcessors root, and a 10 s timer while the fallback is active; debounced >= 1 s, runs off the window thread and off the main loop. - complete adoption: when a refresh adopts a plug-in under the fallback's head device, info.head_device_stale is set; once no client has been connected for 2 s the service ends its loop and starts a successor (--adoption-restart-after-pid) that builds its system on the new plug-in. A successor never restarts itself again. - tray tooltip: active display processor + degraded reason. Surfacing: displayxr-cli info/selftest (text + --json) list every registered plug-in with its state and hint; the vendor_dp note carries the rejected plug-in's state (exit codes unchanged); the Control Panel shows the same. ADR-045 + plugin-discovery.md. Part of #1803 (#1804, #1805). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
force-pushed
the
feat/plugin-platform-state
branch
from
October 3, 2026 01:07
5a040cd to
7a72186
Compare
1 of 9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1803. Closes #1804, closes #1805.
What
Implements ADR-045 (new, in this PR). A registered plug-in is a fact. Every plug-in is loadable without its vendor platform, and reports a generic platform state. The service re-evaluates selection on world events, but it never live-swaps a vendor plug-in that is already active.
Commit 1: ABI (append-only, ABI stays v5)
xrt_plugin.hadds:enum xrt_plugin_platform_state:UNKNOWN,READY,PLATFORM_ABSENT,PLATFORM_NOT_RUNNING,NO_DISPLAY,INCOMPATIBLE.struct xrt_plugin_platform_status:struct_size,state,flags, a 128-byte UTF-8hint, and a reserved tail.XRT_PLUGIN_PLATFORM_FLAG_FALLBACK.xrt_plugin_iface::get_platform_state(out), gated bystruct_size, with the feature macroXRT_PLUGIN_HAS_PLATFORM_STATE.probe(). The loader sequence is load → negotiate →get_platform_state→ probe.READY+FALLBACK.plugin-discovery.md§4.1/§4.2 documents the call sequence, the ~100 ms non-blockingprobe()rule, and the no-live-swap re-probe.Commit 2: loader, service, surfacing
target_plugin_get_status()keeps one record per registered plug-in: the load outcome plus the reported state and hint. The active plug-in's state is queried live.ACTIVE,LOADED,DECLINED,BINARY_MISSING,DEPENDENCY_MISSING,LOAD_FAILED,PATH_REFUSED,NO_ENTRY_POINT,NEGOTIATE_FAILED,ABI_MISMATCHandPROBE_FAILED.err=126with the binary present) now get separate outcomes.loading plug-in binarybreadcrumb and the Install flow: locked plug-in DLL is silently skipped → registry/disk version skew #461 skew check also WARN only on the first attempt.target_plugin_refresh_activereturns immediately unless the active plug-in is a fallback. "Fallback" means the plug-in reports the flag. For a plug-in too old to report state, only the runtime's ownsim-displayid counts as fallback; a vendor id never does.dxr-reprobeworker inipc_server_process.ctakes requests throughipc_server_request_display_reprobe(reason).WM_DISPLAYCHANGEon the hidden session window;WM_DEVICECHANGE/DBT_DEVNODES_CHANGEDon the same window;RegNotifyChangeKeyValuewaiter onHKLM\Software\DisplayXR\DisplayProcessors(subtree). It waits on the parent key until the root exists.info.active_plugin_is_fallbackis set.global_state.lock, the same as the compositor-create caller: a pre-contract plug-in can block inprobe()for seconds, and the main loop takes that lock every tick.info.head_device_staleis set. The weaving DP and the display info switch immediately, as before.ipc_server_restart_requested()returns true.main.cthen starts a successor,displayxr-service.exe --adoption-restart-after-pid <pid> [--workspace]. The successor waits for the old process to exit, then builds its whole system on the new plug-in.allow_adoption_restart=false, so it never restarts again for adoption. A flapping probe cannot loop.displayxr-cli infoandselftest(text and--jsonplugins[]) print one line per registered plug-in. The name and version come from the registration.vendor_dpnote adds the rejected plug-in's platform state. Exit codes are unchanged.NO_DISPLAY, it says so. The tooltip refreshes every 5 s from the loader's records and never triggers discovery.Trade-off: why adoption completes by restart, not a live head swap
The head device is held by the D3D11 service system compositor (35
sys->xdevuses, including the worst-case atlas sizing at system creation). It is also inxsysdroles, in the qwerty and rig pose binding, and in every client's shared-memory mode-table snapshot. Replacing it under live sessions is not safe.So a client that is connected when an adoption happens keeps the partial adoption (new weaving DP and geometry, old mode table, no eye tracking) until it reconnects. The service restarts as soon as it is idle. Before this PR, that state lasted until the next manual restart.
Verified (this box, dev build, in-process cli only)
scripts\build_windows.bat buildsucceeds, including service, cli and Control Panel._package\bin\displayxr-cli.exe selftestreturns rc=0, PASS. Against the installed released vendor plug-in (ABI 5, no slot) it now prints:infoandinfo --jsonshow the same data (plugins[]; JSON is 7.7 KB, inside the panel's 16 KB buffer).DXR_PLUGIN_EXCLUSIVE=sim-display, the fallback is ACTIVE and the vendor plug-in is NOT_ATTEMPTED.Not verified here (owed hardware/service tests)
The service was not run on this box: the box rules forbid touching the installed service and the
HKLMregistrations. The owed steps are in the implementation report. In short:display re-probe (plug-in registration changed).display re-probe (display change).plug-in adoption: '<sim>' -> '<vendor>'and, once idle,plug-in adoption: no client connected — restarting the service …. The successor logsStarted to complete a display plug-in adoption.🤖 Generated with Claude Code