Crate team04_server

Source
Expand description

The team04 server for “Battlefront Tactics”.

The following sections will give you a rough overview over the server’s architecture. For more details on certain topics, check the documentation of the relevant modules.

§Connection Lifecycle

Incoming connections are accepted in server::run.

Each connection spawns a task1 which is responsible for running server::connection::handle, which handles incoming messages for that connection.

§pre lobby

In server::connection::handle, the connection is split into a receiving and a sending half.

The pre_lobby function in server::connection implements the connection logic before the client has joined a lobby, including the process of joining the lobby.

Once the client has joined a lobby, pre_lobby returns the lobby the client is in and the client’s player id. It also adds the client to the LobbyState, as a Player (or a Spectator) containing the sending half of the player’s connection, represented as a Connection.

§in lobby

The lobby phase is handled by server::connection::handle, which interacts with the lobby state directly, by locking it and calling functions on the LobbyState.

Once the game starts, server::connection::handle calls message_from for all gameplay-relevant messages it receives, but continues to handle the chat functionality.

§in-game

The gameplay-relevant messages received by a lobby while in-game are handled by message_from in lobby::state::handle_messages. Unlike server::connection::handle in the lobby phase, this does not directly change the lobby’s current state, it only stores players’ decisions or requests in it. While these decisions and requests are stored inside the LobbyState, they are only implementation details and not part of the state which would, for example, be used to generate a GAMESTATE message.

The decisions along with other game logic are actually handled by the lobby’s task, which is explained in the Game Logic section.

§Game Logic

For every created lobby, lobby::game::run is executed in a background task1. This task handles the clients’ decisions and processes gameplay logic.

§Rounds and Phases

The lobby::game::run function is where the rounds and their phases are implemented. For every phase which should be executed in the current round, a respective function is called.

After every phase, maybe_pause is called, which pauses the game if a player has requested a pause.

The phases are:

The lightsaber purchase, unit purchase, and placement phases start by calling lightsaber_purchase, unit_purchase, or placements, respectively, which are functions on SharedLobbyState. These functions send a gamestate message to all clients, followed by the unit or lightsaber options message (for lightsaber or unit purchase phases), then wait for either the phase’s timeout to elapse or for all clients to have made their decisions. They return a LockedLobbyState and guarantee certain things about this state, notable that every player has made a decision (if no decision was received, a random one is made or the previous unit placement is reused).

With those decisions, the phase handling functions then implement the actual logic of their respective phase, such as adding purchased lightsabers or units to players’ states, or setting players’ unit placements.

The fight and completion phases do not wait for players’ decisions, because players cannot influence those phases. The completion phase handler returns a bool which is true if the game should end (because at most one player still has more than 0 lives).

§Locking

The server has a ServerState, which is passed around and shared between different tasks as an Arc<SyncedServerState>. To access the shared server state, call .lock() on SyncedServerState

The ServerState stores LobbyStates, in the form of shareable SharedLobbyStates, which can be locked just like the server state.

§holding multiple locks at once

To avoid deadlocks, be careful when holding multiple locks at once:

While LobbyState contains an Arc<SyncedServerState>, locking the SyncedServerState containing a SharedLobbyState while also holding a lock on that SharedLobbyState can lead to deadlocks.

The rule is:

  1. If you have a lock on the SyncedServerState, you can lock every SharedLobbyState in it,
  2. if you do not have and other locks, you can always lock a SyncedServerState or SharedLobbyState, but
  3. if you have a lock on a SharedLobbyState, you cannot lock the SyncedServerState it belongs to.

  1. A task on the tokio runtime, see tokio::task. Used for connections and the lobby task

  2. This is because holding a lock on a SharedLobbyState would cause a task trying to do 1. to block until you drop that lock. If you then try to acquire a lock on the SyncedServerState, you would have to wait for that task, which is waiting on you, thereby deadlocking the program. 

Modules§

board
Representation of the board specified by the board config and used in-game, as well as associated functions and data structures. See: Board
config
Loading of the config files, including serialization and deserialization.
lobby
Definitions of the things that happen inside a lobby, most importantly including the actual gameplay-controlling code.
log
This module is used for logging.
messages
The messages which can be sent to or received from clients over the websocket connection.
server
Contains connection handling, clients joining/leaving lobbies, and creation of new lobbies.
unit
Definition of units and fight-related things used by those units.

Macros§

debug
debug_force
error
fatal
info
warning

Constants§

LOCK_WAIT_TIME_MS
If a task waits for a lobby or server lock for more than this many milliseconds, an info or warning level messsage will be written to the log. For warning level, in total, the sum of the two values has to have elapsed.

Functions§

async_main 🔒
main 🔒