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}