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

Delegated dispatchers #699

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions wayland-backend/tests/rs_sys_impls.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(clippy::test_attr_in_doctest)]

//! Tests to ensure the rust and sys types implement the same traits.

/// A macro used to assert a type defined in both the rust and sys implementations of wayland-backend
Expand Down
5 changes: 5 additions & 0 deletions wayland-client/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

#### Breaking changes
- `QueueHandle::make_data` now accepts additional `DelegateTo` generic,
therefore allowing users to dispatch events to types different than main `State`
- `delegate_dispatch` Removed in favour of `DelegateTo` generic on `QueueHandle::make_data`

## 0.31.2 -- 2024-01-29

#### Additions
Expand Down
120 changes: 120 additions & 0 deletions wayland-client/examples/delegated.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#![allow(clippy::single_match)]

use wayland_client::{
protocol::{
wl_compositor::{self, WlCompositor},
wl_display::{self, WlDisplay},
wl_registry::{self, WlRegistry},
},
Connection, Dispatch, Proxy, QueueHandle,
};

/// A demonstration of how delegateing can make implementing protocols an implementation detail
///
/// Users of this module only need to implement `RegistryHandler` trait on their state,
/// Implementation of `Dispatch` can remain an internal detail of the module.
///
/// In a way you can pretend that everything inside of this submodule is a library / different crate
mod delegated {
use super::*;

pub trait RegistryHandler: 'static {
fn state(&mut self) -> &mut Registry;
fn new_global(&mut self, name: u32, interface: &str, version: u32);
}

pub struct Registry {
wl_registry: WlRegistry,
}

impl Registry {
/// Create a [`WlRegistry`] object, and handle it's events internally
/// It can use [`RegistryHandler`] trait to callback to your `D` state.
pub fn new<D: RegistryHandler>(qh: &QueueHandle<D>, display: &WlDisplay) -> Self {
// Let's construct a `WlRegistry` object that dispatches it's events to our
// `Registry::event` rather than to `D`,
// that way it can remain an implementation detail
let data = qh.make_data::<WlRegistry, _, Self>(());
let wl_registry =
display.send_constructor(wl_display::Request::GetRegistry {}, data).unwrap();

Self { wl_registry }
}

pub fn wl_registry(&self) -> WlRegistry {
self.wl_registry.clone()
}
}

impl<D: RegistryHandler> Dispatch<WlRegistry, (), D> for Registry {
/// Called whenever an object created via `make_data<WlRegistry, _, Registry>`
/// receives a server event
fn event(
state: &mut D,
_: &wl_registry::WlRegistry,
event: wl_registry::Event,
_: &(),
_: &Connection,
_: &QueueHandle<D>,
) {
if let wl_registry::Event::Global { name, interface, version } = event {
// Let's callback the user of this abstraction, informing them about new global
state.new_global(name, &interface, version);
}
}
}
}

struct AppData {
registry: delegated::Registry,
qh: QueueHandle<Self>,
}

impl delegated::RegistryHandler for AppData {
fn state(&mut self) -> &mut delegated::Registry {
&mut self.registry
}

// Even tho we did not implement WlRegistry, `delegated::Registry` implemented it for us,
// and will call this method whenever new globals appear
fn new_global(&mut self, name: u32, interface: &str, version: u32) {
println!("[{}] {} (v{})", name, interface, version);

match interface {
"wl_compositor" => {
self.registry.wl_registry().bind(name, version, &self.qh, ());
}
_ => {}
}
}
}

impl Dispatch<WlCompositor, ()> for AppData {
fn event(
_state: &mut Self,
_proxy: &WlCompositor,
_event: wl_compositor::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}

fn main() {
let conn = Connection::connect_to_env().unwrap();

let display = conn.display();

let mut event_queue = conn.new_event_queue::<AppData>();
let qh = event_queue.handle();

// Let's ask `delegated::Registry` to implement `WlRegistry` for us, only calling us back whenever
// necessary via `RegistryHandler` trait
let registry = delegated::Registry::new(&qh, &display);

let mut app = AppData { registry, qh: qh.clone() };

println!("Advertized globals:");
event_queue.roundtrip(&mut app).unwrap();
}
151 changes: 43 additions & 108 deletions wayland-client/src/event_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,11 @@
/// currently fails to understand that it also provides `Dispatch<I, U, Self>` (assuming all other
/// trait bounds are respected as well).
///
/// [`delegate_dispatch!`]: crate::delegate_dispatch

Check warning on line 92 in wayland-client/src/event_queue.rs

View workflow job for this annotation

GitHub Actions / Documentation on Github Pages

unresolved link to `crate::delegate_dispatch`
pub trait Dispatch<I, UserData, State = Self>
where
Self: Sized,
I: Proxy,
State: Dispatch<I, UserData, State>,
{
/// Called when an event from the server is processed
///
Expand Down Expand Up @@ -172,7 +171,7 @@
match opcode {
$(
$opcode => {
qhandle.make_data::<$child_iface, _>({$child_udata})
qhandle.make_data::<$child_iface, _, $selftype>({$child_udata})
},
)*
_ => {
Expand Down Expand Up @@ -324,16 +323,17 @@
}

impl<State> EventQueueInner<State> {
pub(crate) fn enqueue_event<I, U>(
pub(crate) fn enqueue_event<I, U, DispatchTo>(
&mut self,
msg: Message<ObjectId, OwnedFd>,
odata: Arc<dyn ObjectData>,
) where
State: Dispatch<I, U> + 'static,
State: 'static,
DispatchTo: Dispatch<I, U, State> + 'static,
U: Send + Sync + 'static,
I: Proxy + 'static,
{
let func = queue_callback::<I, U, State>;
let func = queue_callback::<I, U, State, DispatchTo>;
self.queue.push_back(QueueEvent(func, msg, odata));
if self.freeze_count == 0 {
if let Some(waker) = self.waker.take() {
Expand Down Expand Up @@ -604,14 +604,35 @@
/// This creates an implementation of [`ObjectData`] fitting for direct use with `wayland-backend` APIs
/// that forwards all events to the event queue associated with this token, integrating the object into
/// the [`Dispatch`]-based logic of `wayland-client`.
pub fn make_data<I: Proxy + 'static, U: Send + Sync + 'static>(
///
/// Events will be dispatched via [`Dispatch`] to a `DelegateTo` generic type.
PolyMeilex marked this conversation as resolved.
Show resolved Hide resolved
/// Eg.
/// ```ignore
/// struct OutputDispatcher;
///
/// impl<D> Dispatch<WlOutput, (), D> for OutputDispatcher {
/// // if `DelegateTo` of an object is set to `OutputDispatcher` events will be dispatched
/// // to this impl
/// }
///
/// struct SeatDispatcher;
///
/// impl<D> Dispatch<WlSeat, (), D> for SeatDispatcher {
/// // if `DelegateTo` of an object is set to `SeatDispatcher` events will be dispatched here
/// // to this impl
/// }
///
/// let obj1 = qh.make_data::<WlOutput, (), OutputDispatcher>(());
/// let obj2 = qh.make_data::<WlSeat, (), SeatDispatcher>(());
/// ```
pub fn make_data<I: Proxy + 'static, U: Send + Sync + 'static, DelegateTo>(
&self,
user_data: U,
) -> Arc<dyn ObjectData>
where
State: Dispatch<I, U, State>,
DelegateTo: Dispatch<I, U, State> + 'static,
{
Arc::new(QueueProxyData::<I, U, State> {
Arc::new(QueueProxyData::<I, U, State, DelegateTo> {
handle: self.clone(),
udata: user_data,
_phantom: PhantomData,
Expand Down Expand Up @@ -643,7 +664,8 @@
fn queue_callback<
I: Proxy + 'static,
U: Send + Sync + 'static,
State: Dispatch<I, U, State> + 'static,
State,
DelegateTo: Dispatch<I, U, State> + 'static,
>(
handle: &Connection,
msg: Message<ObjectId, OwnedFd>,
Expand All @@ -653,21 +675,23 @@
) -> Result<(), DispatchError> {
let (proxy, event) = I::parse_event(handle, msg)?;
let udata = odata.data_as_any().downcast_ref().expect("Wrong user_data value for object");
<State as Dispatch<I, U, State>>::event(data, &proxy, event, udata, handle, qhandle);
<DelegateTo as Dispatch<I, U, State>>::event(data, &proxy, event, udata, handle, qhandle);
Ok(())
}

/// The [`ObjectData`] implementation used by Wayland proxies, integrating with [`Dispatch`]
pub struct QueueProxyData<I: Proxy, U, State> {
pub struct QueueProxyData<I: Proxy, U, State, DispatchTo = State> {
handle: QueueHandle<State>,
/// The user data associated with this object
pub udata: U,
_phantom: PhantomData<fn(&I)>,
_phantom: PhantomData<fn(&I, &DispatchTo)>,
}

impl<I: Proxy + 'static, U: Send + Sync + 'static, State> ObjectData for QueueProxyData<I, U, State>
impl<I: Proxy + 'static, U: Send + Sync + 'static, State, DispatchTo> ObjectData
for QueueProxyData<I, U, State, DispatchTo>
where
State: Dispatch<I, U, State> + 'static,
State: 'static,
DispatchTo: Dispatch<I, U, State> + 'static,
{
fn event(
self: Arc<Self>,
Expand All @@ -678,9 +702,9 @@
.args
.iter()
.any(|arg| matches!(arg, Argument::NewId(id) if !id.is_null()))
.then(|| State::event_created_child(msg.opcode, &self.handle));
.then(|| DispatchTo::event_created_child(msg.opcode, &self.handle));

self.handle.inner.lock().unwrap().enqueue_event::<I, U>(msg, self.clone());
self.handle.inner.lock().unwrap().enqueue_event::<I, U, DispatchTo>(msg, self.clone());

new_data
}
Expand All @@ -692,7 +716,9 @@
}
}

impl<I: Proxy, U: std::fmt::Debug, State> std::fmt::Debug for QueueProxyData<I, U, State> {
impl<I: Proxy, U: std::fmt::Debug, State, DispatchTo> std::fmt::Debug
for QueueProxyData<I, U, State, DispatchTo>
{
#[cfg_attr(coverage, coverage(off))]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QueueProxyData").field("udata", &self.udata).finish()
Expand All @@ -717,97 +743,6 @@
* Dispatch delegation helpers
*/

/// A helper macro which delegates a set of [`Dispatch`] implementations for proxies to some other type which
/// provides a generic [`Dispatch`] implementation.
///
/// This macro allows more easily delegating smaller parts of the protocol an application may wish to handle
/// in a modular fashion.
///
/// # Usage
///
/// For example, say you want to delegate events for [`WlRegistry`](crate::protocol::wl_registry::WlRegistry)
/// to the struct `DelegateToMe` for the [`Dispatch`] documentatione example.
///
/// ```
/// use wayland_client::{delegate_dispatch, protocol::wl_registry};
/// #
/// # use wayland_client::Dispatch;
/// #
/// # struct DelegateToMe;
/// # struct MyUserData;
/// #
/// # impl<State> Dispatch<wl_registry::WlRegistry, MyUserData, State> for DelegateToMe
/// # where
/// # State: Dispatch<wl_registry::WlRegistry, MyUserData> + AsMut<DelegateToMe>,
/// # {
/// # fn event(
/// # _state: &mut State,
/// # _proxy: &wl_registry::WlRegistry,
/// # _event: wl_registry::Event,
/// # _udata: &MyUserData,
/// # _conn: &wayland_client::Connection,
/// # _qhandle: &wayland_client::QueueHandle<State>,
/// # ) {
/// # }
/// # }
///
/// // ExampleApp is the type events will be dispatched to.
///
/// /// The application state
/// struct ExampleApp {
/// /// The delegate for handling wl_registry events.
/// delegate: DelegateToMe,
/// }
///
/// // Use delegate_dispatch to implement Dispatch<wl_registry::WlRegistry, MyUserData> for ExampleApp
/// delegate_dispatch!(ExampleApp: [wl_registry::WlRegistry: MyUserData] => DelegateToMe);
///
/// // DelegateToMe requires that ExampleApp implements AsMut<DelegateToMe>, so we provide the
/// // trait implementation.
/// impl AsMut<DelegateToMe> for ExampleApp {
/// fn as_mut(&mut self) -> &mut DelegateToMe {
/// &mut self.delegate
/// }
/// }
///
/// // To explain the macro above, you may read it as the following:
/// //
/// // For ExampleApp, delegate WlRegistry to DelegateToMe.
///
/// // Assert ExampleApp can Dispatch events for wl_registry
/// fn assert_is_registry_delegate<T>()
/// where
/// T: Dispatch<wl_registry::WlRegistry, MyUserData>,
/// {
/// }
///
/// assert_is_registry_delegate::<ExampleApp>();
/// ```
#[macro_export]
macro_rules! delegate_dispatch {
($(@< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? $dispatch_from:ty : [$interface: ty: $udata: ty] => $dispatch_to: ty) => {
impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $crate::Dispatch<$interface, $udata> for $dispatch_from {
fn event(
state: &mut Self,
proxy: &$interface,
event: <$interface as $crate::Proxy>::Event,
data: &$udata,
conn: &$crate::Connection,
qhandle: &$crate::QueueHandle<Self>,
) {
<$dispatch_to as $crate::Dispatch<$interface, $udata, Self>>::event(state, proxy, event, data, conn, qhandle)
}

fn event_created_child(
opcode: u16,
qhandle: &$crate::QueueHandle<Self>
) -> ::std::sync::Arc<dyn $crate::backend::ObjectData> {
<$dispatch_to as $crate::Dispatch<$interface, $udata, Self>>::event_created_child(opcode, qhandle)
}
}
};
}

/// A helper macro which delegates a set of [`Dispatch`] implementations for proxies to a static handler.
///
/// # Usage
Expand Down
5 changes: 4 additions & 1 deletion wayland-client/src/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,10 @@ where
.inner
.lock()
.unwrap()
.enqueue_event::<wl_registry::WlRegistry, GlobalListContents>(msg, self.clone())
.enqueue_event::<wl_registry::WlRegistry, GlobalListContents, State>(
msg,
self.clone(),
)
}

// We do not create any objects in this event handler.
Expand Down
Loading
Loading