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}