Skip to main content

firefly_rust/
net.rs

1/// A peer obtained either from [`Peers`] ([`get_peers`]) or from [`get_me`].
2pub trait AnyPeer {
3    /// Restore the peer from a primitive type (u8).
4    ///
5    /// ## Safety
6    ///
7    /// See [`AnyPeer::into_u8`].
8    #[must_use]
9    unsafe fn from_u8(p: u8) -> Self;
10
11    /// Dump the peer as a primitive type (u8).
12    ///
13    /// ## Safety
14    ///
15    /// For most applications, [`Peers`], [`Peer`], and [`Me`] types should
16    /// be considered opaque and agnostic of their internal representation.
17    /// However, some code interpreters written for Firefly in Rust (firefly-lua)
18    /// might need the ability to save values on the virtual stack as primitive types,
19    /// and this is where this function comes in handy.
20    #[must_use]
21    unsafe fn into_u8(self) -> u8;
22}
23
24/// The peer ID.
25///
26/// Constructed from [`Peers`] (which is constructed by [`get_peers`]).
27#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
28pub struct Peer(pub(crate) u8);
29
30/// Represents a specific device (or a combination of all devices).
31///
32/// Used for reading and writing state of a device: input, stash, scores, etc.
33impl Peer {
34    /// A combination of all connected peers.
35    pub const COMBINED: Self = Peer(0xFF);
36}
37
38impl Default for Peer {
39    fn default() -> Self {
40        Self::COMBINED
41    }
42}
43
44impl AnyPeer for Peer {
45    unsafe fn from_u8(p: u8) -> Self {
46        Self(p)
47    }
48
49    unsafe fn into_u8(self) -> u8 {
50        self.0
51    }
52}
53
54/// The peer representing the current device.
55///
56/// Can be compared to [`Peer`] or used to [`get_settings`][crate::get_settings].
57///
58/// **IMPORTANT:** Using this type may cause state drift between device in multiplayer.
59/// See [the docs](https://docs.fireflyzero.com/dev/net/) for more info.
60#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
61pub struct Me(pub(crate) u8);
62
63impl AnyPeer for Me {
64    unsafe fn from_u8(p: u8) -> Self {
65        Self(p)
66    }
67
68    unsafe fn into_u8(self) -> u8 {
69        self.0
70    }
71}
72
73impl PartialEq<Peer> for Me {
74    fn eq(&self, other: &Peer) -> bool {
75        self.0 == other.0
76    }
77}
78
79impl PartialEq<Me> for Peer {
80    fn eq(&self, other: &Me) -> bool {
81        self.0 == other.0
82    }
83}
84
85/// The list of peers online.
86///
87/// Includes all connected peers as well as the local device.
88///
89/// The order is deterministic between calls and between runs.
90///
91/// Constructed by [`get_peers`].
92#[derive(Copy, Clone, Eq, PartialEq, Debug)]
93pub struct Peers(pub(crate) u32);
94
95impl Peers {
96    /// Dump [`Peers`] as a primitive type (u32).
97    ///
98    /// ## Safety
99    ///
100    /// See [`Peers::to_u32`].
101    #[must_use]
102    pub unsafe fn from_u32(p: u32) -> Self {
103        Self(p)
104    }
105
106    /// Restore [`Peers`] from a primitive type (u32).
107    ///
108    /// ## Safety
109    ///
110    /// For most applications, [`Peers`] and [`Peer`] types should be considered
111    /// opaque and agnostic of their internal representation.
112    /// However, some code interpreters written for Firefly in Rust
113    /// might need the ability to save values on virtual stack as primitive types,
114    /// and this is where this function comes in handy.
115    #[must_use]
116    pub unsafe fn into_u32(self) -> u32 {
117        self.0
118    }
119
120    /// Iterate over peers.
121    #[must_use]
122    pub fn iter(&self) -> PeersIter {
123        PeersIter {
124            peer: 0,
125            peers: self.0,
126        }
127    }
128
129    /// Check if the given peer is online.
130    #[must_use]
131    pub fn contains(&self, p: &Peer) -> bool {
132        (self.0 >> p.0) & 1 != 0
133    }
134
135    /// Get the number of peers online.
136    ///
137    /// Never zero. 1 for local single-player game. 2 or more for multiplayer.
138    #[must_use]
139    #[expect(clippy::len_without_is_empty)] // always non-empty
140    pub fn len(&self) -> usize {
141        self.0.count_ones() as usize
142    }
143
144    /// Convert the list of peers into a vector.
145    #[cfg(feature = "alloc")]
146    #[must_use]
147    pub fn as_vec(&self) -> alloc::vec::Vec<Peer> {
148        self.iter().collect()
149    }
150}
151
152impl IntoIterator for Peers {
153    type Item = Peer;
154    type IntoIter = PeersIter;
155
156    fn into_iter(self) -> Self::IntoIter {
157        PeersIter {
158            peer: 0,
159            peers: self.0,
160        }
161    }
162}
163
164pub struct PeersIter {
165    peer: u8,
166    peers: u32,
167}
168
169impl Iterator for PeersIter {
170    type Item = Peer;
171
172    fn next(&mut self) -> Option<Self::Item> {
173        while self.peers != 0 {
174            let peer = self.peer;
175            let peers = self.peers;
176            self.peer += 1;
177            self.peers >>= 1;
178            if peers & 1 != 0 {
179                return Some(Peer(peer));
180            }
181        }
182        None
183    }
184
185    fn size_hint(&self) -> (usize, Option<usize>) {
186        let size = self.peers.count_ones() as usize;
187        (size, Some(size))
188    }
189
190    fn count(self) -> usize {
191        self.peers.count_ones() as usize
192    }
193}
194
195/// Stash is a serialized binary state of the app that you want to persist
196/// between app runs and to be available in multiplayer.
197///
198/// For single-player purposes, you can save data in a regular file
199/// using [`dump_file`](crate::dump_file). File saved that way can be bigger
200/// (and you can create lots of them) but it cannot be accessed in multiplayer.
201///
202/// It's your job to serialize data into a binary stash and later parse it.
203/// Stash can be saved using [`save_stash`] and later read using [`load_stash`].
204type Stash = [u8];
205
206/// Get the peer corresponding to the local device.
207#[must_use]
208pub fn get_me() -> Me {
209    let me = unsafe { bindings::get_me() };
210    Me(me as u8)
211}
212
213/// Get the list of peers online.
214#[must_use]
215pub fn get_peers() -> Peers {
216    let peers = unsafe { bindings::get_peers() };
217    Peers(peers)
218}
219
220/// Save the given [`Stash`].
221///
222/// When called, the stash for the given peer will be stored in RAM.
223/// Calling [`load_stash`] for the same peer will return that stash.
224/// On exit, the runtime will persist the stash in FS.
225/// Next time the app starts, calling [`load_stash`] will restore the stash
226/// saved earlier.
227pub fn save_stash(peer: Peer, stash: &Stash) {
228    let ptr = stash.as_ptr();
229    let peer = u32::from(peer.0);
230    unsafe {
231        bindings::save_stash(peer, ptr as u32, stash.len() as u32);
232    }
233}
234
235/// Read the stash of the given peer using the passed buffer.
236///
237/// It's the app's responsibility to ensure that the passed buffer is big enough
238/// to fit the stash. If it doesn't fit, runtime will fill the buffer
239/// and discard the rest.
240///
241/// The returned stash is a slice of the passed in buffer of the actual content size
242/// (up to the buffer size).
243///
244/// If there is no stash (which is always true before [`save_stash`]
245/// is called for the first time ever), the result is `None`.
246#[must_use]
247pub fn load_stash(peer: Peer, stash: &mut Stash) -> Option<&mut Stash> {
248    let ptr = stash.as_ptr();
249    let peer = u32::from(peer.0);
250    let size = unsafe { bindings::load_stash(peer, ptr as u32, stash.len() as u32) };
251    if size == 0 {
252        return None;
253    }
254    let size = size as usize;
255    Some(&mut stash[..size])
256}
257
258/// Similar to [`load_stash`] but statically allocates the stash of the right size.
259///
260/// Unlike [`load_stash`], the returned stash size is not adjusted
261/// for the actual content size.
262///
263/// Unlike other `_buf` functions, like [`load_file_buf`][crate::load_file_buf],
264/// this one allocates the buffer statically, not dynamically.
265/// The app must statically know the max stash size.
266#[must_use]
267pub fn load_stash_buf<const N: usize>(peer: Peer) -> Option<[u8; N]> {
268    let stash = [0u8; N];
269    let ptr = stash.as_ptr();
270    let peer = u32::from(peer.0);
271    let size = unsafe { bindings::load_stash(peer, ptr as u32, stash.len() as u32) };
272    if size == 0 {
273        return None;
274    }
275    Some(stash)
276}
277
278/// Internal bindings to the raw runtime functions.
279mod bindings {
280    #[link(wasm_import_module = "net")]
281    unsafe extern "C" {
282        pub(crate) fn get_me() -> u32;
283        pub(crate) fn get_peers() -> u32;
284        pub(crate) fn save_stash(peer: u32, ptr: u32, len: u32);
285        pub(crate) fn load_stash(peer: u32, ptr: u32, len: u32) -> u32;
286    }
287}