-
Notifications
You must be signed in to change notification settings - Fork 124
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
RFC: Rethinking the design of the crates API #721
Comments
I don't have mental energy to go on a big discussion here. But the only thing which frustrates me is that I have very little control over how things are represented internally. For example, my basic desire is to have things just use I guess the API you suggest could work for my case, while still providing MT safety guarantees for users who actually want that instead of doing queue local processing and preventing objects from moving from the queue thread. It's a bit unclear for me how compositing will work, I'd assume there will be a module which does the parsing and then dispatches to the right module generated by the Also, could you mock up how e.g. interaction between different crates will work? I'd assume that crate A should define some interface and implement a trait some core crate provides, then it'll get its messages under certain conditions defined by the user? For example, let's say I have my main There's a very big chance that I read proposal not entirely correctly. |
Okay, I'll first start this with my current pain-points with wayland-rs. Both from a perspective of a smithay maintainer and cosmic-comp developer: Thread-safetyThis is a huge one and I am happy this RFC tries to address this problem. As a smithay maintainer we have two opposed design goals here:
I think the So if I want to make parts of it accessible to different threads, do I always need to lock up the whole thing? Or store Which brings me neatly to the composability issue here. In smithay we would want abstractions, that can be thread-safe, so are we inherently backing locks into the data-types here again? If so, we gain nothing, because anybody seriously about eliminating locks would need to re-implement all protocols. We have so much data, which we either associate with a specific object or the State in smithay, sometimes it can be read by downstream, sometimes it is a private part of the implementation of the protocol and tracing access here is challenging. So I would value any approach, that does offload the aspect of locking to the user of the library. Which might mean smithay shouldn't manipulate the State-centric DesignThis is obviously a much harder issue. I perceive it as one, because we have a couple of places, where we are generic over the whole state, because we don't know what parts downstream might need to access. The biggest offender here in smithay is our An attempt to solve this with the current structure was made here (but didn't lead to much feedback and only complicates the API): Smithay/smithay#1384 This RFC feels too vague to be judged in this regard to me. Given we wouldn't have a giant (Obviously this has also been a huge problem for any attempts at asyncifying wayland-rs. I still don't think that is a particular good idea, but it is obvious that a lot of the I/O-abstractions in rust have embraced async IO at this point. So any improvements here might have very positive effects on these attempts. - I am not saying we should provide a async-implemention at this point.) |
I don't have much to add for now, but I belive it's worth linking those two (sadly compeating) dispatching reworks that we have at the moment Exsistence of those two kinda shows that the current API is greate on it's own, but gets anoying when we try to abstract implementations in smitahy or sctk. So having a way to dispatch to types diferent then D seems like an important topic. Either by my delegate dispatch work or Drakulix's forward dispathing. My work on this got kinda stalled, as I'm myself not sure which solution is more worth pursuing. |
(Not) AsyncIf wayland-rs used async for it's API, it seems like that would naturally involve Some ideas I might have for async APIs also may not work well with some requirements for ordering of messages from the server and responses to them... (like if you had different async tasks handling messages from different Wayland object, instead of the well-ordered callback-based approrach Wayland is designed around, that might be an issue with some things? Or at least it would somehow have to group things like surfaces and role objects together...). BackendMaking We could also make
It's kind of a minor point, but for Threading and
|
Triggering protocol errors is important to me as someone who wants to help a Wayland test suite. However, I do believe it makes sense to make that ability opt-in. An average user likely has no need for footguns.
Is there a way to have both? I think a huge majority of the time, clients of this library don't want to do this themselves and the code will be duplicated for each. In other words, the existing destroyed fn in
What is Overall, it sounds like a good progression. Some bits are a bit too abstract for me to follow, but I'm happy to attempt to code it up in way-assay at some point. It took some finessing but we've managed to work around some of the constraints associated with ObjectData. Below is how we handle it, ObjectData still needs to be implemented, but it ends up being pretty neat and tidy IMO. I'd like to make sure it won't be too bad to switch to the new macro_rules! protocol_bind {
($reg:expr, $proto:tt, $global:expr, $constraints:expr, $objdata:expr) => {{
let global_str = paste::paste! { stringify!([<$proto:snake>]) };
// Bind the specific version set by the caller of the protocol, or fallback to
// the latest version supported by the server.
let version: u32 = $constraints
.iter()
.find(|r| r.0 .0 == global_str)
.map_or($global.version, { |w| w.0 .1 });
(
match $reg.send_constructor::<$proto>(
wl_registry::Request::Bind {
name: $global.name,
id: ($proto::interface(), version),
},
$objdata,
) {
Ok(y) => y,
Err(e) => {
info!("Couldn't bind {} {e}", stringify!($proto));
continue;
}
},
// Return the name as string, and version bound
(global_str, version),
)
}};
}
...
contents.with_list(|globals| {
for global in globals.iter() {
macro_rules! bind {
($proto:tt, $objdata:expr, $state:expr) => {{
let (x, proto) =
protocol_bind!(registry, $proto, global, required_globals, $objdata);
if required_globals.extract_if_higher_protocol(&proto.into()) {
$state.replace(x);
}
}};
}
match &global.interface[..] {
// TODO: When stable: https://github.com/rust-lang/rust/pull/104087
"wl_compositor" => {
bind!(WlCompositor, Arc::new(EventlessState), state.compositor)
}
"wl_subcompositor" => bind!(
WlSubcompositor,
Arc::new(EventlessState),
state.subcompositor
),
// shm_state implemenst ObjectData
"wl_shm" => bind!(WlShm, shm_state.clone(), state.shm),
_ => (),
}
}
}); |
Thanks for that initial feedback, that gives me some food for thought. To quickly answer a few of the questions before I come back at a later point with a more fleshed-out proposal:
The exact shape is still to be determined, but yes, there would be something like that provided by wayland-client/-server/-protocols. Also, regarding interaction with multiple crates, yes, my general idea is to still make it possible for them to coexist without need to be aware with each other beyond some initial setting-up by the downstream app.
Yes, and the same object can be used as a key in multiple different
You could for examples have a Overall, I expect this kind of design to give much more control to downstream (including Smithay/SCTK) on what data is stored how and where. But there are certainly many details to iron out.
My general expectation for long-term was (and is still) that the API of the backend, if designed in a sufficiently low-level way, would be much more stable than that of wayland-client/wayland-server. To me this could potentially be desirable in two main ways:
I proposed this callback-based API because it is lower-level than a one returning an
I think this is answered earlier in my message, as an answer to @Drakulix 's similar question
My idea is to remove this from the backend, and instead move this kind of tracking into wayland-client/wayland-server, but I'm not yet sure of how that would be implemented exactly.
Something like that: https://github.com/azriel91/anymap2 |
That sounds really nice actually and I feel like that might fix a bunch of problems I am fighting against with the current api.
I am all for that! |
So far this hasn't really happened, but a stable wayland-backend would be useful for rust-windowing/raw-window-handle#120. With the sys backend, it doesn't matter as much, since multiple versions are already interoperable with each other and with C libraries. But it could be better to have a stable wayland-backend, especially when there are bug fixes (the sys backend to wayland-backend is where almost all the unsafe code in wayland-rs resides). |
While I agree with most of your points, an issue I am having is that there is no way to create custom backends, like the |
So, I've been thinking about wayland-rs' API again (gotta love my brain, the running gag is still going strong), and I'm dumping these thoughts here, to see if they can serve as a starting point for a positive discussion.
As a preamble, as far as I can tell, the current API is for most users "good enough". Although some pain points remain (in particular regarding thread-safety), it is mostly usable and reasonable. As such, I don't think it's worth going into yet another redesign of the API unless we have a strong confidence that it would make things significantly better. This is why I'm opening this "RFC" issue: to gather feedback on those ideas, see if/how they can be fleshed out, and evaluate if it is a direction worth pursuing or not.
Design of the backend
In the latest big redesign, I've tried to make
wayland-backend
into an "as low-level as possible" wrapper around both a rust implementation of the protocol, and libwayland. It turns out that I could have gone deeper into this "going lower-level" route by relaxing a constraint I had set myself at the time: I tried to make the backend not only rust-safe, but also make it catch a lot of footguns that could trigger protocol errors. I considering relaxing that.The backend would be reworked to completely remove the
ObjectData
trait, and the core API it would expose to process messages would be something akin to:The user thus provides a callback that is repeatedly called for all incoming messages. Free to them to use it to process them in real time, or just store them into a buffer for later processing.
At the level of the backend, objects are only represented by their
ObjectId
, a handle that isEq + Hash + Clone
, and keeps representing uniquely the same object, even after it is destroyed.The backend no longer automatically tries to track object destruction for you, and will expose a method like:
Similarly, the backend no longer checks the signature of outgoing messages, and will happily serialize to the wire whatever you give to it. This means that it would be easy to trigger protocol errors (but no rust-unsafety) if you use those API wrong.
Associating data to objects
An important need is the capacity to associate data with specific instances of Wayland objects. With the removal of
ObjectData
from the backend, we need a new mechanism to cover that.I'm thinking of introducing a
DataMap
container, which would in essence be a glorifiedHashMap<ObjectId, TypeMap>
(I'm assuming the runtime cost of the map lookup is negligible.). This container has in itself no synchronization backed into it, and the user would be free to use more than one if practical or relevant. Data is not automatically removed from the container, and the user needs to actively remove it when the relevant object is destroyed.This puts data management in a much more manual fashion, as a trade-off for removing a large part of the friction related to thread-safety that was caused by storing the user data inside of the
ObjectData
of each object.Dispatching messages
Probably the most central difficulty in API design for the Wayland protocol is the dispatching of incoming messages to the bits of logic that need to handle it. In this new potential design, the backend no longer does anything regarding that, so all must be done in wayland-client and wayland-server (or any other thing built on top of wayland-backend).
As a reminder of the necessary or desirable properties of such dispatching:
My current idea regarding that would be a system comprised of modules somewhat similar to an actor model structure: blocks that take messages as input (either raw messages from the Wayland socket, or higher-level user-defined enums), standardized with a trait, and that can be connected into one another to create a pipeline: a module keeps a reference (via owning, or an
Rc<_>
, etc..) to the nest module in the pipeline, and invokes it as necessary.The entry point for this system would be integrated into the
DataMap
: each object could be associated with a handle to the initial module of the chain that needs to process its messages. As a whole, the full pipeline of modules would thus be a DAG. We could thus have a method like:Where the type parameter
E
represents the final return type of the pipeline, to allow the representation of messages that need to be processed outside of the pipeline logic. Each module of the pipeline would have mutable access to theDataMap
(or at least a subset of it) and maybe we can fit something like the global&mut State
we are currently using as well.The core idea of that is that this pipeline can take any shape the user wants, for example:
ObjectData
)dispatch_message()
and handled directly by the main loop (like with a hugematch { ... }
In this, the modularity of crates like SCTK or Smithay would be achieved by providing such pre-implemented modules that a user could integrate in its pipeline.
Questions
So, this design is still quite high-level, and there are a lot of details to iron out, but I'd like to get the general feeling of the current users of wayland-rs with theses ideas. In particular: compared to what you are currently doing with wayland-rs, do you think this would make your life easier? Harder? What does this evoke you?
The text was updated successfully, but these errors were encountered: