blob: 5365354735468cf763d5926f8f33b76172cbe1a9 [file] [log] [blame]
// Copyright 2024, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! This library implements a simple executor using cyclic scheduling.
#![cfg_attr(not(test), no_std)]
extern crate alloc;
use alloc::{boxed::Box, vec::Vec};
use core::{future::Future, pin::Pin};
use gbl_async::poll;
/// `CyclicExecutor` is a simple single thread executor that simply cyclically polls all Futures.
#[derive(Default)]
pub struct CyclicExecutor<'a> {
tasks: Vec<Pin<Box<dyn Future<Output = ()> + 'a>>>,
}
impl<'a> CyclicExecutor<'a> {
/// Adds a new task.
pub fn spawn_task(&mut self, task: impl Future<Output = ()> + 'a) {
let mut task = Box::pin(task);
// Schedule the task once.
match poll(&mut task.as_mut()) {
Some(_) => {}
_ => self.tasks.push(task),
}
}
/// Polls all `Future`s once.
pub fn poll(&mut self) {
let mut idx = 0;
while let Some(task) = self.tasks.get_mut(idx) {
if poll(&mut task.as_mut()).is_some() {
let _ = self.tasks.swap_remove(idx);
} else {
idx += 1;
}
}
}
/// Runs all `Future`s until completion.
pub fn run(&mut self) {
while !self.tasks.is_empty() {
self.poll();
}
}
/// Returns the current number of tasks.
pub fn num_tasks(&self) -> usize {
self.tasks.len()
}
}
#[cfg(test)]
mod test {
use super::*;
use gbl_async::yield_now;
use std::sync::Mutex;
#[test]
fn test_spawn_and_poll_task() {
let val1 = Mutex::new(0);
let val2 = Mutex::new(1);
let mut executor: CyclicExecutor = Default::default();
// Spawns 2 tasks.
executor.spawn_task(async {
*val1.try_lock().unwrap() += 1;
yield_now().await;
*val1.try_lock().unwrap() += 1;
yield_now().await;
*val1.try_lock().unwrap() += 1;
yield_now().await;
});
executor.spawn_task(async {
*val2.try_lock().unwrap() += 1;
yield_now().await;
*val2.try_lock().unwrap() += 1;
yield_now().await;
*val2.try_lock().unwrap() += 1;
yield_now().await;
});
// Test that spawning a task schedules it immediately.
assert_eq!(*val1.try_lock().unwrap(), 1);
assert_eq!(*val2.try_lock().unwrap(), 2);
// Polls all Futures once.
executor.poll();
assert_eq!(*val1.try_lock().unwrap(), 2);
assert_eq!(*val2.try_lock().unwrap(), 3);
// Runs to completion.
executor.run();
assert_eq!(*val1.try_lock().unwrap(), 3);
assert_eq!(*val2.try_lock().unwrap(), 4);
}
#[test]
fn test_complete_on_spawn_not_added() {
let mut executor: CyclicExecutor = Default::default();
executor.spawn_task(async {});
assert_eq!(executor.num_tasks(), 0);
}
}