poll_promise/
lib.rs

1//! `poll-promise` is a Rust crate for polling the result of a concurrent (e.g. `async`) operation.
2//!
3//! It is particularly useful in games and immediate mode GUI:s, where one often wants to start
4//! a background operation and then ask "are we there yet?" on each subsequent frame
5//! until the operation completes.
6//!
7//! Example:
8//!
9//! ```
10//! # fn something_slow() {}
11//! # use poll_promise::Promise;
12//! #
13//! let promise = Promise::spawn_thread("slow_operation", something_slow);
14//!
15//! // Then in the game loop or immediate mode GUI code:
16//! if let Some(result) = promise.ready() {
17//!     // Use/show result
18//! } else {
19//!     // Show a loading screen
20//! }
21//! ```
22//!
23//! ## Features
24//! `poll-promise` can be used with any async runtime (or without one!),
25//! but a few convenience methods are added
26//! when compiled with the following features:
27//!
28#![doc = document_features::document_features!()]
29#![cfg_attr(docsrs, feature(doc_auto_cfg))]
30//!
31
32// BEGIN - Embark standard lints v6 for Rust 1.55+
33// do not change or add/remove here, but one can add exceptions after this section
34// for more info see: <https://github.com/EmbarkStudios/rust-ecosystem/issues/59>
35#![deny(unsafe_code)]
36#![warn(
37    clippy::all,
38    clippy::await_holding_lock,
39    clippy::char_lit_as_u8,
40    clippy::checked_conversions,
41    clippy::dbg_macro,
42    clippy::debug_assert_with_mut_call,
43    clippy::doc_markdown,
44    clippy::empty_enum,
45    clippy::enum_glob_use,
46    clippy::exit,
47    clippy::expl_impl_clone_on_copy,
48    clippy::explicit_deref_methods,
49    clippy::explicit_into_iter_loop,
50    clippy::fallible_impl_from,
51    clippy::filter_map_next,
52    clippy::flat_map_option,
53    clippy::float_cmp_const,
54    clippy::fn_params_excessive_bools,
55    clippy::from_iter_instead_of_collect,
56    clippy::if_let_mutex,
57    clippy::implicit_clone,
58    clippy::imprecise_flops,
59    clippy::inefficient_to_string,
60    clippy::invalid_upcast_comparisons,
61    clippy::large_digit_groups,
62    clippy::large_stack_arrays,
63    clippy::large_types_passed_by_value,
64    clippy::let_unit_value,
65    clippy::linkedlist,
66    clippy::lossy_float_literal,
67    clippy::macro_use_imports,
68    clippy::manual_ok_or,
69    clippy::map_err_ignore,
70    clippy::map_flatten,
71    clippy::map_unwrap_or,
72    clippy::match_on_vec_items,
73    clippy::match_same_arms,
74    clippy::match_wild_err_arm,
75    clippy::match_wildcard_for_single_variants,
76    clippy::mem_forget,
77    clippy::mismatched_target_os,
78    clippy::missing_enforced_import_renames,
79    clippy::mut_mut,
80    clippy::mutex_integer,
81    clippy::needless_borrow,
82    clippy::needless_continue,
83    clippy::needless_for_each,
84    clippy::option_option,
85    clippy::path_buf_push_overwrite,
86    clippy::ptr_as_ptr,
87    clippy::rc_mutex,
88    clippy::ref_option_ref,
89    clippy::rest_pat_in_fully_bound_structs,
90    clippy::same_functions_in_if_condition,
91    clippy::semicolon_if_nothing_returned,
92    clippy::single_match_else,
93    clippy::string_add_assign,
94    clippy::string_add,
95    clippy::string_lit_as_bytes,
96    clippy::string_to_string,
97    clippy::todo,
98    clippy::trait_duplication_in_bounds,
99    clippy::unimplemented,
100    clippy::unnested_or_patterns,
101    clippy::unused_self,
102    clippy::useless_transmute,
103    clippy::verbose_file_reads,
104    clippy::zero_sized_map_values,
105    future_incompatible,
106    nonstandard_style,
107    rust_2018_idioms
108)]
109// END - Embark standard lints v6 for Rust 1.55+
110// crate-specific exceptions:
111#![deny(missing_docs, rustdoc::missing_crate_level_docs)]
112
113mod promise;
114
115pub use promise::{Promise, Sender, TaskType};
116
117#[cfg(feature = "smol")]
118static EXECUTOR: smol::Executor<'static> = smol::Executor::new();
119#[cfg(feature = "smol")]
120thread_local! {
121    static LOCAL_EXECUTOR: smol::LocalExecutor<'static> = smol::LocalExecutor::new();
122}
123
124/// 'Tick' the `smol` thread executor.
125///
126/// Poll promise will call this for you when using [`Promise::block_until_ready`] and friends.
127/// If so desired [`Promise::poll`] will run this for you with the `smol_tick_poll` feature.
128#[cfg(feature = "smol")]
129pub fn tick() -> bool {
130    crate::EXECUTOR.try_tick()
131}
132
133/// 'Tick' the `smol` local thread executor.
134///
135/// Poll promise will call this for you when using [`Promise::block_until_ready`] and friends.
136/// If so desired [`Promise::poll`] will run this for you with the `smol_tick_poll` feature.
137#[cfg(feature = "smol")]
138pub fn tick_local() -> bool {
139    crate::LOCAL_EXECUTOR.with(|exec| exec.try_tick())
140}
141
142#[cfg(test)]
143mod test {
144    use crate::Promise;
145
146    #[test]
147    fn it_spawns_threads() {
148        let promise = Promise::spawn_thread("test", || {
149            std::thread::sleep(std::time::Duration::from_secs(1));
150            0
151        });
152
153        assert_eq!(0, promise.block_and_take());
154    }
155
156    #[test]
157    #[cfg(feature = "smol")]
158    fn it_runs_async_threaded() {
159        let promise = Promise::spawn_async(async move { 0 });
160
161        assert_eq!(0, promise.block_and_take());
162    }
163
164    #[tokio::test(flavor = "multi_thread")]
165    #[cfg(feature = "tokio")]
166    async fn it_runs_async_threaded() {
167        let promise = Promise::spawn_async(async move { 0 });
168
169        assert_eq!(0, promise.block_and_take());
170    }
171
172    #[test]
173    #[cfg(feature = "async-std")]
174    fn it_runs_async_threaded() {
175        let promise = Promise::spawn_async(async move { 0 });
176
177        assert_eq!(0, promise.block_and_take());
178    }
179
180    #[test]
181    #[cfg(feature = "smol")]
182    fn it_runs_locally() {
183        let promise = Promise::spawn_local(async move { 0 });
184
185        assert_eq!(0, promise.block_and_take());
186    }
187
188    #[test]
189    #[cfg(feature = "smol")]
190    fn it_runs_background() {
191        let promise = Promise::spawn_async(async move {
192            let mut e = 0;
193            for i in -10000..0 {
194                e += i;
195            }
196            e
197        });
198        #[cfg(not(feature = "smol_tick_poll"))]
199        crate::tick();
200
201        std::thread::sleep(std::time::Duration::from_secs(1));
202        assert!(promise.ready().is_some(), "was not finished");
203    }
204
205    #[tokio::test(flavor = "multi_thread")]
206    #[cfg(feature = "tokio")]
207    async fn it_runs_background() {
208        let promise = Promise::spawn_async(async move {
209            let mut e = 0;
210            for i in -10000..0 {
211                e += i;
212            }
213            e
214        });
215
216        std::thread::sleep(std::time::Duration::from_secs(1));
217        assert!(promise.ready().is_some(), "was not finished");
218    }
219
220    #[test]
221    #[cfg(feature = "async-std")]
222    fn it_runs_background() {
223        let promise = Promise::spawn_async(async move {
224            let mut e = 0;
225            for i in -10000..0 {
226                e += i;
227            }
228            e
229        });
230
231        std::thread::sleep(std::time::Duration::from_secs(1));
232        assert!(promise.ready().is_some(), "was not finished");
233    }
234
235    #[test]
236    #[cfg(feature = "smol")]
237    fn it_can_block() {
238        let promise = Promise::spawn_local(async move {
239            std::thread::sleep(std::time::Duration::from_secs(1));
240        });
241        #[cfg(not(feature = "smol_tick_poll"))]
242        crate::tick_local();
243
244        assert!(promise.ready().is_some(), "was not finished");
245    }
246
247    #[test]
248    #[cfg(feature = "smol")]
249    fn it_can_run_async_functions() {
250        let promise = Promise::spawn_async(async move { something_async().await });
251        #[cfg(not(feature = "smol_tick_poll"))]
252        crate::tick();
253
254        assert!(promise.block_and_take(), "example.com is ipv4");
255    }
256    #[tokio::test(flavor = "multi_thread")]
257    #[cfg(feature = "tokio")]
258    async fn it_can_run_async_functions() {
259        let promise = Promise::spawn_async(async move { something_async().await });
260
261        assert!(promise.block_and_take(), "example.com is ipv4");
262    }
263
264    #[test]
265    #[cfg(feature = "async-std")]
266    fn it_can_run_async_functions() {
267        let promise = Promise::spawn_async(async move { something_async().await });
268
269        assert!(promise.block_and_take(), "example.com is ipv4");
270    }
271
272    #[test]
273    #[cfg(feature = "smol")]
274    fn it_can_run_async_functions_locally() {
275        let promise = Promise::spawn_local(async move { something_async().await });
276        #[cfg(not(feature = "smol_tick_poll"))]
277        crate::tick_local();
278
279        assert!(promise.block_and_take(), "example.com is ipv4");
280    }
281
282    #[cfg(any(feature = "smol", feature = "tokio", feature = "async-std"))]
283    async fn something_async() -> bool {
284        async_net::resolve("example.com:80").await.unwrap()[0].is_ipv4()
285    }
286}