4 ms·
So how does this work? Does the "host" run the emulator and stream the video feed of it and accept inputs, or do both players run the emulator in lockstep or so
by grishka 6y ago
So how does this work? Does the "host" run the emulator and stream the video feed of it and accept inputs, or do both players run the emulator in lockstep or something? The first approach would introduce a delay that's likely to be unacceptable for games. The second one would work better in terms of delay but would require some very tricky state synchronization.
edit: the first approach would also give the host an unfair advantage because they'll have no delay at all.
- derefr 6y ago> would require some very tricky state synchronization Games for these older consoles — any generation while games were either still unikernels, or still ran on RTOSes — are already 100% deterministic in terms of what the game will do on a given frame, given a fixed history of per-frame inputs. (That might be surprising, but devs would strive to keep this property, as it makes reproducing bugs far easier.) So you don’t have to do much, other than execute the game faithfully and ship button-presses back and forth, to ensure synchronization. Of course, shipping these button-presses around to achieve state-consensus synchronously would be slow — but there’s no need to do it synchronously. All modern emulators are built not in terms of a single mutable virtual-machine state, but rather in terms of a functional-persistent chain of VM states (think a HAMT.) This is what enables “rewind” support in emulators — and more recently, “run-ahead” latency reduction (basically a type of speculative execution of VM states.) Thus, with any emulator constructed this way, it’s actually very easy to ship+receive+resolve network inputs asynchronously: i.e. to receive inputs “about” frame N while rendering frame N+M, and then to go back and re-compute the correct VM state for frames N..N+M, such that frame N+M+1 will inherit from the recomputed frame N+M. (Remember, you don’t need to run any of the IO-emulation logic for the recomputed frames, so they’re actually quite cheap to recompute!) It’s actually oddly similar to what blockchain nodes do to “reorg” when they discover a fork — just done in real-time, between 16.7ms frames.
- sktrdie 6y agoInteresting so commands are sent to other players async? Doesn’t that mean that some players will see things different than others? I my friend A stabs a monster and my other friend B also stabs a monster but they come in different order I would see A killing monster, but others might see B killing it instead? Edit: probably the order of commands is kept in order via the chat room server no? But then what is it using WebRTC for? Perhaps the “host” is e source of truth for the order... meaning that all the commands speed and delay depend on the host network bandwidth.
- derefr 6y agoInput events don't need network linearization. Instead, every input event "happens" on an explicit frame. Each network-broadcast input-event message looks like "during frame N, player P {was/wasn't} holding down button B." Nodes can receive and apply such messages in any arbitrary order, and the result will be correct. It's the frame-counter in each message, not the order each message was received, that determines how history will be rewound and altered. Keep in mind, every node is running the same deterministic simulation. As long as nodes achieve eventual consistency in their "input movie" somehow (a gossip protocol, say), then every node will end up on the same VM state. You don't need a leader to declare what the consensus state is; presuming trustworthy peers(!), there is a single "objectively-correct" consensus state that every node will converge upon once all nodes have all messages. Every time an input-event message is received from the network, the receiving node just rewinds to frame N, and recomputes it (and all frames after it) with an input-movie where player P {is/isn't} holding button B. (And then, only if that explicit message wasn't already what was predicted during run-ahead.) Note that this is also exactly what happens when the local node produces an input-event message — that's what run-ahead is!
- monkpit 6y agoOk, but what about de-sync? > the receiving node just rewinds to frame N, and recomputes it (and all frames after it) What about situation where frame N is a “long” time ago (for some definition of “long”)? There must be some sort of threshold after which the game state cannot be changed even if an input was received.
- derefr 6y agoThere usually is, though it's arbitrary — basically an implementation detail of the emulator which was tuned based on in-practice behavior observation. Input-event messages that haven't been integrated into the canonical state after a "long time" must be dropped/undone by those who did integrate them, including by the player who originally generated them. In effect, an implicit "anti-event" is generated saying the opposite of the original input-event message, and applied to history by all players who applied the original message. How do players tell if their event managed to become part of the consensus state? Depends on the networking protocol. In a gossip protocol, messages get sent to peers individually, and ACKed by those peers. In an elected-leader client-server protocol, as long as the server peer ACKs the message as "in the queue", it's canonical. In an IRC-like spanning-tree message-relay system†... something complicated involving vector clocks containing message IDs. † I don't think I've ever seen an emulator choose a hierarchical message-relay architecture. It makes good robustness+efficiency trade-offs, but it's just far too complex to work with compared to the alternatives. It's more the domain of MMOs, that need to be concerned with many nodes colocated in one site speaking to many nodes colocated at other sites.
- jbister 6y agoI doubt it's what's being done in this case but an interesting take on this topic is "rollback netcode" which AFAIK was originally used for playing emulated arcade games over the internet. The idea is that the host guesses what the other player's inputs are on a certain frame, and when the actual input then arrives the host emulator will roll back its state to that frame and then re-emulate the frames after it, if it guessed wrong. If it guessed right the game continues as usual. It's a good fit for emulation since "rolling back" in an emulator is usually easier than rolling back the state in a game that wasn't built with this type of netcode in mind, but it has also become a popular type of netcode for modern fighting games. If you're interested in reading more about it: https://en.wikipedia.org/wiki/GGPO https://en.wikipedia.org/wiki/GGPO https://en.wikipedia.org/wiki/Netcode#Rollback https://en.wikipedia.org/wiki/Netcode#Rollback https://github.com/pond3r/ggpo https://github.com/pond3r/ggpo