created_at2022-02-07 04:59:09.540996
updated_at2022-04-22 15:29:23.811382
descriptiona single threaded, sequential, parameterized task pool for games
Jonathan Spira (sanbox-irl)




This crate provides a single-threaded, sequential, parameterized async runtime. In other words, this creates coroutines, specifically targeting video game logic, though cosync is suitable for creating any sequences of directions which take time.

Here's a basic Cosync example:

use cosync::{Cosync, CosyncInput};

fn main() {
    // the type parameter is the *value* which other functions will get.
    let mut cosync: Cosync<i32> = Cosync::new();
    let example_move = 20;

    // there are a few ways to queue tasks, but here's a simple one:
    cosync.queue(move |mut input: CosyncInput<i32>| async move {
        // set our input to `example_move`...
        *input.get() = example_move;

    let mut value = 0;
    cosync.run_until_stall(&mut value);

    // okay, we ran our future, and since it has no awaits, we know
    // it will have completed!
    assert_eq!(value, example_move);

Additionally, Cosync can handle unsized Ts, including dynamic dispatch:

use cosync::Cosync;

// unsized type
let mut cosync: Cosync<str> = Cosync::new();
cosync.queue(|mut input| async move {
    let input_guard = input.get();
    let inner_str: &str = &input_guard;
    println!("inner str = {}", inner_str);

// dynamic dispatch
trait DynDispatch {
    fn test(&self);
let mut cosync_dyn: Cosync<dyn DynDispatch> = Cosync::new();
cosync_dyn.queue(|mut input| async move {
    let inner: &mut dyn DynDispatch = &mut *input.get();

Cosync is not multithreaded, nor parallel -- it works entirely sequentially. Think of it as a useful way of expressing code that is multistaged and takes time to complete, that you want to do later. Moving cameras, staging actors, and performing animations often work well with Cosync. Loading asset files, doing mathematical computations, or doing IO should be done by more easily multithreaded runtimes such as switchyard.

This crate exposes two methods for driving the runtime: run_until_stall and run_blocking. You generally want run_until_stall, which attempts process as much of the queue as it can, until it cannot (ie, a future returns Poll::Pending), at which point control is returned to the caller.

There are three ways to make new tasks. First, the Cosync struct itself has a queue method on it. Secondly, each task gets a CosyncInput<T> as a parameter, which has get (to get access to your &mut T) and queue to queue another task (which is at the end of the queue, not necessarily after the task which added it). Lastly, you can create a CosyncQueueHandle with Cosync::create_queue_handle which is Send and can be given to other threads to create new tasks for the Cosync.

This crate depends on only std. It is in an early state of development, but is in production ready state right now.

Commit count: 79

cargo fmt