wayland-rs icon indicating copy to clipboard operation
wayland-rs copied to clipboard

Delegated dispatchers

Open PolyMeilex opened this issue 1 year ago • 11 comments

This is an attempt to remove all delegate macros from Smithay, as well as remove obnoxious type constraints on D.

Here is an example diff of ZxdgOutputManagerV1 implementation in Smithay with new delegated dispatches.

impl<D> GlobalDispatch<ZxdgOutputManagerV1, (), D> for OutputManagerState
where
-    D: GlobalDispatch<ZxdgOutputManagerV1, ()>,
-    D: Dispatch<ZxdgOutputManagerV1, ()>,
-    D: Dispatch<ZxdgOutputV1, XdgOutputUserData>,
-    D: 'static,
{
}

All of the constraints on D can be removed, as OutputManagerState is the one implementing them.

This also completely removes the need to write and invoke delegate_*! macros.

I managed to implement delegated GlobalData without any breaking changes just by adding new default generic, but for ResourceData it's more cumbersome, as I had to add new DelegatedResourceData user data type. While this does not introduce any braking changes, the split is weird. As we now have .data::<U>() and .delegated_data::<U, DelegatedTo>() variants. Ideas on how to merge those types together without polluting the whole API are welcome.

(Smithay PR that demonstrates this in action https://github.com/Smithay/smithay/pull/1327)

PolyMeilex avatar Feb 11 '24 20:02 PolyMeilex

This is a nice take on this problem! Thanks a lot, the general principle looks good to me :+1:

Given this alters the generated code in a non-backward-compatible way, this will be a breaking change of wayland-scanner, and I think we should probably treat it as a breaking change of wayland-server as well as remove the cumbersome macros in the process.

Apart from waiting on the resolution of https://github.com/Smithay/wayland-rs/issues/698 for our CI to be functional again, I think before this is in a mergeable state it needs:

  • Proper documentation of the newly introduced API, and update of the relevant module-level docs
  • Equivalent changes for wayland-client
  • Proper changelog entries

elinorbgr avatar Feb 12 '24 12:02 elinorbgr

For an implementation in wayland-client, will this run into issues with requests that take a new_id?

https://docs.rs/wayland-client/latest/wayland_client/protocol/wl_seat/struct.WlSeat.html#method.get_keyboard for instance is defined as:

pub fn get_keyboard<U: Send + Sync + 'static, D: Dispatch<WlKeyboard, U> + 'static>(
    &self,
    qh: &QueueHandle<D>,
    udata: U
) -> WlKeyboard

Without the delegated implementation the macro generated, Dispatch<WlKeyboard, U> wouldn't be implemented.

Events with a new_id meanwhile are rare, and it seems wayland-server handles these differently (Smithay has to use backend.create_object directly.) It seems a bit odd that wayland-server and wayland-client do this differently.

ids1024 avatar Feb 12 '24 22:02 ids1024

Codecov Report

Attention: Patch coverage is 54.50000% with 91 lines in your changes are missing coverage. Please review.

Project coverage is 74.11%. Comparing base (eb7a57b) to head (9c1e369).

Files Patch % Lines
wayland-server/src/dispatch.rs 59.39% 54 Missing :warning:
wayland-server/src/client.rs 0.00% 19 Missing :warning:
wayland-server/src/display.rs 14.28% 18 Missing :warning:
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #699      +/-   ##
==========================================
- Coverage   75.34%   74.11%   -1.23%     
==========================================
  Files          48       45       -3     
  Lines        8257     7969     -288     
==========================================
- Hits         6221     5906     -315     
- Misses       2036     2063      +27     
Flag Coverage Δ
main 58.35% <4.02%> (-0.02%) :arrow_down:
test-- 77.90% <54.50%> (-3.18%) :arrow_down:
test--server_system 61.72% <54.50%> (-2.78%) :arrow_down:
test-client_system- 69.17% <54.50%> (-2.98%) :arrow_down:
test-client_system-server_system 52.12% <54.50%> (-2.70%) :arrow_down:

Flags with carried forward coverage won't be shown. Click here to find out more.

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

codecov[bot] avatar Feb 13 '24 17:02 codecov[bot]

Ok, client side is done. I went ahead and broke the API of QueueHandle::make_data<_,_, DelegateTo>, by adding the DelegateTo, as I suppose it's rarely used anyway, and this allows us to avoid delegated/non-delegated split of the API.

Code generated by the scanner will just output:

fn get_registry(qh) -> WlRegistry {
    let data = qh.make_data::<_, _, D>();
    ...
}

So nothing will change for the general case of global D type.

We as SCTK on the other hand can call the constructor manually with our DelegateTo type instead:

let data = qh.make_data::<WlRegistry, _, DelegateTo>(());
let wl_registry = display.send_constructor(wl_display::Request::GetRegistry {}, data).unwrap();

I belive that the code needed to call constructors manually is simple enough to no warrant an additional get_registry_delegated::<DelegateTo>() generation, or forcing the DelegateTo generic on regular get_registry.


Also it turns out that thanks to ObjectData::data_as_any on client side the generic on Proxy::data getter is totally not needed, I will experiment with similar approach on server side as well.

PolyMeilex avatar Feb 16 '24 21:02 PolyMeilex

I belive that the code needed to call constructors manually is simple enough to no warrant an additional get_registry_delegated::<DelegateTo>() generation, or forcing the DelegateTo generic on regular get_registry.

Yeah, we wouldn't really want to end up with a duplicated _delegated method for every method that creates an object. Not to require awkward explicit type generics. (And we can't have a generic with a default on methods: https://github.com/rust-lang/rust/issues/36887).

The only other possibility I can think of is if Dispatch was implemented on UserData, not State. Then the dispatcher would be implied by the type of the udata, at the cost of not being able to use standard types like () as a udata. This would also be a bigger breaking change.

ids1024 avatar Feb 16 '24 22:02 ids1024

Also it turns out that thanks to ObjectData::data_as_any on client side the generic on Proxy::data getter is totally not needed, I will experiment with similar approach on server side as well.

I gave up, the D generic on server side ObjectData<D> trait makes it harder to do, especially withtrait_upcasting being nightly only, so I'll leave it as is.

Btw, Docs should be up-to-date now as well. So I guess this could be considered ready.

PolyMeilex avatar Feb 16 '24 23:02 PolyMeilex

Gentle ping @elinorbgr, no rush tho.

PolyMeilex avatar Feb 24 '24 22:02 PolyMeilex

Or do you think there is a good reason to keep them alongside this new API?

Nope, removed. Those are easy to copy-paste if someone really needs them for some reason.

PolyMeilex avatar Mar 07 '24 22:03 PolyMeilex

In wayland-client, Globals::bind still has a bound on State: Dispatch<I, U> + 'static. Presumably it needs to be changed/duplicated to support delegation?

ids1024 avatar Mar 26 '24 03:03 ids1024

Seems to work with something like:

    pub fn bind_delegated<I, State, U, DelegateTo>(
        &self,
        qh: &QueueHandle<State>,
        version: RangeInclusive<u32>,
        udata: U,
    ) -> Result<I, BindError>
    where
        I: Proxy + 'static,
        State: 'static,
        U: Send + Sync + 'static,
        DelegateTo: Dispatch<I, U, State> + 'static,
    {
        ...

        Ok(self
            .registry
            .send_constructor(
                wl_registry::Request::Bind { name, id: (I::interface(), version) },
                qh.make_data::<I, U, DelegateTo>(udata),
            )
            .unwrap())

Then bind can be defined as:

    pub fn bind<I, State, U>(
        &self,
        qh: &QueueHandle<State>,
        version: RangeInclusive<u32>,
        udata: U,
    ) -> Result<I, BindError>
    where
        I: Proxy + 'static,
        State: Dispatch<I, U> + 'static,
        U: Send + Sync + 'static,
    {
        self.bind_delegated::<I, State, U, State>(qh, version, udata)
    }

ids1024 avatar Mar 26 '24 04:03 ids1024

registry_queue_init likewise requires State: Dispatch<wl_registry::WlRegistry, GlobalListContents>. So without sctk's relegate_registry, this now requires the application using sctk to explicitly implement Dispatch<wl_registry::WlRegistry, GlobalListContents>.

ids1024 avatar Mar 26 '24 04:03 ids1024