pinnacle_api/
util.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Utility types.
6
7use std::pin::Pin;
8
9use futures::{stream::FuturesOrdered, Future, StreamExt};
10
11pub use crate::batch_boxed;
12pub use crate::batch_boxed_async;
13use crate::BlockOnTokio;
14
15/// A horizontal or vertical axis.
16#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
17pub enum Axis {
18    /// A horizontal axis.
19    Horizontal,
20    /// A vertical axis.
21    Vertical,
22}
23
24/// Batches a set of requests that will be sent to the compositor all at once.
25///
26/// # Rationale
27///
28/// Normally, all API calls are blocking. For example, calling [`window::get_all`][crate::window::get_all]
29/// then calling [`WindowHandle::app_id`][crate::window::WindowHandle::app_id]
30/// on each returned window handle will block after each `app_id` call waiting for the compositor to respond.
31///
32/// In order to mitigate this issue, you can batch up a set of API calls using this function.
33/// This will send all requests to the compositor at once without blocking, then wait for the compositor
34/// to respond.
35///
36/// You'll see that this function takes in an `IntoIterator` of `Future`s. As such,
37/// most API calls that return something have an async variant named `*_async` that returns a future.
38/// You must pass these futures into the batch function instead of their non-async counterparts.
39///
40/// # The `batch_boxed` macro
41/// The [`util`][crate::util] module also provides the [`batch_boxed`] macro.
42///
43/// The [`batch`] function only accepts one concrete type of future, meaning that you
44/// can only batch a collection of futures from one specific function or method.
45///
46/// As a convenience, `batch_boxed` accepts one or more different futures that return the same type.
47/// It will place provided futures in a `Pin<Box<_>>` to erase the types and pass them along to `batch`.
48///
49/// # Examples
50///
51/// ```no_run
52/// # use pinnacle_api::util::batch;
53/// # use pinnacle_api::window;
54/// let windows = window::get_all().collect::<Vec<_>>();
55/// let props: Vec<String> = batch(windows.iter().map(|window| window.app_id_async()));
56/// ```
57///
58pub fn batch<T>(requests: impl IntoIterator<Item = impl Future<Output = T>>) -> Vec<T> {
59    batch_async(requests).block_on_tokio()
60}
61
62/// The async version of [`batch`].
63///
64/// See [`batch`] for more information.
65pub async fn batch_async<T>(requests: impl IntoIterator<Item = impl Future<Output = T>>) -> Vec<T> {
66    let results = FuturesOrdered::from_iter(requests).collect::<Vec<_>>();
67    results.await
68}
69
70/// Batches API calls in different concrete futures.
71///
72/// The [`batch`] function only accepts a collection of the same concrete future e.g.
73/// from a single async function or method.
74///
75/// To support different futures (that still return the same type), this macro will place provided
76/// futures in a `Pin<Box<_>>` to erase their type and pass them along to `batch`.
77///
78/// # Examples
79///
80/// ```no_run
81/// # use pinnacle_api::util::batch_boxed;
82/// # use pinnacle_api::window;
83/// # || {
84/// let mut windows = window::get_all();
85/// let first = windows.next()?;
86/// let last = windows.last()?;
87///
88/// let classes: Vec<String> = batch_boxed![
89///     async {
90///         let class = first.app_id_async().await;
91///         class
92///     },
93///     async {
94///         let mut class = last.app_id_async().await;
95///         class += "hello";
96///         class
97///     },
98/// ];
99/// # Some(())
100/// # };
101/// ```
102#[macro_export]
103macro_rules! batch_boxed {
104    [ $($request:expr),* $(,)? ] => {
105        $crate::util::batch([
106            $(
107                ::std::boxed::Box::pin($request) as ::std::pin::Pin<::std::boxed::Box<dyn std::future::Future<Output = _>>>,
108            )*
109        ])
110    };
111}
112
113/// The async version of [`batch_boxed`].
114///
115/// See [`batch_boxed`] for more information.
116#[macro_export]
117macro_rules! batch_boxed_async {
118    [ $first:expr, $($request:expr),* ] => {
119        $crate::util::batch_async([
120            ::std::boxed::Box::pin($first) as ::std::pin::Pin<::std::boxed::Box<dyn std::future::Future<Output = _>>>,
121            $(
122                ::std::boxed::Box::pin($request),
123            )*
124        ])
125    };
126}
127
128/// Methods for batch sending API requests to the compositor.
129pub trait Batch<I> {
130    /// [`batch_map`][Batch::batch_map]s then finds the object for which `find` with the results
131    /// of awaiting `map_to_future(item)` returns `true`.
132    fn batch_find<M, F, FutOp>(self, map_to_future: M, find: F) -> Option<I>
133    where
134        Self: Sized,
135        M: for<'a> FnMut(&'a I) -> Pin<Box<dyn Future<Output = FutOp> + 'a>>,
136        F: FnMut(&FutOp) -> bool;
137
138    /// Maps the collection to compositor requests, batching all calls.
139    fn batch_map<F, FutOp>(self, map: F) -> impl Iterator<Item = FutOp>
140    where
141        Self: Sized,
142        F: for<'a> FnMut(&'a I) -> Pin<Box<dyn Future<Output = FutOp> + 'a>>;
143
144    /// [`batch_map`][Batch::batch_map]s then filters for objects for which `predicate` with the
145    /// results of awaiting `map_to_future(item)` returns `true`.
146    fn batch_filter<M, F, FutOp>(self, map_to_future: M, predicate: F) -> impl Iterator<Item = I>
147    where
148        Self: Sized,
149        M: for<'a> FnMut(&'a I) -> Pin<Box<dyn Future<Output = FutOp> + 'a>>,
150        F: FnMut(FutOp) -> bool;
151}
152
153impl<T: IntoIterator<Item = I>, I> Batch<I> for T {
154    fn batch_find<M, F, FutOp>(self, map_to_future: M, mut find: F) -> Option<I>
155    where
156        Self: Sized,
157        M: for<'a> FnMut(&'a I) -> Pin<Box<dyn Future<Output = FutOp> + 'a>>,
158        F: FnMut(&FutOp) -> bool,
159    {
160        let items = self.into_iter().collect::<Vec<_>>();
161        let futures = items.iter().map(map_to_future);
162        let results = crate::util::batch(futures);
163
164        assert_eq!(items.len(), results.len());
165
166        items
167            .into_iter()
168            .zip(results)
169            .find(|(_, fut_op)| find(fut_op))
170            .map(|(item, _)| item)
171    }
172
173    fn batch_map<F, FutOp>(self, map: F) -> impl Iterator<Item = FutOp>
174    where
175        Self: Sized,
176        F: for<'a> FnMut(&'a I) -> Pin<Box<dyn Future<Output = FutOp> + 'a>>,
177    {
178        let items = self.into_iter().collect::<Vec<_>>();
179        let futures = items.iter().map(map);
180        crate::util::batch(futures).into_iter()
181    }
182
183    fn batch_filter<M, F, FutOp>(
184        self,
185        map_to_future: M,
186        mut predicate: F,
187    ) -> impl Iterator<Item = I>
188    where
189        Self: Sized,
190        M: for<'a> FnMut(&'a I) -> Pin<Box<dyn Future<Output = FutOp> + 'a>>,
191        F: FnMut(FutOp) -> bool,
192    {
193        let items = self.into_iter().collect::<Vec<_>>();
194        let futures = items.iter().map(map_to_future);
195        let results = crate::util::batch(futures);
196
197        assert_eq!(items.len(), results.len());
198
199        items
200            .into_iter()
201            .zip(results)
202            .filter_map(move |(item, fut_op)| predicate(fut_op).then_some(item))
203    }
204}
205
206/// A point in space.
207#[derive(Copy, Clone, PartialEq, Eq, Hash, Default, Debug)]
208pub struct Point {
209    /// The x-coordinate.
210    pub x: i32,
211    /// The y-coordinate.
212    pub y: i32,
213}
214
215impl From<Point> for pinnacle_api_defs::pinnacle::util::v1::Point {
216    fn from(value: Point) -> Self {
217        Self {
218            x: value.x,
219            y: value.y,
220        }
221    }
222}
223
224/// A size with a width and height.
225#[derive(Copy, Clone, PartialEq, Eq, Hash, Default, Debug)]
226pub struct Size {
227    /// The width.
228    pub w: u32,
229    /// The height.
230    pub h: u32,
231}
232
233impl From<Size> for pinnacle_api_defs::pinnacle::util::v1::Size {
234    fn from(value: Size) -> Self {
235        Self {
236            width: value.w,
237            height: value.h,
238        }
239    }
240}
241
242/// A rectangle with a location and size.
243#[derive(Copy, Clone, PartialEq, Eq, Hash, Default, Debug)]
244pub struct Rect {
245    /// The location.
246    pub loc: Point,
247    /// The size.
248    pub size: Size,
249}
250
251impl From<Rect> for pinnacle_api_defs::pinnacle::util::v1::Rect {
252    fn from(value: Rect) -> Self {
253        Self {
254            loc: Some(value.loc.into()),
255            size: Some(value.size.into()),
256        }
257    }
258}