Files
Grovewright/game/scripts/session_link.gd
Steven Wroblewski be22e9f358 Abschlusskorrekturen M1c: Schläge der Gäste, Figuren und Fragebogen
- Der Host hält einen Schlag eines Gastes, der während der Abklingzeit
  ankommt, bis zum ersten Takt danach. Übers Internet ging sonst etwa
  jeder fünfte Schlag eines Gastes verloren.
- ENet drosselt die Figuren nie mehr; sonst konnten die Figuren eines
  von zwei Gästen dauerhaft stehen bleiben. Figuren gehen geordnet, und
  eine stille Verbindung gilt nach höchstens acht Sekunden als verloren.
- Positionen, die keine Zahlen sind, und Figuren von Spielern, die
  nicht online sind, werden verworfen.
- Ein Host mit angehaltener Simulation sagt das in der Statuszeile.
- Neue Tests: Schlag während der Abklingzeit, Zielauswahl von NetLink,
  verworfene Figuren, Neuabgleich über die Brücke.
- Der Fragebogen sagt, was jeder Rechner braucht, und die Freigabe in
  der Firewall gehört zum Host. CLAUDE.md beschreibt das Halten der
  Schläge, die Drosselung und die Zeitüberschreitung und ergänzt die
  Vertrauensregel um die Schlüssel.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-02 01:22:36 +02:00

110 lines
3.9 KiB
GDScript

class_name SessionLink
extends RefCounted
## Carries the messages of a GwSession over a NetLink, and the positions of
## the figures, which are not part of the shared world. Tells the game what
## happened on the way.
## Bytes of a position: x and y as 32-bit floats.
const POSITION_BYTES := 8
var session: GwSession
var link: NetLink
func _init(game_session: GwSession, net_link: NetLink) -> void:
session = game_session
link = net_link
## Moves messages in both directions and returns notices, oldest first, as
## dictionaries with a `type` entry:
## - "guest_arrived" and "guest_left" (entry `peer`), at the host
## - "world_received" (entry `resync`), "refused" (entry `reason`),
## "protocol_error", "host_lost" and "failed", at a guest
## - "divergence" (entries `peer`, `tick`, `hash`), at the host
## - "figure" (entries `player`, `position`), everywhere, for another player
## who is online, with a position of real numbers
## `delta` is the time since the last call.
func pump(delta: float) -> Array[Dictionary]:
var notices: Array[Dictionary] = []
for event in link.poll(delta):
match event["type"]:
"connected":
if session.is_host():
session.connect_peer(event["peer"])
notices.append({"type": "guest_arrived", "peer": event["peer"]})
"disconnected":
if session.is_host():
session.disconnect_peer(event["peer"])
notices.append({"type": "guest_left", "peer": event["peer"]})
else:
notices.append({"type": "host_lost"})
"failed":
notices.append({"type": "failed"})
"message":
notices.append_array(session.receive(event["peer"], event["bytes"]))
"figure":
_receive_figure(event["peer"], event["bytes"], notices)
for divergence in session.take_divergences():
divergence["type"] = "divergence"
notices.append(divergence)
flush()
return notices
## Sends what the session has for the network and closes the connections
## the host gave up. A guest's messages wait until its host is reached.
func flush() -> void:
if not link.is_connected_to_peers():
return
for message in session.take_messages():
link.send(message["peer"], message["bytes"])
for peer in session.take_closing():
link.close_peer(peer)
## Tells the other machines where this machine's figure stands.
func send_figure(position: Vector2) -> void:
if session.is_host():
var player := session.local_player()
if player >= 0:
link.send_figure(0, _figure_bytes(player, position))
elif session.local_player() >= 0:
link.send_figure(GwSession.HOST_PEER, _position_bytes(position))
func _receive_figure(peer: int, bytes: PackedByteArray, notices: Array[Dictionary]) -> void:
if session.is_host():
# A guest sends only a position; the host knows whose it is and
# passes it on to every other guest.
var player := session.player_of_peer(peer)
if player < 0 or bytes.size() != POSITION_BYTES:
return
var position := Vector2(bytes.decode_float(0), bytes.decode_float(4))
# Not a number or infinite, as a broken or changed client may send it:
# such a position would spoil the figure for good.
if not position.is_finite():
return
notices.append({"type": "figure", "player": player, "position": position})
link.send_figure(-peer, _figure_bytes(player, position))
elif bytes.size() == 1 + POSITION_BYTES:
var player := bytes.decode_u8(0)
var position := Vector2(bytes.decode_float(1), bytes.decode_float(5))
# A position can arrive after its player left the world.
if player != session.local_player() and session.online_players().has(player) and position.is_finite():
notices.append({"type": "figure", "player": player, "position": position})
func _position_bytes(position: Vector2) -> PackedByteArray:
var bytes := PackedByteArray()
bytes.resize(POSITION_BYTES)
bytes.encode_float(0, position.x)
bytes.encode_float(4, position.y)
return bytes
func _figure_bytes(player: int, position: Vector2) -> PackedByteArray:
var bytes := PackedByteArray([player])
bytes.append_array(_position_bytes(position))
return bytes