-
Notifications
You must be signed in to change notification settings - Fork 72
Firngrod/global doubletap handling #1477
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
Firngrod/global doubletap handling #1477
Conversation
Also switched detection of same press from a time-base to a key press count base
right/src/macros/commands.c
Outdated
| } | ||
|
|
||
| return doubletapFound != negate; | ||
| return S->ms.keyPress.isDoubletap != negate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Felt good. The reason for this whole endeavor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😊
right/src/key_states.h
Outdated
| bool previous : 1; | ||
| bool debouncing : 1; | ||
| secondary_role_state_t secondaryState : 2; | ||
| bool padding : 1; // This allows the KeyState_NoActivity() function to not trigger false because of sequence |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing this was just too tempting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we have already allocated another byte, shouldn't we move the secondary role bits there too?
kareltucek
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a good feeling about this refactor (thanks for it! I think it really benefits from your clean plate perspective).
We could merge it as is I think, but at the same time, to get the most benefit from it, we should make one more iteration of refactors.
right/src/key_states.h
Outdated
| typedef struct { | ||
| key_state_t *keyState; | ||
| bool isDoubletap: 1; | ||
| } key_press_info_t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like uint32_t activationTime should become part of this structure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As well as the sequence number and secondary state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Secondary state does not make sense on the press info. It is only resolved later. See my other comment on those.
right/src/key_states.h
Outdated
| bool previous : 1; | ||
| bool debouncing : 1; | ||
| secondary_role_state_t secondaryState : 2; | ||
| bool padding : 1; // This allows the KeyState_NoActivity() function to not trigger false because of sequence |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we have already allocated another byte, shouldn't we move the secondary role bits there too?
right/src/macros/core.c
Outdated
| uint8_t keyActivationsSeq, | ||
| uint8_t parentMacroSlot, | ||
| bool runFirstAction, | ||
| const char *inlineText) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[philosophical nitpick]
I have very mixed feelings about this kind of formatting.
My scala/functional background screems loudly for uniform parenthesis formatting handling and says it should be:
uint8_t Macros_StartMacro(
uint8_t index,
const key_press_info_t *keyPress,
uint16_t argumentOffset,
uint8_t keyActivationsSeq,
uint8_t parentMacroSlot,
bool runFirstAction,
const char *inlineText
) {
But I do recognize that this is not a C conventional style, and the clang format we provide isn't configured properly, so this is more of an opinion exchange than any request for any kind of reformat.
If you have your opinion on this, you are welcome to share 😊. Otherwise this is a resolve-without-any-action case ;-).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no strong feelings other than lines should be short enough to fit in my editor, and once the line breaks, I want only one argument on each line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't even consistent in my breaking of lines.
right/src/usb_report_updater.c
Outdated
| key_action_t *actionBase; | ||
|
|
||
| if(((uint8_t*)keyState)[2] == 0) { | ||
| if(KeyState_NoActivity(keyState)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use the dirty way here. Feel free to add a comment about it.
Reasoning:
This loop is a performance-critical place.
If I am not mistaken, an inline function is still a function, which means that it introduces memory barriers which may hinder compiler optimizations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll find a different kind of dirt.
right/src/usb_report_updater.c
Outdated
| keyState->secondaryState = SecondaryRoleState_DontKnowYet; | ||
| ++keyState->activationSeq; | ||
| KeyHistory_RecordPress(keyState); | ||
| pressInfo.isDoubletap = KeyHistory_WasLastDoubletap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I find this place intuitively a bit surprising, but probably correct. I would expect these to be evaluated closer to commitKeystate)
It means that the isDoubletap that we pass around in any future evaluation is wrong, isn't it?
The isDoubletap flag should be kept in the key_state_t structure I think.
Passing the key_press_info_t around the usb report updater is clumsy, as we are loosing the activation time and the doubletap flag. I mean, it is correct to have them in there, but not to pass it around in this context.
My idea was to use the key_press_info_t to pass the information through postponer and then from activatedNow to macro engine and keep it in macro state, but accept loosing the information for "normal actions", which would mean to not pass it around in the native action (usb report updater) call tree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Evaluating it earlier and putting it through the postponer gains us nothing, I think. Especially as the user can, at any time, modify the postponer queue, possibly requiring us to rewrite history. I think this is the best place to evaluate doubletaps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
|
If my perfectionistic nitpicks and requests are too much, please let me know. Even if merged as is, this definitely is a step forward. |
|
No problem, I do this for fun, and obsessing over details is fun. |
|
More to come, putting kids to bed. |
|
I lean in a very opposing direction from you on key state vs key press info, but the more I think about it, the more I'm certain that we should go all the way one way or all the way the other. But before that, I actually think that secondary role state cache should be moved. I think that secondary role driver should own the state, not key state. Put 8 or 16 slots in there for caching resolutions and clear them on release. It can be done pretty cleanly. Macros should always manage their own memory of secondary roles. Back to key state vs key event. I think that if we keep key events, the key state should only contain information about the key state. Basically what it contains now, minus secondary role. Everything else should live in events. Ideally, there should be no need to pass around key state pointers, key ID should be enough for the purpose of differentiating keys, nothing else. Everything that the logic needs in order to execute should be encapsulated in the event type: Is it a new press (ActivatedNow), new release (DeactivatedNow), is it doubletap, what was the ID of the press for macros to check back if that ID has been released, so on. Macro engine is the only place where any of this information needs to live past the event, so it can store what it needs to store. Realistically, nothing else needs the keyState really, does it? To be clear, I don't want to get rid of passing key state around (yet), but it should theoretically be straightforward to do. The alternative to this would be to drop the event idea and instead letting the places which need the information extract that information when needed. This requires that these things are not changed during ApplyKeyAction, which I'm pretty sure is already true. For example, as things are, there is no reason that Secondary Role driver and macro engine could not just call KeyHistory_WasLastDoubletap. The upside of the former approach is that it's formalized and clear, but wastes resources on operations which are not always needed. The upside of the latter is that it's leaner and more efficient, but the flow of information is less clear. I'm ambivalent. Now that I've thought about it, dropping the event type is probably my preferred way to go. The macro engine needs to be tuned a bit for it, it needs to gather the information it needs on start, then pass it on to spin-offs. The main thing about the event approach is that one can construct an event and inject it. If Secondary Role calls WasLastDoubletap, I cannot easily execute something as a doubletap, say from a macro. Now I lean that way again! Argh! |
|
I'm going to move secondary role state cache. That one I really like. I'll make it one commit which can be reverted if you don't like it. |
|
Now that I've slept on it, I think I still like the idea of passing a key event around rather than the key state, for the following reasons:
I think the additional processing to prepare this state is worth it, but I will let you be the judge. |
I like about the key_state_t solution that it guarantees a full nkey rollover for native actions. I do realize that it is more of an emotional fondness than an rational reasoning though.
Well, I have been doing pure functional programming for my other jobs... but this is not a pure functional language and codebase.
I think our ideas about what this refactor should be have been diverging in two directions which are not incompatible, just not one and the same thing (probably falling apart into two press info structures - one event-oriented for the native actions logic, and one state-oriented for macro states), so I am dropping my branch for another time and lets focus on your branch. I am not sure what changes your idea would imply. Let's say that refactors that objectively improve state of affairs are welcome. On the other hand, if it is not broken, don't fix it. I don't think I am open to reconstructing the entire codebase just to get code that looks nicer. I think I have stated my case and will look forward to whatever implementation you come up with. |
|
This is getting a bit messy (🙈), but I’ve read through all your comments and I don’t think I have any objections. |
I get it, and I'm not really advocating for making everything injectable, it's just a reasoning for why I lean that way.
Absolutely! I don't want to overhaul the code, I just want to settle on which approach to take for the flow of information to the handling of an event.
Agreed. Let's clean this up to be only about the global doubletap handling. I will make a minimally disruptive version of global double check, without the press event concept. Regardless of where any discussion of the event concept goes, it will do so elsewhere. This implementation is not good enough, so I will remove it. I will also split the secondary role state thing to another PR where we can discuss the merits of it in isolation, and later, I will make a PR about macro initial state snapshot handling, I have some ideas in that regard. |
Now you got me curious. Please keep me in the loop on this (when you are starting the PR). |
|
It's just that when the macro starts, it should gather the information it might need during it's lifetime, but which might not be available later, such as doubletap, keytap id and so on. It does so now, but not in a very formalized way. I also want it to remember secondary role resolution and pass all of these things on to macros it might call. |
b5a9888 to
9452706
Compare
|
It builds, haven't tested it, still needs code cleanup. |
|
Also some of your comments are unaddressed. |
Also, macros remember secondary role resolution once it has one
|
This now only has the global doubletap and a couple of, I think, objective improvements and/or fixes. Since doubletap evaluation went on-site, I just made it use individual timeout values again. By the way, you mentioned that you had a branch with a key event type implementation. I would love to take a look at that when it's ready. |
Have I? More context please? I guess it is deprecated by this PR. |
I don't understand this part either. What do you mean "went on-site"? Which one is the relevant commit? |
Maybe you meant dropping your idea of a press event implementation?
I no longer call |
|
Okay, now that we're not in any way bound by my previous implementation of the key press event concept, if I was to do it again, what do you think it should look like? |
I think that unifying doubletap timeouts into a single one was a good thing.
Totally agreed.
No, I think it is fine.
I believe you have better idea what to do and how to do it. If I am to sum up my understanding of what has been going on: My original idea was there to be a new struct to store the info in the macro state. It would probably look something like this: But that is basically just gathering stuff that is already lying around and putting it at one more centralized place. The concept which you had in mind was about passing things inside report updater, which kind of sounds similar, but turns out to be a very different thing.
Sounds fine, except that there is a number of states that don't have keyid assigned - the navigation mode states for instance. These would have to be identified and dealt with.
If I understand correctly, you are suggesting to create a new structure similar to this and pass it around the native action processing (usb_report_updater): I would add: In any case, you seem to have a pretty good understanding of the keystate handling as well as an opinion on how it should be done. As for whether to do it or how or not, you be the judge. I trust the sanity of your jugdment and would prefer to not have to get too engaged into this. |
|
I have re-deprecated secondary role specific doubletap timeout. For the issue with macros doing secondary role evaluation after other key actions, I personally am fine with that for now for the following reasons:
I think this branch is now at a stage where it is ready for a final review and, hopefully, merge, without key press events. That might not be for at least a week as I will be visiting the in-laws for some days this coming week. Thank you for your patience, and sorry for all the mess. |
|
I think everything said is correct.
Alright, will give it another look and probably merge soon!
No, no, no, thank you for all the "mess". 😊 |
| S->ms.currentMacroStartTime = CurrentPostponedTime; | ||
| S->ms.currentMacroArgumentOffset = argumentOffset; | ||
| S->ms.parentMacroSlot = parentMacroSlot; | ||
| S->ms.isDoubletap = KeyHistory_WasLastDoubletap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some macros run without owning a key - those started from macroEvents or via usb, or those bound in navigation modes.
Iinm, this line can make them think they were a doubletap. It would be safer to ask for a "keystate" rather than "last key".
Of course, such macros shouldn't be asking for doubletaps in the first place, so this shouldn't be a problem in practice.
| // some systems do debouncing because mouse switches are unreliable | ||
| uint16_t delay = 20; | ||
| PostponerCore_TrackKeyEvent(singleTap, false, 0xff, Timer_GetCurrentTime()); | ||
| const int16_t delay = 20; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why signed? Is this a typo?
| bool debouncing : 1; | ||
| secondary_role_state_t secondaryState : 2; | ||
| bool padding : 1; // This allows the KEY_INACTIVE() macro to not trigger false because of sequence | ||
| uint8_t activationId: 3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would feel safer with 4 bits.
|
Let's merge this. If you want to solve any of the nitpicks, please do so in a new PR. |
Running this now, seems to work fine.
I want to run it for a bit to make sure.
I only propagated the press as far as it needed to go down the tree in
usb_report_updater.c.I think maybe the sequence number for macros should go into the key press info.
Do you have a procedure for remembering to remove removed config values in the future?