poll-promise

repository·main·Indexed 41 days ago

https://github.com/embarkstudios/poll-promise

A Rust crate for polling the results of concurrent operations, specifically designed for frame-based environments like games and immediate mode GUIs. It allows developers to trigger background operations via Promise::spawn_thread or spawn_async and check their status using .ready() without blocking the main execution loop. Version 0.3.0 supports integration with Tokio, smol, and async-std.

Tokens
2.6K
Snippets
10
Records
16
Agent score
98%

What's inside poll-promise

  1. What is poll-promise?

    main
    poll-promise is a Rust crate designed for polling the results of concurrent operations (such as async tasks). It is specifically optimized for use cases like games and immediate mode GUIs, where you need to trigger a background operation and check its status on every frame (e.g., "are we there yet?") without blocking the main execution loop.
  2. Usage Caveat: Libraries vs. Applications

    main

    The poll-promise crate is intended as a high-level building block for applications (like games or GUIs).

    Do not use this crate in libraries.

    Because the crate provides convenience methods for spawning threads and Tokio tasks, as well as methods that block while waiting for results, it makes decisions about execution environments and thread blocking. These decisions should be left to the application using the library, not the library itself.

  3. What is a Promise and how to use it

    main

    A Promise<T> is a type that waits for a single value to be sent from a background task. It is designed for scenarios like game loops or immediate-mode GUIs where you want to check if a background operation is finished without blocking the main thread.

    Key Lifecycle:

    1. A task is spawned (using methods like spawn_thread, spawn_async, etc.).
    2. The Promise starts in a Pending state.
    3. You poll the Promise (using .ready()) to check for completion.
    4. Once the task completes, the Promise stores the value and returns it on subsequent polls.

    Important Safety Note: If the Sender associated with a Promise is dropped before a value is sent, polling the Promise will cause a panic.

    # fn something_slow() {}
    # use poll_promise::Promise;
    
    let promise = Promise::spawn_thread("slow_operation", move || something_slow());
    
    // In a loop or UI update:
    if let Some(result) = promise.ready() {
        // Use/show result
    } else {
        // Show a loading screen
    }
  4. Use `Promise` to poll concurrent operations

    main

    The Promise type allows you to start a background operation (either a synchronous function in a new thread or an asynchronous task) and poll for its result later. This is ideal for game loops or immediate mode GUIs where you want to check if a task is finished without blocking the main thread.

    Common patterns include:

    • spawn_thread: Runs a synchronous closure in a new thread.
    • spawn_async: Runs an asynchronous task (requires an async runtime feature like tokio, async-std, or smol).
    • spawn_local: Runs an asynchronous task on the current thread's local executor (requires smol feature).
    • ready(): Non-blocking check that returns Some(T) if the task is complete, or None if it is still running.
    • block_and_take(): Blocks the current thread until the promise is ready and returns the result.
    # fn something_slow() {}
    # use poll_promise::Promise;
    #
    let promise = Promise::spawn_thread("slow_operation", something_slow);
    
    // Then in the game loop or immediate mode GUI code:
    if let Some(result) = promise.ready() {
        // Use/show result
    } else {
        // Show a loading screen
    }
  5. How to use poll-promise in a game loop

    main

    To use poll-promise, you can spawn a background operation using Promise::spawn_thread and then call .ready() within your main loop. .ready() returns Some(T) if the operation is complete, or None if it is still running. This allows you to switch between showing a loading state and the actual result seamlessly.

    let promise = poll_promise::Promise::spawn_thread("slow_operation", something_slow);
    
    // Then in the game loop or immediate mode GUI code:
    if let Some(result) = promise.ready() {
        // Use/show result
    } else {
        // Show a loading icon
    }
  6. Spawn a background thread with `spawn_thread`

    main

    Use spawn_thread to run a blocking closure in a dedicated background thread. This is the preferred method if you are not using an async runtime (like Tokio or Smol). The first argument is a thread name used for identification in panic messages.

    Note: If you are using tokio or web features, use spawn_blocking or spawn_async instead.

    Constraint: This method is not available when compiling for wasm32 (WebAssembly) as Wasm does not support spawning threads.

    # fn something_slow() {}
    # use poll_promise::Promise;
    
    let promise = Promise::spawn_thread("slow_operation", move || something_slow());
  7. Abort a Tokio-spawned task

    main

    If you have spawned a task using spawn_async and are using the tokio feature, you can call .abort() on the Promise to cancel the underlying task.

    // Requires 'tokio' feature
    let promise = Promise::spawn_async(async { ... });
    
    // ... later ...
    promise.abort();
  8. Poll a Promise for results

    main

    Use these methods to check the status of a Promise without blocking execution:

    • ready(&self) -> Option<&T>: Returns a reference to the value if ready, otherwise None.
    • ready_mut(&mut self) -> Option<&mut T>: Returns a mutable reference to the value if ready, otherwise None.
    • poll(&self) -> std::task::Poll<&T>: Returns Poll::Ready(value) or Poll::Pending.
    • poll_mut(&mut self) -> std::task::Poll<&mut T>: Returns Poll::Ready(value) or Poll::Pending.

    Warning: All polling methods will panic if the connected Sender was dropped before a value was sent.

  9. Block execution until a Promise is ready

    main

    If you need to halt execution until the background task completes, use these blocking methods.

    Warning: These methods are not allowed on Wasm and will not compile/run there.

    • block_until_ready(&self) -> &T: Blocks until ready, then returns a reference.
    • block_until_ready_mut(&mut self) -> &mut T: Blocks until ready, then returns a mutable reference.
    • block_and_take(self) -> T: Blocks until ready, then consumes the Promise and returns the owned value T.
  10. Spawn local async tasks with `spawn_local`

    main

    Spawns a Future to run on the local thread. Unlike spawn_async, the future passed to spawn_local does not need to implement Send.

    Requirements: Requires tokio, smol, or web features.

    Note: If using tokio, you must manually set up tokio::task::LocalSets for this to function correctly.

    # async fn something_async() {}
    # use poll_promise::Promise;
    
    let promise = Promise::spawn_local(async move { something_async().await });
  11. Spawn blocking tasks with `spawn_blocking`

    main

    Offloads heavy CPU-bound work to a thread pool designed for blocking operations. This prevents the async executor from being stalled.

    Requirements: Requires tokio or async-std features.

    Usage: Use this instead of spawn_async for any task that performs significant computation or synchronous blocking IO.

    # fn something_cpu_intensive() {}
    # use poll_promise::Promise;
    
    let promise = Promise::spawn_blocking(move || something_cpu_intensive());