Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance enhancements for media key shortcuts, repeated keys #134

Merged
merged 4 commits into from Oct 24, 2019
Merged

Performance enhancements for media key shortcuts, repeated keys #134

merged 4 commits into from Oct 24, 2019

Conversation

robertbressi
Copy link
Contributor

@robertbressi robertbressi commented Oct 1, 2019

NOTE: This PR depends on #131, #133 and should not be merged before those two fixes - for the sake of avoiding conflicts.

This set of changes is bound to be controversial, so I wanted to get eyes on it ASAP. Each change has been split into a separate commit:

Add handling for holding down media shortcut keys

Currently, the media key handler doesn't handle shortcut keys being held down. This change sets a 50ms timer for key repeats and also handles when the "opposite" key is pressed

Add handling to not send through DDC commands for values which had already been set

Currently, it's possible to keep flooding the DDC interface with values which are already set on the monitor (e.g.: going from 0 to 0 or 100 to 100). This change prevents this from happening by simply not sending through the duplicate command.

Moved DDC reads and writes onto the main thread

This commit is the big change.

I noticed that when setting the volume, brightness and contrast from the sliders (which occurs on the main thread), there is no lag in sending many commands through to the DDC interface.

This is in comparison to the media shortcut keys, in which DDC commands are sent through a background queue, and huge amounts of lag are experienced, especially while holding down the media shortcut keys - as shown below (this experience was so bad that I almost had to quit the app altogether after recording because it was still trying to send through commands after I released the media key):
ezgif-3-186fb896db38

When running the DDC command writes through the main thread however, the experience looks more like this:
ezgif-3-2dadb4475c0e

Similarly, for reading DDC values, here's a log with timestamps of the app starting app with values being read on the background queue (reading is 3+ seconds and there are multiple request failures):

Log (background queue)
2019-09-30 23:16:46.151362-0700 MonitorControl[33853:2126451] Polling 10 times
2019-09-30 23:16:46.153968-0700 MonitorControl[33853:2126461] Polling 10 times
2019-09-30 23:16:46.154140-0700 MonitorControl[33853:2126451] Checksum of reply does not match. Expected 2, got 182.
2019-09-30 23:16:46.154218-0700 MonitorControl[33853:2126451] Response was: 6E 88 02
2019-09-30 23:16:46.154237-0700 MonitorControl[33853:2126451] Display does not support DDC.
2019-09-30 23:16:46.156276-0700 MonitorControl[33853:2126482] Polling 10 times
2019-09-30 23:16:46.302909-0700 MonitorControl[33853:2126482] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:46.302913-0700 MonitorControl[33853:2126461] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:46.303051-0700 MonitorControl[33853:2126461] Display does not support DDC.
2019-09-30 23:16:46.303084-0700 MonitorControl[33853:2126482] Display does not support DDC.
2019-09-30 23:16:46.304052-0700 MonitorControl[33853:2126451] Display supports enabling DDC application report.
2019-09-30 23:16:46.744061-0700 MonitorControl[33853:2126451] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:46.744070-0700 MonitorControl[33853:2126461] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:46.744276-0700 MonitorControl[33853:2126461] Display does not support enabling DDC application report.
2019-09-30 23:16:46.745262-0700 MonitorControl[33853:2126482] Display supports enabling DDC application report.
2019-09-30 23:16:46.981954-0700 MonitorControl[33853:2126482] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:46.981970-0700 MonitorControl[33853:2126461] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:46.984832-0700 MonitorControl[33853:2126451] Checksum of reply does not match. Expected 128, got 110.
2019-09-30 23:16:46.984912-0700 MonitorControl[33853:2126451] Response was: 6E 80 BE 6E 80 BE 6E 80 BE 6E 80
2019-09-30 23:16:47.356457-0700 MonitorControl[33853:2126451] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:47.356471-0700 MonitorControl[33853:2126482] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:47.359147-0700 MonitorControl[33853:2126461] Checksum of reply does not match. Expected 0, got 46.
2019-09-30 23:16:47.359232-0700 MonitorControl[33853:2126461] Response was: 6E 80 6E 88 02 00 10 00 00 64 00
2019-09-30 23:16:47.757467-0700 MonitorControl[33853:2126482] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:47.757510-0700 MonitorControl[33853:2126461] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:47.760247-0700 MonitorControl[33853:2126451] Reading 98 took 4 tries.
2019-09-30 23:16:47.761281-0700 MonitorControl[33853:2126451] C34J79x (DDC.DDC.Command.audioSpeakerVolume):
2019-09-30 23:16:47.761306-0700 MonitorControl[33853:2126451]  - current ddc value: 100
2019-09-30 23:16:47.761317-0700 MonitorControl[33853:2126451]  - maximum ddc value: 100
2019-09-30 23:16:47.761348-0700 MonitorControl[33853:2126451] Polling 10 times
2019-09-30 23:16:48.104756-0700 MonitorControl[33853:2126451] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:48.104798-0700 MonitorControl[33853:2126461] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:48.105164-0700 MonitorControl[33853:2126451] Display does not support DDC.
2019-09-30 23:16:48.107486-0700 MonitorControl[33853:2126482] Reading 18 took 4 tries.
2019-09-30 23:16:48.107881-0700 MonitorControl[33853:2126482] C34J79x (DDC.DDC.Command.contrast):
2019-09-30 23:16:48.107905-0700 MonitorControl[33853:2126482]  - current ddc value: 0
2019-09-30 23:16:48.107928-0700 MonitorControl[33853:2126482]  - maximum ddc value: 100
2019-09-30 23:16:48.459560-0700 MonitorControl[33853:2126461] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:48.460802-0700 MonitorControl[33853:2126451] Display supports enabling DDC application report.
2019-09-30 23:16:48.727048-0700 MonitorControl[33853:2126461] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:48.730078-0700 MonitorControl[33853:2126451] Checksum of reply does not match. Expected 128, got 110.
2019-09-30 23:16:48.730149-0700 MonitorControl[33853:2126451] Response was: 6E 80 BE 6E 80 BE 6E 80 BE 6E 80
2019-09-30 23:16:48.994596-0700 MonitorControl[33853:2126451] Failed to send request to interface 0 for framebuffer with ID 76843.
2019-09-30 23:16:48.997236-0700 MonitorControl[33853:2126461] Reading 16 took 7 tries.
2019-09-30 23:16:48.997730-0700 MonitorControl[33853:2126461] C34J79x (DDC.DDC.Command.luminance):
2019-09-30 23:16:48.997752-0700 MonitorControl[33853:2126461]  - current ddc value: 1
2019-09-30 23:16:48.997763-0700 MonitorControl[33853:2126461]  - maximum ddc value: 2
2019-09-30 23:16:49.261625-0700 MonitorControl[33853:2126451] Reading 141 took 3 tries.
2019-09-30 23:16:49.261672-0700 MonitorControl[33853:2126451] C34J79x (DDC.DDC.Command.audioMuteScreenBlank):
2019-09-30 23:16:49.261685-0700 MonitorControl[33853:2126451]  - current ddc value: 100
2019-09-30 23:16:49.261729-0700 MonitorControl[33853:2126451]  - maximum ddc value: 100

Compare this to results on the main thread (startup in 800ms, no failed requests):

Log (main queue)
2019-09-30 23:15:57.154108-0700 MonitorControl[33458:2124134] Polling 10 times
2019-09-30 23:15:57.156532-0700 MonitorControl[33458:2124134] Display supports DDC.
2019-09-30 23:15:57.210950-0700 MonitorControl[33458:2124134] Display supports enabling DDC application report.
2019-09-30 23:15:57.264996-0700 MonitorControl[33458:2124134] Checksum of reply does not match. Expected 128, got 110.
2019-09-30 23:15:57.265158-0700 MonitorControl[33458:2124134] Response was: 6E 80 BE 6E 80 BE 6E 80 BE 6E 80
2019-09-30 23:15:57.308459-0700 MonitorControl[33458:2124134] Reading 98 took 2 tries.
2019-09-30 23:15:57.308981-0700 MonitorControl[33458:2124134] C34J79x (DDC.DDC.Command.audioSpeakerVolume):
2019-09-30 23:15:57.309009-0700 MonitorControl[33458:2124134]  - current ddc value: 0
2019-09-30 23:15:57.309022-0700 MonitorControl[33458:2124134]  - maximum ddc value: 100
2019-09-30 23:15:57.309081-0700 MonitorControl[33458:2124134] Polling 10 times
2019-09-30 23:15:57.355790-0700 MonitorControl[33458:2124134] Checksum of reply does not match. Expected 2, got 182.
2019-09-30 23:15:57.355852-0700 MonitorControl[33458:2124134] Response was: 6E 88 02
2019-09-30 23:15:57.355867-0700 MonitorControl[33458:2124134] Display does not support DDC.
2019-09-30 23:15:57.409362-0700 MonitorControl[33458:2124134] Display supports enabling DDC application report.
2019-09-30 23:15:57.464029-0700 MonitorControl[33458:2124134] Checksum of reply does not match. Expected 128, got 110.
2019-09-30 23:15:57.464123-0700 MonitorControl[33458:2124134] Response was: 6E 80 BE 6E 80 BE 6E 80 BE 6E 80
2019-09-30 23:15:57.510880-0700 MonitorControl[33458:2124134] Reading 141 took 2 tries.
2019-09-30 23:15:57.510979-0700 MonitorControl[33458:2124134] C34J79x (DDC.DDC.Command.audioMuteScreenBlank):
2019-09-30 23:15:57.510999-0700 MonitorControl[33458:2124134]  - current ddc value: 1
2019-09-30 23:15:57.511014-0700 MonitorControl[33458:2124134]  - maximum ddc value: 2
2019-09-30 23:15:57.515783-0700 MonitorControl[33458:2124134] Polling 10 times
2019-09-30 23:15:57.557535-0700 MonitorControl[33458:2124134] Checksum of reply does not match. Expected 2, got 182.
2019-09-30 23:15:57.557596-0700 MonitorControl[33458:2124134] Response was: 6E 88 02
2019-09-30 23:15:57.557612-0700 MonitorControl[33458:2124134] Display does not support DDC.
2019-09-30 23:15:57.613274-0700 MonitorControl[33458:2124134] Display supports enabling DDC application report.
2019-09-30 23:15:57.666458-0700 MonitorControl[33458:2124134] Checksum of reply does not match. Expected 128, got 110.
2019-09-30 23:15:57.666581-0700 MonitorControl[33458:2124134] Response was: 6E 80 BE 6E 80 BE 6E 80 BE 6E 80
2019-09-30 23:15:57.711186-0700 MonitorControl[33458:2124134] Reading 16 took 2 tries.
2019-09-30 23:15:57.711344-0700 MonitorControl[33458:2124134] C34J79x (DDC.DDC.Command.luminance):
2019-09-30 23:15:57.711396-0700 MonitorControl[33458:2124134]  - current ddc value: 100
2019-09-30 23:15:57.711408-0700 MonitorControl[33458:2124134]  - maximum ddc value: 100
2019-09-30 23:15:57.716513-0700 MonitorControl[33458:2124134] Polling 10 times
2019-09-30 23:15:57.758162-0700 MonitorControl[33458:2124134] Checksum of reply does not match. Expected 2, got 182.
2019-09-30 23:15:57.758224-0700 MonitorControl[33458:2124134] Response was: 6E 88 02
2019-09-30 23:15:57.758241-0700 MonitorControl[33458:2124134] Display does not support DDC.
2019-09-30 23:15:57.813480-0700 MonitorControl[33458:2124134] Display supports enabling DDC application report.
2019-09-30 23:15:57.868205-0700 MonitorControl[33458:2124134] Checksum of reply does not match. Expected 128, got 110.
2019-09-30 23:15:57.868321-0700 MonitorControl[33458:2124134] Response was: 6E 80 BE 6E 80 BE 6E 80 BE 6E 80
2019-09-30 23:15:57.915629-0700 MonitorControl[33458:2124134] Reading 18 took 2 tries.
2019-09-30 23:15:57.916390-0700 MonitorControl[33458:2124134] C34J79x (DDC.DDC.Command.contrast):
2019-09-30 23:15:57.916423-0700 MonitorControl[33458:2124134]  - current ddc value: 1
2019-09-30 23:15:57.916438-0700 MonitorControl[33458:2124134]  - maximum ddc value: 100

So, I'm not sure whether this experience differs with other configuration sets, such as if there's a longer delay or higher polling set, but I wanted to get it in front of the contributors' eyes to see if this may be a meaningful set of improvements.

As always, happy to accept feedback and make whatever changes are necessary (or to split out individual changes if the whole thing is a little much for one PR!) - let me know :)

@JoniVR JoniVR added Priority: Major Issue is major (e.g. A lot of things broken…) Type: Bug Issue is a bug (e.g. Crash, …) labels Oct 6, 2019
@robertbressi
Copy link
Contributor Author

Testing out the scenario presented in #133 today made me realize something pertinent to this change - reading DDC values on the background thread triggers a race condition which sometimes causes incorrect values to be set against individual preferences.

Background here is that I sometimes experience issues on restarting MonitorControl where individual values would be wildly different from their current DDC values - for example: before starting MonitorControl, my brightness would be at 100 and my speaker volume at a very low value or 0, but when I tried to adjust either of these, my speaker volume would be at 100 (which, trust me, while you have audio playing, is extremely startling, especially on monitors that have loud speakers) and my brightness would be at 0.

For example, I noticed the following from the "Log (background queue)" in the above comment - my contrast DDC value (which was 1/100) has been set to my audioSpeakerVolume DDC value (which was 0/100), my brightness DDC value (which was 100/100) has been set to my audioMuteScreenBlank DDC value (which was 1/2) and my audioSpeakerVolume DDC value (which was 0/100) has been set to my brightness DDC value (which was 100/100):

C34J79x (DDC.DDC.Command.audioSpeakerVolume):
- current ddc value: 100
- maximum ddc value: 100

C34J79x (DDC.DDC.Command.contrast):
- current ddc value: 0
- maximum ddc value: 100

C34J79x (DDC.DDC.Command.luminance):
- current ddc value: 1
- maximum ddc value: 2

C34J79x (DDC.DDC.Command.audioMuteScreenBlank):
- current ddc value: 100
- maximum ddc value: 100

Compare this with the "Log (main queue)" in the above comment - all of the values are aligned correctly with their corresponding commands (0/100 for audioSpeakerVolume, 1/2 for audioMuteScreenBlank, 100/100 for brightness/luminance and 1/100 for contrast):

C34J79x (DDC.DDC.Command.audioSpeakerVolume):
- current ddc value: 0
- maximum ddc value: 100

C34J79x (DDC.DDC.Command.audioMuteScreenBlank):
- current ddc value: 1
- maximum ddc value: 2

C34J79x (DDC.DDC.Command.luminance):
- current ddc value: 100
- maximum ddc value: 100

C34J79x (DDC.DDC.Command.contrast):
- current ddc value: 1
- maximum value: 100

I believe that moving DDC read/write commands to the main thread also removes the race condition from DDC reads, preventing the issue that I described - just another reason why I think this change should be considered :)

@JoniVR
Copy link
Member

JoniVR commented Oct 6, 2019

I agree with you on this. I had also thought about the background calls possibly causing some of the performance issues but hadn't looked into it properly. I do remember back with some of the earlier versions of MonitorControl when it used ddcctl for ddc, there were less background calls and better performance overall.

I can confirm that I have also noticed the same scenario you mentioned above with the restart.

So as far as I'm concerned, once I (or someone else) get to reviewing this PR and can properly test and confirm that it does indeed solve the issues and improve performance, it's fine by me. :)

@robertbressi
Copy link
Contributor Author

Rebased this PR on 39f76e5 - there were merge conflicts that have been resolved.

@robertbressi
Copy link
Contributor Author

I've been running MonitorControl for a few days using a build generated from this branch and it seems pretty good on my monitor (Samsung C34J79x).

Once the code is reviewed, I'd like to get testing on this change on a wider-array of monitors than I have access to - do we have a core of beta testers who might be willing to use a development build of MonitorControl based on this branch and provide feedback?

@JoniVR
Copy link
Member

JoniVR commented Oct 20, 2019

Once the code is reviewed, I'd like to get testing on this change on a wider-array of monitors than I have access to - do we have a core of beta testers who might be willing to use a development build of MonitorControl based on this branch and provide feedback?

Not as far as I'm aware, but we could perhaps open an issue for this where we can ask people who want to be testers?

@JoniVR
Copy link
Member

JoniVR commented Oct 20, 2019

Can you try using safe unwrapping a bit more wherever possible? I understand that it can be a lot of extra syntax but it's a bit safer than manual nil checking and then force unwrapping and prevents mistakes much more easily.

@JoniVR JoniVR added the Status: In progress Issue currently being worked on label Oct 20, 2019
@robertbressi
Copy link
Contributor Author

Can you try using safe unwrapping a bit more wherever possible? I understand that it can be a lot of extra syntax but it's a bit safer than manual nil checking and then force unwrapping and prevents mistakes much more easily.

I assume you're referring to optional binding (e.g.: if let nonOptional = optional)? If not, please let me know.

I'm not super well-versed in Swift, so I'm happy to take any suggestions and feedback on board and make changes accordingly :)

@JoniVR
Copy link
Member

JoniVR commented Oct 21, 2019

I assume you're referring to optional binding (e.g.: if let nonOptional = optional)? If not, please let me know.

Yep, that's it 🙂 so instead of:

if oppositeKey != nil, self.keyRepeatTimers[oppositeKey!] != nil, self.keyRepeatTimers[oppositeKey!]!.isValid {
  self.keyRepeatTimers[oppositeKey!]!.invalidate()
} else if self.keyRepeatTimers[mediaKey] != nil, self.keyRepeatTimers[mediaKey]!.isValid {
  // If there's already an active timer for the key being held down, let it run rather than executing it again
  if isRepeat {
    return
  }
  self.keyRepeatTimers[mediaKey]!.invalidate()
}

Try something like (I have quickly adapted this so might contain mistakes):

if let oppositeKey = oppositeKey, let oppositeKeyTimer = self.keyRepeatTimers[oppositeKey], oppositeKeyTimer.isValid {
    oppositeKeyTimer.invalidate()
} else if let mediaKeyTimer = self.keyRepeatTimers[mediaKey], mediaKeyTimer.isValid {
  if isRepeat {
    return
  }
  mediaKeyTimer.invalidate()
}

Not sure about that else if let as I've never actually thought about that one before but I guess that should work.. lol.. learn something new every day 😁

Also, if you ever have a line like this:
self.keyRepeatTimers[mediaKey]!.invalidate()
just use:
self.keyRepeatTimers[mediaKey]?.invalidate()
instead. In the case that self.keyRepeatTimers[mediaKey] would be nil it won't execute the function instead of crashing in the former case, or it can save you a nil check.

It's a bit different syntax wise than most languages, but because you write it this way, you can mostly prevent nil errors because you have to explicitly unwrap your values. The goal in Swift is mostly to always prevent force unwrapping (! vs ?) and try to safe unwrap wherever possible because you write safer code by doing so.

https://docs.swift.org/swift-book/LanguageGuide/OptionalChaining.html

@JoniVR JoniVR added Type: Enhancement Issue is an app enhancement and removed Type: Bug Issue is a bug (e.g. Crash, …) labels Oct 21, 2019
@robertbressi
Copy link
Contributor Author

Done! Thanks for calling this out - I didn't really like the force-unwrapping thing either, but didn't realize there was an alternative.

Glad to have learned something new! 👍

@JoniVR
Copy link
Member

JoniVR commented Oct 22, 2019

Yeah, Swift is really fun once you start to really get into it's powerful features but the nice thing about it is that you can mostly still use it like any other language if you don't know the language specifics.

Copy link
Member

@JoniVR JoniVR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works like a charm, great work! 😉

@JoniVR JoniVR changed the title Performance enhancements for media key shortcuts, especially for repeated keys Performance enhancements for media key shortcuts, repeated keys Oct 24, 2019
@JoniVR JoniVR merged commit 13df82d into MonitorControl:master Oct 24, 2019
@robertbressi
Copy link
Contributor Author

Oh cool - thanks for merging! I hadn't had a chance to squash yet though 😅

@JoniVR
Copy link
Member

JoniVR commented Oct 24, 2019

Does it matter for you to squash yourself? Figured it was on a separate branch so I did a squash and merge on here

@robertbressi
Copy link
Contributor Author

Oh, not at all - didn’t realize you had squashed it yourself, haha. Thanks for that!

Sent with GitHawk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority: Major Issue is major (e.g. A lot of things broken…) Status: In progress Issue currently being worked on Type: Enhancement Issue is an app enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants