mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 14:36:01 -06:00
Split generated leaf runtime modules
This commit is contained in:
@@ -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<LeafOutboxEntry>,
|
||||
}
|
||||
|
||||
/// 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<InterfaceTarget>,
|
||||
}
|
||||
|
||||
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<L, S>(
|
||||
endpoint: &mut Endpoint,
|
||||
leaf: &mut L,
|
||||
family: &mut SessionFamily<S>,
|
||||
packet: Packet,
|
||||
) where
|
||||
S: Session<L>,
|
||||
{
|
||||
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<L, S>(
|
||||
endpoint: &mut Endpoint,
|
||||
leaf: &mut L,
|
||||
family: &mut SessionFamily<S>,
|
||||
) where
|
||||
S: Session<L>,
|
||||
{
|
||||
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<L, P>(
|
||||
leaf: &mut L,
|
||||
endpoint: &mut Endpoint,
|
||||
packet: Packet,
|
||||
outbox: &mut LeafOutbox,
|
||||
) where
|
||||
P: Procedure<L>,
|
||||
{
|
||||
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<L, S>(
|
||||
endpoint: &mut Endpoint,
|
||||
leaf_id: u32,
|
||||
@@ -295,7 +114,6 @@ pub fn dispatch_session_interface<L, S>(
|
||||
}
|
||||
|
||||
/// Updates every live session in one generated session family with interface logging.
|
||||
#[cfg(feature = "interface")]
|
||||
pub fn update_session_family_interface<L, S>(
|
||||
endpoint: &mut Endpoint,
|
||||
leaf_id: u32,
|
||||
@@ -334,7 +152,6 @@ pub fn update_session_family_interface<L, S>(
|
||||
}
|
||||
|
||||
/// Dispatches one packet into a generated one-shot procedure with interface logging.
|
||||
#[cfg(feature = "interface")]
|
||||
pub fn dispatch_procedure_interface<L, P>(
|
||||
leaf_id: u32,
|
||||
leaf: &mut L,
|
||||
@@ -386,7 +203,6 @@ pub fn dispatch_procedure_interface<L, P>(
|
||||
}
|
||||
|
||||
/// 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<T>(
|
||||
endpoint: &mut Endpoint,
|
||||
outbox: &mut VecDeque<T>,
|
||||
@@ -422,7 +237,6 @@ fn flush_outbox<T>(
|
||||
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<u32> {
|
||||
if endpoint.path.len() > 1 {
|
||||
endpoint.path[..endpoint.path.len() - 1].to_vec()
|
||||
} else {
|
||||
endpoint.path.clone()
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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<LeafOutboxEntry>,
|
||||
}
|
||||
|
||||
/// 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<InterfaceTarget>,
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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<L, P>(
|
||||
leaf: &mut L,
|
||||
endpoint: &mut Endpoint,
|
||||
packet: Packet,
|
||||
outbox: &mut LeafOutbox,
|
||||
) where
|
||||
P: Procedure<L>,
|
||||
{
|
||||
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<u32> {
|
||||
if endpoint.path.len() > 1 {
|
||||
endpoint.path[..endpoint.path.len() - 1].to_vec()
|
||||
} else {
|
||||
endpoint.path.clone()
|
||||
}
|
||||
}
|
||||
@@ -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<L, S>(
|
||||
endpoint: &mut Endpoint,
|
||||
leaf: &mut L,
|
||||
family: &mut SessionFamily<S>,
|
||||
packet: Packet,
|
||||
) where
|
||||
S: Session<L>,
|
||||
{
|
||||
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<L, S>(
|
||||
endpoint: &mut Endpoint,
|
||||
leaf: &mut L,
|
||||
family: &mut SessionFamily<S>,
|
||||
) where
|
||||
S: Session<L>,
|
||||
{
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user