roam_types/link.rs
1#![allow(async_fn_in_trait)]
2
3use std::future::Future;
4
5use crate::Backing;
6
7/// Marker trait that requires [`Send`] on native targets, nothing on wasm32.
8#[cfg(not(target_arch = "wasm32"))]
9pub trait MaybeSend: Send {}
10#[cfg(not(target_arch = "wasm32"))]
11impl<T: Send> MaybeSend for T {}
12
13#[cfg(target_arch = "wasm32")]
14pub trait MaybeSend {}
15#[cfg(target_arch = "wasm32")]
16impl<T> MaybeSend for T {}
17
18/// Marker trait that requires [`Sync`] on native targets, nothing on wasm32.
19#[cfg(not(target_arch = "wasm32"))]
20pub trait MaybeSync: Sync {}
21#[cfg(not(target_arch = "wasm32"))]
22impl<T: Sync> MaybeSync for T {}
23
24#[cfg(target_arch = "wasm32")]
25pub trait MaybeSync {}
26#[cfg(target_arch = "wasm32")]
27impl<T> MaybeSync for T {}
28
29/// Bidirectional raw-bytes transport.
30///
31/// TCP, WebSocket, SHM all implement this. No knowledge of what's being
32/// sent — just bytes in, bytes out. The transport provides write buffers
33/// so callers can encode directly into the destination (zero-copy for SHM).
34// r[impl link]
35// r[impl link.message]
36// r[impl link.order]
37pub trait Link {
38 type Tx: LinkTx;
39 type Rx: LinkRx;
40
41 // r[impl link.split]
42 fn split(self) -> (Self::Tx, Self::Rx);
43}
44
45/// A permit for allocating exactly one outbound payload.
46///
47/// Returned by [`LinkTx::reserve`]. The permit represents *message-level*
48/// capacity (not bytes). Once you have a permit, turning it into a concrete
49/// buffer for a specific payload size is synchronous.
50// r[impl link.tx.permit.drop]
51pub trait LinkTxPermit {
52 type Slot: WriteSlot;
53
54 /// Allocate a writable buffer of exactly `len` bytes.
55 ///
56 /// This is synchronous once the permit has been acquired.
57 // r[impl link.tx.alloc.limits]
58 // r[impl link.message.empty]
59 fn alloc(self, len: usize) -> std::io::Result<Self::Slot>;
60}
61
62/// Sending half of a [`Link`].
63///
64/// Uses a two-phase write:
65///
66/// 1. [`reserve`](LinkTx::reserve) awaits until the transport can accept *one*
67/// more payload and returns a [`LinkTxPermit`].
68/// 2. [`LinkTxPermit::alloc`] allocates a [`WriteSlot`] backed by the
69/// transport's own buffer (bipbuffer slot, kernel write buffer, etc.),
70/// then the caller fills it and calls [`WriteSlot::commit`].
71///
72/// `reserve` is the backpressure point.
73pub trait LinkTx: MaybeSend + MaybeSync + 'static {
74 type Permit: LinkTxPermit + MaybeSend;
75
76 /// Reserve capacity to send exactly one payload.
77 ///
78 /// Backpressure lives here — it awaits until the transport can accept a
79 /// payload (or errors).
80 ///
81 /// Dropping the returned permit without allocating/committing MUST
82 /// release the reservation.
83 // r[impl link.tx.reserve]
84 // r[impl link.tx.cancel-safe]
85 fn reserve(&self) -> impl Future<Output = std::io::Result<Self::Permit>> + MaybeSend + '_;
86
87 /// Graceful close of the outbound direction.
88 // r[impl link.tx.close]
89 fn close(self) -> impl Future<Output = std::io::Result<()>> + MaybeSend
90 where
91 Self: Sized;
92}
93
94/// A writable slot in the transport's output buffer.
95///
96/// Obtained from [`LinkTxPermit::alloc`]. The caller writes encoded bytes into
97/// [`as_mut_slice`](WriteSlot::as_mut_slice), then calls
98/// [`commit`](WriteSlot::commit) to make them visible to the receiver.
99///
100/// Dropping without commit = discard (no bytes sent, space reclaimed).
101// r[impl link.tx.discard]
102// r[impl zerocopy.framing.link]
103pub trait WriteSlot {
104 /// The writable buffer, exactly the size requested in `alloc`.
105 // r[impl link.tx.slot.len]
106 fn as_mut_slice(&mut self) -> &mut [u8];
107
108 /// Commit the written bytes. After this, the receiver can see them.
109 /// Sync — the bytes are already in the transport's buffer.
110 // r[impl link.tx.commit]
111 fn commit(self);
112}
113
114/// Receiving half of a [`Link`].
115///
116/// Yields [`Backing`] values: the raw bytes plus their ownership handle.
117/// The transport handles framing (length-prefix, WebSocket frames, etc.)
118/// and returns exactly one message's bytes per `recv` call.
119///
120/// For SHM: the Backing might be a VarSlot reference.
121/// For TCP: the Backing is a heap-allocated buffer.
122pub trait LinkRx: MaybeSend + 'static {
123 type Error: std::error::Error + MaybeSend + MaybeSync + 'static;
124
125 /// Receive the next message's raw bytes.
126 ///
127 /// Returns `Ok(None)` when the peer has closed the connection.
128 // r[impl link.rx.recv]
129 // r[impl link.rx.error]
130 // r[impl link.rx.eof]
131 fn recv(
132 &mut self,
133 ) -> impl Future<Output = Result<Option<Backing>, Self::Error>> + MaybeSend + '_;
134}
135
136/// A [`Link`] assembled from pre-split Tx and Rx halves.
137pub struct SplitLink<Tx, Rx> {
138 pub tx: Tx,
139 pub rx: Rx,
140}
141
142impl<Tx: LinkTx, Rx: LinkRx> Link for SplitLink<Tx, Rx> {
143 type Tx = Tx;
144 type Rx = Rx;
145
146 fn split(self) -> (Tx, Rx) {
147 (self.tx, self.rx)
148 }
149}