firefly_rust/
net.rs

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