Unify leaf macro expansion

This commit is contained in:
Michael Mikovsky
2026-06-01 13:15:31 -06:00
parent 7749f62629
commit 693ba7c040
2 changed files with 102 additions and 299 deletions
+102 -59
View File
@@ -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);
}};
}
-240
View File
@@ -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,
);
}
)*
}
}
};
}