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}