Skip to content

Player Anchoring

Duncan edited this page Mar 25, 2020 · 3 revisions

TLDR

Player anchoring is the term we use to designate the proxying the server does when a player walks onto a peers map.

High Level

From the perspective of the client, there is only a single map. The ownership of territory is hidden from them as a feature- they should expect to walk anywhere on the visible map without encountering any hiccups. However, since we don't manage the state of the entire map centrally (which would eliminate the point of peer to peer) we need to forward event packets to peers as necessary. The way we accomplish this is by doing a pseudo-login procedure we call a border crossing to allow the player to temporarily exist as a player on the peers server.

The border crossing procedure involves creating a new connection from our peer to the peer that owns the map the player has walked onto. It follows a slightly different login procedure, but ends up in an identical play state and as such is treated as though the player connected directly to that peer for the rest of their time on that map.

Though the peer's server is now processing all the events from that client according to their rules, the server the player logged into initially still acts as a middleman. We call this process "anchoring", as in the player is "anchored" to their local server. It has three responsibilities: translation, filtering, and connection swapping.

Details

First we'll cover translation. Packets must have their positions translated so that they make sense to the peer. This is because the peer and the client see different patchworks, or map layouts, and do not agree on coordinates. We can use the position of their map on our local patchwork and the assertion that their map begins on the origin and has the same orientation as ours to determine how the positions should be translated.

Next we'll cover filtering. There are some packets that shouldn't be sent to local clients as a result of this border crossing. For instance, when we walk onto the peer's server they will send out a "Spawn Player" packet. But everybody on the local server already knows about this player- if they see that packet again it causes undesired behaviour. Additionally, since events from the peer come through a single shared connection, we need to determine which packets were caused by local players. We need this information since if the client receives information about something it did itself, it produces undesirable behaviour (screen flashing, teleporting randomly). To do this we use an allocation scheme- when we first connect to a peer it allocates a range of entity ids (hard coded to be the range 950-1000 atm) on its own server to be used for our players when they walk onto the server. Then when we see an entity id in this range, we apply a simple translation to determine what player is involved and don't send them the packet.

The third responsibility is connection swapping. We constantly keep track of the players position regardless of where they are and switch their connections when they enter a portion of the map with a different owner than their previous position. At that point we need to disconnect them from their previous owner, and connect them to their new owner.

Some Relevant Files

  • border_cross_login: Called on the remote peer after the initial handshake- reads the players position, calls upon the player state to summon the new player. Currently mostly hardcoded, and also has a flaw- it can potentially discard some of the event packets from the client if some packets are proxied before this login completes.

  • patchwork: There's currently a lot in this file, pending refactor in this issue: #106. Contains the connection swapping logic and the creation of the initial connection (see Anchor struct)

  • peer subscription: in the handle_peer_packet and source_anchored_event functions we perform the first part of the packet filtering described above. These are fairly scrappy and hard coded as of writing this.

  • player: The BroadcastAnchoredEvent message handles the second part of the packet filtering, determining the local source entity (if one exists) and preventing them from seeing the packet.

Clone this wiki locally