1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
//! The non-connection-oriented interface to send and receive messages
//! (whether they be "clients" or "servers").
//!
//! ISteamNetworkingSockets is connection-oriented (like TCP), meaning you
//! need to listen and connect, and then you send messages using a connection
//! handle.  ISteamNetworkingMessages is more like UDP, in that you can just send
//! messages to arbitrary peers at any time.  The underlying connections are
//! established implicitly.
//!
//! Under the hood ISteamNetworkingMessages works on top of the ISteamNetworkingSockets
//! code, so you get the same routing and messaging efficiency.  The difference is
//! mainly in your responsibility to explicitly establish a connection and
//! the type of feedback you get about the state of the connection.  Both
//! interfaces can do "P2P" communications, and both support both unreliable
//! and reliable messages, fragmentation and reassembly.
//!
//! The primary purpose of this interface is to be "like UDP", so that UDP-based code
//! can be ported easily to take advantage of relayed connections.  If you find
//! yourself needing more low level information or control, or to be able to better
//! handle failure, then you probably need to use ISteamNetworkingSockets directly.
//! Also, note that if your main goal is to obtain a connection between two peers
//! without concerning yourself with assigning roles of "client" and "server",
//! you may find the symmetric connection mode of ISteamNetworkingSockets useful.
//! (See k_ESteamNetworkingConfig_SymmetricConnect.)
// TODO: examples here
use crate::networking_types::{
    NetConnectionInfo, NetworkingIdentity, NetworkingMessage, SendFlags,
};
use crate::{register_callback, Callback, Inner, SteamError};
use std::ffi::c_void;
use std::sync::{Arc, Weak};

use steamworks_sys as sys;

/// Access to the steam networking messages interface
pub struct NetworkingMessages<Manager> {
    pub(crate) net: *mut sys::ISteamNetworkingMessages,
    pub(crate) inner: Arc<Inner<Manager>>,
}

unsafe impl<Manager> Sync for NetworkingMessages<Manager> {}
unsafe impl<Manager> Send for NetworkingMessages<Manager> {}

impl<Manager: 'static> NetworkingMessages<Manager> {
    /// Sends a message to the specified host.
    ///
    /// If we don't already have a session with that user, a session is implicitly created.
    /// There might be some handshaking that needs to happen before we can actually begin sending message data.
    /// If this handshaking fails and we can't get through, an error will be posted via the callback
    /// SteamNetworkingMessagesSessionFailed_t.
    /// There is no notification when the operation succeeds.  (You should have the peer send a reply
    /// for this purpose.)
    ///
    /// Sending a message to a host will also implicitly accept any incoming connection from that host.
    ///
    /// `channel` is a routing number you can use to help route message to different systems.
    /// You'll have to call ReceiveMessagesOnChannel() with the same channel number in order to retrieve
    /// the data on the other end.
    ///
    /// Using different channels to talk to the same user will still use the same underlying
    /// connection, saving on resources.  If you don't need this feature, use 0.
    /// Otherwise, small integers are the most efficient.
    ///
    /// It is guaranteed that reliable messages to the same host on the same channel
    /// will be be received by the remote host (if they are received at all) exactly once,
    /// and in the same order that they were sent.
    ///
    /// NO other order guarantees exist!  In particular, unreliable messages may be dropped,
    /// received out of order with respect to each other and with respect to reliable data,
    /// or may be received multiple times.  Messages on different channels are *not* guaranteed
    /// to be received in the order they were sent.
    ///
    /// A note for those familiar with TCP/IP ports, or converting an existing codebase that
    /// opened multiple sockets:  You might notice that there is only one channel, and with
    /// TCP/IP each endpoint has a port number.  You can think of the channel number as the
    /// *destination* port.  If you need each message to also include a "source port" (so the
    /// recipient can route the reply), then just put that in your message.  That is essentially
    /// how UDP works!
    ///
    /// Returns:
    /// - k_EREsultOK on success.
    /// - k_EResultNoConnection will be returned if the session has failed or was closed by the peer,
    ///   and k_nSteamNetworkingSend_AutoRestartBrokenSession is not used.  (You can use
    ///   GetSessionConnectionInfo to get the details.)  In order to acknowledge the broken session
    ///   and start a new one, you must call CloseSessionWithUser
    /// - See ISteamNetworkingSockets::SendMessageToConnection for more possible return values
    pub fn send_message_to_user(
        &self,
        user: NetworkingIdentity,
        send_type: SendFlags,
        data: &[u8],
        channel: u32,
    ) -> Result<(), SteamError> {
        let result = unsafe {
            sys::SteamAPI_ISteamNetworkingMessages_SendMessageToUser(
                self.net,
                user.as_ptr(),
                data.as_ptr() as *const c_void,
                data.len() as u32,
                send_type.bits(),
                channel as i32,
            )
        };

        if result == sys::EResult::k_EResultOK {
            return Ok(());
        }

        Err(result.into())
    }

    /// Reads the next message that has been sent from another user on the given channel.
    ///
    /// `batch_size` is the maximum number of messages that can be received at once.
    ///
    /// # Example
    /// ```
    /// # use steamworks::Client;
    /// # use std::time::Duration;
    /// let (client, single) = Client::init().unwrap();
    ///
    /// // run_callbacks must be called regularly, or no incoming connections can be received
    /// let callback_loop = std::thread::spawn(move || loop {
    ///     single.run_callbacks();
    ///     std::thread::sleep(Duration::from_millis(10));
    /// });
    /// let networking_messages = client.networking_messages();
    ///
    /// // Accept all new connections
    /// networking_messages.session_request_callback(|request| request.accept());
    ///
    /// let _received = networking_messages.receive_messages_on_channel(0, 10);
    /// ```
    pub fn receive_messages_on_channel(
        &self,
        channel: u32,
        batch_size: usize,
    ) -> Vec<NetworkingMessage<Manager>> {
        let mut buffer = Vec::with_capacity(batch_size);
        unsafe {
            let message_count = sys::SteamAPI_ISteamNetworkingMessages_ReceiveMessagesOnChannel(
                self.net,
                channel as i32,
                buffer.as_mut_ptr(),
                batch_size as _,
            );
            buffer.set_len(message_count as usize);
        }

        buffer
            .into_iter()
            .map(|x| NetworkingMessage {
                message: x,
                _inner: self.inner.clone(),
            })
            .collect()
    }

    /// Register a callback that will be called whenever a peer requests a connection.
    ///
    /// Use the [`SessionRequest`](../networking_messages/struct.SessionRequest.html) to accept or reject the connection.
    ///
    /// Requires regularly calling [`SingleClient.run_callbacks()`](../struct.SingleClient.html#method.run_callbacks).
    /// Calling this function more than once will replace the previous callback.
    ///
    /// # Example
    /// ```
    /// # use steamworks::Client;
    /// # use std::time::Duration;
    /// let (client, single) = Client::init().unwrap();
    ///
    /// // run_callbacks must be called regularly, or no incoming connections can be received
    /// let callback_loop = std::thread::spawn(move || loop {
    ///     single.run_callbacks();
    ///     std::thread::sleep(Duration::from_millis(10));
    /// });
    /// let messages = client.networking_messages();
    ///
    /// // Accept all incoming connections
    /// messages.session_request_callback(|request| {
    ///     request.accept();
    /// });
    /// ```
    pub fn session_request_callback(
        &self,
        mut callback: impl FnMut(SessionRequest<Manager>) + Send + 'static,
    ) {
        let builder = SessionRequestBuilder {
            message: self.net,
            inner: Arc::downgrade(&self.inner),
        };
        unsafe {
            register_callback(
                &self.inner,
                move |request: NetworkingMessagesSessionRequest| {
                    if let Some(request) = builder.build_request(request.remote) {
                        callback(request);
                    }
                },
            );
        }
    }

    /// Register a callback that will be called whenever a connection fails to be established.
    ///
    /// Requires regularly calling [`SingleClient.run_callbacks()`](../struct.SingleClient.html#method.run_callbacks).
    /// Calling this function more than once will replace the previous callback.
    pub fn session_failed_callback(
        &self,
        mut callback: impl FnMut(NetConnectionInfo) + Send + 'static,
    ) {
        unsafe {
            register_callback(
                &self.inner,
                move |failed: NetworkingMessagesSessionFailed| {
                    callback(failed.info);
                },
            );
        }
    }
}

/// A helper for creating SessionRequests.
///
/// It's Send and Sync, so it can be moved into the callback.
struct SessionRequestBuilder<Manager> {
    message: *mut sys::ISteamNetworkingMessages,
    // Once the builder is in the callback, it creates a cyclic reference, so this has to be Weak
    inner: Weak<Inner<Manager>>,
}

unsafe impl<Manager> Sync for SessionRequestBuilder<Manager> {}
unsafe impl<Manager> Send for SessionRequestBuilder<Manager> {}

impl<Manager> SessionRequestBuilder<Manager> {
    pub fn build_request(&self, remote: NetworkingIdentity) -> Option<SessionRequest<Manager>> {
        self.inner.upgrade().map(|inner| SessionRequest {
            remote,
            messages: self.message,
            _inner: inner,
        })
    }
}

struct NetworkingMessagesSessionRequest {
    remote: NetworkingIdentity,
}

unsafe impl Callback for NetworkingMessagesSessionRequest {
    const ID: i32 = sys::SteamNetworkingMessagesSessionRequest_t_k_iCallback as _;
    const SIZE: i32 = std::mem::size_of::<sys::SteamNetworkingMessagesSessionRequest_t>() as _;

    unsafe fn from_raw(raw: *mut c_void) -> Self {
        let remote = *(raw as *mut sys::SteamNetworkingMessagesSessionRequest_t);
        let remote = remote.m_identityRemote.into();
        Self { remote }
    }
}

struct NetworkingMessagesSessionFailed {
    pub info: NetConnectionInfo,
}

unsafe impl Callback for NetworkingMessagesSessionFailed {
    const ID: i32 = sys::SteamNetworkingMessagesSessionFailed_t_k_iCallback as _;
    const SIZE: i32 = std::mem::size_of::<sys::SteamNetworkingMessagesSessionFailed_t>() as _;

    unsafe fn from_raw(raw: *mut c_void) -> Self {
        let remote = *(raw as *mut sys::SteamNetworkingMessagesSessionFailed_t);
        let remote = remote.m_info.into();
        Self { info: remote }
    }
}

/// A request for a new connection.
///
/// Use this to accept or reject the connection.
/// Letting this struct go out of scope will reject the connection.
pub struct SessionRequest<Manager> {
    remote: NetworkingIdentity,
    messages: *mut sys::ISteamNetworkingMessages,
    _inner: Arc<Inner<Manager>>,
}

unsafe impl<Manager> Sync for SessionRequest<Manager> {}
unsafe impl<Manager> Send for SessionRequest<Manager> {}

impl<Manager> SessionRequest<Manager> {
    /// The remote peer requesting the connection.
    pub fn remote(&self) -> &NetworkingIdentity {
        &self.remote
    }

    /// Accept the connection.
    pub fn accept(self) {
        unsafe {
            sys::SteamAPI_ISteamNetworkingMessages_AcceptSessionWithUser(
                self.messages,
                self.remote.as_ptr(),
            );
        }
    }

    /// Reject the connection.
    pub fn reject(mut self) {
        self.reject_inner();
    }

    /// Reject the connection without consuming self, useful for implementing [`Drop`]
    fn reject_inner(&mut self) {
        unsafe {
            sys::SteamAPI_ISteamNetworkingMessages_CloseSessionWithUser(
                self.messages,
                self.remote.as_ptr(),
            );
        }
    }
}

impl<Manager> Drop for SessionRequest<Manager> {
    fn drop(&mut self) {
        self.reject_inner();
    }
}