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}