Compare commits
4 Commits
921cc42fd4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4a8501635 | ||
|
|
56ac74e916 | ||
|
|
b94aac71f8 | ||
|
|
7337c27898 |
25
run.py
25
run.py
@@ -73,7 +73,7 @@ if __name__ == "__main__":
|
|||||||
try:
|
try:
|
||||||
if any(a in ("-h", "--help") for a in sys.argv[1:]):
|
if any(a in ("-h", "--help") for a in sys.argv[1:]):
|
||||||
print(
|
print(
|
||||||
"Usage: python run.py [--mode mem|quic|wt] [--log-level LEVEL] [--run-seconds N]\n"
|
"Usage: python run.py [--mode mem|quic|wt|net] [--log-level LEVEL] [--run-seconds N]\n"
|
||||||
" TLS (for wt/quic): set QUIC_CERT/QUIC_KEY or WT_CERT/WT_KEY env vars\n"
|
" TLS (for wt/quic): set QUIC_CERT/QUIC_KEY or WT_CERT/WT_KEY env vars\n"
|
||||||
" WT static server (MODE=wt): STATIC=1 [STATIC_HOST/PORT/ROOT]\n"
|
" WT static server (MODE=wt): STATIC=1 [STATIC_HOST/PORT/ROOT]\n"
|
||||||
"Examples:\n MODE=wt QUIC_CERT=cert.pem QUIC_KEY=key.pem python run.py\n MODE=mem python run.py"
|
"Examples:\n MODE=wt QUIC_CERT=cert.pem QUIC_KEY=key.pem python run.py\n MODE=mem python run.py"
|
||||||
@@ -89,6 +89,28 @@ if __name__ == "__main__":
|
|||||||
elif mode == "quic":
|
elif mode == "quic":
|
||||||
logging.info("Starting in QUIC datagram mode")
|
logging.info("Starting in QUIC datagram mode")
|
||||||
asyncio.run(run_quic())
|
asyncio.run(run_quic())
|
||||||
|
elif mode == "net":
|
||||||
|
logging.info("Starting in combined WebTransport+QUIC mode")
|
||||||
|
from server.webtransport_server import WebTransportServer
|
||||||
|
from server.quic_transport import QuicWebTransportServer
|
||||||
|
from server.multi_transport import MultiTransport
|
||||||
|
cfg = ServerConfig()
|
||||||
|
host_wt = os.environ.get("WT_HOST", os.environ.get("QUIC_HOST", "0.0.0.0"))
|
||||||
|
port_wt = int(os.environ.get("WT_PORT", os.environ.get("QUIC_PORT", "4433")))
|
||||||
|
host_quic = os.environ.get("QUIC_HOST", host_wt)
|
||||||
|
port_quic = int(os.environ.get("QUIC_PORT", "4443"))
|
||||||
|
cert = os.environ.get("WT_CERT") or os.environ.get("QUIC_CERT")
|
||||||
|
key = os.environ.get("WT_KEY") or os.environ.get("QUIC_KEY")
|
||||||
|
if not cert or not key:
|
||||||
|
raise SystemExit("WT/QUIC cert/key required: set WT_CERT/WT_KEY or QUIC_CERT/QUIC_KEY")
|
||||||
|
async def _run_net():
|
||||||
|
server: GameServer
|
||||||
|
wt = WebTransportServer(host_wt, port_wt, cert, key, lambda d, p: server.on_datagram(d, p))
|
||||||
|
qu = QuicWebTransportServer(host_quic, port_quic, cert, key, lambda d, p: server.on_datagram(d, p))
|
||||||
|
m = MultiTransport(wt, qu)
|
||||||
|
server = GameServer(transport=m, config=cfg)
|
||||||
|
await asyncio.gather(m.run(), server.tick_loop())
|
||||||
|
asyncio.run(_run_net())
|
||||||
else:
|
else:
|
||||||
logging.info("Starting in in-memory transport mode")
|
logging.info("Starting in in-memory transport mode")
|
||||||
asyncio.run(run_in_memory())
|
asyncio.run(run_in_memory())
|
||||||
@@ -113,3 +135,4 @@ async def _run_tasks_with_optional_timeout(tasks):
|
|||||||
for t in tasks:
|
for t in tasks:
|
||||||
t.cancel()
|
t.cancel()
|
||||||
await asyncio.gather(*tasks, return_exceptions=True)
|
await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
|
||||||
|
|||||||
26
server/multi_transport.py
Normal file
26
server/multi_transport.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from .transport import DatagramServerTransport, TransportPeer
|
||||||
|
|
||||||
|
|
||||||
|
class MultiTransport(DatagramServerTransport):
|
||||||
|
"""Run both WebTransport and QUIC transports and route sends.
|
||||||
|
|
||||||
|
Inbound datagrams share the same on_datagram callback from GameServer,
|
||||||
|
so GameServer sees a unified source of datagrams.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, wt_transport: DatagramServerTransport, quic_transport: DatagramServerTransport):
|
||||||
|
self._wt = wt_transport
|
||||||
|
self._quic = quic_transport
|
||||||
|
|
||||||
|
async def send(self, data: bytes, peer: TransportPeer) -> None:
|
||||||
|
# WebTransport peers are tuples (proto, flow_id); QUIC uses protocol instance
|
||||||
|
if isinstance(peer.addr, tuple) and len(peer.addr) == 2:
|
||||||
|
await self._wt.send(data, peer)
|
||||||
|
else:
|
||||||
|
await self._quic.send(data, peer)
|
||||||
|
|
||||||
|
async def run(self) -> None:
|
||||||
|
await asyncio.gather(self._wt.run(), self._quic.run())
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@@ -136,7 +136,7 @@ class GameServer:
|
|||||||
# --- Simulation ---
|
# --- Simulation ---
|
||||||
|
|
||||||
def _consume_input_for_snake(self, s: Snake) -> None:
|
def _consume_input_for_snake(self, s: Snake) -> None:
|
||||||
# Consume at most one input; skip 180° turns when length>1
|
# Consume at most one input; skip 180° turns when length>1
|
||||||
while s.input_buf:
|
while s.input_buf:
|
||||||
nd = s.input_buf[0]
|
nd = s.input_buf[0]
|
||||||
# 180-degree check
|
# 180-degree check
|
||||||
@@ -452,7 +452,7 @@ class GameServer:
|
|||||||
full = pack_header(
|
full = pack_header(
|
||||||
self.runtime.version, PacketType.STATE_FULL, 0, self.runtime.next_seq(), self.runtime.state.tick & 0xFFFF
|
self.runtime.version, PacketType.STATE_FULL, 0, self.runtime.next_seq(), self.runtime.state.tick & 0xFFFF
|
||||||
) + body
|
) + body
|
||||||
await self.transport.send(full, peer)
|
await self.transport.send(full, peer)
|
||||||
else:
|
else:
|
||||||
# Partition by packing whole snakes first, apples only in first part; chunk a single oversized snake using 2-bit chunks
|
# Partition by packing whole snakes first, apples only in first part; chunk a single oversized snake using 2-bit chunks
|
||||||
update_id = self.runtime.next_update_id()
|
update_id = self.runtime.next_update_id()
|
||||||
@@ -507,7 +507,7 @@ class GameServer:
|
|||||||
dir_capacity_bytes = max(0, budget - overhead)
|
dir_capacity_bytes = max(0, budget - overhead)
|
||||||
if dir_capacity_bytes <= 0:
|
if dir_capacity_bytes <= 0:
|
||||||
break
|
break
|
||||||
# Convert capacity bytes to number of directions (each 2 bits → 4 dirs per byte)
|
# Convert capacity bytes to number of directions (each 2 bits → 4 dirs per byte)
|
||||||
dir_capacity = dir_capacity_bytes * 4
|
dir_capacity = dir_capacity_bytes * 4
|
||||||
if dir_capacity <= 0:
|
if dir_capacity <= 0:
|
||||||
break
|
break
|
||||||
|
|||||||
Reference in New Issue
Block a user