const SelectFrames = struct {
    frames: []@Frame(runInternal),
    next_get_result: usize,
    next_put_result: usize,
    result_frames: []@Frame(awaitResultInternal);
    result_values: []Result;

    pub fn init(self: *@This(), in_frames: []anyframe->Result) !void {
        const wrapped_frames = try allocator.alloc(@Frame(runInternal), in_frames.len);
        errdefer allocator.free(wrapped_frames);

        const result_frames = try allocator.alloc(@Frame(awaitResultInternal), in_frames.len);
        errdefer allocator.free(result_frames);

        const results = try allocator.alloc(Result, in_frames.len);
        errdefer allocator.free(results);
        
        self = .{
            .frames = wrapped_frames,
            .next_get_result = 0,
            .next_put_result = 0,
            .result_frames = result_frames,
            .dispose_frames = results,
        };
        
        for (result_frames) |*frame| {
            frame.* = async awaitResultInternal(self);
        }
        
        for (wrapped_frames) |*frame, i| {
            frame.* = async runInternal(self, in_frames[i]);
        }
    }
    
    /// Blocks until any result is available, and returns it.
    pub fn next(self: *PollFrames) Result {
        const index = atomic_increment(&self.next_get_result);
        await self.result_frames[index];
        return self.result_values[index];
    }
    
    fn runInternal(self: *PollFrames, frame: anyframe->Result) void {
        const result = await frame;
        const index = atomic_increment(&self.next_put_result);
        result_values[index] = result;
        resume result_frames[index];
    }
    
    fn awaitResultInternal(self: *PollFrames) Result {
        suspend; // resumed by finished function
    }
}