Files
Commonwealth-Online-Server/server/tests/test_networking_v2_load.py
T

136 lines
4.6 KiB
Python

from __future__ import annotations
import json
import socket
import time
from server_core import PROTOCOL_VERSION, FalloutTogetherServer
_RECV_BUFFERS: dict[socket.socket, bytes] = {}
def recv_packet(sock: socket.socket, timeout: float = 2.0) -> dict:
sock.settimeout(timeout)
data = _RECV_BUFFERS.get(sock, b"")
while b"\n" not in data:
chunk = sock.recv(4096)
if not chunk:
raise ConnectionError("socket closed before a complete packet was received")
data += chunk
line, remainder = data.split(b"\n", 1)
_RECV_BUFFERS[sock] = remainder
return json.loads(line.decode("utf-8"))
def recv_until(sock: socket.socket, predicate, timeout: float = 2.0) -> dict:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
try:
packet = recv_packet(sock, timeout=max(0.01, deadline - time.monotonic()))
except socket.timeout:
break
if predicate(packet):
return packet
raise AssertionError("did not receive matching packet")
def send_packet(sock: socket.socket, packet: dict) -> None:
sock.sendall(json.dumps(packet, separators=(",", ":")).encode() + b"\n")
def connect_v2(server: FalloutTogetherServer) -> tuple[socket.socket, int]:
sock = socket.create_connection(("127.0.0.1", server.port), timeout=2.0)
welcome = recv_until(sock, lambda packet: packet.get("type") == "welcome")
send_packet(sock, {"type": "hello", "protocolVersion": PROTOCOL_VERSION})
ready = recv_until(sock, lambda packet: packet.get("type") == "sessionReady")
assert ready["playerId"] == welcome["playerId"]
return sock, int(welcome["playerId"])
def transform(cell: str, x: float, y: float = 0.0) -> dict:
return {
"type": "transform",
"x": x,
"y": y,
"z": 0.0,
"angleZ": 0.0,
"cellId": cell,
"worldspaceId": "0000003C",
"movementType": "normal",
}
def start_server() -> FalloutTogetherServer:
server = FalloutTogetherServer(host="127.0.0.1", port=0, max_players=24)
server.start()
deadline = time.monotonic() + 2.0
while not server.is_running() and time.monotonic() < deadline:
time.sleep(0.01)
return server
def test_sixteen_clients_do_not_cross_broadcast_distant_cell_transforms():
server = start_server()
clients: list[tuple[socket.socket, int]] = []
try:
clients = [connect_v2(server) for _ in range(16)]
assert server.get_stats()["connectedClients"] == 16
near = clients[:8]
far = clients[8:]
for index, (sock, _player_id) in enumerate(near):
send_packet(sock, transform("00000010", float(index * 32)))
for index, (sock, _player_id) in enumerate(far):
send_packet(sock, transform("00000020", 30000.0 + float(index * 32)))
deadline = time.monotonic() + 2.0
while time.monotonic() < deadline:
sessions = [server._find_client_by_player_id(player_id) for _sock, player_id in clients]
if all(session is not None and session.last_transform is not None for session in sessions):
break
time.sleep(0.01)
assert all(
session is not None and session.last_transform is not None
for session in (server._find_client_by_player_id(player_id) for _sock, player_id in clients)
)
sender_sock, sender_id = near[0]
near_peer_sock, _near_peer_id = near[1]
far_peer_sock, _far_peer_id = far[0]
marker_x = 128.0
send_packet(sender_sock, transform("00000010", marker_x))
relayed = recv_until(
near_peer_sock,
lambda packet: packet.get("type") == "transform"
and packet.get("playerId") == sender_id
and packet.get("x") == marker_x,
)
assert relayed["cellId"] == "00000010"
try:
recv_until(
far_peer_sock,
lambda packet: packet.get("type") == "transform"
and packet.get("playerId") == sender_id
and packet.get("x") == marker_x,
timeout=0.4,
)
except AssertionError:
pass
else:
raise AssertionError("distant cell received a transform that should have been interest-filtered")
stats = server.get_stats()
assert stats["connectedClients"] == 16
assert stats["transformPacketsInterestFiltered"] >= len(far)
finally:
for sock, _player_id in clients:
_RECV_BUFFERS.pop(sock, None)
try:
sock.close()
except OSError:
pass
server.stop()