mirror of
https://github.com/rtic-rs/rtic.git
synced 2025-12-19 06:15:45 +01:00
Add the concept of local tasks with an example
This commit is contained in:
parent
300ad99b74
commit
5bb3ba984d
7 changed files with 189 additions and 47 deletions
42
examples/embassy-stm32g4/src/bin/spawn_local.rs
Normal file
42
examples/embassy-stm32g4/src/bin/spawn_local.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
use rtic::app;
|
||||||
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
pub mod pac {
|
||||||
|
pub use embassy_stm32::pac::Interrupt as interrupt;
|
||||||
|
pub use embassy_stm32::pac::*;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[app(device = pac, peripherals = false, dispatchers = [SPI1])]
|
||||||
|
mod app {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(_cx: init::Context) -> (Shared, Local) {
|
||||||
|
task1::spawn().ok();
|
||||||
|
//task2::spawn(Default::default()).ok(); <--- This is rejected since it is a local task
|
||||||
|
(Shared {}, Local {})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(priority = 1)]
|
||||||
|
async fn task1(cx: task1::Context) {
|
||||||
|
defmt::info!("Hello from task1!");
|
||||||
|
cx.local_spawner.task2(Default::default()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(priority = 1, is_local_task = true)]
|
||||||
|
async fn task2(_cx: task2::Context, _nsns: super::NotSendNotSync) {
|
||||||
|
defmt::info!("Hello from task1!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct NotSendNotSync(PhantomData<*mut u8>);
|
||||||
|
|
@ -10,6 +10,7 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Outer attributes applied to RTIC app module are now forwarded to the generated code.
|
- Outer attributes applied to RTIC app module are now forwarded to the generated code.
|
||||||
|
- Local spawner for spawning tasks with !Send/!Sync args on the same executor
|
||||||
|
|
||||||
## [v2.2.0] - 2025-06-22
|
## [v2.2.0] - 2025-06-22
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::syntax::{ast::App, Context};
|
use crate::syntax::{ast::App, Context};
|
||||||
use crate::{analyze::Analysis, codegen::bindings::interrupt_mod, codegen::util};
|
use crate::{analyze::Analysis, codegen::bindings::interrupt_mod, codegen::util};
|
||||||
|
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
|
|
@ -112,37 +113,7 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||||
let internal_context_name = util::internal_task_ident(name, "Context");
|
let internal_context_name = util::internal_task_ident(name, "Context");
|
||||||
let exec_name = util::internal_task_ident(name, "EXEC");
|
let exec_name = util::internal_task_ident(name, "EXEC");
|
||||||
|
|
||||||
items.push(quote!(
|
if let Context::SoftwareTask(t) = ctxt {
|
||||||
#(#cfgs)*
|
|
||||||
/// Execution context
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
pub struct #internal_context_name<'a> {
|
|
||||||
#[doc(hidden)]
|
|
||||||
__rtic_internal_p: ::core::marker::PhantomData<&'a ()>,
|
|
||||||
#(#fields,)*
|
|
||||||
}
|
|
||||||
|
|
||||||
#(#cfgs)*
|
|
||||||
impl<'a> #internal_context_name<'a> {
|
|
||||||
#[inline(always)]
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub unsafe fn new(#core) -> Self {
|
|
||||||
#internal_context_name {
|
|
||||||
__rtic_internal_p: ::core::marker::PhantomData,
|
|
||||||
#(#values,)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
module_items.push(quote!(
|
|
||||||
#(#cfgs)*
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use super::#internal_context_name as Context;
|
|
||||||
));
|
|
||||||
|
|
||||||
if let Context::SoftwareTask(..) = ctxt {
|
|
||||||
let spawnee = &app.software_tasks[name];
|
let spawnee = &app.software_tasks[name];
|
||||||
let priority = spawnee.args.priority;
|
let priority = spawnee.args.priority;
|
||||||
let cfgs = &spawnee.cfgs;
|
let cfgs = &spawnee.cfgs;
|
||||||
|
|
@ -163,13 +134,21 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||||
let (input_args, input_tupled, input_untupled, input_ty) =
|
let (input_args, input_tupled, input_untupled, input_ty) =
|
||||||
util::regroup_inputs(&spawnee.inputs);
|
util::regroup_inputs(&spawnee.inputs);
|
||||||
|
|
||||||
|
let is_local_task = app.software_tasks[t].args.is_local_task;
|
||||||
|
let unsafety = if is_local_task {
|
||||||
|
// local tasks are only safe to call from the same executor
|
||||||
|
quote! { unsafe }
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
// Spawn caller
|
// Spawn caller
|
||||||
items.push(quote!(
|
items.push(quote!(
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
/// Spawns the task directly
|
/// Spawns the task directly
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn #internal_spawn_ident(#(#input_args,)*) -> ::core::result::Result<(), #input_ty> {
|
pub #unsafety fn #internal_spawn_ident(#(#input_args,)*) -> ::core::result::Result<(), #input_ty> {
|
||||||
// SAFETY: If `try_allocate` succeeds one must call `spawn`, which we do.
|
// SAFETY: If `try_allocate` succeeds one must call `spawn`, which we do.
|
||||||
unsafe {
|
unsafe {
|
||||||
let exec = rtic::export::executor::AsyncTaskExecutor::#from_ptr_n_args(#name, &#exec_name);
|
let exec = rtic::export::executor::AsyncTaskExecutor::#from_ptr_n_args(#name, &#exec_name);
|
||||||
|
|
@ -204,11 +183,70 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
module_items.push(quote!(
|
if !is_local_task {
|
||||||
#(#cfgs)*
|
module_items.push(quote!(
|
||||||
#[doc(inline)]
|
#(#cfgs)*
|
||||||
pub use super::#internal_spawn_ident as spawn;
|
#[doc(inline)]
|
||||||
));
|
pub use super::#internal_spawn_ident as spawn;
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_tasks_on_same_executor: Vec<_> = app
|
||||||
|
.software_tasks
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, t)| t.args.is_local_task && t.args.priority == priority)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !local_tasks_on_same_executor.is_empty() {
|
||||||
|
let local_spawner = util::internal_task_ident(t, "LocalSpawner");
|
||||||
|
fields.push(quote! {
|
||||||
|
/// Used to spawn tasks on the same executor
|
||||||
|
///
|
||||||
|
/// This is useful for tasks that take args which are !Send/!Sync.
|
||||||
|
///
|
||||||
|
/// NOTE: This only works with tasks marked `is_local_task = true`
|
||||||
|
/// and which have the same priority and thus will run on the
|
||||||
|
/// same executor.
|
||||||
|
pub local_spawner: #local_spawner
|
||||||
|
});
|
||||||
|
let tasks = local_tasks_on_same_executor
|
||||||
|
.iter()
|
||||||
|
.map(|(ident, task)| {
|
||||||
|
// Copied mostly from software_tasks.rs
|
||||||
|
let internal_spawn_ident = util::internal_task_ident(ident, "spawn");
|
||||||
|
let attrs = &task.attrs;
|
||||||
|
let cfgs = &task.cfgs;
|
||||||
|
let inputs = &task.inputs;
|
||||||
|
let generics = if task.is_bottom {
|
||||||
|
quote!()
|
||||||
|
} else {
|
||||||
|
quote!(<'a>)
|
||||||
|
};
|
||||||
|
let input_vals = inputs.iter().map(|i| &i.pat).collect::<Vec<_>>();
|
||||||
|
let (_input_args, _input_tupled, _input_untupled, input_ty) = util::regroup_inputs(&task.inputs);
|
||||||
|
quote! {
|
||||||
|
#(#attrs)*
|
||||||
|
#(#cfgs)*
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub(super) fn #ident #generics(&self #(,#inputs)*) -> ::core::result::Result<(), #input_ty> {
|
||||||
|
// SAFETY: This is safe to call since this can only be called
|
||||||
|
// from the same executor
|
||||||
|
unsafe { #internal_spawn_ident(#(#input_vals,)*) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
values.push(quote!(local_spawner: #local_spawner { _p: core::marker::PhantomData }));
|
||||||
|
items.push(quote! {
|
||||||
|
struct #local_spawner {
|
||||||
|
_p: core::marker::PhantomData<*mut ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #local_spawner {
|
||||||
|
#(#tasks)*
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module_items.push(quote!(
|
module_items.push(quote!(
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
|
|
@ -217,6 +255,36 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items.push(quote!(
|
||||||
|
#(#cfgs)*
|
||||||
|
/// Execution context
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub struct #internal_context_name<'a> {
|
||||||
|
#[doc(hidden)]
|
||||||
|
__rtic_internal_p: ::core::marker::PhantomData<&'a ()>,
|
||||||
|
#(#fields,)*
|
||||||
|
}
|
||||||
|
|
||||||
|
#(#cfgs)*
|
||||||
|
impl<'a> #internal_context_name<'a> {
|
||||||
|
#[inline(always)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub unsafe fn new(#core) -> Self {
|
||||||
|
#internal_context_name {
|
||||||
|
__rtic_internal_p: ::core::marker::PhantomData,
|
||||||
|
#(#values,)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
module_items.push(quote!(
|
||||||
|
#(#cfgs)*
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use super::#internal_context_name as Context;
|
||||||
|
));
|
||||||
|
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
quote!()
|
quote!()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,16 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||||
let cfgs = &task.cfgs;
|
let cfgs = &task.cfgs;
|
||||||
let stmts = &task.stmts;
|
let stmts = &task.stmts;
|
||||||
let inputs = &task.inputs;
|
let inputs = &task.inputs;
|
||||||
let lifetime = if task.is_bottom { quote!('static) } else { quote!('a) };
|
let lifetime = if task.is_bottom {
|
||||||
let generics = if task.is_bottom { quote!() } else { quote!(<'a>) };
|
quote!('static)
|
||||||
|
} else {
|
||||||
|
quote!('a)
|
||||||
|
};
|
||||||
|
let generics = if task.is_bottom {
|
||||||
|
quote!()
|
||||||
|
} else {
|
||||||
|
quote!(<'a>)
|
||||||
|
};
|
||||||
|
|
||||||
user_tasks.push(quote!(
|
user_tasks.push(quote!(
|
||||||
#(#attrs)*
|
#(#attrs)*
|
||||||
|
|
|
||||||
|
|
@ -285,13 +285,16 @@ pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
|
||||||
for (name, spawnee) in &app.software_tasks {
|
for (name, spawnee) in &app.software_tasks {
|
||||||
let spawnee_prio = spawnee.args.priority;
|
let spawnee_prio = spawnee.args.priority;
|
||||||
|
|
||||||
|
// TODO: What is this?
|
||||||
let channel = channels.entry(spawnee_prio).or_default();
|
let channel = channels.entry(spawnee_prio).or_default();
|
||||||
channel.tasks.insert(name.clone());
|
channel.tasks.insert(name.clone());
|
||||||
|
|
||||||
// All inputs are send as we do not know from where they may be spawned.
|
if !spawnee.args.is_local_task {
|
||||||
spawnee.inputs.iter().for_each(|input| {
|
// All inputs are send as we do not know from where they may be spawned.
|
||||||
send_types.insert(input.ty.clone());
|
spawnee.inputs.iter().for_each(|input| {
|
||||||
});
|
send_types.insert(input.ty.clone());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No channel should ever be empty
|
// No channel should ever be empty
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,12 @@ pub struct SoftwareTaskArgs {
|
||||||
|
|
||||||
/// Shared resources that can be accessed from this context
|
/// Shared resources that can be accessed from this context
|
||||||
pub shared_resources: SharedResources,
|
pub shared_resources: SharedResources,
|
||||||
|
|
||||||
|
/// Local tasks
|
||||||
|
///
|
||||||
|
/// Local tasks can only be spawned from the same executor.
|
||||||
|
/// However they do not require Send and Sync
|
||||||
|
pub is_local_task: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SoftwareTaskArgs {
|
impl Default for SoftwareTaskArgs {
|
||||||
|
|
@ -264,6 +270,7 @@ impl Default for SoftwareTaskArgs {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
local_resources: LocalResources::new(),
|
local_resources: LocalResources::new(),
|
||||||
shared_resources: SharedResources::new(),
|
shared_resources: SharedResources::new(),
|
||||||
|
is_local_task: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,7 @@ mod util;
|
||||||
|
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use syn::{
|
use syn::{
|
||||||
braced,
|
braced, parse::{self, Parse, ParseStream, Parser}, token::Brace, Attribute, Ident, Item, LitBool, LitInt, Meta, Token
|
||||||
parse::{self, Parse, ParseStream, Parser},
|
|
||||||
token::Brace,
|
|
||||||
Attribute, Ident, Item, LitInt, Meta, Token,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::syntax::{
|
use crate::syntax::{
|
||||||
|
|
@ -197,6 +194,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
|
||||||
let mut shared_resources = None;
|
let mut shared_resources = None;
|
||||||
let mut local_resources = None;
|
let mut local_resources = None;
|
||||||
let mut prio_span = None;
|
let mut prio_span = None;
|
||||||
|
let mut is_local_task = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
|
|
@ -277,6 +275,19 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
|
||||||
local_resources = Some(util::parse_local_resources(input)?);
|
local_resources = Some(util::parse_local_resources(input)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"is_local_task" => {
|
||||||
|
if is_local_task.is_some() {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
ident.span(),
|
||||||
|
"argument appears more than once",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let lit: LitBool = input.parse()?;
|
||||||
|
|
||||||
|
is_local_task = Some(lit.value);
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
return Err(parse::Error::new(ident.span(), "unexpected argument"));
|
return Err(parse::Error::new(ident.span(), "unexpected argument"));
|
||||||
}
|
}
|
||||||
|
|
@ -291,6 +302,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
|
||||||
}
|
}
|
||||||
let shared_resources = shared_resources.unwrap_or_default();
|
let shared_resources = shared_resources.unwrap_or_default();
|
||||||
let local_resources = local_resources.unwrap_or_default();
|
let local_resources = local_resources.unwrap_or_default();
|
||||||
|
let is_local_task = is_local_task.unwrap_or(false);
|
||||||
|
|
||||||
Ok(if let Some(binds) = binds {
|
Ok(if let Some(binds) = binds {
|
||||||
// Hardware tasks can't run at anything lower than 1
|
// Hardware tasks can't run at anything lower than 1
|
||||||
|
|
@ -317,6 +329,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
|
||||||
priority,
|
priority,
|
||||||
shared_resources,
|
shared_resources,
|
||||||
local_resources,
|
local_resources,
|
||||||
|
is_local_task,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue