Skip to content

One removal path frees everything a departing player owned ​

Status: Accepted (2026-06-27)

Context ​

Player ids are reused: a new player gets the lowest free id (Player IDs). Anything a departed player leaves behind (a latch, a seat, a place in someone's streamed set, a pending respawn) would be inherited by the next player with that id. A player leaves in four ways (Disconnect): ClientBye, timeout, kick and overflow of the reliable queue. If each way cleaned up on its own, they would drift apart.

A general resource-ownership framework (owned timers, handlers and entities revoked together) would also solve this, but it is a large mechanism; the question is whether the server state needs one.

Decision ​

  • One seam. Every removal goes through Roster::remove_client (src/roster/mod.rs). It removes the client record, releases the player's state outside that record and raises the departure into the event stream (ADR 0048). A player the server counts dead but whose death is not committed yet leaves dead: player_death is raised before player_disconnect.
  • Two levels of ownership.
    • Everything inside the client record goes with it: the connection and its reliable endpoint, the life state and generation, the pending respawn decision and the granted point (ADR 0020), combat correlation.
    • Everything that lives outside it is released by one function, release_player, together: the departed player's place in other players' streamed sets and their pending shows and hides, nametags, chat bubbles and player-list rows; vehicle seats and the vehicle roles it held; static pickup latches; per-player primitives, objects and colshape state; waiting events that need the live player. New per-player state kept outside the client record must be released there.
  • The id is freed last. The id slot is released when player_disconnect is taken from the event stream, so a newcomer never gets the id before the resources have heard that its previous owner left.
  • Stale traffic is rejected by generation, not by cancellation. A late packet of the previous owner carries the old session token and is dropped by validation (ADR 0004); server events about a player carry its generation (player_token), so a client never applies an edge of the old owner to the new one.
  • Events in flight. Events that need the live player to act on (action input, offer answers, streaming changes, checkpoint and colshape crossings, view events) go with the player. Damage, death and the rest still reach the resources. A facade call on a departed id fails with PlayerNotFound.
  • Resources clean up their own per-player state in a player_disconnect handler. The handle of the departed player stays valid in those handlers and goes dead after them.
  • No general cleanup framework. Nothing in the per-player state needs one; the seam is the place where such ownership would be added if a real case appears.

Consequences ​

  • Bye, timeout, kick and overflow behave identically, so one set of tests covers id reuse after any of them.
  • Adding per-player state means deciding where it lives: inside the client record (free) or outside it (an explicit line in release_player).

Alternatives considered ​

  • Clean up in each removal path. Rejected: four copies of the same logic diverge.
  • A generic owned-resource framework. Rejected: no per-player state needs revocation beyond what the seam already does, and the framework would be surface without a user.
  • Free the id at removal. Rejected: a newcomer could take the id while the departure still waits in the event stream, and resources would see events of two players under one id.

Documentation licensed under CC BY 4.0. DarqlineMP is not affiliated with Rockstar Games or Take-Two Interactive. Grand Theft Auto is a trademark of Take-Two Interactive Software, Inc.