pub enum NetMsg {
Hello {
name: WireName,
},
Watch,
Input(InputMsg),
Hash {
frame: u32,
hash: u64,
},
Start {
seats: u8,
seat: Option<u8>,
terms: MatchTerms,
names: [WireName; 6],
round: u8,
wins: [u8; 6],
beach: Vec<u8>,
},
Pause {
frame: u32,
},
Resume {
frame: u32,
},
Abandoned {
seat: u8,
frame: u32,
},
Roster {
seats: u8,
names: [WireName; 6],
terms: MatchTerms,
},
Chat {
name: WireName,
text: WireChat,
},
Queued {
ahead: u8,
},
Incompatible {
version: u8,
},
}Variants§
Hello
Handshake ping; the host learns the peer’s address (and what to call them) from it.
Watch
Handshake ping from a peer that wants to watch, not play. Repeated
like Hello until a Start lands.
Input(InputMsg)
Hash
State fingerprint after frame, for loud desync detection.
Start
Host → joiner: the match begins with seats seats on terms; you are
seat, and the table is called names (empty entries fall back to
seat labels). Re-sent whenever a joiner is still saying hello.
Fields
seat: Option<u8>The seat this peer is given, or None for a peer that came to
watch. On the wire that None is [SPECTATOR_SEAT], a number
outside the range of real seats. In memory it is an absence,
which is what a watcher is, and what the launch plan beside it
has called one all along.
terms: MatchTermsround: u8Where the series stands as this round begins: its 1-based number, and the rounds each seat has won so far. Zero and empty for a single round.
The host says, because seats move: a peer that leaves between rounds frees its chair and everyone behind it moves up one, so a tally each peer kept by seat number credited the departed player’s rounds to whoever moved into the seat. The host holds the mapping and re-deals the tally with the chairs; a peer admitted from the queue mid-series learns the standings the same way, rather than starting a series of its own.
beach: Vec<u8>The beach itself, when the host picked one it built rather than one both peers already have. A generated arena travels as a seed; a handmade one has to travel as itself, because the joiner has never seen the file. Empty for the built-in maps.
Compressed with the same coder the share codes use, and it has to be: a 20x13 beach is fifteen hundred characters of text and the datagram is a kilobyte.
Pause
Someone hit pause: everybody stops committing at frame. Re-sent
every tick while paused, so a dropped datagram costs a moment of
confusion rather than a stuck match.
Resume
Play on: the pause that was to freeze on frame is lifted. Also
re-sent until the sim visibly moves again. The frame is what lets a
peer tell the Pause echoes still in flight from before the resume
(see Lockstep::receive_pause) from a fresh pause.
Abandoned
Host → the table: seat has stopped sending and an AI is taking
the chair.
Only the host says so, and everyone acts on its word rather than on their own patience. A peer that decided for itself would fill the seat on whichever frame its own timer happened to run out, and two peers filling it on different frames is a desync, which lockstep has no way to recover from.
frame is the one the host was held up on, and every peer empties
the seat from there: they do not all hold the same inputs from a
player who has gone quiet (the host relays each as it arrives, and
a peer that missed the relay of one may hold a later one), so
“from the frame you are stuck on” is not the same frame everywhere.
Repeated for the rest of the round, since a lost one leaves that
peer frozen while the others play on.
Roster
Host → the lobby: who is at the table right now, in seat order.
A joiner has only ever spoken to the host, so without this it knows nobody else is even there until the match starts. Re-sent whenever the table changes and on a timer besides, since a roster that went missing would leave a screen wrong for good rather than briefly.
Fields
terms: MatchTermsThe host’s dials as they stand, so a joiner’s card shows the match it is joining rather than its own setup screen’s idea of one. Everything but the seed is meaningful before the launch.
Chat
A line said in the lobby, and who said it. The sender names itself rather than the host stamping it: a joiner’s greeting is the only other place the host learns a name, and a peer that never greeted would otherwise speak anonymously.
Relayed by the host to the rest of the table, like an input: the spokes of the star cannot hear each other.
Queued
Host → a peer that turned up after the launch: the round is under
way and cannot take you, but you are in line for the next one, with
ahead people in front of you.
The answer to a greeting that used to be met with a spectator seat, which was worse than useless: lockstep replays from frame zero, so such a peer built a board nobody would ever send it inputs for and sat there, apparently connected, forever.
Incompatible
“I speak protocol version, and what you sent me is not it.” The
answer to a datagram from another build, so a mismatched joiner is
told why nothing is happening instead of greeting a host that ignores
it forever.
The one message exempt from the version gate, and the one whose layout
is frozen along with the version byte: every build, past and future,
can read [TAG_INCOMPATIBLE, version].
Implementations§
Source§impl NetMsg
impl NetMsg
pub fn encode(self) -> Vec<u8> ⓘ
Sourcepub fn peek_version(bytes: &[u8]) -> Option<u8>
pub fn peek_version(bytes: &[u8]) -> Option<u8>
The protocol version a datagram was written by, whatever else it
says, and None for anything that is not one of our messages at
all. Byte 1 is frozen across versions so this always answers, and
the tag is checked first so stray traffic on the port draws no reply.
Trait Implementations§
impl Eq for NetMsg
impl StructuralPartialEq for NetMsg
Auto Trait Implementations§
impl Freeze for NetMsg
impl RefUnwindSafe for NetMsg
impl Send for NetMsg
impl Sync for NetMsg
impl Unpin for NetMsg
impl UnsafeUnpin for NetMsg
impl UnwindSafe for NetMsg
Blanket Implementations§
Source§impl<T, U> AsBindGroupShaderType<U> for T
impl<T, U> AsBindGroupShaderType<U> for T
Source§fn as_bind_group_shader_type(&self, _images: &RenderAssets<GpuImage>) -> U
fn as_bind_group_shader_type(&self, _images: &RenderAssets<GpuImage>) -> U
T ShaderType for self. When used in AsBindGroup
derives, it is safe to assume that all images in self exist.Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> ConditionalSend for Twhere
T: Send,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<S, T> Duplex<S> for Twhere
T: FromSample<S> + ToSample<S>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
Source§impl<T, W> HasTypeWitness<W> for Twhere
W: MakeTypeWitness<Arg = T>,
T: ?Sized,
impl<T, W> HasTypeWitness<W> for Twhere
W: MakeTypeWitness<Arg = T>,
T: ?Sized,
impl<T> HitDataExtra for T
Source§impl<T> Identity for Twhere
T: ?Sized,
impl<T> Identity for Twhere
T: ?Sized,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more