From 702692f386acf6c3743f5fac47fb649ea0b494e2 Mon Sep 17 00:00:00 2001 From: wheaney <42350981+wheaney@users.noreply.github.com> Date: Tue, 23 Jul 2024 22:07:58 -0700 Subject: [PATCH 1/2] Add handling of rapid monitor updates, prevent concurrent config updates and effect/sbs mode changes --- gnome/src/monitormanager.js | 52 +++++++++++++++++++++++++++---------- modules/XRLinuxDriver | 2 +- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/gnome/src/monitormanager.js b/gnome/src/monitormanager.js index 1d4b98c..6079447 100644 --- a/gnome/src/monitormanager.js +++ b/gnome/src/monitormanager.js @@ -86,9 +86,18 @@ function getMonitorConfig(displayConfigProxy, callback) { } // triggers callback with true result if an an async monitor config change was triggered, false if no config change needed -function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPrimary, useHighestRefreshRate, callback) { +function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPrimary, useHighestRefreshRate, + allowConfigUpdateFn, callback) { Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck for ${connectorName}`); + displayConfigProxy.GetCurrentStateRemote((result, error) => { + if (!allowConfigUpdateFn()) { + // other requests are in progress, this monitor state may be stale, do nothing + Globals.logger.log_debug('MonitorManager performOptimalModeCheck: allowConfigUpdate is false'); + callback(false, null); + return; + } + if (error) { callback(null, `GetCurrentState failed: ${error}`); } else { @@ -239,7 +248,10 @@ export const MonitorManager = GObject.registerClass({ this._backendManager = null; this._monitorProperties = null; this._changeHookFn = null; - this._needsConfigCheck = this.use_optimal_monitor_config; + + // help prevent certain actions from taking place multiple times in the event of rapid monitor updates + this._configCheckRequestCount = this.use_optimal_monitor_config ? 1 : 0; + this._asyncRequestsCount = 0; } enable() { @@ -279,36 +291,43 @@ export const MonitorManager = GObject.registerClass({ return this._monitorProperties; } - // returns true if a check is needed, caller should wait for the next change hook call + // returns true if an async check is needed, caller should wait for the next change hook call needsOptimalModeCheck(monitorConnector) { - Globals.logger.log_debug(`MonitorManager checkOptimalMode: ${monitorConnector}`); + Globals.logger.log_debug(`MonitorManager needsOptimalModeCheck: ${monitorConnector}`); if (this._displayConfigProxy == null) { - Globals.logger.log('MonitorManager checkOptimalMode: _displayConfigProxy not set!'); + Globals.logger.log('MonitorManager needsOptimalModeCheck: _displayConfigProxy not set!'); return false; } - if (this._needsConfigCheck) { + let needsConfigCheck = this._configCheckRequestCount > 0; + if (needsConfigCheck && --this._configCheckRequestCount === 0) { + this._asyncRequestsCount++; + const allowConfigUpdateFn = (() => this._asyncRequestsCount === 1).bind(this); performOptimalModeCheck(this._displayConfigProxy, monitorConnector, this.headset_as_primary, this.use_highest_refresh_rate, ((configChanged, error) => { - this._needsConfigCheck = false; + if (--this._asyncRequestsCount > 0) { + Globals.logger.log_debug(`MonitorManager _on_monitors_change: ${this._asyncRequestsCount} async requests still pending, skipping change hook`); + return; + } + if (error) { Globals.logger.log(`Failed to switch to optimal mode for monitor ${monitorConnector}: ${error}`); } else { if (configChanged) { Globals.logger.log(`Switched to optimal mode for monitor ${monitorConnector}`); } else if (!!this._changeHookFn) { - Globals.logger.log_debug('MonitorManager checkOptimalMode: no config change'); + Globals.logger.log_debug('MonitorManager needsOptimalModeCheck: no config change'); // no config change means this won't be triggered automatically, so trigger it manually this._changeHookFn(); } else { - Globals.logger.log('MonitorManager checkOptimalMode: can\'t trigger change hook, no hook set!'); + Globals.logger.log('MonitorManager needsOptimalModeCheck: can\'t trigger change hook, no hook set!'); } } - }).bind(this)); + }).bind(this), this._allowConfigChange.bind(this)); } else { - Globals.logger.log_debug('MonitorManager checkOptimalMode: skipping config check'); + Globals.logger.log_debug('MonitorManager needsOptimalModeCheck: skipping config check'); } - return this._needsConfigCheck; + return needsConfigCheck; } _on_monitors_change() { @@ -316,9 +335,16 @@ export const MonitorManager = GObject.registerClass({ if (this._displayConfigProxy == null) { return; } - this._needsConfigCheck = this.use_optimal_monitor_config; + if (this.use_optimal_monitor_config) this._configCheckRequestCount++; + this._asyncRequestsCount++; getMonitorConfig(this._displayConfigProxy, ((result, error) => { + if (--this._asyncRequestsCount > 0) { + Globals.logger.log_debug(`MonitorManager _on_monitors_change: ${this._asyncRequestsCount} async requests still pending, skipping change hook`); + return; + } + if (error) { + Globals.logger.log(error); return; } const monitorProperties = []; diff --git a/modules/XRLinuxDriver b/modules/XRLinuxDriver index 95c2fbf..b0080ca 160000 --- a/modules/XRLinuxDriver +++ b/modules/XRLinuxDriver @@ -1 +1 @@ -Subproject commit 95c2fbfae235d7637b10f24c1f75bcc7d1de8142 +Subproject commit b0080ca844e057d31aae0e70aa6d026059ea304f From 150f183aadad3c2af302e8cc1c85f75f08479218 Mon Sep 17 00:00:00 2001 From: wheaney <42350981+wheaney@users.noreply.github.com> Date: Wed, 24 Jul 2024 10:59:15 -0700 Subject: [PATCH 2/2] Fix async request handling --- gnome/src/monitormanager.js | 60 ++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/gnome/src/monitormanager.js b/gnome/src/monitormanager.js index 6079447..48a6dee 100644 --- a/gnome/src/monitormanager.js +++ b/gnome/src/monitormanager.js @@ -87,7 +87,7 @@ function getMonitorConfig(displayConfigProxy, callback) { // triggers callback with true result if an an async monitor config change was triggered, false if no config change needed function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPrimary, useHighestRefreshRate, - allowConfigUpdateFn, callback) { + callback, allowConfigUpdateFn) { Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck for ${connectorName}`); displayConfigProxy.GetCurrentStateRemote((result, error) => { @@ -248,10 +248,11 @@ export const MonitorManager = GObject.registerClass({ this._backendManager = null; this._monitorProperties = null; this._changeHookFn = null; + this._needsConfigCheck = this.use_optimal_monitor_config; // help prevent certain actions from taking place multiple times in the event of rapid monitor updates - this._configCheckRequestCount = this.use_optimal_monitor_config ? 1 : 0; - this._asyncRequestsCount = 0; + this._asyncRequestsInFlight = 0; + this._configCheckRequestsCount = 0; } enable() { @@ -299,19 +300,33 @@ export const MonitorManager = GObject.registerClass({ return false; } - let needsConfigCheck = this._configCheckRequestCount > 0; - if (needsConfigCheck && --this._configCheckRequestCount === 0) { - this._asyncRequestsCount++; - const allowConfigUpdateFn = (() => this._asyncRequestsCount === 1).bind(this); + const isCheckingConfig = this._needsConfigCheck; + if (this._needsConfigCheck && this._asyncRequestsInFlight === 0) { + this._asyncRequestsInFlight++; + + const configCheckCountSnapshot = this._configCheckRequestsCount; + const allowConfigUpdateFn = (() => { + // allow updates to the config if this is the only in-flight request and no more requests + // were made while we were waiting for the previous request to complete + return this._asyncRequestsInFlight === 1 && this._configCheckRequestsCount === configCheckCountSnapshot; + }).bind(this); + performOptimalModeCheck(this._displayConfigProxy, monitorConnector, this.headset_as_primary, this.use_highest_refresh_rate, ((configChanged, error) => { - if (--this._asyncRequestsCount > 0) { - Globals.logger.log_debug(`MonitorManager _on_monitors_change: ${this._asyncRequestsCount} async requests still pending, skipping change hook`); + if (--this._asyncRequestsInFlight > 0) { + Globals.logger.log_debug(`MonitorManager needsOptimalModeCheck: ${this._asyncRequestsInFlight} async requests still pending, skipping change hook`); + return; + } else if (this._configCheckRequestsCount !== configCheckCountSnapshot) { + Globals.logger.log_debug('MonitorManager needsOptimalModeCheck: config checks requested while in-flight, skipping change hook'); return; } if (error) { Globals.logger.log(`Failed to switch to optimal mode for monitor ${monitorConnector}: ${error}`); + + // tell the extension to proceed, this should result in another config check + this._changeHookFn(); } else { + this._needsConfigCheck = false; if (configChanged) { Globals.logger.log(`Switched to optimal mode for monitor ${monitorConnector}`); } else if (!!this._changeHookFn) { @@ -323,11 +338,13 @@ export const MonitorManager = GObject.registerClass({ Globals.logger.log('MonitorManager needsOptimalModeCheck: can\'t trigger change hook, no hook set!'); } } - }).bind(this), this._allowConfigChange.bind(this)); - } else { + }).bind(this), allowConfigUpdateFn); + } else if (!this._needsConfigCheck) { Globals.logger.log_debug('MonitorManager needsOptimalModeCheck: skipping config check'); + } else { + Globals.logger.log_debug(`MonitorManager needsOptimalModeCheck: skipping due to async requests ${this._asyncRequestsInFlight}`); } - return needsConfigCheck; + return isCheckingConfig; } _on_monitors_change() { @@ -335,18 +352,17 @@ export const MonitorManager = GObject.registerClass({ if (this._displayConfigProxy == null) { return; } - if (this.use_optimal_monitor_config) this._configCheckRequestCount++; - this._asyncRequestsCount++; + if (this.use_optimal_monitor_config) { + this._needsConfigCheck = true; + this._configCheckRequestsCount++; + } + this._asyncRequestsInFlight++; getMonitorConfig(this._displayConfigProxy, ((result, error) => { - if (--this._asyncRequestsCount > 0) { - Globals.logger.log_debug(`MonitorManager _on_monitors_change: ${this._asyncRequestsCount} async requests still pending, skipping change hook`); - return; - } - if (error) { Globals.logger.log(error); return; } + const monitorProperties = []; for (let i = 0; i < result.length; i++) { const [monitorName, connectorName, vendor, product, serial, refreshRate] = result[i]; @@ -366,7 +382,11 @@ export const MonitorManager = GObject.registerClass({ } this._monitorProperties = monitorProperties; if (!!this._changeHookFn) { - this._changeHookFn(); + if (--this._asyncRequestsInFlight === 0) { + this._changeHookFn(); + } else { + Globals.logger.log_debug(`MonitorManager _on_monitors_change: ${this._asyncRequestsInFlight} requests still pending, skipping change hook`); + } } else { Globals.logger.log('MonitorManager _on_monitors_change: can\'t trigger change hook, no hook set!'); }