mirror of
https://github.com/rtic-rs/rtic.git
synced 2025-12-18 13:55:23 +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
|
|
@ -10,6 +10,7 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
|
|||
### Added
|
||||
|
||||
- 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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::syntax::{ast::App, Context};
|
||||
use crate::{analyze::Analysis, codegen::bindings::interrupt_mod, codegen::util};
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
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 exec_name = util::internal_task_ident(name, "EXEC");
|
||||
|
||||
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 let Context::SoftwareTask(..) = ctxt {
|
||||
if let Context::SoftwareTask(t) = ctxt {
|
||||
let spawnee = &app.software_tasks[name];
|
||||
let priority = spawnee.args.priority;
|
||||
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) =
|
||||
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
|
||||
items.push(quote!(
|
||||
#(#cfgs)*
|
||||
/// Spawns the task directly
|
||||
#[allow(non_snake_case)]
|
||||
#[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.
|
||||
unsafe {
|
||||
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!(
|
||||
#(#cfgs)*
|
||||
#[doc(inline)]
|
||||
pub use super::#internal_spawn_ident as spawn;
|
||||
));
|
||||
if !is_local_task {
|
||||
module_items.push(quote!(
|
||||
#(#cfgs)*
|
||||
#[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!(
|
||||
#(#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() {
|
||||
quote!()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -37,8 +37,16 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
|
|||
let cfgs = &task.cfgs;
|
||||
let stmts = &task.stmts;
|
||||
let inputs = &task.inputs;
|
||||
let lifetime = if task.is_bottom { quote!('static) } else { quote!('a) };
|
||||
let generics = if task.is_bottom { quote!() } else { quote!(<'a>) };
|
||||
let lifetime = if task.is_bottom {
|
||||
quote!('static)
|
||||
} else {
|
||||
quote!('a)
|
||||
};
|
||||
let generics = if task.is_bottom {
|
||||
quote!()
|
||||
} else {
|
||||
quote!(<'a>)
|
||||
};
|
||||
|
||||
user_tasks.push(quote!(
|
||||
#(#attrs)*
|
||||
|
|
|
|||
|
|
@ -285,13 +285,16 @@ pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
|
|||
for (name, spawnee) in &app.software_tasks {
|
||||
let spawnee_prio = spawnee.args.priority;
|
||||
|
||||
// TODO: What is this?
|
||||
let channel = channels.entry(spawnee_prio).or_default();
|
||||
channel.tasks.insert(name.clone());
|
||||
|
||||
// All inputs are send as we do not know from where they may be spawned.
|
||||
spawnee.inputs.iter().for_each(|input| {
|
||||
send_types.insert(input.ty.clone());
|
||||
});
|
||||
if !spawnee.args.is_local_task {
|
||||
// All inputs are send as we do not know from where they may be spawned.
|
||||
spawnee.inputs.iter().for_each(|input| {
|
||||
send_types.insert(input.ty.clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// No channel should ever be empty
|
||||
|
|
|
|||
|
|
@ -256,6 +256,12 @@ pub struct SoftwareTaskArgs {
|
|||
|
||||
/// Shared resources that can be accessed from this context
|
||||
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 {
|
||||
|
|
@ -264,6 +270,7 @@ impl Default for SoftwareTaskArgs {
|
|||
priority: 0,
|
||||
local_resources: LocalResources::new(),
|
||||
shared_resources: SharedResources::new(),
|
||||
is_local_task: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ mod util;
|
|||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use syn::{
|
||||
braced,
|
||||
parse::{self, Parse, ParseStream, Parser},
|
||||
token::Brace,
|
||||
Attribute, Ident, Item, LitInt, Meta, Token,
|
||||
braced, parse::{self, Parse, ParseStream, Parser}, token::Brace, Attribute, Ident, Item, LitBool, LitInt, Meta, Token
|
||||
};
|
||||
|
||||
use crate::syntax::{
|
||||
|
|
@ -197,6 +194,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
|
|||
let mut shared_resources = None;
|
||||
let mut local_resources = None;
|
||||
let mut prio_span = None;
|
||||
let mut is_local_task = None;
|
||||
|
||||
loop {
|
||||
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)?);
|
||||
}
|
||||
|
||||
"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"));
|
||||
}
|
||||
|
|
@ -291,6 +302,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
|
|||
}
|
||||
let shared_resources = shared_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 {
|
||||
// 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,
|
||||
shared_resources,
|
||||
local_resources,
|
||||
is_local_task,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue