From b4344a8d6a89ac4f0496af67cf11962d8d444597 Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Mon, 1 Jun 2026 13:35:30 -0600 Subject: [PATCH] Split generated leaf runtime modules --- .../{runtime.rs => runtime/interface.rs} | 209 +----------------- src/protocol/runtime/mod.rs | 23 ++ src/protocol/runtime/outbox.rs | 84 +++++++ src/protocol/runtime/procedure.rs | 46 ++++ src/protocol/runtime/session.rs | 73 ++++++ 5 files changed, 233 insertions(+), 202 deletions(-) rename src/protocol/{runtime.rs => runtime/interface.rs} (54%) create mode 100644 src/protocol/runtime/mod.rs create mode 100644 src/protocol/runtime/outbox.rs create mode 100644 src/protocol/runtime/procedure.rs create mode 100644 src/protocol/runtime/session.rs diff --git a/src/protocol/runtime.rs b/src/protocol/runtime/interface.rs similarity index 54% rename from src/protocol/runtime.rs rename to src/protocol/runtime/interface.rs index 1448226..5da610c 100644 --- a/src/protocol/runtime.rs +++ b/src/protocol/runtime/interface.rs @@ -1,197 +1,16 @@ use alloc::collections::VecDeque; -#[cfg(feature = "interface")] -use crate::interface::{InterfaceEventKind, InterfaceStore, InterfaceTarget}; -use crate::protocol::{ - Endpoint, Packet, PacketQueue, Procedure, ProcedureOut, Session, SessionEntry, SessionFamily, - SessionInitError, SessionStatus, +use crate::{ + interface::{InterfaceEventKind, InterfaceStore, InterfaceTarget}, + protocol::{ + Endpoint, Packet, Procedure, ProcedureOut, Session, SessionEntry, SessionFamily, + SessionInitError, SessionStatus, + }, }; -/// Retry queue shared by generated leaves. -/// -/// Leaf-level retry queue shared by generated leaves. -/// -/// Sessions route directly through `Endpoint` to keep their runtime shape small. This -/// queue remains only for one-shot procedures, whose handlers still use `ProcedureOut` -/// and should not route while the procedure is borrowing leaf state. -pub struct LeafOutbox { - packets: VecDeque, -} - -/// One packet retained by a leaf-level retry queue. -/// -/// Procedure responses from different generated branches share one queue. Storing the -/// owner beside the packet keeps route logging precise without exposing another public -/// queue type. -#[derive(Clone)] -struct LeafOutboxEntry { - packet: Packet, - #[cfg(feature = "interface")] - target: Option, -} - -impl LeafOutbox { - /// Creates an empty leaf-level outbox. - pub fn new() -> Self { - Self { - packets: VecDeque::new(), - } - } - - /// Adds one packet to the retry queue. - pub fn push(&mut self, packet: Packet) { - self.packets.push_back(LeafOutboxEntry { - packet, - #[cfg(feature = "interface")] - target: None, - }); - } - - /// Adds all packets from `packets` in FIFO order. - pub fn extend(&mut self, packets: PacketQueue) { - for packet in packets { - self.push(packet); - } - } - - /// Returns the number of queued packets. - pub fn len(&self) -> usize { - self.packets.len() - } - - /// Returns true when the queue has no pending packets. - pub fn is_empty(&self) -> bool { - self.packets.is_empty() - } - - /// Adds one packet with a runtime-known interface target. - #[cfg(feature = "interface")] - pub(crate) fn push_for_target(&mut self, packet: Packet, target: InterfaceTarget) { - self.packets.push_back(LeafOutboxEntry { - packet, - target: Some(target), - }); - } - - /// Adds all packets with the same runtime-known interface target. - #[cfg(feature = "interface")] - pub(crate) fn extend_for_target(&mut self, packets: PacketQueue, target: InterfaceTarget) { - for packet in packets { - self.push_for_target(packet, target); - } - } -} - -impl Default for LeafOutbox { - fn default() -> Self { - Self::new() - } -} - -/// Dispatches one packet into a generated session family. -/// -/// The macro picks `S` and the family field. This helper owns the boring details: -/// find the hook, initialize missing sessions, and route rejected responses. The -/// interface build uses the sibling logging helper so the smallest endpoint binary -/// does not mention the interface logging types on its hot update path. -pub fn dispatch_session( - endpoint: &mut Endpoint, - leaf: &mut L, - family: &mut SessionFamily, - packet: Packet, -) where - S: Session, -{ - let hook_id = packet.hook_id; - let procedure_id = S::PROCEDURE_ID; - if let Some(entry) = family - .entries - .iter_mut() - .find(|entry| entry.hook_id == hook_id) - { - entry.inbox.push_back(packet); - return; - } - - let Ok(path) = endpoint.hook_path(hook_id) else { - return; - }; - match S::init(leaf, packet) { - Ok(state) => { - family.entries.push(SessionEntry::new(hook_id, state)); - } - Err(SessionInitError::Rejected) => {} - Err(SessionInitError::Response { data, end_hook }) => { - let packet = Packet { - hook_id, - end_hook, - path, - procedure_id, - data, - }; - - let _ = endpoint.add_outbound(packet); - } - } -} - -/// Updates every live session in one generated session family. -pub fn update_session_family( - endpoint: &mut Endpoint, - leaf: &mut L, - family: &mut SessionFamily, -) where - S: Session, -{ - for entry in &mut family.entries { - if entry.closed { - continue; - } - - let status = S::update(leaf, &mut entry.state, &mut entry.inbox, endpoint); - - if matches!(status, SessionStatus::Closed) { - entry.closed = true; - } - } - - family.entries.retain(|entry| !entry.closed); -} - -/// Dispatches one packet into a generated one-shot procedure. -pub fn dispatch_procedure( - leaf: &mut L, - endpoint: &mut Endpoint, - packet: Packet, - outbox: &mut LeafOutbox, -) where - P: Procedure, -{ - let hook_id = packet.hook_id; - let mut procedure_out = - ProcedureOut::new(hook_id, parent_reply_path(endpoint), P::PROCEDURE_ID); - - P::handle(leaf, endpoint, packet, &mut procedure_out); - - let packets = procedure_out.into_packets(); - outbox.extend(packets); -} - -/// Flushes a generated leaf-level outbox through endpoint routing. -pub fn flush_leaf_outbox(endpoint: &mut Endpoint, outbox: &mut LeafOutbox) -> bool { - while let Some(entry) = outbox.packets.front() { - if endpoint.add_outbound(entry.packet.clone()).is_err() { - return false; - } - - outbox.packets.pop_front(); - } - - true -} +use super::{LeafOutbox, procedure::parent_reply_path}; /// Dispatches one packet into a generated session family with interface logging. -#[cfg(feature = "interface")] pub fn dispatch_session_interface( endpoint: &mut Endpoint, leaf_id: u32, @@ -295,7 +114,6 @@ pub fn dispatch_session_interface( } /// Updates every live session in one generated session family with interface logging. -#[cfg(feature = "interface")] pub fn update_session_family_interface( endpoint: &mut Endpoint, leaf_id: u32, @@ -334,7 +152,6 @@ pub fn update_session_family_interface( } /// Dispatches one packet into a generated one-shot procedure with interface logging. -#[cfg(feature = "interface")] pub fn dispatch_procedure_interface( leaf_id: u32, leaf: &mut L, @@ -386,7 +203,6 @@ pub fn dispatch_procedure_interface( } /// Flushes a generated leaf-level outbox through endpoint routing with interface logging. -#[cfg(feature = "interface")] pub fn flush_leaf_outbox_interface( endpoint: &mut Endpoint, leaf_id: u32, @@ -402,7 +218,6 @@ pub fn flush_leaf_outbox_interface( }) } -#[cfg(feature = "interface")] fn flush_outbox( endpoint: &mut Endpoint, outbox: &mut VecDeque, @@ -422,7 +237,6 @@ fn flush_outbox( true } -#[cfg(feature = "interface")] fn flush_packet_with_target( endpoint: &mut Endpoint, target: InterfaceTarget, @@ -460,12 +274,3 @@ fn flush_packet_with_target( } } } - -/// Returns the path used by generated procedure responses. -fn parent_reply_path(endpoint: &Endpoint) -> alloc::vec::Vec { - if endpoint.path.len() > 1 { - endpoint.path[..endpoint.path.len() - 1].to_vec() - } else { - endpoint.path.clone() - } -} diff --git a/src/protocol/runtime/mod.rs b/src/protocol/runtime/mod.rs new file mode 100644 index 0000000..97056ad --- /dev/null +++ b/src/protocol/runtime/mod.rs @@ -0,0 +1,23 @@ +//! Runtime helpers used by generated leaves. +//! +//! The `unshell_leaf!` macro emits static dispatch code and delegates the reusable +//! session, procedure, retry, and interface-observation behavior to this module. +//! Keeping those pieces in normal Rust makes the macro easier to audit and keeps the +//! smallest endpoint builds free of interface-only logging paths. + +mod outbox; +mod procedure; +mod session; + +#[cfg(feature = "interface")] +mod interface; + +pub use outbox::LeafOutbox; +pub use procedure::{dispatch_procedure, flush_leaf_outbox}; +pub use session::{dispatch_session, update_session_family}; + +#[cfg(feature = "interface")] +pub use interface::{ + dispatch_procedure_interface, dispatch_session_interface, flush_leaf_outbox_interface, + update_session_family_interface, +}; diff --git a/src/protocol/runtime/outbox.rs b/src/protocol/runtime/outbox.rs new file mode 100644 index 0000000..f720df4 --- /dev/null +++ b/src/protocol/runtime/outbox.rs @@ -0,0 +1,84 @@ +use alloc::collections::VecDeque; + +#[cfg(feature = "interface")] +use crate::interface::InterfaceTarget; +use crate::protocol::{Packet, PacketQueue}; + +/// Retry queue shared by generated leaves. +/// +/// Sessions route directly through `Endpoint` to keep their runtime shape small. This +/// queue remains only for one-shot procedures, whose handlers still use `ProcedureOut` +/// and should not route while the procedure is borrowing leaf state. +pub struct LeafOutbox { + pub(super) packets: VecDeque, +} + +/// One packet retained by a leaf-level retry queue. +/// +/// Procedure responses from different generated branches share one queue. Storing the +/// owner beside the packet keeps route logging precise without exposing another public +/// queue type. +#[derive(Clone)] +pub(super) struct LeafOutboxEntry { + pub(super) packet: Packet, + #[cfg(feature = "interface")] + pub(super) target: Option, +} + +impl LeafOutbox { + /// Creates an empty leaf-level outbox. + pub fn new() -> Self { + Self { + packets: VecDeque::new(), + } + } + + /// Adds one packet to the retry queue. + pub fn push(&mut self, packet: Packet) { + self.packets.push_back(LeafOutboxEntry { + packet, + #[cfg(feature = "interface")] + target: None, + }); + } + + /// Adds all packets from `packets` in FIFO order. + pub fn extend(&mut self, packets: PacketQueue) { + for packet in packets { + self.push(packet); + } + } + + /// Returns the number of queued packets. + pub fn len(&self) -> usize { + self.packets.len() + } + + /// Returns true when the queue has no pending packets. + pub fn is_empty(&self) -> bool { + self.packets.is_empty() + } + + /// Adds one packet with a runtime-known interface target. + #[cfg(feature = "interface")] + pub(crate) fn push_for_target(&mut self, packet: Packet, target: InterfaceTarget) { + self.packets.push_back(LeafOutboxEntry { + packet, + target: Some(target), + }); + } + + /// Adds all packets with the same runtime-known interface target. + #[cfg(feature = "interface")] + pub(crate) fn extend_for_target(&mut self, packets: PacketQueue, target: InterfaceTarget) { + for packet in packets { + self.push_for_target(packet, target); + } + } +} + +impl Default for LeafOutbox { + fn default() -> Self { + Self::new() + } +} diff --git a/src/protocol/runtime/procedure.rs b/src/protocol/runtime/procedure.rs new file mode 100644 index 0000000..9859283 --- /dev/null +++ b/src/protocol/runtime/procedure.rs @@ -0,0 +1,46 @@ +use alloc::vec::Vec; + +use crate::protocol::{Endpoint, Packet, Procedure, ProcedureOut}; + +use super::LeafOutbox; + +/// Dispatches one packet into a generated one-shot procedure. +pub fn dispatch_procedure( + leaf: &mut L, + endpoint: &mut Endpoint, + packet: Packet, + outbox: &mut LeafOutbox, +) where + P: Procedure, +{ + let hook_id = packet.hook_id; + let mut procedure_out = + ProcedureOut::new(hook_id, parent_reply_path(endpoint), P::PROCEDURE_ID); + + P::handle(leaf, endpoint, packet, &mut procedure_out); + + let packets = procedure_out.into_packets(); + outbox.extend(packets); +} + +/// Flushes a generated leaf-level outbox through endpoint routing. +pub fn flush_leaf_outbox(endpoint: &mut Endpoint, outbox: &mut LeafOutbox) -> bool { + while let Some(entry) = outbox.packets.front() { + if endpoint.add_outbound(entry.packet.clone()).is_err() { + return false; + } + + outbox.packets.pop_front(); + } + + true +} + +/// Returns the path used by generated procedure responses. +pub(super) fn parent_reply_path(endpoint: &Endpoint) -> Vec { + if endpoint.path.len() > 1 { + endpoint.path[..endpoint.path.len() - 1].to_vec() + } else { + endpoint.path.clone() + } +} diff --git a/src/protocol/runtime/session.rs b/src/protocol/runtime/session.rs new file mode 100644 index 0000000..5dda6ae --- /dev/null +++ b/src/protocol/runtime/session.rs @@ -0,0 +1,73 @@ +use crate::protocol::{ + Endpoint, Packet, Session, SessionEntry, SessionFamily, SessionInitError, SessionStatus, +}; + +/// Dispatches one packet into a generated session family. +/// +/// The macro picks `S` and the family field. This helper owns the boring details: +/// find the hook, initialize missing sessions, and route rejected responses. The +/// interface build uses the sibling logging helper so the smallest endpoint binary +/// does not mention the interface logging types on its hot update path. +pub fn dispatch_session( + endpoint: &mut Endpoint, + leaf: &mut L, + family: &mut SessionFamily, + packet: Packet, +) where + S: Session, +{ + let hook_id = packet.hook_id; + let procedure_id = S::PROCEDURE_ID; + if let Some(entry) = family + .entries + .iter_mut() + .find(|entry| entry.hook_id == hook_id) + { + entry.inbox.push_back(packet); + return; + } + + let Ok(path) = endpoint.hook_path(hook_id) else { + return; + }; + match S::init(leaf, packet) { + Ok(state) => { + family.entries.push(SessionEntry::new(hook_id, state)); + } + Err(SessionInitError::Rejected) => {} + Err(SessionInitError::Response { data, end_hook }) => { + let packet = Packet { + hook_id, + end_hook, + path, + procedure_id, + data, + }; + + let _ = endpoint.add_outbound(packet); + } + } +} + +/// Updates every live session in one generated session family. +pub fn update_session_family( + endpoint: &mut Endpoint, + leaf: &mut L, + family: &mut SessionFamily, +) where + S: Session, +{ + for entry in &mut family.entries { + if entry.closed { + continue; + } + + let status = S::update(leaf, &mut entry.state, &mut entry.inbox, endpoint); + + if matches!(status, SessionStatus::Closed) { + entry.closed = true; + } + } + + family.entries.retain(|entry| !entry.closed); +}