mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Split generated leaf runtime modules
This commit is contained in:
@@ -1,197 +1,16 @@
|
|||||||
use alloc::collections::VecDeque;
|
use alloc::collections::VecDeque;
|
||||||
|
|
||||||
#[cfg(feature = "interface")]
|
use crate::{
|
||||||
use crate::interface::{InterfaceEventKind, InterfaceStore, InterfaceTarget};
|
interface::{InterfaceEventKind, InterfaceStore, InterfaceTarget},
|
||||||
use crate::protocol::{
|
protocol::{
|
||||||
Endpoint, Packet, PacketQueue, Procedure, ProcedureOut, Session, SessionEntry, SessionFamily,
|
Endpoint, Packet, Procedure, ProcedureOut, Session, SessionEntry, SessionFamily,
|
||||||
SessionInitError, SessionStatus,
|
SessionInitError, SessionStatus,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Retry queue shared by generated leaves.
|
use super::{LeafOutbox, procedure::parent_reply_path};
|
||||||
///
|
|
||||||
/// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dispatches one packet into a generated session family with interface logging.
|
/// Dispatches one packet into a generated session family with interface logging.
|
||||||
#[cfg(feature = "interface")]
|
|
||||||
pub fn dispatch_session_interface<L, S>(
|
pub fn dispatch_session_interface<L, S>(
|
||||||
endpoint: &mut Endpoint,
|
endpoint: &mut Endpoint,
|
||||||
leaf_id: u32,
|
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.
|
/// Updates every live session in one generated session family with interface logging.
|
||||||
#[cfg(feature = "interface")]
|
|
||||||
pub fn update_session_family_interface<L, S>(
|
pub fn update_session_family_interface<L, S>(
|
||||||
endpoint: &mut Endpoint,
|
endpoint: &mut Endpoint,
|
||||||
leaf_id: u32,
|
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.
|
/// Dispatches one packet into a generated one-shot procedure with interface logging.
|
||||||
#[cfg(feature = "interface")]
|
|
||||||
pub fn dispatch_procedure_interface<L, P>(
|
pub fn dispatch_procedure_interface<L, P>(
|
||||||
leaf_id: u32,
|
leaf_id: u32,
|
||||||
leaf: &mut L,
|
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.
|
/// Flushes a generated leaf-level outbox through endpoint routing with interface logging.
|
||||||
#[cfg(feature = "interface")]
|
|
||||||
pub fn flush_leaf_outbox_interface(
|
pub fn flush_leaf_outbox_interface(
|
||||||
endpoint: &mut Endpoint,
|
endpoint: &mut Endpoint,
|
||||||
leaf_id: u32,
|
leaf_id: u32,
|
||||||
@@ -402,7 +218,6 @@ pub fn flush_leaf_outbox_interface(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "interface")]
|
|
||||||
fn flush_outbox<T>(
|
fn flush_outbox<T>(
|
||||||
endpoint: &mut Endpoint,
|
endpoint: &mut Endpoint,
|
||||||
outbox: &mut VecDeque<T>,
|
outbox: &mut VecDeque<T>,
|
||||||
@@ -422,7 +237,6 @@ fn flush_outbox<T>(
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "interface")]
|
|
||||||
fn flush_packet_with_target(
|
fn flush_packet_with_target(
|
||||||
endpoint: &mut Endpoint,
|
endpoint: &mut Endpoint,
|
||||||
target: InterfaceTarget,
|
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