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}