//! Code generation for the `statechart!` macro. use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use syn::Ident; use crate::ir::{HandlerKindIr, Ir, TriggerIr, VariantKind}; use crate::parse::{During, PayloadKind}; /// For each user-declared leaf state that has one or more `during:` activities /// active (either declared on it, on any ancestor, or on root), return its /// state id and the flattened list of durings in leaf-first order. Durings /// declared on the same state follow their declaration order. /// /// Leaf states with no active durings are omitted — the run loop uses its /// default (channel+timer) path for those. fn active_durings_per_leaf(ir: &Ir) -> Vec<(u16, Vec)> { let mut out = Vec::new(); for s in &ir.states { if !s.children.is_empty() { continue; } if s.parent.is_none() { continue; // synthetic __Root } let mut chain: Vec = Vec::new(); let mut cur = Some(s.id); while let Some(id) = cur { for d in &ir.states[id as usize].durings { chain.push(d.clone()); } cur = ir.states[id as usize].parent; } if !chain.is_empty() { out.push((s.id, chain)); } } out } /// Emit `let dN = fn(&mut self.ctx.fN_0, &mut self.ctx.fN_1, ...);` for each /// during in `chain`. Returns the list of binding identifiers in order so the /// call site can reference them in the `select!`/`select_N` arguments. fn emit_during_bindings(chain: &[During]) -> (Vec, TokenStream) { let mut idents = Vec::with_capacity(chain.len()); let mut stmts: Vec = Vec::with_capacity(chain.len()); for (i, d) in chain.iter().enumerate() { let bind = format_ident!("__hsmc_d{}", i); let fn_name = &d.fn_name; let field_refs = d.fields.iter().map(|f| quote! { &mut self.ctx.#f }); stmts.push(quote! { let #bind = #fn_name(#(#field_refs),*); }); idents.push(bind); } (idents, quote! { #(#stmts)* }) } /// Emit the per-leaf match arms for the tokio run loop. Each arm constructs /// the active during: futures (split-borrowing context fields), then races /// them against the event channel and the next-timer sleep via /// `tokio::select!`. The default arm — used for leaves with no durings — is /// emitted by the caller. fn emit_tokio_race_arms_with_channel(ir: &Ir) -> Vec { let mut arms = Vec::new(); for (sid, chain) in active_durings_per_leaf(ir) { let (binds, stmts) = emit_during_bindings(&chain); let select_arms = binds.iter().map(|b| { quote! { ev = #b => __HsmcRace::Event(ev), } }); arms.push(quote! { Some(#sid) => { #stmts let sleep = ::tokio::time::sleep(sleep_dur); ::tokio::pin!(sleep); let rx = self.__tokio_rx.as_mut().expect("hsmc: tokio channel"); ::tokio::select! { biased; #(#select_arms)* maybe = rx.recv() => match maybe { Some(ev) => __HsmcRace::Event(ev), None => __HsmcRace::ChannelClosed, }, _ = &mut sleep => __HsmcRace::Timer, } } }); } arms } /// Same as `emit_tokio_race_arms_with_channel` but for timer-only machines /// (no `events:` declared, no channel). The default arm races only the timer. fn emit_tokio_race_arms_timer_only(ir: &Ir) -> Vec { let mut arms = Vec::new(); for (sid, chain) in active_durings_per_leaf(ir) { let (binds, stmts) = emit_during_bindings(&chain); let select_arms = binds.iter().map(|b| { quote! { ev = #b => __HsmcRace::Event(ev), } }); arms.push(quote! { Some(#sid) => { #stmts let sleep = ::tokio::time::sleep(sleep_dur); ::tokio::pin!(sleep); ::tokio::select! { biased; #(#select_arms)* _ = &mut sleep => __HsmcRace::Timer, } } }); } arms } /// Build a human-readable ASCII tree of the machine's hierarchy, suitable /// for emitting as a `STATE_CHART` const. Each state shows its default-child /// marker, entry/exit actions (if any), during activities, and handler /// triggers. Used for `defmt::info!(M::STATE_CHART)` or panic messages. fn emit_state_chart_string(ir: &Ir) -> String { let mut out = String::new(); // Start with the machine name as the root label (root state name is // synthetic `__Root` — don't surface it). out.push_str(&ir.name.to_string()); out.push('\n'); // Top-level children of __Root: walk via __Root's children list. let root_children: Vec = ir.states[0].children.clone(); let default_child_of_root = ir.states[0].default_child; for (i, cid) in root_children.iter().enumerate() { let last = i + 1 == root_children.len(); let is_default = Some(*cid) == default_child_of_root; render_state(ir, *cid, "", last, is_default, &mut out); } out } fn render_state( ir: &Ir, sid: u16, prefix: &str, is_last: bool, is_default: bool, out: &mut String, ) { let s = &ir.states[sid as usize]; let branch = if is_last { "└── " } else { "├── " }; out.push_str(prefix); out.push_str(branch); if is_default { out.push_str("[default] "); } out.push_str(&s.name.to_string()); out.push('\n'); let child_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " }); // Entry actions for (_i, aid) in s.entries.iter().enumerate() { let name = &ir.actions[*aid as usize].name; out.push_str(&child_prefix); out.push_str("• entry: "); out.push_str(&name.to_string()); out.push('\n'); } // Exit actions for aid in &s.exits { let name = &ir.actions[*aid as usize].name; out.push_str(&child_prefix); out.push_str("• exit: "); out.push_str(&name.to_string()); out.push('\n'); } // Durings for d in &s.durings { out.push_str(&child_prefix); out.push_str("• during: "); out.push_str(&d.fn_name.to_string()); out.push('('); for (i, f) in d.fields.iter().enumerate() { if i > 0 { out.push_str(", "); } out.push_str(&f.to_string()); } out.push_str(")\n"); } // Handlers (summary) for h in &s.handlers { let trig = match &h.trigger { crate::ir::TriggerIr::Event(_, name, _) => format!("on({})", name), crate::ir::TriggerIr::Duration(tid) => { let d = &ir.duration_triggers[*tid as usize]; let verb = if d.repeat { "every" } else { "after" }; format!("on({} {})", verb, d.key.split(':').next().unwrap_or("?")) } }; let target = match &h.kind { crate::ir::HandlerKindIr::Transition(tgt, _) => format!("→ {}", tgt), crate::ir::HandlerKindIr::Action(_, name) => format!("⇒ {}", name), }; out.push_str(&child_prefix); out.push_str("• "); out.push_str(&trig); out.push(' '); out.push_str(&target); out.push('\n'); } // Child states let default_child_of_s = s.default_child; for (i, cid) in s.children.iter().enumerate() { let last = i + 1 == s.children.len(); let is_def = Some(*cid) == default_child_of_s; render_state(ir, *cid, &child_prefix, last, is_def, out); } } /// Return (select_fn_ident, either_variant_idents) for embassy_futures::select /// with the given total arity. `arity` must be one of 2, 3, 4, 5, 6. fn embassy_select_for_arity(arity: usize) -> (Ident, Vec) { match arity { 2 => ( format_ident!("select"), vec![format_ident!("First"), format_ident!("Second")], ), 3 => ( format_ident!("select3"), vec![ format_ident!("First"), format_ident!("Second"), format_ident!("Third"), ], ), 4 => ( format_ident!("select4"), vec![ format_ident!("First"), format_ident!("Second"), format_ident!("Third"), format_ident!("Fourth"), ], ), 5 => ( format_ident!("select5"), vec![ format_ident!("First"), format_ident!("Second"), format_ident!("Third"), format_ident!("Fourth"), format_ident!("Fifth"), ], ), 6 => ( format_ident!("select6"), vec![ format_ident!("First"), format_ident!("Second"), format_ident!("Third"), format_ident!("Fourth"), format_ident!("Fifth"), format_ident!("Sixth"), ], ), _ => unreachable!("embassy_select_for_arity called with unsupported arity"), } } /// Emit embassy run-loop match arms for machines with a bound channel. Arity /// per state = active_durings + 2 (channel + timer). Embassy caps at `select6` /// → max 4 concurrent durings on a path with events. Over-limit paths emit /// a `compile_error!` inside the embassy-only arm so tokio codegen still /// compiles unchanged. fn emit_embassy_race_arms_with_channel(ir: &Ir) -> Vec { let mut arms = Vec::new(); for (sid, chain) in active_durings_per_leaf(ir) { let n = chain.len(); if n + 2 > 6 { let msg = format!( "too many concurrent `during:` activities on the active path for state id {} \ under the embassy feature ({} + channel + timer > 6); embassy_futures supports \ at most 4 concurrent durings on a path with events. Combine activities into a \ single during that uses `select` internally.", sid, n ); arms.push(quote! { Some(#sid) => { ::core::compile_error!(#msg); unreachable!() } }); continue; } let (binds, stmts) = emit_during_bindings(&chain); let (sel_fn, variants) = embassy_select_for_arity(n + 2); let ch_variant = &variants[n]; let tm_variant = &variants[n + 1]; let call_args = binds.iter() .map(|b| quote! { #b }) .chain(std::iter::once(quote! { rx.receive() })) .chain(std::iter::once(quote! { timer_fut })); let either_path = if n + 2 == 2 { quote! { ::embassy_futures::select::Either } } else { let either_ty = format_ident!("Either{}", (n + 2).to_string()); quote! { ::embassy_futures::select::#either_ty } }; let during_match = binds.iter().enumerate().map(|(i, _b)| { let v = &variants[i]; quote! { #either_path::#v(ev) => __HsmcRace::Event(ev), } }); arms.push(quote! { Some(#sid) => { #stmts let timer_fut = async { match next { Some(d) => { ::embassy_time::Timer::after( ::embassy_time::Duration::from_micros(d.as_micros() as u64) ).await; } None => ::core::future::pending::<()>().await, } }; let rx = self.__embassy_rx.as_ref().expect("hsmc: embassy channel"); match ::embassy_futures::select::#sel_fn(#(#call_args),*).await { #(#during_match)* #either_path::#ch_variant(ev) => __HsmcRace::Event(ev), #either_path::#tm_variant(()) => __HsmcRace::Timer, } } }); } arms } /// Emit embassy run-loop match arms for timer-only machines (no channel). /// Arity per state = active_durings + 1 (timer only). Max 5 concurrent /// durings on a timer-only path. fn emit_embassy_race_arms_timer_only(ir: &Ir) -> Vec { let mut arms = Vec::new(); for (sid, chain) in active_durings_per_leaf(ir) { let n = chain.len(); if n == 0 { continue; // falls through to default (just timer) } if n + 1 > 6 { let msg = format!( "too many concurrent `during:` activities on the active path for state id {} \ under the embassy feature ({} + timer > 6); embassy_futures supports at most 5 \ concurrent durings on a timer-only path.", sid, n ); arms.push(quote! { Some(#sid) => { ::core::compile_error!(#msg); unreachable!() } }); continue; } let (binds, stmts) = emit_during_bindings(&chain); let (sel_fn, variants) = embassy_select_for_arity(n + 1); let call_args = binds.iter() .map(|b| quote! { #b }) .chain(std::iter::once(quote! { timer_fut })); let either_path = if n + 1 == 2 { quote! { ::embassy_futures::select::Either } } else { let either_ty = format_ident!("Either{}", (n + 1).to_string()); quote! { ::embassy_futures::select::#either_ty } }; let tm_variant = &variants[n]; let during_match = binds.iter().enumerate().map(|(i, _b)| { let v = &variants[i]; quote! { #either_path::#v(ev) => __HsmcRace::Event(ev), } }); arms.push(quote! { Some(#sid) => { #stmts let timer_fut = async { match next { Some(d) => { ::embassy_time::Timer::after( ::embassy_time::Duration::from_micros(d.as_micros() as u64) ).await; } None => ::core::future::pending::<()>().await, } }; match ::embassy_futures::select::#sel_fn(#(#call_args),*).await { #(#during_match)* #either_path::#tm_variant(()) => __HsmcRace::Timer, } } }); } arms } pub fn generate(ir: &Ir) -> TokenStream { let machine_name = &ir.name; let state_enum_name = format_ident!("{}State", machine_name); let actions_trait_name = format_ident!("{}Actions", machine_name); let action_ctx_name = format_ident!("{}ActionContext", machine_name); let ctx_alias_name = format_ident!("{}Ctx", machine_name); let sender_name = format_ident!("{}Sender", machine_name); let ctx_ty = &ir.context_ty; let ev_ty = &ir.event_ty; let n_states = ir.states.len(); let n_actions = ir.actions.len(); let n_dur_triggers = ir.duration_triggers.len(); let max_timers = n_dur_triggers.max(1); // Public state enum — only user-declared states (skip __Root). let state_variants: Vec<_> = ir .states .iter() .filter(|s| s.parent.is_some()) .map(|s| s.name.clone()) .collect(); let entries_of_arms: Vec<_> = ir.states.iter().map(|s| { let sid = s.id; let entries_tok: Vec<_> = s.entries.iter().map(|a| quote! { #a }).collect(); quote! { #sid => &[#(#entries_tok),*], } }).collect(); let exits_of_arms: Vec<_> = ir.states.iter().map(|s| { let sid = s.id; let exits_tok: Vec<_> = s.exits.iter().map(|a| quote! { #a }).collect(); quote! { #sid => &[#(#exits_tok),*], } }).collect(); let parent_table = ir.states.iter().map(|s| match s.parent { Some(p) => quote! { Some(#p) }, None => quote! { None }, }).collect::>(); let depth_table = ir.states.iter().map(|s| { let d = s.depth; quote! { #d } }).collect::>(); let default_child_table = ir.states.iter().map(|s| match s.default_child { Some(c) => quote! { Some(#c) }, None => quote! { None }, }).collect::>(); let state_id_to_variant_arms = ir.states.iter().filter(|s| s.parent.is_some()).map(|s| { let id = s.id; let nm = &s.name; quote! { #id => #state_enum_name::#nm, } }).collect::>(); // Trait methods, possibly with typed params for payload-bearing actions. let action_methods_sync = ir.actions.iter().map(|a| { let name = &a.name; let params = a.params.iter().map(|f| { let n = &f.name; let t = &f.ty; quote! { #n: #t } }); quote! { fn #name(&mut self, #(#params),*); } }).collect::>(); let action_methods_async = ir.actions.iter().map(|a| { let name = &a.name; let params = a.params.iter().map(|f| { let n = &f.name; let t = &f.ty; quote! { #n: #t } }); quote! { fn #name(&mut self, #(#params),*) -> impl ::core::future::Future; } }).collect::>(); // Dispatch arms: for unit-signature actions call straight through; for // payload-bearing actions, re-match on the passed event and destructure. // Payload dispatch binds each field by reference (scrutinee is `&Ev`) // then `.clone()`s it to produce an owned value matching the handler's // declared signature. For `Copy` types this optimizes away. let run_action_arms_sync = ir.actions.iter().enumerate().map(|(i, a)| { let i = i as u16; let name = &a.name; if a.params.is_empty() { quote! { #i => ::#name(ctx), } } else { let field_idents: Vec<_> = a.params.iter().map(|f| f.name.clone()).collect(); let clone_args = field_idents.iter().map(|n| quote! { #n.clone() }); let variant_arms = a.bound_variants.iter().map(|bv| { let vname = &bv.name; let binds = &field_idents; let call = { let clones = clone_args.clone(); quote! { ::#name(ctx, #(#clones),*); } }; match bv.kind { PayloadKind::Tuple => quote! { #ev_ty::#vname(#(#binds),*) => { #call } }, PayloadKind::Struct => quote! { #ev_ty::#vname { #(#binds),* } => { #call } }, } }); quote! { #i => { if let Some(__ev) = __event { match __ev { #(#variant_arms)* _ => {} } } } } } }).collect::>(); let run_action_arms_async = ir.actions.iter().enumerate().map(|(i, a)| { let i = i as u16; let name = &a.name; if a.params.is_empty() { quote! { #i => ::#name(ctx).await, } } else { let field_idents: Vec<_> = a.params.iter().map(|f| f.name.clone()).collect(); let clone_args = field_idents.iter().map(|n| quote! { #n.clone() }); let variant_arms = a.bound_variants.iter().map(|bv| { let vname = &bv.name; let binds = &field_idents; let call = { let clones = clone_args.clone(); quote! { ::#name(ctx, #(#clones),*).await; } }; match bv.kind { PayloadKind::Tuple => quote! { #ev_ty::#vname(#(#binds),*) => { #call } }, PayloadKind::Struct => quote! { #ev_ty::#vname { #(#binds),* } => { #call } }, } }); quote! { #i => { if let Some(__ev) = __event { match __ev { #(#variant_arms)* _ => {} } } } } } }).collect::>(); let duration_expr_arms = ir.duration_triggers.iter().enumerate().map(|(i, d)| { let i = i as u16; let expr = &d.expr; quote! { #i => { let __d: ::hsmc::Duration = #expr; __d }, } }).collect::>(); let duration_repeat_arms = ir.duration_triggers.iter().enumerate().map(|(i, d)| { let i = i as u16; let repeat = d.repeat; quote! { #i => #repeat, } }).collect::>(); let event_variant_arms = ir.event_variants.iter().enumerate().map(|(i, v)| { let i = i as u16; let name = &v.name; let pat = match v.kind { VariantKind::Unit => quote! { #ev_ty::#name }, VariantKind::Tuple => quote! { #ev_ty::#name(..) }, VariantKind::Struct => quote! { #ev_ty::#name { .. } }, }; quote! { #pat => #i, } }).collect::>(); let state_handler_arms = ir.states.iter().map(|s| { use std::collections::BTreeMap; #[derive(Default)] struct Group { action_ids: Vec, transition_target: Option } let mut groups: BTreeMap<(u8, u16), Group> = BTreeMap::new(); let mut handlers = s.handlers.iter().collect::>(); handlers.sort_by_key(|h| h.decl_index); for h in handlers { let key = match &h.trigger { TriggerIr::Event(id, _, _) => (0u8, *id), TriggerIr::Duration(id) => (1u8, *id), }; let g = groups.entry(key).or_default(); match &h.kind { HandlerKindIr::Action(aid, _) => g.action_ids.push(*aid), HandlerKindIr::Transition(_, Some(tid)) => g.transition_target = Some(*tid), HandlerKindIr::Transition(_, None) => {} } } let sid = s.id; let inner_arms = groups.iter().map(|((kind, tid), g)| { let kind = *kind; let tid = *tid; let actions = g.action_ids.iter().map(|a| quote! { #a }); let transition_tok = match g.transition_target { Some(t) => quote! { Some(#t) }, None => quote! { None }, }; quote! { (#kind, #tid) => { let actions: &'static [u16] = &[#(#actions),*]; let target: Option = #transition_tok; return Some((actions, target)); } } }).collect::>(); quote! { #sid => { match (trigger_kind, trigger_id) { #(#inner_arms)* _ => {} } } } }).collect::>(); let owned_timers_arms = ir.states.iter().map(|s| { let sid = s.id; let ids = s.owned_timers.iter().map(|t| quote! { #t }); quote! { #sid => &[#(#ids),*], } }).collect::>(); let terminate_match = if let Some(tev) = &ir.terminate_event { // Use the kind recorded for this variant (unit/tuple/struct). let kind = ir .event_variants .iter() .find(|v| v.name == *tev) .map(|v| v.kind) .unwrap_or(VariantKind::Unit); let pat = match kind { VariantKind::Unit => quote! { #ev_ty::#tev }, VariantKind::Tuple => quote! { #ev_ty::#tev(..) }, VariantKind::Struct => quote! { #ev_ty::#tev { .. } }, }; quote! { if matches!(__ev, #pat) { return TerminateCheck::Yes; } } } else { quote! {} }; let _ = n_actions; // Doc strings as literals for quoting. let state_enum_doc = format!( "Enum of every state declared in the `{}` statechart. Each variant names a user-declared state; the root is implicit and never surfaced.", machine_name ); let actions_trait_doc = format!( "Trait implemented by the user to provide action function bodies for the `{}` statechart. One method per unique action name.", machine_name ); let action_ctx_doc = format!( "Wrapper passed to action methods of the `{}` statechart. `Deref`s to the user's context and exposes [`emit()`](#method.emit) for re-entrant event emission (§4.3).", machine_name ); let ctx_alias_doc = format!( "Convenience type alias for [`{}ActionContext`] (spec §12.4).", machine_name ); let machine_doc = format!( "The `{}` state machine. Drive it with [`step()`](Self::step) or, under an async feature, `run().await`. See the crate docs for the generated API (§4.4).", machine_name ); let sender_doc = format!( "Handle for sending events into a running `{}` from other threads, tasks, or ISRs (§4.5).", machine_name ); // Optional-events branch: decide whether to emit Sender / sender() / channel // plumbing at all. When events: was omitted the machine is timer-only and // has no Sender type. let emit_sender = !ir.events_omitted; // Synthesize the empty event type when events: was omitted. let no_events_enum = if ir.events_omitted { quote! { #[doc(hidden)] #[allow(dead_code)] pub enum __NoEvents {} } } else { quote! {} }; // Race-loop match arms for each run() variant. When the active state has // `during:` activities on its path, the arm constructs those futures by // split-borrowing context fields and races them against the channel and // next-timer sleep via `tokio::select!` / `embassy_futures::select`. let tokio_race_arms_with_ch = emit_tokio_race_arms_with_channel(ir); let tokio_race_arms_timer_only = emit_tokio_race_arms_timer_only(ir); let embassy_race_arms_with_ch = emit_embassy_race_arms_with_channel(ir); let embassy_race_arms_timer_only = emit_embassy_race_arms_timer_only(ir); // Whether any state in the machine declares at least one `during:`. When // false, run() uses the original v0.1 pre-during code path verbatim, // which keeps backward compatibility bit-for-bit for machines that // never opted into the new feature. let has_any_durings = ir.states.iter().any(|s| !s.durings.is_empty()); // The tokio with-channel run() body. v0.1 verbatim when no durings are // declared; otherwise a per-state match dispatching to `select!` with // the appropriate active during set. let tokio_run_with_ch_body = if has_any_durings { quote! { #[allow(dead_code)] enum __HsmcRace { Event(E), ChannelClosed, Timer } if self.terminated { return Err(::hsmc::HsmcError::AlreadyTerminated); } let mut next = self.step(::hsmc::Duration::ZERO).await; if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } let mut last_instant = ::tokio::time::Instant::now(); while !self.terminated { if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } if !self.queue.is_empty() { let now = ::tokio::time::Instant::now(); let elapsed = now.duration_since(last_instant); last_instant = now; next = self.step(elapsed).await; continue; } let sleep_dur = next.unwrap_or(::hsmc::Duration::from_secs(3600)); let race: __HsmcRace<#ev_ty> = match self.current { #(#tokio_race_arms_with_ch)* _ => { let sleep = ::tokio::time::sleep(sleep_dur); ::tokio::pin!(sleep); let rx = self.__tokio_rx.as_mut().expect("hsmc: tokio channel"); ::tokio::select! { biased; maybe = rx.recv() => match maybe { Some(ev) => __HsmcRace::Event(ev), None => __HsmcRace::ChannelClosed, }, _ = &mut sleep => __HsmcRace::Timer, } } }; match race { __HsmcRace::Event(ev) => { let _ = self.queue.push_back(ev); } __HsmcRace::ChannelClosed => { if next.is_none() { break; } } __HsmcRace::Timer => {} } let now = ::tokio::time::Instant::now(); let elapsed = now.duration_since(last_instant); last_instant = now; next = self.step(elapsed).await; } Ok(()) } } else { quote! { if self.terminated { return Err(::hsmc::HsmcError::AlreadyTerminated); } let mut next = self.step(::hsmc::Duration::ZERO).await; if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } let mut last_instant = ::tokio::time::Instant::now(); while !self.terminated { if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } if !self.queue.is_empty() { let now = ::tokio::time::Instant::now(); let elapsed = now.duration_since(last_instant); last_instant = now; next = self.step(elapsed).await; continue; } let sleep_dur = next.unwrap_or(::hsmc::Duration::from_secs(3600)); let sleep = ::tokio::time::sleep(sleep_dur); ::tokio::pin!(sleep); let rx = self.__tokio_rx.as_mut().expect("tokio channel"); ::tokio::select! { maybe_ev = rx.recv() => { match maybe_ev { Some(ev) => { let _ = self.queue.push_back(ev); } None => { if next.is_none() { break; } } } let now = ::tokio::time::Instant::now(); let elapsed = now.duration_since(last_instant); last_instant = now; next = self.step(elapsed).await; } _ = &mut sleep => { let now = ::tokio::time::Instant::now(); let elapsed = now.duration_since(last_instant); last_instant = now; next = self.step(elapsed).await; } } } Ok(()) } }; // The embassy with-channel run() body. v0.1 verbatim when no durings // are declared; otherwise a per-state match dispatching to // embassy_futures::select_N with the appropriate active during set. let embassy_run_with_ch_body = if has_any_durings { quote! { #[allow(dead_code)] enum __HsmcRace { Event(E), ChannelClosed, Timer } if self.terminated { return Err(::hsmc::HsmcError::AlreadyTerminated); } let mut next = self.step(::hsmc::Duration::ZERO).await; if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } let mut last_instant = ::embassy_time::Instant::now(); while !self.terminated { if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } if !self.queue.is_empty() { let now = ::embassy_time::Instant::now(); let elapsed_us = now.duration_since(last_instant).as_micros(); last_instant = now; next = self.step(::hsmc::Duration::from_micros(elapsed_us)).await; continue; } let race: __HsmcRace<#ev_ty> = match self.current { #(#embassy_race_arms_with_ch)* _ => { let timer_fut = async { match next { Some(d) => { ::embassy_time::Timer::after( ::embassy_time::Duration::from_micros(d.as_micros() as u64) ).await; } None => ::core::future::pending::<()>().await, } }; let rx = self.__embassy_rx.as_ref().expect("hsmc: embassy channel"); match ::embassy_futures::select::select(rx.receive(), timer_fut).await { ::embassy_futures::select::Either::First(ev) => __HsmcRace::Event(ev), ::embassy_futures::select::Either::Second(()) => __HsmcRace::Timer, } } }; match race { __HsmcRace::Event(ev) => { let _ = self.queue.push_back(ev); } __HsmcRace::ChannelClosed => {} __HsmcRace::Timer => {} } let now = ::embassy_time::Instant::now(); let elapsed_us = now.duration_since(last_instant).as_micros(); last_instant = now; next = self.step(::hsmc::Duration::from_micros(elapsed_us)).await; } Ok(()) } } else { quote! { if self.terminated { return Err(::hsmc::HsmcError::AlreadyTerminated); } let rx = self.__embassy_rx .expect("hsmc: embassy channel (populated by Machine::new)"); let mut next = self.step(::hsmc::Duration::ZERO).await; if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } let mut last_instant = ::embassy_time::Instant::now(); while !self.terminated { if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } if !self.queue.is_empty() { let now = ::embassy_time::Instant::now(); let elapsed_us = now.duration_since(last_instant).as_micros(); last_instant = now; next = self.step(::hsmc::Duration::from_micros(elapsed_us)).await; continue; } let timer_fut = async { match next { Some(d) => { ::embassy_time::Timer::after( ::embassy_time::Duration::from_micros(d.as_micros() as u64) ).await; } None => ::core::future::pending::<()>().await, } }; match ::embassy_futures::select::select(rx.receive(), timer_fut).await { ::embassy_futures::select::Either::First(ev) => { let _ = self.queue.push_back(ev); } ::embassy_futures::select::Either::Second(()) => {} } let now = ::embassy_time::Instant::now(); let elapsed_us = now.duration_since(last_instant).as_micros(); last_instant = now; next = self.step(::hsmc::Duration::from_micros(elapsed_us)).await; } Ok(()) } }; // State ids whose active `during:` chain is non-empty. Used by timer-only // run() bodies to decide whether to exit when no timers are pending: if // the current state has an active during, the run loop should keep // awaiting rather than break (the during might yet produce an event). let states_with_active_durings: Vec = active_durings_per_leaf(ir) .into_iter() .map(|(sid, _)| sid) .collect(); let has_active_during_arms: Vec = states_with_active_durings .iter() .map(|sid| quote! { Some(#sid) => true, }) .collect(); // Human-readable ASCII diagram of the machine's hierarchy. Emitted as a // `pub const STATE_CHART: &str` on the machine struct. Useful for // `defmt::info!`, panic messages, and documentation. let state_chart_str = emit_state_chart_string(ir); // Sender / sender() under tokio. With eager channel creation in // `with_queue_capacity_internal()`, `sender()` is `&self`. let tokio_sender_impl = if emit_sender { quote! { #[cfg(feature = "tokio")] #[doc = #sender_doc] #[derive(Clone)] pub struct #sender_name { tx: ::tokio::sync::mpsc::UnboundedSender<#ev_ty>, } #[cfg(feature = "tokio")] impl #sender_name { /// Send an event to the machine. Returns `Err(AlreadyTerminated)` if /// the machine has terminated and the receiver has been dropped. pub fn send(&self, event: #ev_ty) -> ::core::result::Result<(), ::hsmc::HsmcError> { self.tx.send(event).map_err(|_| ::hsmc::HsmcError::AlreadyTerminated) } } #[cfg(feature = "tokio")] impl #machine_name<__QN> { /// Obtain a clonable, `Send` handle for pushing events into this /// machine. The channel is eagerly created in `new()` (§4.4). pub fn sender(&self) -> #sender_name { #sender_name { tx: self.__tokio_tx.as_ref().expect("hsmc: tokio channel").clone() } } /// Run the machine to completion on the current Tokio runtime. pub async fn run(&mut self) -> ::core::result::Result<(), ::hsmc::HsmcError> { #tokio_run_with_ch_body } } } } else { // Timer-only: no Sender, no tokio channel, but keep a timer-only run(). quote! { #[cfg(feature = "tokio")] impl #machine_name<__QN> { /// Run the machine to completion, honouring timers and /// `during:` activities. No event channel is generated /// because this statechart declares no `events:` (§12.2). pub async fn run(&mut self) -> ::core::result::Result<(), ::hsmc::HsmcError> { #[allow(dead_code)] enum __HsmcRace { Event(E), ChannelClosed, Timer } if self.terminated { return Err(::hsmc::HsmcError::AlreadyTerminated); } let mut next = self.step(::hsmc::Duration::ZERO).await; if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } let mut last_instant = ::tokio::time::Instant::now(); while !self.terminated { if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } if !self.queue.is_empty() { let now = ::tokio::time::Instant::now(); let elapsed = now.duration_since(last_instant); last_instant = now; next = self.step(elapsed).await; continue; } let sleep_dur = match next { Some(d) => d, None => { // No timers pending. If the current state // has no `during:` activities either, there // is nothing left to wait on — exit. let has_any_during: bool = match self.current { #(#has_active_during_arms)* _ => false, }; if !has_any_during { break; } ::hsmc::Duration::from_secs(3600) } }; let race: __HsmcRace<#ev_ty> = match self.current { #(#tokio_race_arms_timer_only)* _ => { ::tokio::time::sleep(sleep_dur).await; __HsmcRace::Timer } }; match race { __HsmcRace::Event(ev) => { let _ = self.queue.push_back(ev); } __HsmcRace::ChannelClosed => {} __HsmcRace::Timer => {} } let now = ::tokio::time::Instant::now(); let elapsed = now.duration_since(last_instant); last_instant = now; next = self.step(elapsed).await; } Ok(()) } } } }; // Default (no feature): stub sender that documents the limitation. We // intentionally keep it non-functional — a proper `Send + Clone` handle // without `alloc` or a platform primitive is out of scope for v0.1. See // the `tokio` or `embassy` features for a working `sender()`. let default_sender_impl = if emit_sender { quote! { #[cfg(not(any(feature = "tokio", feature = "embassy")))] #[doc = #sender_doc] /// /// **Limitation:** without the `tokio` or `embassy` feature, the /// default sender is a non-functional stub and /// [`send()`](Self::send) always returns /// [`HsmcError::AlreadyTerminated`]. Use the `tokio` or `embassy` /// feature to get a working cross-thread sender. #[derive(Clone)] pub struct #sender_name { _priv: () } #[cfg(not(any(feature = "tokio", feature = "embassy")))] impl #sender_name { /// Always returns [`HsmcError::AlreadyTerminated`] under /// default features — enable `tokio` or `embassy` for a /// functional implementation. pub fn send(&self, _event: #ev_ty) -> ::core::result::Result<(), ::hsmc::HsmcError> { Err(::hsmc::HsmcError::AlreadyTerminated) } } #[cfg(not(any(feature = "tokio", feature = "embassy")))] impl #machine_name<__QN> { /// Non-functional stub under default features — see /// [`#sender_name`] for the limitation. pub fn sender(&self) -> #sender_name { #sender_name { _priv: () } } } } } else { quote! {} }; // Embassy: first-class support. `Machine::new(ctx, &'static CHAN)` stores // type-erased send/receive endpoints so the Machine struct stays free of // a `M: RawMutex` generic. `sender()` hands out a `Copy + Send + Sync` // newtype usable from ISRs (`try_send`) and tasks (`send().await`). let send_assert_name = format_ident!("__hsmc_embassy_ev_send_assert_{}", machine_name); let embassy_sender_impl = if emit_sender { quote! { // Compile-time assertion: the event type must be `Send` under the // embassy feature (its sender handle is `Send + Sync`, which // requires `E: Send`). Fails with a pointer to `#ev_ty`. #[cfg(feature = "embassy")] #[allow(dead_code, non_upper_case_globals)] const #send_assert_name: fn() = || { fn __assert_send() {} __assert_send::<#ev_ty>(); }; #[cfg(feature = "embassy")] #[doc = #sender_doc] /// /// `Copy + Send + Sync`-able handle around a /// [`embassy_sync::channel::SendDynamicSender`]. Safe to capture in /// a `static` for ISR use and to clone across Embassy tasks. #[derive(Clone, Copy)] pub struct #sender_name { inner: ::embassy_sync::channel::SendDynamicSender<'static, #ev_ty>, } #[cfg(feature = "embassy")] impl #sender_name { /// Non-blocking send. Returns /// [`HsmcError::QueueFull`] if the channel has no room. Safe /// from ISR context when the channel is backed by /// `CriticalSectionRawMutex`. #[track_caller] pub fn try_send(self, event: #ev_ty) -> ::core::result::Result<(), ::hsmc::HsmcError> { self.inner.try_send(event).map_err(|_| ::hsmc::HsmcError::QueueFull) } /// Async send. Backpressures (awaits) when the channel is /// full. Use from Embassy tasks that must not drop events. pub async fn send(self, event: #ev_ty) -> ::core::result::Result<(), ::hsmc::HsmcError> { self.inner.send(event).await; Ok(()) } /// Build a sender directly from a user-declared static /// channel — convenient for ISR shims that run before the /// machine task has spawned. The channel's event type and /// capacity must match the machine. pub fn from_channel<__M, const __N: usize>( channel: &'static ::embassy_sync::channel::Channel<__M, #ev_ty, __N>, ) -> Self where __M: ::embassy_sync::blocking_mutex::raw::RawMutex + ::core::marker::Sync + ::core::marker::Send + 'static, #ev_ty: ::core::marker::Send, { Self { inner: channel.sender().into() } } } #[cfg(feature = "embassy")] impl #machine_name<8> { /// Construct a new machine with the default event-queue /// capacity of 8, bound to a user-declared static /// `embassy_sync::channel::Channel`. The channel's capacity /// must match the machine's `__QN` — here, 8. pub fn new<__M>( ctx: #ctx_ty, channel: &'static ::embassy_sync::channel::Channel<__M, #ev_ty, 8>, ) -> Self where __M: ::embassy_sync::blocking_mutex::raw::RawMutex + ::core::marker::Sync + ::core::marker::Send + 'static, #ev_ty: ::core::marker::Send, { let mut __self = <#machine_name<8>>::with_queue_capacity_internal(ctx); __self.__embassy_rx = Some(channel.receiver().into()); __self.__embassy_tx = Some(channel.sender().into()); __self } /// Construct a machine with a custom event-queue capacity /// `__N`. The channel's capacity must also be `__N`. pub fn with_queue_capacity<__M, const __N: usize>( ctx: #ctx_ty, channel: &'static ::embassy_sync::channel::Channel<__M, #ev_ty, __N>, ) -> #machine_name<__N> where __M: ::embassy_sync::blocking_mutex::raw::RawMutex + ::core::marker::Sync + ::core::marker::Send + 'static, #ev_ty: ::core::marker::Send, { let mut __self = <#machine_name<__N>>::with_queue_capacity_internal(ctx); __self.__embassy_rx = Some(channel.receiver().into()); __self.__embassy_tx = Some(channel.sender().into()); __self } /// Construct a machine that is driven only via `send()` + /// `step().await`. No external `Channel` is wired up, so /// [`sender()`](Self::sender) and [`run()`](Self::run) /// panic if called. Use this when the single task that /// owns the machine is also its only event producer — /// the internal queue is sufficient and a /// `&'static Channel` would be dead weight. pub fn new_local(ctx: #ctx_ty) -> Self { <#machine_name<8>>::with_queue_capacity_internal(ctx) } } #[cfg(feature = "embassy")] impl #machine_name<__QN> { /// Channel-less variant of /// [`with_queue_capacity`](Self::with_queue_capacity) /// — see [`new_local`](Self::new_local). pub fn with_queue_capacity_local(ctx: #ctx_ty) -> #machine_name<__QN> { <#machine_name<__QN>>::with_queue_capacity_internal(ctx) } } #[cfg(feature = "embassy")] impl #machine_name<__QN> { /// Obtain a clonable, `Send` handle for pushing events into /// this machine from ISRs or other Embassy tasks. The channel /// was provided at construction. pub fn sender(&self) -> #sender_name { #sender_name { inner: self.__embassy_tx .expect("hsmc: embassy channel (populated by Machine::new)"), } } /// Run the machine to completion, awaiting events from the /// bound channel, firing timers via `embassy_time`, and /// polling any active `during:` activities for the current /// state path. Returns `Ok(())` on clean termination; /// `Err(HsmcError::QueueFull)` if an action's `emit()` /// overflowed the internal queue. pub async fn run(&mut self) -> ::core::result::Result<(), ::hsmc::HsmcError> { #embassy_run_with_ch_body } } } } else { // Timer-only machines (no `events:` declared) have no channel. Keep // the existing timer-driven run(). quote! { #[cfg(feature = "embassy")] impl #machine_name<8> { /// Construct a timer-only machine (no `events:` declared). pub fn new(ctx: #ctx_ty) -> Self { <#machine_name<8>>::with_queue_capacity_internal(ctx) } /// Construct a timer-only machine with custom queue capacity. pub fn with_queue_capacity(ctx: #ctx_ty) -> #machine_name<__N> { <#machine_name<__N>>::with_queue_capacity_internal(ctx) } } #[cfg(feature = "embassy")] impl #machine_name<__QN> { /// Timer-only run loop (no `events:` declared) using /// `embassy_time::Timer`, plus any `during:` activities /// declared on the active state path. pub async fn run(&mut self) -> ::core::result::Result<(), ::hsmc::HsmcError> { #[allow(dead_code)] enum __HsmcRace { Event(E), ChannelClosed, Timer } if self.terminated { return Err(::hsmc::HsmcError::AlreadyTerminated); } let mut next = self.step(::hsmc::Duration::ZERO).await; if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } let mut last_instant = ::embassy_time::Instant::now(); while !self.terminated { if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } if !self.queue.is_empty() { let now = ::embassy_time::Instant::now(); let elapsed_us = now.duration_since(last_instant).as_micros(); last_instant = now; next = self.step(::hsmc::Duration::from_micros(elapsed_us)).await; continue; } // No queued events. Decide whether to wait. let has_any_during: bool = match self.current { #(#has_active_during_arms)* _ => false, }; if next.is_none() && !has_any_during { break; } let race: __HsmcRace<#ev_ty> = match self.current { #(#embassy_race_arms_timer_only)* _ => { // No durings active; just await the timer. match next { Some(d) => { ::embassy_time::Timer::after( ::embassy_time::Duration::from_micros(d.as_micros() as u64) ).await; } None => ::core::future::pending::<()>().await, } __HsmcRace::Timer } }; match race { __HsmcRace::Event(ev) => { let _ = self.queue.push_back(ev); } __HsmcRace::ChannelClosed => {} __HsmcRace::Timer => {} } let now = ::embassy_time::Instant::now(); let elapsed_us = now.duration_since(last_instant).as_micros(); last_instant = now; next = self.step(::hsmc::Duration::from_micros(elapsed_us)).await; } Ok(()) } } } }; // Embassy stored receiver/sender fields. Populated at construction time // when the user provides an `&'static embassy_sync::channel::Channel`. let embassy_fields = if emit_sender { quote! { #[cfg(feature = "embassy")] __embassy_rx: Option<::embassy_sync::channel::SendDynamicReceiver<'static, #ev_ty>>, #[cfg(feature = "embassy")] __embassy_tx: Option<::embassy_sync::channel::SendDynamicSender<'static, #ev_ty>>, } } else { quote! {} }; let embassy_ctor = if emit_sender { quote! { #[cfg(feature = "embassy")] __embassy_rx: None, #[cfg(feature = "embassy")] __embassy_tx: None, } } else { quote! {} }; // Eager tokio channel fields. Always emitted when `tokio` feature is // compiled in AND this machine has events. let tokio_fields = if emit_sender { quote! { #[cfg(feature = "tokio")] __tokio_tx: Option<::tokio::sync::mpsc::UnboundedSender<#ev_ty>>, #[cfg(feature = "tokio")] __tokio_rx: Option<::tokio::sync::mpsc::UnboundedReceiver<#ev_ty>>, } } else { quote! {} }; let tokio_ctor = if emit_sender { quote! { #[cfg(feature = "tokio")] __tokio_tx: { let (tx, _rx) = ::tokio::sync::mpsc::unbounded_channel(); Some(tx) }, #[cfg(feature = "tokio")] __tokio_rx: None, } } else { quote! {} }; // We need to create tx+rx together; do it via a small helper. let tokio_ctor_setup = if emit_sender { quote! { #[cfg(feature = "tokio")] { let (tx, rx) = ::tokio::sync::mpsc::unbounded_channel(); __self.__tokio_tx = Some(tx); __self.__tokio_rx = Some(rx); } } } else { quote! {} }; quote! { #no_events_enum #[doc = #state_enum_doc] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(non_camel_case_types, dead_code)] pub enum #state_enum_name { #(#state_variants),* } #[doc = #actions_trait_doc] #[cfg(not(any(feature = "tokio", feature = "embassy")))] #[allow(non_snake_case)] pub trait #actions_trait_name { #(#action_methods_sync)* } #[doc = #actions_trait_doc] /// /// Under the `tokio` or `embassy` feature, action methods are /// `async fn` so that entry/exit/event-action bodies can `.await` /// peripheral calls (I2C flush, radio ops, etc.). Returned futures /// must be `Send` for compatibility with multi-threaded / Send /// executors. #[cfg(any(feature = "tokio", feature = "embassy"))] #[allow(non_snake_case)] pub trait #actions_trait_name { #(#action_methods_async)* } #[doc = #action_ctx_doc] pub struct #action_ctx_name<'a> { ctx: &'a mut #ctx_ty, __queue: &'a mut (dyn ::hsmc::__private::QueuePush<#ev_ty> + ::core::marker::Send), __terminated: &'a bool, } #[doc = #ctx_alias_doc] pub type #ctx_alias_name<'a> = #action_ctx_name<'a>; impl<'a> ::core::ops::Deref for #action_ctx_name<'a> { type Target = #ctx_ty; fn deref(&self) -> &#ctx_ty { self.ctx } } impl<'a> ::core::ops::DerefMut for #action_ctx_name<'a> { fn deref_mut(&mut self) -> &mut #ctx_ty { self.ctx } } impl<'a> #action_ctx_name<'a> { /// Push an event into the internal queue. It will be processed /// after the current event's handling completes (§2.12). Returns /// [`HsmcError::QueueFull`] if the queue is full, or /// [`HsmcError::AlreadyTerminated`] if the machine is shutting /// down. pub fn emit(&mut self, event: #ev_ty) -> ::core::result::Result<(), ::hsmc::HsmcError> { if *self.__terminated { return Err(::hsmc::HsmcError::AlreadyTerminated); } self.__queue.push(event) } /// Convenience wrapper around [`emit()`](Self::emit) that panics on /// failure (§12.1). Use in contexts where a full queue is a bug /// rather than a recoverable condition. pub fn emit_or_panic(&mut self, event: #ev_ty) { self.emit(event).expect("hsmc: event queue full"); } } #[doc = #machine_doc] #[allow(non_snake_case, non_camel_case_types)] pub struct #machine_name { ctx: #ctx_ty, current: Option, queue: ::hsmc::__private::heapless::Deque<#ev_ty, __QN>, timers: ::hsmc::__private::TimerTable<#max_timers>, terminated: bool, __overflow: bool, #tokio_fields #embassy_fields } const __PARENT: [Option; #n_states] = [#(#parent_table),*]; const __DEPTH: [u8; #n_states] = [#(#depth_table),*]; const __DEFAULT_CHILD: [Option; #n_states] = [#(#default_child_table),*]; #[allow(dead_code, unreachable_patterns)] fn __state_id_to_public(id: u16) -> #state_enum_name { match id { #(#state_id_to_variant_arms)* _ => panic!("internal: state id {} has no public variant", id), } } #[allow(unreachable_code, unused_variables)] fn __duration_for(tid: u16) -> ::hsmc::Duration { match tid { #(#duration_expr_arms)* _ => ::hsmc::Duration::ZERO, } } #[allow(unreachable_code, unused_variables)] fn __duration_repeats(tid: u16) -> bool { match tid { #(#duration_repeat_arms)* _ => false, } } #[allow(unreachable_patterns, unused_variables)] fn __event_to_id(ev: &#ev_ty) -> u16 { match ev { #(#event_variant_arms)* _ => u16::MAX, } } #[allow(unused_variables)] fn __handler_lookup( state_id: u16, trigger_kind: u8, trigger_id: u16, ) -> Option<(&'static [u16], Option)> { match state_id { #(#state_handler_arms)* _ => {} } None } #[allow(unused_variables)] fn __owned_timers(state_id: u16) -> &'static [u16] { match state_id { #(#owned_timers_arms)* _ => &[], } } enum TerminateCheck { Yes, No } #[allow(unused_variables)] fn __check_terminate(__ev: &#ev_ty) -> TerminateCheck { #terminate_match TerminateCheck::No } impl #machine_name<8> { /// Construct a new machine with the default event-queue capacity of 8 (§4.4). #[cfg(not(feature = "embassy"))] pub fn new(ctx: #ctx_ty) -> Self { <#machine_name<8>>::with_queue_capacity_internal(ctx) } /// Construct a machine with a custom event-queue capacity `__N` (§5.2). #[cfg(not(feature = "embassy"))] pub fn with_queue_capacity(ctx: #ctx_ty) -> #machine_name<__N> { <#machine_name<__N>>::with_queue_capacity_internal(ctx) } } impl #machine_name<__QN> { /// ASCII diagram of the state hierarchy, default children, entry/ /// exit actions, `during:` activities, and handler triggers. /// Intended for `defmt::info!`, panic messages, and docs. /// /// Accessible as `Self::STATE_CHART` from any impl block on the /// machine; externally requires the turbofish (for example /// `Machine::<8>::STATE_CHART`) since the machine carries a const /// generic queue capacity. pub const STATE_CHART: &'static str = #state_chart_str; fn with_queue_capacity_internal(ctx: #ctx_ty) -> Self { #[allow(unused_mut)] let mut __self = Self { ctx, current: None, queue: ::hsmc::__private::heapless::Deque::new(), timers: ::hsmc::__private::TimerTable::new(), terminated: false, __overflow: false, #tokio_ctor #embassy_ctor }; #tokio_ctor_setup __self } /// Returns and clears the internal-emit overflow flag. Set by /// the generated action dispatch when `emit()` inside an action /// fails to enqueue — `run()` consults this to surface /// [`HsmcError::QueueFull`] out of the async loop rather than /// silently dropping the event. Exposed for callers driving the /// machine via `step()` directly. pub fn take_overflow(&mut self) -> bool { let v = self.__overflow; self.__overflow = false; v } /// Returns `true` once the machine has processed its `terminate` event (§4.4). pub fn is_terminated(&self) -> bool { self.terminated } /// Return the current leaf state. Panics if called before the /// first `step()` (§4.4). pub fn current_state(&self) -> #state_enum_name { __state_id_to_public(self.current.expect("machine not started; call step() first")) } /// Returns `true` when internal event queue has pending events /// (§12.3). Useful for schedulers that want to poll `step()` /// again eagerly. pub fn has_pending_events(&self) -> bool { !self.queue.is_empty() } /// Consume the machine and yield the user-provided context (§4.4). pub fn into_context(self) -> #ctx_ty { self.ctx } /// Borrow the user-provided context (extension beyond spec §4.4; provided for test convenience). pub fn context(&self) -> &#ctx_ty { &self.ctx } /// Mutably borrow the user-provided context (extension beyond spec §4.4; provided for test convenience). pub fn context_mut(&mut self) -> &mut #ctx_ty { &mut self.ctx } /// Advance the machine by `elapsed` wall-clock time. Returns the /// duration until the next timer fires, or `None` if the machine /// has no live timers (§4.4). /// /// Under the `tokio` or `embassy` feature, `step()` is `async` /// because action methods are `async fn` and must be `.await`ed /// during dispatch. Under default features it stays sync. #[cfg(not(any(feature = "tokio", feature = "embassy")))] pub fn step(&mut self, elapsed: ::hsmc::Duration) -> Option<::hsmc::Duration> { if self.terminated { return None; } if self.current.is_none() { self.enter_path(0); return self.timers.min_remaining(); } self.timers.decrement(elapsed); if let Some((state, trig)) = self.timers.pop_expired(&__DEPTH) { // Re-arm repeating timers before dispatch. If the // handler transitions out of `state`, the state's exit // path will cancel the freshly-armed timer. if __duration_repeats(trig) { let d = __duration_for(trig); self.timers.start(state, trig, d); } self.dispatch_trigger(state, 1, trig); return self.timers.min_remaining(); } if let Some(ev) = self.queue.pop_front() { if matches!(__check_terminate(&ev), TerminateCheck::Yes) { self.do_terminate(); return None; } let eid = __event_to_id(&ev); if eid != u16::MAX { let mut cur = self.current; while let Some(s) = cur { if let Some((actions, target)) = __handler_lookup(s, 0, eid) { self.run_handlers(s, actions, target, Some(&ev)); break; } cur = __PARENT[s as usize]; } } return self.timers.min_remaining(); } self.timers.min_remaining() } #[cfg(any(feature = "tokio", feature = "embassy"))] pub async fn step(&mut self, elapsed: ::hsmc::Duration) -> Option<::hsmc::Duration> { if self.terminated { return None; } if self.current.is_none() { self.enter_path(0).await; return self.timers.min_remaining(); } self.timers.decrement(elapsed); if let Some((state, trig)) = self.timers.pop_expired(&__DEPTH) { if __duration_repeats(trig) { let d = __duration_for(trig); self.timers.start(state, trig, d); } self.dispatch_trigger(state, 1, trig).await; return self.timers.min_remaining(); } if let Some(ev) = self.queue.pop_front() { if matches!(__check_terminate(&ev), TerminateCheck::Yes) { self.do_terminate().await; return None; } let eid = __event_to_id(&ev); if eid != u16::MAX { let mut cur = self.current; while let Some(s) = cur { if let Some((actions, target)) = __handler_lookup(s, 0, eid) { self.run_handlers(s, actions, target, Some(&ev)).await; break; } cur = __PARENT[s as usize]; } } return self.timers.min_remaining(); } self.timers.min_remaining() } /// Push an event directly into the machine's queue (synchronous, /// single-threaded). Extension beyond spec §4.4 — the canonical /// cross-thread path is [`sender().send()`](Self::sender). Useful /// for tests and tight `step()` driver loops. #[doc(hidden)] pub fn send(&mut self, event: #ev_ty) -> ::core::result::Result<(), ::hsmc::HsmcError> { if self.terminated { return Err(::hsmc::HsmcError::AlreadyTerminated); } self.queue.push_back(event).map_err(|_| ::hsmc::HsmcError::QueueFull) } /// Push an event into the internal queue and drain the queue to /// quiescence before returning. Under an async feature the step /// loop awaits action futures; after `dispatch()` returns, /// `current_state()` reflects the post-dispatch state. Use this /// from the task that owns the machine when you want "event /// processed" semantics (a drop-in replacement for the /// `send + drain` pattern). For cross-task / ISR injection use /// [`sender()`](Self::sender) instead. #[cfg(any(feature = "tokio", feature = "embassy"))] pub async fn dispatch(&mut self, event: #ev_ty) -> ::core::result::Result<(), ::hsmc::HsmcError> { if self.terminated { return Err(::hsmc::HsmcError::AlreadyTerminated); } self.queue.push_back(event).map_err(|_| ::hsmc::HsmcError::QueueFull)?; // Prime state on first call. if self.current.is_none() { let _ = self.step(::hsmc::Duration::ZERO).await; if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } if self.terminated { return Ok(()); } } while !self.queue.is_empty() { let _ = self.step(::hsmc::Duration::ZERO).await; if self.take_overflow() { return Err(::hsmc::HsmcError::QueueFull); } if self.terminated { return Ok(()); } } Ok(()) } #[cfg(not(any(feature = "tokio", feature = "embassy")))] fn run_handlers( &mut self, source_state: u16, actions: &'static [u16], target: Option, event: Option<&#ev_ty>, ) { for &aid in actions { self.run_action(aid, event); if self.terminated { return; } } if let Some(tgt) = target { self.transition(source_state, tgt); } } #[cfg(any(feature = "tokio", feature = "embassy"))] async fn run_handlers( &mut self, source_state: u16, actions: &'static [u16], target: Option, event: Option<&#ev_ty>, ) { for &aid in actions { self.run_action(aid, event).await; if self.terminated { return; } } if let Some(tgt) = target { self.transition(source_state, tgt).await; } } #[cfg(not(any(feature = "tokio", feature = "embassy")))] fn run_action(&mut self, aid: u16, event: Option<&#ev_ty>) { let ctx = &mut self.ctx; let mut __proxy = ::hsmc::__private::EmitProxy { queue: &mut self.queue, overflow: &mut self.__overflow, }; let queue: &mut (dyn ::hsmc::__private::QueuePush<#ev_ty> + ::core::marker::Send) = &mut __proxy; let terminated = &self.terminated; let mut actx = #action_ctx_name { ctx, __queue: queue, __terminated: terminated, }; fn __dispatch( ctx: &mut Ctx, aid: u16, __event: Option<&#ev_ty>, ) { let _ = __event; match aid { #(#run_action_arms_sync)* _ => {} } } __dispatch(&mut actx, aid, event); } #[cfg(any(feature = "tokio", feature = "embassy"))] async fn run_action(&mut self, aid: u16, event: Option<&#ev_ty>) { let ctx = &mut self.ctx; let mut __proxy = ::hsmc::__private::EmitProxy { queue: &mut self.queue, overflow: &mut self.__overflow, }; let queue: &mut (dyn ::hsmc::__private::QueuePush<#ev_ty> + ::core::marker::Send) = &mut __proxy; let terminated = &self.terminated; let mut actx = #action_ctx_name { ctx, __queue: queue, __terminated: terminated, }; async fn __dispatch( ctx: &mut Ctx, aid: u16, __event: Option<&#ev_ty>, ) { let _ = __event; match aid { #(#run_action_arms_async)* _ => {} } } __dispatch(&mut actx, aid, event).await; } #[cfg(not(any(feature = "tokio", feature = "embassy")))] fn transition(&mut self, source_matched_state: u16, target: u16) { let mut lca = Self::lca(source_matched_state, target); if let Some(l) = lca { if l == target { lca = __PARENT[l as usize]; } } if let Some(mut cur) = self.current { loop { if Some(cur) == lca { break; } self.exit_state(cur); match __PARENT[cur as usize] { Some(p) => cur = p, None => break, } } } let mut path: ::hsmc::__private::heapless::Vec = ::hsmc::__private::heapless::Vec::new(); let mut node = Some(target); while node != lca && node.is_some() { let n = node.unwrap(); let _ = path.push(n); node = __PARENT[n as usize]; } for i in (0..path.len()).rev() { let s = path[i]; self.enter_state_no_descent(s); } self.descend_defaults(target); } #[cfg(any(feature = "tokio", feature = "embassy"))] async fn transition(&mut self, source_matched_state: u16, target: u16) { let mut lca = Self::lca(source_matched_state, target); if let Some(l) = lca { if l == target { lca = __PARENT[l as usize]; } } if let Some(mut cur) = self.current { loop { if Some(cur) == lca { break; } self.exit_state(cur).await; match __PARENT[cur as usize] { Some(p) => cur = p, None => break, } } } let mut path: ::hsmc::__private::heapless::Vec = ::hsmc::__private::heapless::Vec::new(); let mut node = Some(target); while node != lca && node.is_some() { let n = node.unwrap(); let _ = path.push(n); node = __PARENT[n as usize]; } for i in (0..path.len()).rev() { let s = path[i]; self.enter_state_no_descent(s).await; } self.descend_defaults(target).await; } fn lca(a: u16, b: u16) -> Option { let mut set: ::hsmc::__private::heapless::Vec = ::hsmc::__private::heapless::Vec::new(); let mut cur = Some(a); while let Some(x) = cur { let _ = set.push(x); cur = __PARENT[x as usize]; } let mut cur = Some(b); while let Some(x) = cur { if set.contains(&x) { return Some(x); } cur = __PARENT[x as usize]; } None } #[cfg(not(any(feature = "tokio", feature = "embassy")))] fn enter_path(&mut self, root: u16) { self.enter_state_no_descent(root); self.descend_defaults(root); } #[cfg(any(feature = "tokio", feature = "embassy"))] async fn enter_path(&mut self, root: u16) { self.enter_state_no_descent(root).await; self.descend_defaults(root).await; } #[cfg(not(any(feature = "tokio", feature = "embassy")))] fn enter_state_no_descent(&mut self, sid: u16) { let entries = Self::entries_of(sid); for &aid in entries { self.run_action(aid, None); if self.terminated { return; } } for &tid in __owned_timers(sid) { let d = __duration_for(tid); self.timers.start(sid, tid, d); } self.current = Some(sid); } #[cfg(any(feature = "tokio", feature = "embassy"))] async fn enter_state_no_descent(&mut self, sid: u16) { let entries = Self::entries_of(sid); for &aid in entries { self.run_action(aid, None).await; if self.terminated { return; } } for &tid in __owned_timers(sid) { let d = __duration_for(tid); self.timers.start(sid, tid, d); } self.current = Some(sid); } #[cfg(not(any(feature = "tokio", feature = "embassy")))] fn descend_defaults(&mut self, sid: u16) { let mut cur = sid; while let Some(child) = __DEFAULT_CHILD[cur as usize] { self.enter_state_no_descent(child); cur = child; } } #[cfg(any(feature = "tokio", feature = "embassy"))] async fn descend_defaults(&mut self, sid: u16) { let mut cur = sid; while let Some(child) = __DEFAULT_CHILD[cur as usize] { self.enter_state_no_descent(child).await; cur = child; } } #[cfg(not(any(feature = "tokio", feature = "embassy")))] fn exit_state(&mut self, sid: u16) { self.timers.cancel_state(sid); let exits = Self::exits_of(sid); for &aid in exits { self.run_action(aid, None); if self.terminated { return; } } } #[cfg(any(feature = "tokio", feature = "embassy"))] async fn exit_state(&mut self, sid: u16) { self.timers.cancel_state(sid); let exits = Self::exits_of(sid); for &aid in exits { self.run_action(aid, None).await; if self.terminated { return; } } } fn entries_of(sid: u16) -> &'static [u16] { match sid { #(#entries_of_arms)* _ => &[], } } fn exits_of(sid: u16) -> &'static [u16] { match sid { #(#exits_of_arms)* _ => &[], } } #[cfg(not(any(feature = "tokio", feature = "embassy")))] fn do_terminate(&mut self) { if let Some(mut cur) = self.current { loop { self.exit_state(cur); match __PARENT[cur as usize] { Some(p) => cur = p, None => break, } } } self.terminated = true; self.current = None; while self.queue.pop_front().is_some() {} } #[cfg(any(feature = "tokio", feature = "embassy"))] async fn do_terminate(&mut self) { if let Some(mut cur) = self.current { loop { self.exit_state(cur).await; match __PARENT[cur as usize] { Some(p) => cur = p, None => break, } } } self.terminated = true; self.current = None; while self.queue.pop_front().is_some() {} } #[cfg(not(any(feature = "tokio", feature = "embassy")))] fn dispatch_trigger(&mut self, state: u16, kind: u8, trig: u16) { if let Some((actions, target)) = __handler_lookup(state, kind, trig) { // Timer-driven dispatch: no event payload in scope. self.run_handlers(state, actions, target, None); } } #[cfg(any(feature = "tokio", feature = "embassy"))] async fn dispatch_trigger(&mut self, state: u16, kind: u8, trig: u16) { if let Some((actions, target)) = __handler_lookup(state, kind, trig) { self.run_handlers(state, actions, target, None).await; } } } #default_sender_impl #tokio_sender_impl #embassy_sender_impl } } #[allow(dead_code)] fn _unused(_: Span, _: &Ident) {}