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