Yes, "vectorizing" futures itself doesn't make sense as part of the language design. In this post, I would like to discuss the language features we would need to allow writing futures in code that wants to exploit SIMD.
The scenario I was mainly thinking about would be ECS. It is easier to write high-level game logic as an interruptible async function instead of a series of isolated logic in multiple per-tick systems. For example a simple pursuit control loop would be like this:
async fn pursuit(subject: Entity, target: Entity) {
future::join2(approach(subject, target), aim(subject, target)).await;
let bow = Loop::<&mut Bow>().next().await;
bow.release = true;
}
async fn approach(subject: Entity, target: Entity) {
let looper = Loop::<(&Position, &mut Direction)>::new();
loop {
let (positions, direction) = looper.next().await;
let delta = positions[target] - positions[subject];
if delta.distance() > 20 {
directions[subject] = delta.direction();
} else {
break;
}
}
}
async fn aim(subject: Entity, target: Entity) {
let looper = Loop::<&mut Bow>::new();
loop {
let bow = looper.next().await;
if *bow < 100 {
*bow += 1;
} else {
break;
}
}
}
However, in the best possible case, the executor that manages this future would be calling a Waker::wake()
function, which just executes some function pointer by address. There is no way for neither the compiler nor the executor to know what future we are executing, or to be precise, which branch (which suspension point we are resuming).
Any chance we can expose the ability to un-erase the underlying type from a Future (or a generator in the general sense) so that the Waker can be scheduled in a type-aware manner? For example:
trait UnsafeFuture {
fn poll_with<W: IntoWaker>(&mut self, data: W::Data) -> Poll;
}
trait IntoWaker {
fn into_waker< F: FnOnce()>(self, continue_future: F) ->Waker;
}
whereas an async block awaits by calling into_waker with a generic type (maybe not necessarily a FnOnce, but whatever that identifies the suspension point by type). Then the waker can be constructed in a non type erased manner that knows how to call multiple continue_future
s with the same type with inlining enabled (and group multiple such wakers by TypeId and Any::downcast).
I am not familiar with the internals of async executors. Dismissing backwards compatibility concerns for now (obviously this future cannot be object-safe), does this idea sound reasonable to further explore into? This is an open-ended question; I just threw a random possible design, but alternative designs to achieve the same result would be welcome.