Add GNS load and reorder acceptance tests
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user