mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Unify leaf macro expansion
This commit is contained in:
+102
-59
@@ -1,29 +1,15 @@
|
|||||||
mod no_procedures;
|
|
||||||
|
|
||||||
/// Declares a generated leaf wrapper using a small template-like syntax.
|
/// Declares a generated leaf wrapper using a small template-like syntax.
|
||||||
///
|
///
|
||||||
/// The macro deliberately requires callers to name every generated session field. It
|
/// 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
|
/// 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.
|
/// 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_export]
|
||||||
macro_rules! unshell_leaf {
|
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 {
|
$vis:vis leaf $Leaf:ident for $State:ty {
|
||||||
id: $id:expr,
|
id: $id:expr,
|
||||||
@@ -34,7 +20,7 @@ macro_rules! unshell_leaf {
|
|||||||
) => {
|
) => {
|
||||||
$vis struct $Leaf {
|
$vis struct $Leaf {
|
||||||
state: $State,
|
state: $State,
|
||||||
outbox: $crate::protocol::LeafOutbox,
|
outbox: $crate::unshell_leaf!(@outbox_type $( $procedure_field : $Procedure ),*),
|
||||||
$(
|
$(
|
||||||
$session_field: $crate::protocol::SessionFamily<$Session>,
|
$session_field: $crate::protocol::SessionFamily<$Session>,
|
||||||
)*
|
)*
|
||||||
@@ -45,7 +31,7 @@ macro_rules! unshell_leaf {
|
|||||||
pub fn new(state: $State) -> Self {
|
pub fn new(state: $State) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state,
|
state,
|
||||||
outbox: $crate::protocol::LeafOutbox::new(),
|
outbox: $crate::unshell_leaf!(@outbox_new $( $procedure_field : $Procedure ),*),
|
||||||
$(
|
$(
|
||||||
$session_field: $crate::protocol::SessionFamily::new(),
|
$session_field: $crate::protocol::SessionFamily::new(),
|
||||||
)*
|
)*
|
||||||
@@ -69,14 +55,16 @@ macro_rules! unshell_leaf {
|
|||||||
|
|
||||||
/// Returns queued packets owned by this generated leaf.
|
/// Returns queued packets owned by this generated leaf.
|
||||||
pub fn pending_packet_count(&self) -> usize {
|
pub fn pending_packet_count(&self) -> usize {
|
||||||
let mut count = self.outbox.len();
|
$crate::unshell_leaf!(
|
||||||
$(
|
@outbox_len
|
||||||
count += self.$session_field.pending_packet_count();
|
&self.outbox;
|
||||||
)*
|
$( $procedure_field : $Procedure ),*
|
||||||
count
|
) $(+ self.$session_field.pending_packet_count())*
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __unshell_packet_is_owned(packet: &$crate::protocol::Packet) -> bool {
|
fn __unshell_packet_is_owned(packet: &$crate::protocol::Packet) -> bool {
|
||||||
|
let _ = packet;
|
||||||
|
|
||||||
false
|
false
|
||||||
$(
|
$(
|
||||||
|| packet.procedure_id
|
|| packet.procedure_id
|
||||||
@@ -93,7 +81,14 @@ macro_rules! unshell_leaf {
|
|||||||
endpoint: &mut $crate::protocol::Endpoint,
|
endpoint: &mut $crate::protocol::Endpoint,
|
||||||
) {
|
) {
|
||||||
let leaf_id = $id;
|
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 {
|
let Some(local_id) = endpoint.path.last().copied() else {
|
||||||
return;
|
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")]
|
#[cfg(feature = "interface")]
|
||||||
@@ -128,7 +128,16 @@ macro_rules! unshell_leaf {
|
|||||||
interface: &mut $crate::interface::InterfaceStore,
|
interface: &mut $crate::interface::InterfaceStore,
|
||||||
) {
|
) {
|
||||||
let leaf_id = $id;
|
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 {
|
let Some(local_id) = endpoint.path.last().copied() else {
|
||||||
return;
|
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(
|
fn __unshell_dispatch_packet(
|
||||||
@@ -207,6 +223,7 @@ macro_rules! unshell_leaf {
|
|||||||
interface: &mut $crate::interface::InterfaceStore,
|
interface: &mut $crate::interface::InterfaceStore,
|
||||||
) {
|
) {
|
||||||
let leaf_id = $id;
|
let leaf_id = $id;
|
||||||
|
let _ = leaf_id;
|
||||||
|
|
||||||
$(
|
$(
|
||||||
if packet.procedure_id
|
if packet.procedure_id
|
||||||
@@ -245,35 +262,6 @@ macro_rules! unshell_leaf {
|
|||||||
let _ = packet;
|
let _ = packet;
|
||||||
let _ = interface;
|
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 {
|
impl $crate::protocol::Leaf for $Leaf {
|
||||||
@@ -309,6 +297,7 @@ macro_rules! unshell_leaf {
|
|||||||
interface: &mut $crate::interface::InterfaceStore,
|
interface: &mut $crate::interface::InterfaceStore,
|
||||||
) {
|
) {
|
||||||
let leaf_id = $id;
|
let leaf_id = $id;
|
||||||
|
let _ = (&frame, &area, &interface, leaf_id);
|
||||||
|
|
||||||
$(
|
$(
|
||||||
for entry in &mut self.$session_field.entries {
|
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);
|
||||||
|
}};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user