1use crate::raw;
8use std::ffi::CString;
9use std::ptr;
10
11#[derive(Clone, Copy, Debug)]
12pub struct NetpacketSession {
13 client_id: NetplayClientId,
14 send: raw::retro_netpacket_send_t,
15 poll_receive: raw::retro_netpacket_poll_receive_t,
16}
17
18impl NetpacketSession {
19 pub(crate) const fn new(
20 client_id: NetplayClientId,
21 send: raw::retro_netpacket_send_t,
22 poll_receive: raw::retro_netpacket_poll_receive_t,
23 ) -> Option<Self> {
24 if send.is_some() {
25 Some(Self {
26 client_id,
27 send,
28 poll_receive,
29 })
30 } else {
31 None
32 }
33 }
34
35 pub const fn client_id(self) -> NetplayClientId {
36 self.client_id
37 }
38
39 pub const fn can_poll_receive(self) -> bool {
40 self.poll_receive.is_some()
41 }
42
43 pub fn send(self, target: NetpacketTarget, flags: NetpacketFlags, data: &[u8]) {
44 let Some(send) = self.send else {
45 return;
46 };
47 let data_ptr = if data.is_empty() {
48 ptr::null()
49 } else {
50 data.as_ptr().cast()
51 };
52 unsafe { send(flags.as_raw(), data_ptr, data.len(), target.as_raw()) };
53 }
54
55 pub fn flush(self, target: NetpacketTarget) {
56 self.send(
57 target,
58 NetpacketFlags::reliable().with_flush_hint(true),
59 &[],
60 );
61 }
62
63 pub fn poll_receive(self) -> bool {
64 let Some(poll_receive) = self.poll_receive else {
65 return false;
66 };
67 unsafe { poll_receive() };
68 true
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
73pub struct Netpacket<'a> {
74 pub client_id: NetplayClientId,
75 pub data: &'a [u8],
76}
77
78#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
79pub struct NetplayClientId(u16);
80
81impl NetplayClientId {
82 pub const fn new(id: u16) -> Self {
83 Self(id)
84 }
85
86 pub const fn host() -> Self {
87 Self(0)
88 }
89
90 pub const fn as_raw(self) -> u16 {
91 self.0
92 }
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
96pub enum NetpacketTarget {
97 Client(NetplayClientId),
98 Broadcast,
99}
100
101impl NetpacketTarget {
102 pub const fn as_raw(self) -> u16 {
103 match self {
104 Self::Client(client) => client.as_raw(),
105 Self::Broadcast => raw::RETRO_NETPACKET_BROADCAST,
106 }
107 }
108}
109
110#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
111pub enum NetpacketDelivery {
112 #[default]
113 Unreliable,
114 Reliable,
115 Unsequenced,
116}
117
118#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
119pub struct NetpacketFlags {
120 delivery: NetpacketDelivery,
121 flush: bool,
122}
123
124pub(crate) struct NetpacketInterfaceStorage {
125 _protocol_version: Option<CString>,
126 pub(crate) raw: raw::retro_netpacket_callback,
127}
128
129impl NetpacketInterfaceStorage {
130 pub(crate) fn new(protocol_version: Option<&str>) -> Self {
131 let protocol_version = protocol_version.map(crate::sanitize_cstring);
132 let protocol_version_ptr = protocol_version
133 .as_ref()
134 .map(|version| version.as_ptr())
135 .unwrap_or(ptr::null());
136 Self {
137 _protocol_version: protocol_version,
138 raw: raw::retro_netpacket_callback {
139 start: Some(crate::netpacket_start_trampoline),
140 receive: Some(crate::netpacket_receive_trampoline),
141 stop: Some(crate::netpacket_stop_trampoline),
142 poll: Some(crate::netpacket_poll_trampoline),
143 connected: Some(crate::netpacket_connected_trampoline),
144 disconnected: Some(crate::netpacket_disconnected_trampoline),
145 protocol_version: protocol_version_ptr,
146 },
147 }
148 }
149}
150
151impl NetpacketFlags {
152 pub const fn unreliable() -> Self {
153 Self {
154 delivery: NetpacketDelivery::Unreliable,
155 flush: false,
156 }
157 }
158
159 pub const fn reliable() -> Self {
160 Self {
161 delivery: NetpacketDelivery::Reliable,
162 flush: false,
163 }
164 }
165
166 pub const fn unsequenced() -> Self {
167 Self {
168 delivery: NetpacketDelivery::Unsequenced,
169 flush: false,
170 }
171 }
172
173 pub const fn with_flush_hint(mut self, flush: bool) -> Self {
174 self.flush = flush;
175 self
176 }
177
178 pub const fn delivery(self) -> NetpacketDelivery {
179 self.delivery
180 }
181
182 pub const fn flush_hint(self) -> bool {
183 self.flush
184 }
185
186 pub const fn as_raw(self) -> i32 {
187 let delivery = match self.delivery {
188 NetpacketDelivery::Unreliable => raw::RETRO_NETPACKET_UNRELIABLE,
189 NetpacketDelivery::Reliable => raw::RETRO_NETPACKET_RELIABLE,
190 NetpacketDelivery::Unsequenced => raw::RETRO_NETPACKET_UNSEQUENCED,
191 };
192 if self.flush {
193 delivery | raw::RETRO_NETPACKET_FLUSH_HINT
194 } else {
195 delivery
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn netpacket_flags_encode_valid_delivery_modes() {
206 assert_eq!(NetpacketFlags::unreliable().as_raw(), 0);
207 assert_eq!(
208 NetpacketFlags::reliable().with_flush_hint(true).as_raw(),
209 raw::RETRO_NETPACKET_RELIABLE | raw::RETRO_NETPACKET_FLUSH_HINT
210 );
211 assert_eq!(
212 NetpacketFlags::unsequenced().as_raw(),
213 raw::RETRO_NETPACKET_UNSEQUENCED
214 );
215 }
216
217 #[test]
218 fn netpacket_target_preserves_broadcast_constant() {
219 assert_eq!(
220 NetpacketTarget::Broadcast.as_raw(),
221 raw::RETRO_NETPACKET_BROADCAST
222 );
223 assert_eq!(NetpacketTarget::Client(NetplayClientId::new(7)).as_raw(), 7);
224 }
225
226 #[test]
227 fn netpacket_session_sends_and_flushes_with_typed_targets() {
228 unsafe extern "C" fn capture_send(
229 flags: i32,
230 buf: *const std::ffi::c_void,
231 len: usize,
232 client_id: u16,
233 ) {
234 assert_eq!(
235 flags,
236 raw::RETRO_NETPACKET_RELIABLE | raw::RETRO_NETPACKET_FLUSH_HINT
237 );
238 assert!(buf.is_null());
239 assert_eq!(len, 0);
240 assert_eq!(client_id, 7);
241 }
242
243 let session = NetpacketSession::new(NetplayClientId::host(), Some(capture_send), None)
244 .expect("send callback should create session");
245 session.flush(NetpacketTarget::Client(NetplayClientId::new(7)));
246 assert!(!session.poll_receive());
247 }
248}