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