├── .cargo └── config.toml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE_APACHE_2_0.txt ├── LICENSE_BOOST_1_0.txt ├── LICENSE_MIT.txt ├── README.md ├── docs ├── Cargo.toml └── src │ └── main.rs ├── examples ├── counter.rs ├── executor.rs ├── no-std │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── pool.rs ├── recursive.rs ├── resume.rs ├── slices.rs ├── spawn.rs ├── tasks.rs └── timer.rs ├── src ├── lib.rs ├── loop.rs ├── notify.rs └── spawn.rs └── tests └── spawn.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = [ 3 | "-Danonymous_parameters", 4 | "-Dmissing_copy_implementations", 5 | "-Dmissing_debug_implementations", 6 | "-Dnonstandard_style", 7 | "-Drust_2018_idioms", 8 | "-Dsingle_use_lifetimes", 9 | "-Dtrivial_casts", 10 | "-Dtrivial_numeric_casts", 11 | "-Dunreachable_pub", 12 | "-Dunused_extern_crates", 13 | "-Dunused_qualifications", 14 | "-Dvariant_size_differences", 15 | "-Dclippy::uninlined_format_args", 16 | "-Dclippy::needless_pass_by_value", 17 | "-Dclippy::semicolon_if_nothing_returned", 18 | "-Dclippy::default-trait-access", 19 | ] 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "11:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: tests 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, macos-latest] # , windows-latest] 11 | tc: [stable, beta, nightly] 12 | ar: [--all, --all --no-default-features] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: ${{ matrix.tc }} 19 | override: true 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: test 23 | args: ${{ matrix.ar }} 24 | cross-compile: 25 | runs-on: ${{ matrix.os }} 26 | strategy: 27 | matrix: 28 | os: [ubuntu-latest] 29 | tc: [stable] # , beta, nightly] 30 | cc: 31 | - aarch64-linux-android 32 | - i686-pc-windows-gnu 33 | - i686-unknown-freebsd 34 | - i686-unknown-linux-gnu 35 | - wasm32-wasi 36 | - x86_64-apple-darwin 37 | - x86_64-unknown-redox 38 | steps: 39 | - uses: actions/checkout@v2 40 | - uses: actions-rs/toolchain@v1 41 | with: 42 | profile: minimal 43 | toolchain: ${{ matrix.tc }} 44 | target: ${{ matrix.cc }} 45 | override: true 46 | - uses: actions-rs/cargo@v1 47 | with: 48 | command: build 49 | args: --no-default-features --target=${{ matrix.cc }} 50 | cross-compile-ios: 51 | runs-on: ${{ matrix.os }} 52 | strategy: 53 | matrix: 54 | os: [macos-latest] 55 | tc: [stable, beta, nightly] 56 | cc: [aarch64-apple-ios] 57 | steps: 58 | - uses: actions/checkout@v2 59 | - uses: actions-rs/toolchain@v1 60 | with: 61 | profile: minimal 62 | toolchain: ${{ matrix.tc }} 63 | target: ${{ matrix.cc }} 64 | override: true 65 | - uses: actions-rs/cargo@v1 66 | with: 67 | command: build 68 | args: --no-default-features --target=${{ matrix.cc }} 69 | cross-compile-web: 70 | runs-on: ${{ matrix.os }} 71 | strategy: 72 | matrix: 73 | os: [ubuntu-latest] 74 | tc: [stable, beta, nightly] 75 | cc: [wasm32-unknown-unknown] 76 | steps: 77 | - uses: actions/checkout@v2 78 | - uses: actions-rs/toolchain@v1 79 | with: 80 | profile: minimal 81 | toolchain: ${{ matrix.tc }} 82 | target: ${{ matrix.cc }} 83 | override: true 84 | - uses: actions-rs/cargo@v1 85 | with: 86 | command: build 87 | args: --features=web --target=${{ matrix.cc }} 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /**/target/ 2 | /.cargo-ok 3 | /Cargo.lock 4 | /**/*.rs.bk 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Don't deviate too much, just reduce columns and stricter enforcement 2 | edition = "2021" 3 | unstable_features = true 4 | max_width = 80 5 | reorder_impl_items = true 6 | group_imports = "StdExternalCrate" 7 | imports_granularity = "Crate" 8 | newline_style = "Unix" 9 | normalize_doc_attributes = true 10 | wrap_comments = true 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to `pasts` will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://jeronlau.tk/semver/). 6 | 7 | ## [0.14.3] - 2023-06-02 8 | ### Changed 9 | - Updated documentation and examples to point to the new `0.4` version of the 10 | `async_main` crate. 11 | 12 | ## [0.14.2] - 2023-03-02 13 | ### Fixed 14 | - Tasks not spawning when *`web`* feature is enabled 15 | 16 | ## [0.14.1] - 2023-02-19 17 | ### Fixed 18 | - Documentation recommending wrong version of `async_main` crate 19 | 20 | ## [0.14.0] - 2023-02-19 21 | ### Added 22 | - `NotifyExt` trait, split off from `Notify` 23 | - `NotifyExt` to the prelude 24 | - `notify::select()`, `notify::Select` 25 | - `notify::pending()`, `notify::Pending` 26 | - `notify::ready()`, `notify::Ready` 27 | - `notify::future_fn()` to match the rest of the async ecosystem 28 | - `notify::poll_fn()` to match the rest of the async ecosystem 29 | - `notify::Map`, returned from `Notify::map()` 30 | - `notify::Next`, returned from `Notify::next()` 31 | - Missing `impl Debug for BoxNotify` (was already on `LocalBoxNotify`) 32 | - `Executor::spawn_boxed()` 33 | - `Executor::spawn_notify()` 34 | 35 | ### Changed 36 | - Rename `Notifier` to `notify::Notify` 37 | - Rename `BoxNotifier` to `notify::BoxNotify` 38 | - Rename `LocalBoxNotifier` to `notify::LocalBoxNotify` 39 | - Rename `Poller` to `PollFn` 40 | - Rename `Loop` to `FutureFn` 41 | - Rename `Join` to `Loop` 42 | 43 | ### Removed 44 | - Unused dependency `pin_utils` 45 | - `Poller::new()`; use `notify::poll_fn()` instead 46 | - `Loop::new()`; use `notify::future_fn()` instead 47 | - `Loop::pin()`; use `notify::future_fn()` instead 48 | - Third generic for `FutureFn` 49 | - `Notify::map()`, use `NotifyExt::map()` instead 50 | - `Notify::next()`, use `NotifyExt::next()` instead 51 | - `Spawn` trait, use `async_main` crate instead 52 | 53 | ### Fixed 54 | - Remove useless generic `B` from `NotifyExt::map()` 55 | 56 | ## [0.13.1] - 2023-01-28 57 | ### Fixed 58 | - Bug where spawning without suspending the future while blocking the executor 59 | never executes the spawned future. 60 | 61 | ## [0.13.0] - 2023-01-16 62 | ### Added 63 | - Added `Spawn` trait for spawning both `Send` futures and local tasks. 64 | - Re-export of `Spawn` in the `prelude`. 65 | - `Executor::block_on()` 66 | - Defaults for `T = ()` on both `BoxNotifier` and `Notifier` 67 | - *`std`* feature, enabled by default 68 | 69 | ### Changed 70 | - `prelude::Poll` is now a type alias with generic default set to unit 71 | - Recommend using `async_main` crate in docs 72 | - `core::task::Context` is now re-exported as `Task` instead of `Exec` in 73 | prelude 74 | - `Local` type alias is renamed to `LocalBoxNotifier` 75 | - `Task` type alias is renamed to `BoxNotifier` 76 | - `Executor` no longer executes on `Drop`; must call `block_on()` instead 77 | 78 | ### Removed 79 | - `Sleep`, use `Pool` and `Park` instead 80 | - *`no-std`* feature, for no-std environments disable *`std`* instead 81 | 82 | ### Fixed 83 | - Infinite recursion in `impl Notifier for Box` 84 | 85 | ## [0.12.0] - 2022-07-31 86 | ### Added 87 | - **`no-std`** feature 88 | 89 | ### Changed 90 | - `Executor::new()` now takes `impl Into>` instead of `I` 91 | - `Executor::spawn()` no longer requires `Unpin` futures 92 | - `Sleep` trait now requires `Send + Sync + 'static` 93 | - Started using `core::hint::spin_loop()` for default no-std executor 94 | 95 | ### Removed 96 | - **`std`** feature - to use pasts on no-std environments use the new 97 | **`no-std`** feature instead 98 | 99 | ## [0.11.0] - 2022-06-10 100 | ### Added 101 | - `Sleep` trait for implementing custom executors 102 | - `Notifier` trait (like `AsyncIterator`, but infinite) 103 | - `Poller` struct for compatibility with futures 104 | - `Fuse` trait for turning `Future`s into `Notifier`s 105 | - `Executor` struct for custom executors 106 | - `Loop` struct for a notifier created from future producers 107 | - `Box`, `Future`, `Pin`, `Exec` (alias to `core::task::Context`), `Executor`, 108 | `Fuse`, `Local`, `Task` and `Notifier` to prelude. 109 | - `Local` type alias for `!Send` boxed `Notifier`s 110 | - `Task` type alias for `Send` boxed `Notifier`s 111 | 112 | ### Changed 113 | - `Loop` renamed to `Join` 114 | - `Join::on()` now takes a closure for the notifier 115 | - `Task` got split into many different specialized types 116 | 117 | ### Removed 118 | - `poll_next_fn()` in favor of new `Poller` type 119 | - `block_on()` - all futures must be spawned locally now (this change was made 120 | to support the same behavior on web assembly as other platforms) 121 | - `BlockOn` trait in favor of new `Executor` struct 122 | - `Executor` trait in favor of using new `Sleep` trait in combination with the 123 | `Wake` trait from the std library. 124 | 125 | ## [0.10.0] - 2022-05-07 126 | ### Added 127 | - More documentation 128 | - `poll_next_fn()` 129 | 130 | ### Changed 131 | - Completely reimplemented `Task` so it doesn't always require allocation or 132 | `Send` (it should be more obvious which methods require allocation now) 133 | - `Loop::on()` accepts different types for the second parameter 134 | 135 | ## [0.9.0] - 2022-03-27 136 | ### Added 137 | - A `prelude` module containing a `core::task::Poll::{self, Pending, Ready}` 138 | re-export 139 | - `Loop::on()` 140 | - `BlockOn` trait 141 | - `Task::new()` 142 | - `Task::poll_next()` 143 | 144 | ### Changed 145 | - Replaced `Loop::when` and `Loop::poll` with `Loop::on()` 146 | - Move `block_on_pinned()` and `block_on` out of `Executor` and into their own 147 | `BlockOn` trait 148 | - `Task` is no longer an alias, but its own type 149 | 150 | ### Removed 151 | - `Loop::when()` - use `Loop::on()` 152 | - `Loop::poll()` - use `Loop::on()` 153 | 154 | ## [0.8.0] - 2021-06-18 155 | ### Added 156 | - `Loop` struct to replace `wait!()` and `exec!()`. 157 | - `Task` type definition for dynamically spawning tasks. 158 | - `Executor` trait for implementing custom executors on no-std. 159 | - `Past` struct for executing `!Unpin` futures. 160 | 161 | ### Changed 162 | - Removed all unsafe! 163 | - Executor no longer terminates program upon future completion. 164 | - Executor now uses thread parking instead of condvars internally. 165 | 166 | ### Removed 167 | - `exec!()` macro - use `Loop::when()` instead. 168 | - `join!()` macro - use `Loop::poll()` instead. 169 | - `race!()` macro - use `Loop::poll()` instead. 170 | - `wait!()` macro - use `Loop::when()` instead. 171 | 172 | ## [0.7.4] - 2021-01-08 173 | ### Fixed 174 | - Executor never going to sleep, wasting CPU cycles. 175 | 176 | ## [0.7.3] - 2021-01-07 177 | ### Fixed 178 | - Executor freezing up and not being recoverable (happenned sometimes when two 179 | asynchronous tasks simultaneously woke the executor). 180 | 181 | ## [0.7.2] - 2020-12-29 182 | ### Fixed 183 | - Links in README 184 | 185 | ## [0.7.1] - 2020-12-29 186 | ### Fixed 187 | - Category slug. 188 | 189 | ## [0.7.0] - 2020-12-29 190 | ### Added 191 | - `block_on()` function to execute a future on a thread. 192 | 193 | ### Changed 194 | - Renamed `poll!()` to `race!()` 195 | - Separated non-array functionality of `poll!()` into new macro: `wait!(). 196 | - `join!()` no longer requires the futures to be pinned, as it can pin them 197 | itself. 198 | - `exec!()` macro can no longer start multiple threads, but you can use it on 199 | multiple threads simultaneously. `exec!()` no longer takes a future, but 200 | an asynchronous expression that gets run in an infinite loop. 201 | 202 | ### Removed 203 | - `prelude` module. 204 | - `task!()` macro. 205 | - `Task` type. 206 | 207 | ## [0.6.0] - 2020-11-22 208 | ### Added 209 | - `Task` type alias: `Pin<&'a mut (dyn Future + Unpin)>` 210 | - `task!()` macro to create a `Task` from an async block or function. 211 | - `exec!()` macro to execute futures from synchronous code, supporting 212 | parallelization when the **std** feature is enabled, and not on WASM. 213 | - `poll!()` macro to create a future that returns ready when the first of a 214 | list of futures returns ready. 215 | - `join!()` macro to concurrently push multiple futures to completion. 216 | 217 | ### Removed 218 | - `DynFuture` and `DynFut` as they were unsound; you may now use the `task!()` 219 | macro for the same effect. 220 | - `spawn()` as it was also unsound due to the fact it made executors that did 221 | not have a `'static` lifetime nor did reference counting; you may now use the 222 | `exec!()` macro instead. 223 | - `JoinHandle` - though there are no replacements for this API, you can use 224 | `exec!()` to create a thread pool with `num_cpus` to execute tasks. 225 | - `Join` trait - use `join!()` macro, which can take any number of arguments 226 | rather than being limited to six. 227 | - `Select` trait - use `poll!()` macro, which automatically changes your 228 | futures into `Task`s. It was renamed to `poll!()` because it works 229 | differently than `select!()` seen elsewhere in the async ecosystem. 230 | - `SelectBoxed` - no longer needed, `poll!()` works on `Box`es 231 | - `SelectOptional` - you may now use task queues (`Vec`), and remove 232 | tasks from the vec on completion. 233 | - A lot of unsafe code, and also lines of code (less than 250 instead of over 234 | 1000). 235 | 236 | ### Fixed 237 | - At least two unsoundness issues. 238 | 239 | ## [0.5.0] - 2020-11-14 240 | ### Added 241 | - `spawn()` function to start a non-blocking task that may run on a separate 242 | thread. 243 | - `JoinHandle` struct that lets you `.await` on the termination of a task 244 | started with `spawn()`. 245 | - `SelectBoxed` and `SelectOptional` traits to reduce boilerplate 246 | 247 | ### Changed 248 | - Now the `alloc` crate is required. 249 | 250 | ### Removed 251 | - `Executor` trait and `CvarExec` implementation, now you should use `spawn()` 252 | instead. 253 | - `spawn_blocking()`, now you should transition to using non-blocking system 254 | APIs 255 | - `DynBoxFut`, as it is now completely useless because `DynFut` works on boxes. 256 | 257 | ## [0.4.0] - 2020-05-17 258 | ### Added 259 | - `DynBoxFut` which can be enabled with the new **alloc** feature. Useful for 260 | converting future trait objects into the `DynFuture` type. Note that enabling 261 | **std** automatically enables the **alloc** feature. 262 | 263 | ### Changed 264 | - Rename `ThreadInterrupt` to `CvarExec`. 265 | - Rename `Interrupt` to `Executor`. No longer requires `new()` to be 266 | implemented, and `block_on` is now a method rather than an associated 267 | function. It is still recommended to implement `new()`, and do it as a `const 268 | fn`. `wait_for()` method is renamed to `wait_for_event()` and is now marked 269 | `unsafe` in order to guarantee soundness. `interrupt` method is now 270 | `trigger_event()` and marked `unsafe` for the same reason. An `is_used()` 271 | method is now required as well. Executors must now have a static lifetime; 272 | This is in order to fix the `block_on()` bug mentioned below. 273 | 274 | ### Fixed 275 | - After return of `block_on()`, `Waker`s from that executor containing pointers 276 | to free'd memory, and dereferencing them on `.wake()`. This unsound behavior 277 | is no longer possible without `unsafe` code. 278 | 279 | ## [0.3.0] - 2020-05-06 280 | ### Changed 281 | - `Join` trait now takes `self` instead of `&mut self`, fixes UB 282 | - Internals of `Select` no longer contain unsafe code. 283 | 284 | ### Fixed 285 | - `.join()` allowing for moving pinned futures. 286 | 287 | ## [0.2.0] - 2020-05-05 288 | ### Changed 289 | - Simplified `select!()` implementation. This also reduces the amount of bounds 290 | checking. 291 | - `Select` trait now requires that `Future`s are `Unpin`, this fixes a bug that 292 | allowed for pinned futures to be moved between calls to `.select()`. 293 | 294 | ### Fixed 295 | - `.select()` allowing for moving pinned futures. 296 | 297 | ### Contributors 298 | Thanks to everyone who contributed to make this version of pasts possible! 299 | 300 | - [AldaronLau](https://github.com/AldaronLau) 301 | - [Darksonn](https://github.com/Darksonn) 302 | 303 | ## [0.1.0] - 2020-05-03 304 | ### Added 305 | - `Join` trait to replace `join!()` 306 | - `Select` trait to replace `select!()` 307 | - `DynFut` trait for converting `Future`s into `DynFuture`s. This lets you put 308 | your futures into arrays. 309 | - `prelude` module for traits. 310 | 311 | ### Removed 312 | - All macros 313 | - `Task` 314 | 315 | ## [0.0.1] - 2019-12-19 316 | ### Added 317 | - `join!()` similar to macro from `futures` crate. 318 | - `select!()` similar to macro from `futures` crate. 319 | - `run!()` a macro that builds an asynchronous loop. 320 | - `task!()` a pinning macro, which unlike `pin-util`'s `pin_mut!()` doesn't 321 | insert unsafe code. 322 | - `Task` - an abstraction over a pinned future, that disallows attempting to run 323 | futures after completion. 324 | - `ThreadInterrupt` - a condvar-based interrupt (requires std library feature to 325 | be enabled). 326 | - `Interrupt` - a safe way to define asynchronous waking in the executor. 327 | - `spawn_blocking` - like `tokio`'s `spawn_blocking`, creates a future from a 328 | closure by running it on a dynamically sized thread pool (also requires std 329 | library feature to be enabled). 330 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at aldaronlau@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Anyone is welcome to contribute! You can [open an issue], 3 | [Post to GitHub Discussions] or [send me an email] about anything related to 4 | this project. You may also [open a PR]. I don't require PRs to be formatted in 5 | a specific manner, since I'll run it through rustfmt after the merge anyway. If 6 | you're going to work on a PR, it would be preferred to let me know ahead of time 7 | (unless it's a quick fix), and open a draft PR if it's a large one. Then I'll 8 | assign the issue to you. Otherwise, I can't guarantee I won't duplicate your 9 | work. If I can't contact you within a week, I may unassign you and finish your 10 | work (opening a Draft PR on this repository puts your code under this crate's 11 | license). 12 | 13 | If you open a bug report, you can usually expect it to be fixed within a week. 14 | If you open a feature request it may stay open indefinitely, until I need it 15 | too. I mark feature requests as "enhancements" on GitHub issues. 16 | 17 | Happy coding! 18 | 19 | [open an issue]: https://github.com/ardaku/pasts/issues 20 | [send me an email]: mailto:aldaronlau@gmail.com 21 | [open a PR]: https://github.com/ardaku/pasts/pulls 22 | [Post to GitHub Discussions]: https://github.com/ardaku/pasts/discussions 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pasts" 3 | version = "0.14.3" 4 | license = "Apache-2.0 OR BSL-1.0 OR MIT" 5 | description = "Minimal asynchronous runtime for Rust" 6 | repository = "https://github.com/ardaku/pasts" 7 | documentation = "https://docs.rs/pasts" 8 | homepage = "https://github.com/ardaku/pasts/blob/stable/CHANGELOG.md" 9 | include = ["/examples/", "/README.md", "/src/"] 10 | keywords = ["futures", "platform-agnostic", "cross-platform", "io", "executor"] 11 | categories = ["asynchronous", "embedded", "no-std", "rust-patterns", "wasm"] 12 | readme = "README.md" 13 | edition = "2021" 14 | autobins = false 15 | 16 | [dependencies.wasm-bindgen-futures] 17 | version = "0.4" 18 | optional = true 19 | 20 | [dev-dependencies] 21 | async_main = { version = "0.4", features = ["pasts"] } 22 | async-std = "1.11" 23 | whisk = "0.13" 24 | 25 | [target.'cfg(all(target_arch="wasm32", target_os="unknown"))'.dev-dependencies] 26 | wasm-bindgen = "0.2" 27 | 28 | [features] 29 | default = ["std"] 30 | 31 | # Target a no-std environment 32 | std = [] 33 | 34 | # Target the DOM via javascript APIs exposed by wasm-bindgen. 35 | web = ["dep:wasm-bindgen-futures"] 36 | 37 | # [patch.crates-io.pasts] 38 | # path = "." 39 | -------------------------------------------------------------------------------- /LICENSE_APACHE_2_0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE_BOOST_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /LICENSE_MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pasts 2 | 3 | #### [Changelog][3] | [Source][4] | [Getting Started][5] 4 | 5 | [![tests](https://github.com/ardaku/pasts/actions/workflows/ci.yml/badge.svg)](https://github.com/ardaku/pasts/actions/workflows/ci.yml) 6 | [![GitHub commit activity](https://img.shields.io/github/commit-activity/y/ardaku/pasts)](https://github.com/ardaku/pasts/) 7 | [![GitHub contributors](https://img.shields.io/github/contributors/ardaku/pasts)](https://github.com/ardaku/pasts/graphs/contributors) 8 | [![Crates.io](https://img.shields.io/crates/v/pasts)](https://crates.io/crates/pasts) 9 | [![Crates.io](https://img.shields.io/crates/d/pasts)](https://crates.io/crates/pasts) 10 | [![Crates.io (recent)](https://img.shields.io/crates/dr/pasts)](https://crates.io/crates/pasts) 11 | [![Crates.io](https://img.shields.io/crates/l/pasts)](https://github.com/ardaku/pasts/search?l=Text&q=license) 12 | [![Docs.rs](https://docs.rs/pasts/badge.svg)](https://docs.rs/pasts/) 13 | 14 | **Minimal asynchronous runtime for Rust** 15 | 16 | The pasts asynchronous runtime is designed for creating user-space software and 17 | embedded software using an asynchronous event loop. It aims to abstract away 18 | all of the pain points of using asynchronous Rust. Pasts is purposely kept 19 | small with the entire source directory under 500 lines of Rust code. 20 | 21 | Check out the [documentation][0] for examples. 22 | 23 | # Goals 24 | - No unsafe (safe and sound) 25 | - No required std (executor requires two allocations at startup, if needed can 26 | use a bump allocator with small capacity) 27 | - No macros (fast compile times) 28 | - No dependencies[^1] (bloat-free) 29 | - No cost (true zero-cost abstractions) 30 | - No pain (API super easy to learn & use) 31 | - No platform-specific API differences (code works everywhere). 32 | 33 | ### Supported Platforms 34 | Pasts targets all platforms that can run Rust. The executor works 35 | on at least the following platforms (may work on others): 36 | - All platforms that support threading (includes all tier 1 and some tier 2, 3) 37 | - Web Assembly In Browser (Tier 2) 38 | - No standard devices (Tiers 2 and 3) 39 | 40 | ## License 41 | Copyright © 2019-2023 The Pasts Contributors. 42 | 43 | Licensed under any of 44 | - Apache License, Version 2.0, ([LICENSE_APACHE_2_0.txt][7] 45 | or [https://www.apache.org/licenses/LICENSE-2.0][8]) 46 | - Boost Software License, Version 1.0, ([LICENSE_BOOST_1_0.txt][11] 47 | or [https://www.boost.org/LICENSE_1_0.txt][12]) 48 | - MIT License, ([LICENSE_MIT.txt][9] or [https://mit-license.org/][10]) 49 | 50 | at your option. 51 | 52 | ### Contribution 53 | Unless you explicitly state otherwise, any contribution intentionally submitted 54 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 55 | licensed as described above, without any additional terms or conditions. 56 | 57 | ## Help 58 | If you want help using or contributing to this library, feel free to send me an 59 | email at [aldaronlau@gmail.com][13]. 60 | 61 | ## Related Projects 62 | Since pasts is not an all-in-one async runtime solution, here's a list of crates 63 | that are designed to work well with pasts: 64 | 65 | - [Async Main] - Proc macro crate to remove boilerplate for the main function 66 | - [Whisk] - No-std compatible MPMC (multi-producer/multiple-consumer) 67 | asynchronous channel 68 | - [Smelling Salts] - Library for asynchronous device waking using OS APIs 69 | - [Lookit] - Library for asynchronously connecting to devices using OS APIs 70 | - [Event Iterator] - Library for lending asynchronous iteration 71 | 72 | [^1]: Some features require a platform integration dependency, for instance: 73 | - **`web`** pulls in [`wasm-bindgen-futures`][14] 74 | 75 | [0]: https://docs.rs/pasts 76 | [1]: https://crates.io/crates/pasts 77 | [2]: https://github.com/ardaku/pasts/actions?query=workflow%3Atests 78 | [3]: https://github.com/ardaku/pasts/blob/stable/CHANGELOG.md 79 | [4]: https://github.com/ardaku/pasts 80 | [5]: https://docs.rs/pasts#getting-started 81 | [6]: https://aldaronlau.com/ 82 | [7]: https://github.com/ardaku/pasts/blob/stable/LICENSE_APACHE_2_0.txt 83 | [8]: https://www.apache.org/licenses/LICENSE-2.0 84 | [9]: https://github.com/ardaku/pasts/blob/stable/LICENSE_MIT.txt 85 | [10]: https://mit-license.org/ 86 | [11]: https://github.com/ardaku/pasts/blob/stable/LICENSE_BOOST_1_0.txt 87 | [12]: https://www.boost.org/LICENSE_1_0.txt 88 | [13]: mailto:aldaronlau@gmail.com 89 | [14]: https://docs.rs/crate/wasm-bindgen-futures/latest 90 | [15]: https://docs.rs/crate/pin-utils/latest 91 | 92 | [Async Main]: https://docs.rs/crate/async_main 93 | [Lookit]: https://docs.rs/crate/lookit 94 | [Smelling Salts]: https://docs.rs/crate/smelling_salts 95 | [Whisk]: https://docs.rs/crate/whisk 96 | [Event Iterator]: https://docs.rs/crate/event_iterator 97 | -------------------------------------------------------------------------------- /docs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "docs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /docs/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | fn main() -> std::io::Result<()> { 4 | fs::remove_dir_all("./gen-docs/")?; 5 | fs::create_dir_all("./gen-docs/")?; 6 | fs::copy("./examples/counter/build.rs", "./gen-docs/build.rs")?; 7 | fs::copy("./examples/counter/src/main.rs", "./gen-docs/counter.rs")?; 8 | fs::copy("./examples/spawn.rs", "./gen-docs/spawn.rs")?; 9 | fs::copy("./examples/slices.rs", "./gen-docs/slices.rs")?; 10 | fs::copy("./examples/tasks.rs", "./gen-docs/tasks.rs")?; 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /examples/counter.rs: -------------------------------------------------------------------------------- 1 | use core::time::Duration; 2 | 3 | use async_main::{async_main, LocalSpawner}; 4 | use async_std::task::sleep; 5 | use pasts::{notify, prelude::*, Loop}; 6 | 7 | /// Shared state between tasks on the thread. 8 | struct App<'a> { 9 | counter: usize, 10 | one: &'a mut (dyn Notify + Unpin), 11 | two: &'a mut (dyn Notify + Unpin), 12 | } 13 | 14 | impl App<'_> { 15 | fn one(&mut self, (): ()) -> Poll { 16 | println!("One {}", self.counter); 17 | self.counter += 1; 18 | 19 | if self.counter > 6 { 20 | Ready(()) 21 | } else { 22 | Pending 23 | } 24 | } 25 | 26 | fn two(&mut self, (): ()) -> Poll { 27 | println!("Two {}", self.counter); 28 | self.counter += 1; 29 | 30 | Pending 31 | } 32 | 33 | async fn run(&mut self) { 34 | Loop::new(self) 35 | .on(|s| &mut s.one, Self::one) 36 | .on(|s| &mut s.two, Self::two) 37 | .await; 38 | } 39 | } 40 | 41 | #[async_main] 42 | async fn main(_spawner: LocalSpawner) { 43 | let sleep = |seconds| sleep(Duration::from_secs_f64(seconds)); 44 | let mut app = App { 45 | counter: 0, 46 | one: &mut notify::future_fn(|| Box::pin(sleep(1.0))), 47 | two: &mut notify::future_fn(|| Box::pin(sleep(2.0))), 48 | }; 49 | 50 | app.run().await; 51 | } 52 | -------------------------------------------------------------------------------- /examples/executor.rs: -------------------------------------------------------------------------------- 1 | use pasts::Executor; 2 | 3 | fn main() { 4 | Executor::default().block_on(async { 5 | println!("Hello from a future!"); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /examples/no-std/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "async_main" 7 | version = "0.4.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "9229c055d66cba2b97b10381b7a1c56db6bdd3234daa4bee21260e96081460a1" 10 | dependencies = [ 11 | "async_main_macro", 12 | "pasts", 13 | ] 14 | 15 | [[package]] 16 | name = "async_main_macro" 17 | version = "0.4.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "2c61a19d4ab2db23de9a52d0085948e6647c0abd46cf0361de57e6716b65c785" 20 | 21 | [[package]] 22 | name = "base64" 23 | version = "0.13.1" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 26 | 27 | [[package]] 28 | name = "cfg-if" 29 | version = "1.0.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 32 | 33 | [[package]] 34 | name = "const-default" 35 | version = "1.0.0" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" 38 | 39 | [[package]] 40 | name = "libc" 41 | version = "0.2.144" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" 44 | 45 | [[package]] 46 | name = "no-std" 47 | version = "0.1.0" 48 | dependencies = [ 49 | "async_main", 50 | "pasts", 51 | "rlsf", 52 | ] 53 | 54 | [[package]] 55 | name = "pasts" 56 | version = "0.14.2" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "5f48fcb2261aeb31c8d00098235381aa8ff4fdd387e018e63aa4ffd24b0cfc8a" 59 | 60 | [[package]] 61 | name = "proc-macro2" 62 | version = "1.0.59" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" 65 | dependencies = [ 66 | "unicode-ident", 67 | ] 68 | 69 | [[package]] 70 | name = "quote" 71 | version = "1.0.28" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" 74 | dependencies = [ 75 | "proc-macro2", 76 | ] 77 | 78 | [[package]] 79 | name = "rlsf" 80 | version = "0.2.1" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "222fb240c3286247ecdee6fa5341e7cdad0ffdf8e7e401d9937f2d58482a20bf" 83 | dependencies = [ 84 | "cfg-if", 85 | "const-default", 86 | "libc", 87 | "svgbobdoc", 88 | ] 89 | 90 | [[package]] 91 | name = "svgbobdoc" 92 | version = "0.3.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" 95 | dependencies = [ 96 | "base64", 97 | "proc-macro2", 98 | "quote", 99 | "syn", 100 | "unicode-width", 101 | ] 102 | 103 | [[package]] 104 | name = "syn" 105 | version = "1.0.109" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 108 | dependencies = [ 109 | "proc-macro2", 110 | "quote", 111 | "unicode-ident", 112 | ] 113 | 114 | [[package]] 115 | name = "unicode-ident" 116 | version = "1.0.9" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 119 | 120 | [[package]] 121 | name = "unicode-width" 122 | version = "0.1.10" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 125 | -------------------------------------------------------------------------------- /examples/no-std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "no-std" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies.async_main] 7 | version = "0.4" 8 | features = ["pasts"] 9 | 10 | [dependencies.pasts] 11 | version = "0.14" 12 | default-features = false 13 | 14 | [dependencies.rlsf] 15 | version = "0.2" 16 | -------------------------------------------------------------------------------- /examples/no-std/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Use pasts on no-std, specifically targeting x86_64-unknown-linux-gnu (may 2 | //! work on others). Requires nightly for `eh_personality` lang item (tested 3 | //! with `rustc 1.72.0-nightly (871b59520 2023-05-31)`). 4 | //! 5 | //! ```shell 6 | //! 7 | //! cargo +nightly run 8 | //! ``` 9 | 10 | #![no_std] 11 | #![no_main] 12 | #![feature(lang_items)] 13 | 14 | extern crate alloc; 15 | 16 | #[global_allocator] 17 | static _GLOBAL_ALLOCATOR: rlsf::SmallGlobalTlsf = rlsf::SmallGlobalTlsf::new(); 18 | 19 | #[lang = "eh_personality"] 20 | extern "C" fn _eh_personality() {} 21 | 22 | #[no_mangle] 23 | extern "C" fn _Unwind_Resume() {} 24 | 25 | fn print(string: &str) { 26 | use core::ffi::{c_int, c_void}; 27 | 28 | #[link(name = "c")] 29 | extern "C" { 30 | fn write(fd: c_int, buf: *const c_void, count: usize) -> isize; 31 | } 32 | 33 | unsafe { write(0, string.as_ptr().cast(), string.len()) }; 34 | } 35 | 36 | #[panic_handler] 37 | fn yeet(panic: &::core::panic::PanicInfo<'_>) -> ! { 38 | print(&panic.to_string()); 39 | print("\n"); 40 | 41 | loop {} 42 | } 43 | 44 | //// End no-std specific boilerplate //// 45 | 46 | use alloc::string::ToString; 47 | 48 | use async_main::{async_main, LocalSpawner}; 49 | use pasts::{prelude::*, Loop}; 50 | 51 | struct State { 52 | // Spawned tasks 53 | tasks: [LocalBoxNotify<'static, &'static str>; 2], 54 | } 55 | 56 | impl State { 57 | fn task_done(&mut self, (_id, text): (usize, &str)) -> Poll { 58 | print(text); 59 | print("\n"); 60 | Pending 61 | } 62 | } 63 | 64 | async fn task_one() -> &'static str { 65 | print("Task 1...\n"); 66 | "Hello" 67 | } 68 | 69 | async fn task_two() -> &'static str { 70 | print("Task 2...\n"); 71 | "World" 72 | } 73 | 74 | #[async_main] 75 | async fn main(_spawner: LocalSpawner) { 76 | // create two tasks to spawn 77 | let task_one = Box::pin(task_one().fuse()); 78 | let task_two = Box::pin(task_two().fuse()); 79 | 80 | // == Allocations end == 81 | 82 | // create array of tasks to spawn 83 | let state = &mut State { 84 | tasks: [task_one, task_two], 85 | }; 86 | 87 | Loop::new(state) 88 | .on(|s| s.tasks.as_mut_slice(), State::task_done) 89 | .await; 90 | } 91 | 92 | mod main { 93 | #[no_mangle] 94 | extern "C" fn main() -> ! { 95 | super::main(); 96 | 97 | loop {} 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /examples/pool.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::Cell, 3 | thread::{self, Thread}, 4 | }; 5 | 6 | use pasts::{prelude::*, Executor, Park, Pool}; 7 | 8 | #[derive(Default)] 9 | struct SingleThreadedPool { 10 | spawning_queue: Cell>>, 11 | } 12 | 13 | impl Pool for SingleThreadedPool { 14 | type Park = ThreadPark; 15 | 16 | fn push(&self, task: LocalBoxNotify<'static>) { 17 | let mut queue = self.spawning_queue.take(); 18 | 19 | queue.push(task); 20 | self.spawning_queue.set(queue); 21 | } 22 | 23 | fn drain(&self, tasks: &mut Vec>) -> bool { 24 | let mut queue = self.spawning_queue.take(); 25 | let mut drained = queue.drain(..).peekable(); 26 | let has_drained = drained.peek().is_some(); 27 | 28 | tasks.extend(drained); 29 | self.spawning_queue.set(queue); 30 | 31 | has_drained 32 | } 33 | } 34 | 35 | struct ThreadPark(Thread); 36 | 37 | impl Default for ThreadPark { 38 | fn default() -> Self { 39 | Self(thread::current()) 40 | } 41 | } 42 | 43 | impl Park for ThreadPark { 44 | fn park(&self) { 45 | thread::park(); 46 | } 47 | 48 | fn unpark(&self) { 49 | self.0.unpark(); 50 | } 51 | } 52 | 53 | fn main() { 54 | // Create a custom executor. 55 | let executor = Executor::new(SingleThreadedPool::default()); 56 | 57 | // Block on a future 58 | executor.block_on(async { 59 | println!("Hi from inside a future!"); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /examples/recursive.rs: -------------------------------------------------------------------------------- 1 | use pasts::Executor; 2 | 3 | fn main() { 4 | Executor::default().block_on(async { 5 | Executor::default().block_on(async { 6 | println!("Hello from the future running on the inner executor!"); 7 | }); 8 | 9 | println!("Hello from the future running on the outer executor!"); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /examples/resume.rs: -------------------------------------------------------------------------------- 1 | use pasts::Executor; 2 | 3 | fn main() { 4 | let executor = Executor::default(); 5 | 6 | executor.clone().block_on(async move { 7 | println!("Hello from a future!"); 8 | 9 | executor.block_on(async { 10 | println!("Resuming execution from within the executor context!"); 11 | }); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /examples/slices.rs: -------------------------------------------------------------------------------- 1 | use async_main::{async_main, LocalSpawner}; 2 | use pasts::{prelude::*, Loop}; 3 | 4 | struct Exit; 5 | 6 | struct App<'a> { 7 | tasks: &'a mut [BoxNotify<'static, &'static str>], 8 | } 9 | 10 | impl App<'_> { 11 | fn completion(&mut self, (id, val): (usize, &str)) -> Poll { 12 | println!("Received message from {id}, completed task: {val}"); 13 | 14 | Ready(Exit) 15 | } 16 | } 17 | 18 | #[async_main] 19 | async fn main(_spawner: LocalSpawner) { 20 | let tasks: &mut [BoxNotify<'_, _>] = &mut [ 21 | Box::pin(async { "Hello" }.fuse()), 22 | Box::pin(async { "World" }.fuse()), 23 | ]; 24 | let mut app = App { tasks }; 25 | 26 | // First task will complete first. 27 | Loop::new(&mut app).on(|s| s.tasks, App::completion).await; 28 | } 29 | -------------------------------------------------------------------------------- /examples/spawn.rs: -------------------------------------------------------------------------------- 1 | use core::time::Duration; 2 | 3 | use pasts::Executor; 4 | 5 | async fn sleep(seconds: f64) { 6 | async_std::task::sleep(Duration::from_secs_f64(seconds)).await; 7 | } 8 | 9 | fn main() { 10 | let executor = Executor::default(); 11 | 12 | // Spawn before blocking puts the task on a queue. 13 | executor.spawn_boxed(async { 14 | sleep(3.0).await; 15 | println!("3 seconds"); 16 | }); 17 | 18 | // Calling `block_on()` starting executing queued tasks. 19 | executor.clone().block_on(async move { 20 | // Spawn tasks (without being queued) 21 | executor.spawn_boxed(async { 22 | sleep(1.0).await; 23 | println!("1 second"); 24 | }); 25 | executor.spawn_boxed(async { 26 | sleep(2.0).await; 27 | println!("2 seconds"); 28 | }); 29 | 30 | // Finish this task before spawned tasks will complete. 31 | sleep(0.5).await; 32 | println!("½ second"); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /examples/tasks.rs: -------------------------------------------------------------------------------- 1 | use async_main::{async_main, LocalSpawner}; 2 | use pasts::{prelude::*, Loop}; 3 | 4 | struct Exit; 5 | 6 | struct App { 7 | tasks: Vec>, 8 | } 9 | 10 | impl App { 11 | fn completion(&mut self, (id, val): (usize, &str)) -> Poll { 12 | println!("Received message from completed task: {val}"); 13 | 14 | self.tasks.swap_remove(id); 15 | 16 | if self.tasks.is_empty() { 17 | Ready(Exit) 18 | } else { 19 | Pending 20 | } 21 | } 22 | } 23 | 24 | #[async_main] 25 | async fn main(_spawner: LocalSpawner) { 26 | let mut app = App { 27 | tasks: vec![ 28 | Box::pin(async { "Hello" }.fuse()), 29 | Box::pin(async { "World" }.fuse()), 30 | ], 31 | }; 32 | 33 | Loop::new(&mut app) 34 | .on(|s| &mut s.tasks[..], App::completion) 35 | .await; 36 | } 37 | -------------------------------------------------------------------------------- /examples/timer.rs: -------------------------------------------------------------------------------- 1 | use core::time::Duration; 2 | 3 | use async_std::task; 4 | 5 | fn main() { 6 | pasts::Executor::default().block_on(async { 7 | println!("Waiting 2 seconds…"); 8 | 9 | task::sleep(Duration::new(2, 0)).await; 10 | 11 | println!("Waited 2 seconds."); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! **Minimal asynchronous runtime for Rust** 2 | //! 3 | //! # Optional Features 4 | //! Only the _`std`_ feature is enabled by default 5 | //! 6 | //! - Disable _`std`_ to use pasts without the standard library. 7 | //! - Enable _`web`_ to use pasts within the javascript DOM. 8 | //! 9 | //! # Getting Started 10 | //! 11 | //! Add the following to your **`./Cargo.toml`**: 12 | //! ```toml 13 | //! [dependencies] 14 | //! pasts = "0.14" 15 | //! 16 | //! ## This example uses async_main for convenience, but it is *not* required to 17 | //! ## use pasts. 18 | //! async_main = { version = "0.4", features = ["pasts"] } 19 | //! 20 | //! ## This example uses async-std for a sleep future, but async-std is *not* 21 | //! ## required to use pasts. 22 | //! async-std = "1.12" 23 | //! 24 | //! ## Also not required for pasts, but allows for portability with WebAssembly 25 | //! ## in the browser. 26 | //! [features] 27 | //! web = ["async_main/web", "pasts/web"] 28 | //! ``` 29 | //! 30 | //! ## Multi-Tasking On Multiple Iterators of Futures 31 | //! This example runs two timers in parallel using the `async-std` crate 32 | //! counting from 0 to 6. The "one" task will always be run for count 6 and 33 | //! stop the program, although which task will run for count 5 may be either 34 | //! "one" or "two" because they trigger at the same time. 35 | //! 36 | //! ```rust,no_run 37 | #![doc = include_str!("../examples/counter.rs")] 38 | //! ``` 39 | 40 | #![cfg_attr(not(feature = "std"), no_std)] 41 | #![forbid(unsafe_code, missing_docs)] 42 | #![doc( 43 | html_logo_url = "https://ardaku.github.io/mm/logo.svg", 44 | html_favicon_url = "https://ardaku.github.io/mm/icon.svg", 45 | html_root_url = "https://docs.rs/pasts" 46 | )] 47 | 48 | extern crate alloc; 49 | 50 | pub mod notify; 51 | 52 | mod r#loop; 53 | mod spawn; 54 | 55 | use self::prelude::*; 56 | pub use self::{ 57 | r#loop::Loop, 58 | spawn::{Executor, Park, Pool}, 59 | }; 60 | 61 | pub mod prelude { 62 | //! Items that are almost always needed. 63 | 64 | #[doc(no_inline)] 65 | pub use alloc::boxed::Box; 66 | #[doc(no_inline)] 67 | pub use core::{ 68 | future::Future, 69 | pin::Pin, 70 | task::{ 71 | Context as Task, 72 | Poll::{Pending, Ready}, 73 | }, 74 | }; 75 | 76 | #[doc(no_inline)] 77 | pub use crate::notify::{ 78 | BoxNotify, Fuse, LocalBoxNotify, Notify, NotifyExt, 79 | }; 80 | 81 | /// Indicates whether a value is available or if the current task has been 82 | /// scheduled to receive a wakeup instead. 83 | pub type Poll = core::task::Poll; 84 | } 85 | -------------------------------------------------------------------------------- /src/loop.rs: -------------------------------------------------------------------------------- 1 | use crate::{prelude::*, Notify}; 2 | 3 | pub trait Stateful: Unpin { 4 | fn state(&mut self) -> &mut S; 5 | 6 | fn poll(&mut self, _: &mut Task<'_>) -> Poll> { 7 | Pending 8 | } 9 | } 10 | 11 | #[derive(Debug)] 12 | pub struct Never<'a, S>(&'a mut S); 13 | 14 | impl Stateful for Never<'_, S> { 15 | fn state(&mut self) -> &mut S { 16 | self.0 17 | } 18 | } 19 | 20 | /// Composable asynchronous event loop. 21 | /// 22 | /// # Selecting on Futures: 23 | /// Select first completed future. 24 | /// 25 | /// ```rust 26 | #[doc = include_str!("../examples/slices.rs")] 27 | /// ``` 28 | /// 29 | /// # Task spawning 30 | /// Spawns tasks in a [`Vec`](alloc::vec::Vec), and removes them as they complete. 31 | /// ```rust 32 | #[doc = include_str!("../examples/tasks.rs")] 33 | /// ``` 34 | #[derive(Debug)] 35 | pub struct Loop> { 36 | other: F, 37 | _phantom: core::marker::PhantomData<(S, T)>, 38 | } 39 | 40 | impl<'a, S: Unpin, T> Loop> { 41 | /// Create an empty event loop. 42 | pub fn new(state: &'a mut S) -> Self { 43 | let other = Never(state); 44 | let _phantom = core::marker::PhantomData; 45 | 46 | Loop { other, _phantom } 47 | } 48 | } 49 | 50 | impl> Loop { 51 | /// Register an event handler. 52 | pub fn on( 53 | self, 54 | noti: impl for<'a> FnMut(&'a mut S) -> &'a mut N + Unpin, 55 | then: fn(&mut S, N::Event) -> Poll, 56 | ) -> Loop> { 57 | let other = self.other; 58 | let _phantom = core::marker::PhantomData; 59 | let other = Looper { other, noti, then }; 60 | 61 | Loop { other, _phantom } 62 | } 63 | } 64 | 65 | impl> Future for Loop { 66 | type Output = T; 67 | 68 | #[inline] 69 | fn poll(mut self: Pin<&mut Self>, t: &mut Task<'_>) -> Poll { 70 | while let Ready(output) = Pin::new(&mut self.other).poll(t) { 71 | if let Ready(output) = output { 72 | return Ready(output); 73 | } 74 | } 75 | 76 | Pending 77 | } 78 | } 79 | 80 | struct Looper, P> { 81 | other: F, 82 | noti: P, 83 | then: fn(&mut S, E) -> Poll, 84 | } 85 | 86 | impl Stateful for Looper 87 | where 88 | F: Stateful, 89 | N: Notify + Unpin + ?Sized, 90 | P: for<'a> FnMut(&'a mut S) -> &'a mut N + Unpin, 91 | { 92 | #[inline] 93 | fn state(&mut self) -> &mut S { 94 | self.other.state() 95 | } 96 | 97 | #[inline] 98 | fn poll(&mut self, t: &mut Task<'_>) -> Poll> { 99 | let state = self.other.state(); 100 | let poll = Pin::new((self.noti)(state)).poll_next(t); 101 | 102 | if let Ready(out) = poll.map(|x| (self.then)(state, x)) { 103 | Ready(out) 104 | } else { 105 | self.other.poll(t) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/notify.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous event notifys 2 | //! 3 | //! A [`Notify`] is kind of like a cross between a [`Future`] and an 4 | //! [`AsyncIterator`](core::async_iter::AsyncIterator). Like streams, they may 5 | //! return more than one value, and are expected to not panic after polling. 6 | //! Like futures, they produce non-optional values. In a sense they are an 7 | //! infinite stream. In another sense, they are a repeating future. 8 | //! 9 | //! # Why Another Abstraction? 10 | //! Notifys allow for some nice ergonomics and guarantees when working with 11 | //! event-loop based asynchronous code, which could lead to some 12 | //! simplifications. Unlike futures and streams, they do not need to be fused, 13 | //! and if your stream is infinite, you won't need to sprinkle `unwrap()`s in 14 | //! your code at each call to `.next()`. They also lend themselves nicely for 15 | //! creating clean and simple multimedia APIs. 16 | 17 | use core::fmt; 18 | 19 | use crate::prelude::*; 20 | 21 | /// An owned dynamically typed [`Notify`] for use in cases where you can’t 22 | /// statically type your result or need to add some indirection. 23 | pub type BoxNotify<'a, T = ()> = Pin + Send + 'a>>; 24 | 25 | impl fmt::Debug for BoxNotify<'_, T> { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | f.write_str("BoxNotify") 28 | } 29 | } 30 | 31 | /// [`BoxNotify`] without the [`Send`] requirement. 32 | pub type LocalBoxNotify<'a, T = ()> = Pin + 'a>>; 33 | 34 | impl fmt::Debug for LocalBoxNotify<'_, T> { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | f.write_str("LocalBoxNotify") 37 | } 38 | } 39 | 40 | /// Trait for asynchronous event notification 41 | /// 42 | /// Similar to [`AsyncIterator`](core::async_iter::AsyncIterator), but infinite. 43 | /// 44 | /// It's expected that [`Notify`]s can be polled indefinitely without causing 45 | /// panics or undefined behavior. They are not required to continue sending 46 | /// events indefinitely, though. 47 | pub trait Notify { 48 | /// The event produced by this notify 49 | type Event; 50 | 51 | /// Get the next event from this notify, registering a wakeup when not 52 | /// ready. 53 | /// 54 | /// # Return Value 55 | /// - `Poll::Pending` - Not ready yet 56 | /// - `Poll::Ready(val)` - Ready with next value 57 | fn poll_next(self: Pin<&mut Self>, t: &mut Task<'_>) -> Poll; 58 | } 59 | 60 | impl Notify for Box 61 | where 62 | N: ?Sized + Notify + Unpin, 63 | { 64 | type Event = N::Event; 65 | 66 | #[inline] 67 | fn poll_next(self: Pin<&mut Self>, t: &mut Task<'_>) -> Poll { 68 | Pin::new(self.get_mut().as_mut()).poll_next(t) 69 | } 70 | } 71 | 72 | impl Notify for Pin

73 | where 74 | P: core::ops::DerefMut + Unpin, 75 | N: Notify + ?Sized, 76 | { 77 | type Event = N::Event; 78 | 79 | fn poll_next(self: Pin<&mut Self>, t: &mut Task<'_>) -> Poll { 80 | Pin::get_mut(self).as_mut().poll_next(t) 81 | } 82 | } 83 | 84 | impl Notify for &mut N 85 | where 86 | N: Notify + Unpin + ?Sized, 87 | { 88 | type Event = N::Event; 89 | 90 | #[inline] 91 | fn poll_next(mut self: Pin<&mut Self>, t: &mut Task<'_>) -> Poll { 92 | Pin::new(&mut **self).poll_next(t) 93 | } 94 | } 95 | 96 | impl Notify for [N] 97 | where 98 | N: Notify + Unpin, 99 | { 100 | type Event = (usize, N::Event); 101 | 102 | #[inline] 103 | fn poll_next(self: Pin<&mut Self>, t: &mut Task<'_>) -> Poll { 104 | for (i, this) in self.get_mut().iter_mut().enumerate() { 105 | if let Poll::Ready(value) = Pin::new(this).poll_next(t) { 106 | return Poll::Ready((i, value)); 107 | } 108 | } 109 | 110 | Poll::Pending 111 | } 112 | } 113 | 114 | /// An extension trait for [`Notify`]s that provides a variety of convenient 115 | /// adapters. 116 | pub trait NotifyExt: Notify + Sized + Unpin { 117 | /// Get the next [`Notify::Event`] 118 | /// 119 | /// # Usage 120 | /// ```rust 121 | /// use pasts::prelude::*; 122 | /// use async_main::Spawn; 123 | /// 124 | /// struct MyAsyncIter; 125 | /// 126 | /// impl Notify for MyAsyncIter { 127 | /// type Event = Option; 128 | /// 129 | /// fn poll_next(self: Pin<&mut Self>, _: &mut Task<'_>) -> Poll { 130 | /// Ready(Some(1)) 131 | /// } 132 | /// } 133 | /// 134 | /// #[async_main::async_main] 135 | /// async fn main(_spawner: impl Spawn) { 136 | /// let mut count = 0; 137 | /// let mut async_iter = MyAsyncIter; 138 | /// let mut iterations = 0; 139 | /// while let Some(i) = async_iter.next().await { 140 | /// count += i; 141 | /// iterations += 1; 142 | /// if iterations == 3 { 143 | /// break; 144 | /// } 145 | /// } 146 | /// assert_eq!(count, 3); 147 | /// } 148 | /// ``` 149 | #[inline(always)] 150 | fn next(&mut self) -> Next<'_, Self> { 151 | Next(self) 152 | } 153 | 154 | /// Transform produced [`Notify::Event`]s with a function. 155 | #[inline(always)] 156 | fn map(self, f: F) -> Map { 157 | let noti = self; 158 | 159 | Map { noti, f } 160 | } 161 | } 162 | 163 | impl NotifyExt for N {} 164 | 165 | /// The [`Future`] returned from [`NotifyExt::next()`] 166 | #[derive(Debug)] 167 | pub struct Next<'a, N>(&'a mut N) 168 | where 169 | N: Notify + Unpin; 170 | 171 | impl Future for Next<'_, N> 172 | where 173 | N: Notify + Unpin, 174 | { 175 | type Output = N::Event; 176 | 177 | #[inline] 178 | fn poll(self: Pin<&mut Self>, t: &mut Task<'_>) -> Poll { 179 | Pin::new(&mut self.get_mut().0).poll_next(t) 180 | } 181 | } 182 | 183 | /// Trait for "fusing" a [`Future`] (conversion to a [`Notify`]) 184 | pub trait Fuse: Sized { 185 | /// Fuse the [`Future`] 186 | fn fuse(self) -> Option; 187 | } 188 | 189 | impl Fuse for F 190 | where 191 | F: Future, 192 | { 193 | fn fuse(self) -> Option { 194 | self.into() 195 | } 196 | } 197 | 198 | impl Notify for Option { 199 | type Event = F::Output; 200 | 201 | #[inline] 202 | fn poll_next(self: Pin<&mut Self>, t: &mut Task<'_>) -> Poll { 203 | let mut s = self; 204 | let out = s.as_mut().as_pin_mut().map(|f| f.poll(t)); 205 | if matches!(out, Some(Poll::Ready(_))) { 206 | s.set(None); 207 | } 208 | out.unwrap_or(Poll::Pending) 209 | } 210 | } 211 | 212 | /// The [`Notify`] returned from [`NotifyExt::map()`] 213 | #[derive(Debug)] 214 | pub struct Map { 215 | noti: N, 216 | f: F, 217 | } 218 | 219 | impl Notify for Map 220 | where 221 | N: Notify + Unpin, 222 | F: FnMut(N::Event) -> E + Unpin, 223 | { 224 | type Event = E; 225 | 226 | #[inline] 227 | fn poll_next(mut self: Pin<&mut Self>, t: &mut Task<'_>) -> Poll { 228 | Pin::new(&mut self.noti).poll_next(t).map(&mut self.f) 229 | } 230 | } 231 | 232 | /// A [`Notify`] that wraps a function returning a [`Future`] 233 | /// 234 | /// This struct is created by [`future_fn()`]. See its documentation for more. 235 | #[derive(Debug)] 236 | pub struct FutureFn(T, F); 237 | 238 | impl Notify for FutureFn 239 | where 240 | T: Future + Unpin, 241 | F: FnMut() -> T + Unpin, 242 | { 243 | type Event = T::Output; 244 | 245 | #[inline] 246 | fn poll_next(self: Pin<&mut Self>, t: &mut Task<'_>) -> Poll { 247 | let this = self.get_mut(); 248 | let poll = Pin::new(&mut this.0).poll(t); 249 | 250 | if poll.is_ready() { 251 | Pin::new(&mut this.0).set(this.1()); 252 | } 253 | 254 | poll 255 | } 256 | } 257 | 258 | /// A [`Notify`] created from a function returning [`Poll`] 259 | #[derive(Debug)] 260 | pub struct PollFn(F); 261 | 262 | impl Notify for PollFn 263 | where 264 | F: FnMut(&mut Task<'_>) -> Poll + Unpin, 265 | { 266 | type Event = T; 267 | 268 | #[inline] 269 | fn poll_next(self: Pin<&mut Self>, t: &mut Task<'_>) -> Poll { 270 | self.get_mut().0(t) 271 | } 272 | } 273 | 274 | /// A [`Notify`] that never produces an event. 275 | /// 276 | /// This struct is created by [`pending()`]. See its documentation for more. 277 | #[derive(Debug)] 278 | pub struct Pending(core::marker::PhantomData T>); 279 | 280 | impl Notify for Pending { 281 | type Event = T; 282 | 283 | fn poll_next(self: Pin<&mut Self>, _task: &mut Task<'_>) -> Poll { 284 | Poll::Pending 285 | } 286 | } 287 | 288 | /// A [`Notify`] that immediately produces a single event. 289 | /// 290 | /// This struct is created by [`ready()`]. See its documentation for more. 291 | #[derive(Debug)] 292 | pub struct Ready(Option); 293 | 294 | impl Notify for Ready { 295 | type Event = T; 296 | 297 | fn poll_next(self: Pin<&mut Self>, _task: &mut Task<'_>) -> Poll { 298 | let Some(event) = self.get_mut().0.take() else { 299 | return Poll::Pending; 300 | }; 301 | 302 | Poll::Ready(event) 303 | } 304 | } 305 | 306 | /// A [`Notify`] that selects over a list of [`Notify`]s 307 | /// 308 | /// This struct is created by [`select()`]. See its documentation for more. 309 | pub struct Select<'a, E, const N: usize>( 310 | [&'a mut (dyn Notify + Unpin); N], 311 | usize, 312 | ); 313 | 314 | impl fmt::Debug for Select<'_, E, N> { 315 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 316 | f.write_str("Select") 317 | } 318 | } 319 | 320 | impl Notify for Select<'_, E, N> { 321 | type Event = E; 322 | 323 | fn poll_next(self: Pin<&mut Self>, task: &mut Task<'_>) -> Poll { 324 | let s = self.get_mut(); 325 | let start = s.1; 326 | 327 | // Early exit for if there is nothing that can be ready. 328 | if N == 0 { 329 | return Poll::Pending; 330 | } 331 | 332 | for i in (start..N).chain(0..start) { 333 | if let Poll::Ready(event) = Pin::new(&mut s.0[i]).poll_next(task) { 334 | return Poll::Ready(event); 335 | } 336 | } 337 | 338 | Poll::Pending 339 | } 340 | } 341 | 342 | /// Create a [`Notify`] that wraps a function returning a [`Future`]. 343 | /// 344 | /// Polling the notify delegates to future returned by the wrapped function. 345 | /// The wrapped function is called immediately, and is only called again once 346 | /// the future is polled and returns `Ready`. 347 | pub fn future_fn(mut f: F) -> FutureFn 348 | where 349 | T: Future + Unpin, 350 | F: FnMut() -> T + Unpin, 351 | { 352 | FutureFn(f(), f) 353 | } 354 | 355 | /// Create a [`Notify`] that wraps a function returning [`Poll`]. 356 | /// 357 | /// Polling the future delegates to the wrapped function. 358 | pub fn poll_fn(f: F) -> PollFn 359 | where 360 | F: FnMut(&mut Task<'_>) -> Poll + Unpin, 361 | { 362 | PollFn(f) 363 | } 364 | 365 | /// Create a [`Notify`] which never becomes ready with an event. 366 | pub fn pending() -> Pending { 367 | Pending(core::marker::PhantomData) 368 | } 369 | 370 | /// Create a [`Notify`] which is immediately ready with an event. 371 | pub fn ready(t: T) -> Ready { 372 | Ready(t.into()) 373 | } 374 | 375 | /// Create a [`Notify`] that selects over a list of [`Notify`]s. 376 | pub fn select( 377 | notifys: [&mut (dyn Notify + Unpin); N], 378 | ) -> Select<'_, E, N> { 379 | Select(notifys, 0) 380 | } 381 | -------------------------------------------------------------------------------- /src/spawn.rs: -------------------------------------------------------------------------------- 1 | use alloc::{sync::Arc, task::Wake, vec::Vec}; 2 | use core::{cell::Cell, fmt, future::Future}; 3 | 4 | use crate::prelude::*; 5 | 6 | /// Pasts' executor. 7 | /// 8 | /// # Run a Future 9 | /// It's relatively simple to block on a future, and run it to completion: 10 | /// 11 | /// ```rust 12 | #[doc = include_str!("../examples/executor.rs")] 13 | /// ``` 14 | /// 15 | /// # Spawn a Future 16 | /// You may spawn tasks on an `Executor`. Only once all tasks have completed, 17 | /// can [`block_on()`](Executor::block_on()) return. 18 | /// ```rust,no_run 19 | #[doc = include_str!("../examples/spawn.rs")] 20 | /// ``` 21 | /// 22 | /// # Recursive `block_on()` 23 | /// One cool feature about the pasts executor is that you can run it from within 24 | /// the context of another: 25 | /// ```rust 26 | #[doc = include_str!("../examples/recursive.rs")] 27 | /// ``` 28 | /// 29 | /// Or even resume the executor from within it's own context: 30 | /// ```rust 31 | #[doc = include_str!("../examples/resume.rs")] 32 | /// ``` 33 | pub struct Executor(Arc

); 34 | 35 | impl Default for Executor { 36 | fn default() -> Self { 37 | Self::new(DefaultPool::default()) 38 | } 39 | } 40 | 41 | impl Clone for Executor

{ 42 | fn clone(&self) -> Self { 43 | Self(Arc::clone(&self.0)) 44 | } 45 | } 46 | 47 | impl fmt::Debug for Executor

{ 48 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 49 | f.debug_tuple("Executor").field(&self.0).finish() 50 | } 51 | } 52 | 53 | impl Executor

{ 54 | /// Create a new executor that can only spawn tasks from the current thread. 55 | /// 56 | /// Custom executors can be built by implementing [`Pool`]. 57 | #[inline(always)] 58 | pub fn new(pool: P) -> Self { 59 | Self(Arc::new(pool)) 60 | } 61 | 62 | /// Block on a future and return it's result. 63 | /// 64 | /// # Platform-Specific Behavior 65 | /// When building with feature _`web`_, spawns task and returns 66 | /// immediately instead of blocking. 67 | #[inline(always)] 68 | pub fn block_on(self, f: impl Future + 'static) { 69 | #[cfg(feature = "web")] 70 | wasm_bindgen_futures::spawn_local(f); 71 | 72 | #[cfg(not(feature = "web"))] 73 | block_on(f, &self.0); 74 | } 75 | } 76 | 77 | impl Executor

{ 78 | /// Spawn a [`LocalBoxNotify`] on this executor. 79 | /// 80 | /// Execution of the [`LocalBoxNotify`] will halt after the first poll that 81 | /// returns [`Ready`]. 82 | #[inline(always)] 83 | pub fn spawn_notify(&self, n: LocalBoxNotify<'static>) { 84 | // Convert the notify into a future and spawn on wasm_bindgen_futures 85 | #[cfg(feature = "web")] 86 | wasm_bindgen_futures::spawn_local(async move { 87 | let mut n = n; 88 | 89 | n.next().await; 90 | }); 91 | 92 | // Push the notify onto the pool. 93 | #[cfg(not(feature = "web"))] 94 | self.0.push(n); 95 | } 96 | 97 | /// Box and spawn a future on this executor. 98 | #[inline(always)] 99 | pub fn spawn_boxed(&self, f: impl Future + 'static) { 100 | // Spawn the future on wasm_bindgen_futures 101 | #[cfg(feature = "web")] 102 | wasm_bindgen_futures::spawn_local(f); 103 | 104 | // Fuse the future, box it, and push it onto the pool. 105 | #[cfg(not(feature = "web"))] 106 | self.spawn_notify(Box::pin(f.fuse())); 107 | } 108 | } 109 | 110 | /// Storage for a task pool. 111 | /// 112 | /// # Implementing `Pool` For A Custom Executor 113 | /// This example shows how to create a custom single-threaded executor using 114 | /// [`Executor::new()`]. 115 | /// 116 | /// ```rust 117 | #[doc = include_str!("../examples/pool.rs")] 118 | /// ``` 119 | pub trait Pool { 120 | /// Type that handles the sleeping / waking of the executor. 121 | type Park: Park; 122 | 123 | /// Push a task into the thread pool queue. 124 | fn push(&self, task: LocalBoxNotify<'static>); 125 | 126 | /// Drain tasks from the thread pool queue. Should returns true if drained 127 | /// at least one task. 128 | fn drain(&self, tasks: &mut Vec>) -> bool; 129 | } 130 | 131 | /// Trait for implementing the parking / unparking threads. 132 | pub trait Park: Default + Send + Sync + 'static { 133 | /// The park routine; should put the processor or thread to sleep in order 134 | /// to save CPU cycles and power, until the hardware tells it to wake up. 135 | fn park(&self); 136 | 137 | /// Wake the processor or thread. 138 | fn unpark(&self); 139 | } 140 | 141 | #[derive(Default)] 142 | pub struct DefaultPool { 143 | spawning_queue: Cell>>, 144 | } 145 | 146 | impl fmt::Debug for DefaultPool { 147 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 148 | let queue = self.spawning_queue.take(); 149 | 150 | f.debug_struct("DefaultPool") 151 | .field("spawning_queue", &queue) 152 | .finish()?; 153 | self.spawning_queue.set(queue); 154 | 155 | Ok(()) 156 | } 157 | } 158 | 159 | impl Pool for DefaultPool { 160 | type Park = DefaultPark; 161 | 162 | // Push onto queue of tasks to spawn. 163 | #[inline(always)] 164 | fn push(&self, task: LocalBoxNotify<'static>) { 165 | let mut queue = self.spawning_queue.take(); 166 | 167 | queue.push(task); 168 | self.spawning_queue.set(queue); 169 | } 170 | 171 | // Drain from queue of tasks to spawn. 172 | #[inline(always)] 173 | fn drain(&self, tasks: &mut Vec>) -> bool { 174 | let mut queue = self.spawning_queue.take(); 175 | let mut drained = queue.drain(..).peekable(); 176 | let has_drained = drained.peek().is_some(); 177 | 178 | tasks.extend(drained); 179 | self.spawning_queue.set(queue); 180 | 181 | has_drained 182 | } 183 | } 184 | 185 | #[cfg(not(feature = "std"))] 186 | #[derive(Copy, Clone, Debug, Default)] 187 | pub struct DefaultPark; 188 | 189 | #[cfg(feature = "std")] 190 | #[derive(Debug)] 191 | pub struct DefaultPark(std::sync::atomic::AtomicBool, std::thread::Thread); 192 | 193 | #[cfg(feature = "std")] 194 | impl Default for DefaultPark { 195 | fn default() -> Self { 196 | Self( 197 | std::sync::atomic::AtomicBool::new(true), 198 | std::thread::current(), 199 | ) 200 | } 201 | } 202 | 203 | impl Park for DefaultPark { 204 | // Park the current thread. 205 | #[inline(always)] 206 | fn park(&self) { 207 | // Only park on std; There is no portable parking for no-std. 208 | #[cfg(feature = "std")] 209 | while self.0.swap(true, std::sync::atomic::Ordering::SeqCst) { 210 | std::thread::park(); 211 | } 212 | 213 | // Hint at spin loop to possibly short sleep on no-std to save CPU time. 214 | #[cfg(not(feature = "std"))] 215 | core::hint::spin_loop(); 216 | } 217 | 218 | // Unpark the parked thread 219 | #[inline(always)] 220 | fn unpark(&self) { 221 | // Only unpark on std; Since no-std doesn't park, it's already unparked. 222 | #[cfg(feature = "std")] 223 | if self.0.swap(false, std::sync::atomic::Ordering::SeqCst) { 224 | self.1.unpark(); 225 | } 226 | } 227 | } 228 | 229 | struct Unpark(P); 230 | 231 | impl Wake for Unpark

{ 232 | #[inline(always)] 233 | fn wake(self: Arc) { 234 | self.0.unpark(); 235 | } 236 | 237 | #[inline(always)] 238 | fn wake_by_ref(self: &Arc) { 239 | self.0.unpark(); 240 | } 241 | } 242 | 243 | #[cfg(not(feature = "web"))] 244 | fn block_on(f: impl Future + 'static, pool: &Arc

) { 245 | // Fuse main task 246 | let f: LocalBoxNotify<'_> = Box::pin(f.fuse()); 247 | 248 | // Set up the notify 249 | let tasks = &mut Vec::new(); 250 | 251 | // Set up the park, waker, and context. 252 | let parky = Arc::new(Unpark(

::Park::default())); 253 | let waker = parky.clone().into(); 254 | let tasky = &mut Task::from_waker(&waker); 255 | 256 | // Spawn main task 257 | tasks.push(f); 258 | 259 | // Run the set of futures to completion. 260 | while !tasks.is_empty() { 261 | // Poll the set of futures 262 | let poll = Pin::new(tasks.as_mut_slice()).poll_next(tasky); 263 | // If no tasks have completed, then park 264 | let Ready((task_index, ())) = poll else { 265 | // Initiate execution of any spawned tasks - if no new tasks, park 266 | if !pool.drain(tasks) { 267 | parky.0.park(); 268 | } 269 | continue; 270 | }; 271 | 272 | // Task has completed 273 | tasks.swap_remove(task_index); 274 | // Drain any spawned tasks into the pool 275 | pool.drain(tasks); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /tests/spawn.rs: -------------------------------------------------------------------------------- 1 | use pasts::Executor; 2 | use whisk::Channel; 3 | 4 | #[test] 5 | fn spawn_inside_block_on() { 6 | let executor = Executor::default(); 7 | let channel = Channel::new(); 8 | let sender = channel.clone(); 9 | 10 | executor.clone().block_on(async move { 11 | executor.spawn_boxed(async move { 12 | sender.send(0xDEADBEEFu32).await; 13 | }); 14 | }); 15 | 16 | Executor::default().block_on(async move { 17 | assert_eq!(0xDEADBEEFu32, channel.recv().await); 18 | }); 19 | } 20 | --------------------------------------------------------------------------------