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

Split ChildrenChanged/TextChanged events into distinct Inserted/Deleted events #194

Closed
wants to merge 5 commits into from

Conversation

TTWNO
Copy link
Member

@TTWNO TTWNO commented Jun 20, 2024

I think this works better than adding an enum like "Inserted/Deleted"; they really are separate events. This is obvious if you look at the name of the other events like "ColumnInserted"/"ColumnDeleted", etc. The only thing I'm curious about is how to test this; our automated tests do not allow modification of the body. We can add manual test as well: of course there is nothing wrong with that!

Commits:

  • Split TextChangedEvent into TextInserted/TextDeleted
  • Split ChildrenChangedEvent into ChildrenInserted/ChildrenDeleted

TODOs:

  • Test the "kind" field fails when it should, succeedes when it should

@TTWNO
Copy link
Member Author

TTWNO commented Jun 20, 2024

Well, scratch that! Looks like the tests caught an error! I had the kind "delete | delete/system" deserailize into the ChildrenInserted type, which indeed is a mistake.

I'm going to s/Removed/Deleted/g since that's the name of the discriminant in AT-SPI.

Copy link

codecov bot commented Jun 20, 2024

Codecov Report

Attention: Patch coverage is 93.47826% with 6 lines in your changes missing coverage. Please review.

Project coverage is 86.81%. Comparing base (27cdaef) to head (c87f82d).
Report is 16 commits behind head on main.

Files Patch % Lines
atspi-common/src/events/object.rs 95.55% 4 Missing ⚠️
atspi-common/src/error.rs 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #194      +/-   ##
==========================================
+ Coverage   86.69%   86.81%   +0.11%     
==========================================
  Files          39       39              
  Lines        3337     3412      +75     
==========================================
+ Hits         2893     2962      +69     
- Misses        444      450       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@TTWNO TTWNO marked this pull request as ready for review June 20, 2024 22:42
@TTWNO TTWNO changed the title Split ChildrenChanged/TextChanged events into distinct events Split ChildrenChanged/TextChanged events into distinct Inserted/Deleted events Jun 20, 2024
@luukvanderduim
Copy link
Collaborator

Good you managed to catch and mash this 'deleted' -> removed' bug!

From the old event table @ Linux Foundatioon:

'kind' major:minor discriminants of events that communicate some change:

Event major minor
text-changed inserted / removed
text-selection-changed -
text-attributes-changed -
property-change accessible-parent, accessible-name, accessible-description, accessible-value, accessible-role, accessible-table-caption, accessible-table-column-description, accessible-table-column-header, accessible-table-row-description, accessible-table-row-header, accessible-table-summary,
state-changed 'enabled' bool
children-changed add, remove
visible-data-changed -
bounds-changed -
selection-changed -
model-changed -
active-descendants-changed -
selection-changed -
content-changed -
attributes-changed (doc) -
line-changed -
columncount-changed -
linecount-changed -
application-changed -
charwidth-changed -

This does break the 1:1 relationship between signals' member and user facing event types doesn't it?

One could argue that all events with minor discriminants warrant separate events..
How about StateChanged with it's 'enabled' field? Why is that different?
Or Button with 'p1', 'r1', 'p2', etc?
Is there perhaps a particular problem you found that is fixed by separating ChildrenChanged and TextChanged ?

Copy link
Collaborator

@luukvanderduim luukvanderduim left a comment

Choose a reason for hiding this comment

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

Why are the discriminants correct?

"ChildrenChanged" => Ok(ObjectEvents::ChildrenChanged(ev.try_into()?)),
"ChildrenChanged" => {
let body: EventBodyOwned = ev.try_into()?;
match body.kind.as_str() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are these discriminants correct?
In the old LF event table it says these minor strings were 'add' and 'remove' did that change?

"TextChanged" => Ok(ObjectEvents::TextChanged(ev.try_into()?)),
"TextChanged" => {
let body: EventBodyOwned = ev.try_into()?;
match body.kind.as_str() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

As above, but for TextChanged, the discriminants may be 'inserted' and 'removed'?

fn from(event: ChildrenInsertedEvent) -> Self {
EventBodyOwned {
properties: std::collections::HashMap::new(),
kind: "insert".to_string(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this not be "add"?

Copy link
Collaborator

Choose a reason for hiding this comment

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

With busmonitoring I saw ChildrenChanged: "add/system", "remove/system" and "add", "remove"
So this should probably be "add" or "add/system"

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes.

EventBodyOwned {
properties: std::collections::HashMap::new(),
kind: event.operation,
kind: "delete".to_string(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this not be "remove"?

I may be misunderstanding things.

Copy link
Collaborator

@luukvanderduim luukvanderduim Jun 21, 2024

Choose a reason for hiding this comment

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

Okay, I just tried a bit of monitoring on the bus

For ChildrenChanged
Path=/org/a11y/atspi/accessible/12747 Interface=org.a11y.atspi.Event.Object Member=ChildrenChanged
"remove/system" , "add/system", "add", "remove"

Perhaps this be "remove", no?.

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right.

@TTWNO
Copy link
Member Author

TTWNO commented Jun 25, 2024

Indeed this is a case of an X/Y problem: I've proposed a solution without explaining why.

In Odilia, events match based on a type (enum discriminant/member+method). Since what needs to be done is significantly different depending on inserted/deleted, it would make sense (for Odilia) if we could write event handlers like this:

async fn my_handl(
    ev: CacheEvent<ChildrenRemovedEvent>,
) -> impl TryIntoCommands {
    // ...
}

This is a minor case with ChildrenChanged since there are only two variants. But, you catch on to my future goals before I even stated them:

One could argue that all events with minor discriminants warrant separate events..
How about StateChanged with it's 'enabled' field? Why is that different?
Is there perhaps a particular problem you found that is fixed by separating ChildrenChanged and TextChanged?

Funny you mention that. I was actually thinking about a way to be generic over different States, and maybe even the enabled field too. Since StateChanged is an event which will inevitably have a lot of event handlers used in Odilia, (for focus/unfocus, expanded/collapsed, checked/unchecked), it would be nice if the specific State+enabled properties could be encoded at the type level. This will release me from the burden of writing a check at the top of every single function that this event type, exiting early if it is not indeed the right state/enabled combination.

Imagine the following code:

async fn focus_item(
    ev: CacheEvent<StateChangedEvent>,
) -> impl TryIntoCommands {
    if ev.inner.state != State::Focus { /* do I return Ok(vec![]) or Err(...) here? */ }
    if ev.inner.enabled != true { /* it's not an error, but the function should not continue running... */ }
}

Compare it to encoding the information at the type level:

async fn focus_item(
    ev: CacheEvent<StateChangedEvent<State::Focus, true>>,
) -> impl TryIntoCommands {
    // ...
}

This way, the function only gets called when it's already known as the right event. Presumably I could find a way to do this with generics of CacheEvent.... I guess I got ahead of myself trying to get this into atspi directly.

@TTWNO TTWNO closed this Jun 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants