Skip to content

Commit

Permalink
riot-rs-embassy: improve SendCell API
Browse files Browse the repository at this point in the history
  • Loading branch information
kaspar030 committed Feb 20, 2024
1 parent ad02804 commit bbc1218
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/riot-rs-embassy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ async fn init_task(mut peripherals: arch::OptionalPeripherals) {
spawner.spawn(network::net_task(stack)).unwrap();

if STACK
.lock(|c| c.set(SendCell::new(stack, &spawner)))
.lock(|c| c.set(SendCell::new(stack, spawner)))
.is_err()
{
unreachable!();
Expand Down
5 changes: 3 additions & 2 deletions src/riot-rs-embassy/src/network.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use core::cell::OnceCell;

use embassy_executor::Spawner;
use embassy_net::Stack;
use embassy_sync::blocking_mutex::CriticalSectionMutex;

Expand All @@ -15,8 +16,8 @@ pub(crate) static STACK: CriticalSectionMutex<OnceCell<SendCell<&'static Network
CriticalSectionMutex::new(OnceCell::new());

pub async fn network_stack() -> Option<&'static NetworkStack> {
let spawner = crate::Spawner::for_current_executor().await;
STACK.lock(|cell| cell.get().map(|x| *x.get(&spawner).unwrap()))
let spawner = Spawner::for_current_executor().await;
STACK.lock(|cell| cell.get().map(|x| *x.get(spawner).unwrap()))
}

#[embassy_executor::task]
Expand Down
40 changes: 30 additions & 10 deletions src/riot-rs-embassy/src/sendcell.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! Pass non-Send objects around on same executor
//! Pass non-Send objects around on same executor.
//!
//! This module provides `SendCell`, a structure that allows passing around
//! non-Send objects from one async task to another, if they are on the same
//! executor. This is allowed because embassy executors are single threaded.
//!
//! `SendCell` checks for the correct executor *at runtime*.

use embassy_executor::Spawner;
Expand All @@ -11,36 +12,55 @@ use embassy_executor::Spawner;
// SendCell guarantees at runtime that its content stays on the same embassy
// executor. Those are single threaded, so it is guaranteed that the content
// stays on the same thread.
// While `SendCell::get()` allows passing any `Spawner` object, those are `!Send`,
// thus they are guaranteed to be for the current Executor.
unsafe impl<T> Send for SendCell<T> {}

/// A cell that allows sending of non-Send types *if they stay on the same executor*.
///
/// This is checked *at runtime*.
/// This is *checked at runtime*.
///
/// Both `new()` and `get()` have async versions (`new_async()` and `get_async()`) that get a
/// handle for the current `Spawner` themselves. They internally call the non-async versions. Use
/// the sync versions if a `Spawner` object is available or the async versions cannot be used,
/// e.g., in closures. Otherwise, the async versions are also fine.
#[derive(Debug)]
pub struct SendCell<T> {
executor_id: usize,
inner: T,
}

impl<T> SendCell<T> {
/// Create a new `SendCell`
///
/// The `spawner` argument *must* point to the current executor.
pub fn new(inner: T, spawner: &Spawner) -> Self {
/// Creates a new `SendCell`.
pub fn new(inner: T, spawner: Spawner) -> Self {
Self {
executor_id: spawner.executor_id(),
inner,
}
}

/// Get content of this `SendCell`
///
/// The `spawner` argument *must* point to the current executor.
pub fn get(&self, spawner: &Spawner) -> Option<&T> {
/// Gets the contents of this `SendCell`.
pub fn get(&self, spawner: Spawner) -> Option<&T> {
if spawner.executor_id() == self.executor_id {
Some(&self.inner)
} else {
None
}
}

/// Creates a new `SendCell` (async version).
///
/// Despite being async, this function never blocks/yields, it returns instantly.
pub async fn new_async(inner: T) -> Self {
let spawner = Spawner::for_current_executor().await;
SendCell::new(inner, spawner)
}

/// Gets the contents of this `SendCell` (async version).
///
/// Despite being async, this function never blocks/yields, it returns instantly.
pub async fn get_async(&self) -> Option<&T> {
let spawner = Spawner::for_current_executor().await;
self.get(spawner)
}
}

0 comments on commit bbc1218

Please sign in to comment.