initial commit

This commit is contained in:
Dark 2023-02-12 20:46:37 -05:00
commit 6a4d6f3e17
Signed by: Dark
GPG key ID: 8910EE89544A1676
4 changed files with 232 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/Cargo.lock

14
Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "synergetic"
version = "0.1.0"
edition = "2021"
description = "Minimal tick-based async executor for constrained environments"
license = "MIT"
[dependencies]
async-task = { version = "4.3.0", default-features = false }
spin = { version = "0.9.5", optional = true }
[features]
default = ["spin"]
spin = ["dep:spin"]

18
LICENSE Normal file
View file

@ -0,0 +1,18 @@
Copyright 2023 Dark
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

198
src/lib.rs Normal file
View file

@ -0,0 +1,198 @@
//! # synergetic
//!
//! `synergetic` is a tick-based, thread-local async executor intended for more
//! constrained environments that lack `std` but do have access to an allocator.
//!
//! An optional, thread-safe version is provided using `spin`
//!
//! ## Alternatives:
//!
//! * `embassy` can be used if you want a full async runtime suitable for
//! embedded development.
//!
//! * `async-executor` is a better option if `std` is available and the only
//! thing you are needing is an async executor.
//!
//! * `smol` can be used if you just want a small, but fully featured runtime.
//!
//! # Examples
//!
//! ```
//! use synergetic::LockingExecutor;
//!
//! let mut executor = LockingExecutor::new();
//!
//! let task = executor.spawn(async {
//! println!("hello world");
//! })
//!
//! loop {
//! executor.tick();
//! if executor.done() {
//! break;
//! }
//! }
//! ```
#![no_std]
#![warn(missing_docs)]
extern crate alloc;
use core::cell::RefCell;
use core::future::Future;
use core::pin::Pin;
use core::task::Context;
use core::task::Poll;
use alloc::rc::Rc;
use alloc::sync::Arc;
use alloc::vec::Vec;
use async_task::Runnable;
use async_task::Task;
#[cfg(feature = "spin")]
use spin::Mutex;
/// An `Executor` is used to execute async tasks.
///
/// This is not thread safe so use with caution.
pub struct Executor {
queue: Rc<RefCell<Vec<Runnable>>>,
}
#[cfg(feature = "spin")]
impl Executor {
/// Creates a new executor.
pub fn new() -> Self {
Self {
queue: RefCell::new(Vec::new()).into(),
}
}
/// Spawns a new task in the executor.
///
/// # Safety
///
/// Any future running in the executor that passes its `Waker` to another
/// thread will cause UB.
pub unsafe fn spawn<F>(&mut self, future: F) -> Task<F::Output>
where
F: Future + 'static,
{
let queue = self.queue.clone();
let (runnable, task) = Self::spawn_static(future, move |runnable| {
let mut queue = queue.borrow_mut();
queue.push(runnable);
});
runnable.schedule();
task
}
/// Runs a single tick of the executor.
///
/// # Safety
///
/// Any future running in the executor that passes its `Waker` to another
/// thread will cause UB.
pub unsafe fn tick(&mut self) {
let mut queue_guard = self.queue.borrow_mut();
let mut tasks: Vec<_> = queue_guard.drain(..).collect();
drop(queue_guard);
for i in tasks.drain(..) {
i.run();
}
}
/// Returns `true` if there are no tasks left to run.
pub fn done(&self) -> bool {
self.queue.borrow_mut().len() == 0
}
// used to enforce static lifetimes on spawn_unchecked
unsafe fn spawn_static<F, S>(future: F, schedule: S) -> (Runnable, Task<F::Output>)
where
F: Future + 'static,
F::Output: 'static,
S: Fn(Runnable) + 'static,
{
async_task::spawn_unchecked(future, schedule)
}
}
/// An `Executor` is used to execute async tasks.
/// This is a thread safe version of `Executor`
#[cfg(feature = "spin")]
pub struct LockingExecutor {
queue: Arc<Mutex<Vec<Runnable>>>,
}
#[cfg(feature = "spin")]
impl LockingExecutor {
/// Creates a new executor.
pub fn new() -> Self {
Self {
queue: Mutex::new(Vec::new()).into(),
}
}
/// Spawns a new task in the executor.
pub fn spawn<F>(&mut self, future: F) -> Task<F::Output>
where
F: Future + 'static + Send + Sync,
<F as Future>::Output: Send,
{
let queue = self.queue.clone();
let (runnable, task) = async_task::spawn(future, move |runnable| {
let mut queue = queue.lock();
queue.push(runnable);
});
runnable.schedule();
task
}
/// Runs a single tick of the executor.
pub fn tick(&mut self) {
let mut queue_guard = self.queue.lock();
let mut tasks: Vec<_> = queue_guard.drain(..).collect();
drop(queue_guard);
for i in tasks.drain(..) {
i.run();
}
}
/// Returns `true` if there are no tasks left to run.
pub fn done(&self) -> bool {
self.queue.lock().len() == 0
}
}
/// A yielder provides an easy way to yield control to another task.
pub struct Yielder(bool);
impl Future for Yielder {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if !self.0 {
self.0 = true;
cx.waker().wake_by_ref();
Poll::Pending
} else {
Poll::Ready(())
}
}
}
impl Yielder {
/// Creates a new yielder.
pub fn new() -> Self {
Self(false)
}
}