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

First attempt at basic axum-style state management #138

Open
wants to merge 199 commits into
base: main
Choose a base branch
from

Conversation

TTWNO
Copy link
Member

@TTWNO TTWNO commented Mar 11, 2024

First attempt at Axum-style state management. The final PR should have:

  • Routers for atspi::Event
  • Handlers for atspi::Event
  • Routers for OdiliaCommand
  • Handlers for OdiliaCommand
  • Routers for input mechanisms
  • Handlers for input mechanisms

All should be able to implement some kind of state extraction, through types like the following:

  • LastFocusedItem
  • CurrentItem
  • CaretPosition
  • LastCaretPosition

Feel free to add additional items in the comments of this PR.

For right now, I've put together a basic EventHandlers type, that is able to connect up atspi events to various routers. At this time, I haven't seen how to add all the various parameters to a function yet, that's still to work on.

I'm hoping this makes for a more readable codebase. It sort of hides some of the complexity.

Copy link

codecov bot commented Mar 18, 2024

Codecov Report

Attention: Patch coverage is 0% with 129 lines in your changes missing coverage. Please review.

Project coverage is 15.47%. Comparing base (fa09dab) to head (50b61e3).
Report is 9 commits behind head on main.

Current head 50b61e3 differs from pull request most recent head aa0b506

Please upload reports for the commit aa0b506 to get more accurate results.

Files Patch % Lines
odilia/src/tower.rs 0.00% 103 Missing ⚠️
odilia/src/main.rs 0.00% 23 Missing ⚠️
odilia/src/state.rs 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #138      +/-   ##
==========================================
- Coverage   18.90%   15.47%   -3.43%     
==========================================
  Files          20       20              
  Lines         984     1661     +677     
==========================================
+ Hits          186      257      +71     
- Misses        798     1404     +606     

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

@TTWNO TTWNO force-pushed the axum-style-handlers branch 3 times, most recently from d19748f to 7c32b7b Compare March 21, 2024 17:15
odilia/src/main.rs Outdated Show resolved Hide resolved
Copy link
Member

@albertotirla albertotirla left a comment

Choose a reason for hiding this comment

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

relatively good so far, except the few points I outlined in the comments which should now hopefully be visible

odilia/src/main.rs Show resolved Hide resolved
odilia/src/main.rs Show resolved Hide resolved
odilia/src/state.rs Show resolved Hide resolved
odilia/src/tower.rs Outdated Show resolved Hide resolved

pub struct Handlers<S> {
state: S,
atspi_handlers: HashMap<(String, String), Vec<BoxService<Event, Response, Error>>>,
Copy link
Member

Choose a reason for hiding this comment

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

perhaps break this down with a type alias or something? that type signature is very verbose

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, Clippy did tell me about this. Still working on exactly which types I want so I didn't want to make them aliased yet.

odilia/src/tower.rs Outdated Show resolved Hide resolved
);
let input = ev.into();
// NOTE: Why not use join_all(...) ?
// Because this drives the futures concurrently, and we want ordered handlers.
Copy link
Member

Choose a reason for hiding this comment

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

maybe use the futures_unordered crate? produces a stream of futures, which if I remember correctly, you can get the results of concurrently and in order

Copy link
Member Author

Choose a reason for hiding this comment

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

I did mention in there that I can not have them compute concurrently, and why. The Vec<Command> may need to modify state.

Copy link
Member

Choose a reason for hiding this comment

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

isn't this the purpose of this refactor, to remove state handling within handlers, but modify state externally outside the handler? if we modify global state in there, we're back to no testability or very hard testability

Copy link
Member Author

Choose a reason for hiding this comment

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

A set of Commands are instructions on how to modify the state. Yes, the idea of this is to move the handling of state separate from the handling of actions. For example, an event may update speak some text, update the cache, fire a key event, or open a remote connection.

The implementations of these actions should be separate from their intention. If I want to speak x and y, then open a remote connection, that should be done through a set of commands, which are applied onto the state.

This way, we de-duplicate code, and de-couple the implmentation. So now, an addon can say "Hey, if I get this event, then I saw to beep at this frequency, or play this sound if some other condition is matched", and Odilia can respond without trying to give direct access to resources across FFI boundaries.

Copy link
Member

Choose a reason for hiding this comment

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

but then, state still doesn't have to be updated in the handler, no? and then, event handlers can indeed run in paralell

Copy link
Member Author

Choose a reason for hiding this comment

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

State will (eventually) be applied within a single call to a boxed service. But, since getting partial state is required (mostly) for each function, the functions that need state will need to be run in series to avoid sending out copies of stale data. Imagine that F1 updates the currently focused item, and F2 depends on the state of the currently focused item. If run in parallel, these functions will both get a copy of the current focused item at time 0, F1 will then run, producing a command to update the currently focused item, then F2 runs saying to speak the currently focused item and its role. This will result in speaking the last focused item instead of the current one.

odilia/src/tower.rs Outdated Show resolved Hide resolved
odilia/src/tower.rs Outdated Show resolved Hide resolved
odilia/src/main.rs Outdated Show resolved Hide resolved
Copy link
Member

@albertotirla albertotirla left a comment

Choose a reason for hiding this comment

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

alright, reviewed the latest changes and left some feedback

odilia/src/tower.rs Outdated Show resolved Hide resolved
@@ -27,17 +33,71 @@ type Response = Vec<Command>;
type Request = Event;
type Error = OdiliaError;

pub struct SerialFutures<'a, T, O> {
Copy link
Member

Choose a reason for hiding this comment

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

from stackoverflow

you can directly use a stream of streams of future, where each future is converted to a stream and then flattened to force it to be poled to completion before anything else is. For that, you can use iter_ok, like so...

iter_ok(tasks).map(|f|f.into_stream()).flatten()

or this, where you get a single future which poles all of them to completion

let tasks = vec![ok(()), ok(()), ok(())];

let combined_task = iter_ok::<_, ()>(tasks).for_each(|f| f);

though I still question the doing of such serially, but this is an approach with mucbh less code, if it can be done

Copy link
Member Author

Choose a reason for hiding this comment

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

I like this! But we need concrete types since we need to return this from a function.

Copy link
Member Author

Choose a reason for hiding this comment

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

I tried really hard to use something more similar to this, especially in relation to not rolling our own SequentialFutures. That does seem silly after all.

I got really close but got slapped by the borrow checker. I can come back to this one.

odilia/src/tower.rs Outdated Show resolved Hide resolved
@TTWNO
Copy link
Member Author

TTWNO commented Apr 18, 2024

Blocked on odilia-app/atspi#176

TTWNO added 29 commits June 16, 2024 23:04
- Remove unused deps
- Use if let ... on loop { ... } instead of while let Some(...)
- Switch to using the ChoiceService model
- Remove attempt at "generic handlers"; handled by ChoiceService
…ture to tokio

Move futures-concurrency to workspace
Stop using channels to communicate commands
- Move `AccessiblePrimitive` to common from cache
- Add `CaretPos` and `Focus` variants to `Command`
@TTWNO TTWNO marked this pull request as ready for review June 19, 2024 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants