diff --git a/src/protocol/leaf_template.rs b/src/protocol/leaf_template.rs index 2f0da51..eac74fe 100644 --- a/src/protocol/leaf_template.rs +++ b/src/protocol/leaf_template.rs @@ -1,29 +1,15 @@ -mod no_procedures; - /// Declares a generated leaf wrapper using a small template-like syntax. /// /// The macro deliberately requires callers to name every generated session field. It /// does not infer identifiers, inspect struct fields, or hide behavior inside a proc /// macro. All real dispatch and retry behavior lives in normal Rust helpers. +/// +/// The procedure list is handled by small internal `@...` rules instead of by +/// separate full macro expansions. That keeps the generated shape easy to audit +/// while still allowing empty `procedures {}` leaves to avoid allocating a +/// `LeafOutbox`. #[macro_export] macro_rules! unshell_leaf { - ( - $vis:vis leaf $Leaf:ident for $State:ty { - id: $id:expr, - meta: $meta:expr, - sessions { $( $session_field:ident : $Session:ty ),* $(,)? } - procedures {} - } - ) => { - $crate::__unshell_leaf_no_procedures! { - $vis leaf $Leaf for $State { - id: $id, - meta: $meta, - sessions { $( $session_field : $Session ),* } - } - } - }; - ( $vis:vis leaf $Leaf:ident for $State:ty { id: $id:expr, @@ -34,7 +20,7 @@ macro_rules! unshell_leaf { ) => { $vis struct $Leaf { state: $State, - outbox: $crate::protocol::LeafOutbox, + outbox: $crate::unshell_leaf!(@outbox_type $( $procedure_field : $Procedure ),*), $( $session_field: $crate::protocol::SessionFamily<$Session>, )* @@ -45,7 +31,7 @@ macro_rules! unshell_leaf { pub fn new(state: $State) -> Self { Self { state, - outbox: $crate::protocol::LeafOutbox::new(), + outbox: $crate::unshell_leaf!(@outbox_new $( $procedure_field : $Procedure ),*), $( $session_field: $crate::protocol::SessionFamily::new(), )* @@ -69,14 +55,16 @@ macro_rules! unshell_leaf { /// Returns queued packets owned by this generated leaf. pub fn pending_packet_count(&self) -> usize { - let mut count = self.outbox.len(); - $( - count += self.$session_field.pending_packet_count(); - )* - count + $crate::unshell_leaf!( + @outbox_len + &self.outbox; + $( $procedure_field : $Procedure ),* + ) $(+ self.$session_field.pending_packet_count())* } fn __unshell_packet_is_owned(packet: &$crate::protocol::Packet) -> bool { + let _ = packet; + false $( || packet.procedure_id @@ -93,7 +81,14 @@ macro_rules! unshell_leaf { endpoint: &mut $crate::protocol::Endpoint, ) { let leaf_id = $id; - self.__unshell_flush_all(endpoint); + let _ = leaf_id; + + $crate::unshell_leaf!( + @flush_outbox + endpoint, + &mut self.outbox; + $( $procedure_field : $Procedure ),* + ); let Some(local_id) = endpoint.path.last().copied() else { return; @@ -118,7 +113,12 @@ macro_rules! unshell_leaf { ); )* - self.__unshell_flush_all(endpoint); + $crate::unshell_leaf!( + @flush_outbox + endpoint, + &mut self.outbox; + $( $procedure_field : $Procedure ),* + ); } #[cfg(feature = "interface")] @@ -128,7 +128,16 @@ macro_rules! unshell_leaf { interface: &mut $crate::interface::InterfaceStore, ) { let leaf_id = $id; - self.__unshell_flush_all_interface(endpoint, interface); + let _ = leaf_id; + + $crate::unshell_leaf!( + @flush_outbox_interface + endpoint, + leaf_id, + &mut self.outbox, + interface; + $( $procedure_field : $Procedure ),* + ); let Some(local_id) = endpoint.path.last().copied() else { return; @@ -155,7 +164,14 @@ macro_rules! unshell_leaf { ); )* - self.__unshell_flush_all_interface(endpoint, interface); + $crate::unshell_leaf!( + @flush_outbox_interface + endpoint, + leaf_id, + &mut self.outbox, + interface; + $( $procedure_field : $Procedure ),* + ); } fn __unshell_dispatch_packet( @@ -207,6 +223,7 @@ macro_rules! unshell_leaf { interface: &mut $crate::interface::InterfaceStore, ) { let leaf_id = $id; + let _ = leaf_id; $( if packet.procedure_id @@ -245,35 +262,6 @@ macro_rules! unshell_leaf { let _ = packet; let _ = interface; } - - fn __unshell_flush_all( - &mut self, - endpoint: &mut $crate::protocol::Endpoint, - ) { - let leaf_id = $id; - let _ = leaf_id; - - $crate::protocol::flush_leaf_outbox( - endpoint, - &mut self.outbox, - ); - } - - #[cfg(feature = "interface")] - fn __unshell_flush_all_interface( - &mut self, - endpoint: &mut $crate::protocol::Endpoint, - interface: &mut $crate::interface::InterfaceStore, - ) { - let leaf_id = $id; - - $crate::protocol::flush_leaf_outbox_interface( - endpoint, - leaf_id, - &mut self.outbox, - interface, - ); - } } impl $crate::protocol::Leaf for $Leaf { @@ -309,6 +297,7 @@ macro_rules! unshell_leaf { interface: &mut $crate::interface::InterfaceStore, ) { let leaf_id = $id; + let _ = (&frame, &area, &interface, leaf_id); $( for entry in &mut self.$session_field.entries { @@ -345,4 +334,58 @@ macro_rules! unshell_leaf { } } }; + + // Select the leaf-level outbox type. Empty procedure lists use `()` so + // session-only leaves carry no retry queue, while non-empty lists share the + // normal procedure response queue. + (@outbox_type) => { + () + }; + + (@outbox_type $first_field:ident : $FirstProcedure:ty $(, $procedure_field:ident : $Procedure:ty )* $(,)?) => { + $crate::protocol::LeafOutbox + }; + + // Construct the procedure outbox selected by `@outbox_type`. + (@outbox_new) => { + () + }; + + (@outbox_new $first_field:ident : $FirstProcedure:ty $(, $procedure_field:ident : $Procedure:ty )* $(,)?) => { + $crate::protocol::LeafOutbox::new() + }; + + // Count queued procedure packets without forcing session-only leaves to own a queue. + (@outbox_len $outbox:expr;) => { + 0usize + }; + + (@outbox_len $outbox:expr; $first_field:ident : $FirstProcedure:ty $(, $procedure_field:ident : $Procedure:ty )* $(,)?) => { + $outbox.len() + }; + + // Flush queued procedure responses when the leaf declares at least one procedure. + (@flush_outbox $endpoint:expr, $outbox:expr;) => {}; + + (@flush_outbox $endpoint:expr, $outbox:expr; $first_field:ident : $FirstProcedure:ty $(, $procedure_field:ident : $Procedure:ty )* $(,)?) => {{ + let _ = stringify!($first_field); + $( + let _ = stringify!($procedure_field); + )* + + $crate::protocol::flush_leaf_outbox($endpoint, $outbox); + }}; + + // Flush queued procedure responses with interface logging when procedures exist. + (@flush_outbox_interface $endpoint:expr, $leaf_id:expr, $outbox:expr, $interface:expr;) => {}; + + (@flush_outbox_interface $endpoint:expr, $leaf_id:expr, $outbox:expr, $interface:expr; $first_field:ident : $FirstProcedure:ty $(, $procedure_field:ident : $Procedure:ty )* $(,)?) => {{ + let _ = stringify!($first_field); + $( + let _ = stringify!($procedure_field); + )* + + $crate::protocol::flush_leaf_outbox_interface($endpoint, $leaf_id, $outbox, $interface); + }}; + } diff --git a/src/protocol/leaf_template/no_procedures.rs b/src/protocol/leaf_template/no_procedures.rs deleted file mode 100644 index ad10a1b..0000000 --- a/src/protocol/leaf_template/no_procedures.rs +++ /dev/null @@ -1,240 +0,0 @@ -/// Expands the `unshell_leaf!` specialization for leaves without one-shot procedures. -/// -/// This helper stays separate from the public macro because the no-procedure shape is -/// intentionally different: it does not allocate a `LeafOutbox`, so tiny leaves such as -/// the shell leaf avoid carrying unused procedure retry machinery in the optimized -/// endpoint binary. -#[doc(hidden)] -#[macro_export] -macro_rules! __unshell_leaf_no_procedures { - ( - $vis:vis leaf $Leaf:ident for $State:ty { - id: $id:expr, - meta: $meta:expr, - sessions { $( $session_field:ident : $Session:ty ),* $(,)? } - } - ) => { - $vis struct $Leaf { - state: $State, - $( - $session_field: $crate::protocol::SessionFamily<$Session>, - )* - } - - impl $Leaf { - /// Creates the generated leaf wrapper around user-owned state. - pub fn new(state: $State) -> Self { - Self { - state, - $( - $session_field: $crate::protocol::SessionFamily::new(), - )* - } - } - - /// Returns immutable access to the user-owned leaf state. - pub fn state(&self) -> &$State { - &self.state - } - - /// Returns mutable access to the user-owned leaf state. - pub fn state_mut(&mut self) -> &mut $State { - &mut self.state - } - - /// Returns the number of active session entries across all families. - pub fn active_session_count(&self) -> usize { - 0usize $(+ self.$session_field.entries.len())* - } - - /// Returns queued packets owned by this generated leaf. - pub fn pending_packet_count(&self) -> usize { - 0usize $(+ self.$session_field.pending_packet_count())* - } - - fn __unshell_packet_is_owned(packet: &$crate::protocol::Packet) -> bool { - false - $( - || packet.procedure_id - == <$Session as $crate::protocol::Session<$State>>::PROCEDURE_ID - )* - } - - fn __unshell_update_inner( - &mut self, - endpoint: &mut $crate::protocol::Endpoint, - ) { - let leaf_id = $id; - let _ = leaf_id; - - let Some(local_id) = endpoint.path.last().copied() else { - return; - }; - - let mut packets = $crate::alloc::vec::Vec::new(); - endpoint.take_inbound_matching( - local_id, - Self::__unshell_packet_is_owned, - |packet| packets.push(packet), - ); - - for packet in packets { - self.__unshell_dispatch_packet(endpoint, packet); - } - - $( - $crate::protocol::update_session_family::<$State, $Session>( - endpoint, - &mut self.state, - &mut self.$session_field, - ); - )* - } - - #[cfg(feature = "interface")] - fn __unshell_update_interface_inner( - &mut self, - endpoint: &mut $crate::protocol::Endpoint, - interface: &mut $crate::interface::InterfaceStore, - ) { - let leaf_id = $id; - let _ = leaf_id; - - let Some(local_id) = endpoint.path.last().copied() else { - return; - }; - - let mut packets = $crate::alloc::vec::Vec::new(); - endpoint.take_inbound_matching( - local_id, - Self::__unshell_packet_is_owned, - |packet| packets.push(packet), - ); - - for packet in packets { - self.__unshell_dispatch_packet_interface(endpoint, packet, interface); - } - - $( - $crate::protocol::update_session_family_interface::<$State, $Session>( - endpoint, - leaf_id, - &mut self.state, - &mut self.$session_field, - interface, - ); - )* - } - - fn __unshell_dispatch_packet( - &mut self, - endpoint: &mut $crate::protocol::Endpoint, - packet: $crate::protocol::Packet, - ) { - let leaf_id = $id; - let _ = leaf_id; - - $( - if packet.procedure_id - == <$Session as $crate::protocol::Session<$State>>::PROCEDURE_ID - { - $crate::protocol::dispatch_session::<$State, $Session>( - endpoint, - &mut self.state, - &mut self.$session_field, - packet, - ); - return; - } - )* - - let _ = endpoint; - let _ = packet; - } - - #[cfg(feature = "interface")] - fn __unshell_dispatch_packet_interface( - &mut self, - endpoint: &mut $crate::protocol::Endpoint, - packet: $crate::protocol::Packet, - interface: &mut $crate::interface::InterfaceStore, - ) { - let leaf_id = $id; - - $( - if packet.procedure_id - == <$Session as $crate::protocol::Session<$State>>::PROCEDURE_ID - { - $crate::protocol::dispatch_session_interface::<$State, $Session>( - endpoint, - leaf_id, - &mut self.state, - &mut self.$session_field, - packet, - interface, - ); - return; - } - )* - - let _ = endpoint; - let _ = packet; - let _ = interface; - } - } - - impl $crate::protocol::Leaf for $Leaf { - fn get_id(&self) -> u32 { - $id - } - - #[inline(never)] - fn update(&mut self, endpoint: &mut $crate::protocol::Endpoint) { - self.__unshell_update_inner(endpoint); - } - - #[cfg(feature = "interface")] - #[inline(never)] - fn update_interface( - &mut self, - endpoint: &mut $crate::protocol::Endpoint, - interface: &mut $crate::interface::InterfaceStore, - ) { - self.__unshell_update_interface_inner(endpoint, interface); - } - - #[cfg(feature = "interface")] - fn get_meta(&self) -> $crate::protocol::LeafMeta { - $meta - } - - #[cfg(feature = "interface_ratatui")] - fn render_ratatui( - &mut self, - frame: &mut $crate::protocol::ratatui::Frame<'_>, - area: $crate::protocol::ratatui::layout::Rect, - interface: &mut $crate::interface::InterfaceStore, - ) { - let leaf_id = $id; - let _ = leaf_id; - - $( - for entry in &mut self.$session_field.entries { - let view = interface.session_view_mut( - leaf_id, - <$Session as $crate::protocol::Session<$State>>::PROCEDURE_ID, - entry.hook_id, - ); - <$Session as $crate::protocol::Session<$State>>::render_ratatui( - &self.state, - &entry.state, - view, - frame, - area, - ); - } - )* - } - } - }; -}