Bug 修复: - server.py: shutdown/close 顺序修正,加 OSError 保护 - world.py: from dataclasses import Field → from world.commons.field import Field - walk.py: execute() 末尾补 return False - field.py: _resolve_side 根据 is_left_team 动态映射 our/their(修复右队区域判断反向) - math_ops.py: 三个硬编码球门坐标函数加 NotImplementedError 防误用 稳定性改进: - server.py: 连接重试加 time.sleep(1.0) 防 CPU 空转 - world_parser.py + math_ops.py: bare except → except Exception/AttributeError - world_parser.py: 球速计算加 EMA 滤波 (α=0.4) 降低视觉噪声
118 lines
3.8 KiB
Python
118 lines
3.8 KiB
Python
import logging
|
|
import socket
|
|
import time
|
|
from select import select
|
|
from communication.world_parser import WorldParser
|
|
|
|
logger = logging.getLogger(__file__)
|
|
|
|
|
|
class Server:
|
|
def __init__(self, host: str, port: int, world_parser: WorldParser):
|
|
self.world_parser: WorldParser = world_parser
|
|
self.__host: str = host
|
|
self.__port: str = port
|
|
self.__socket: socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
self.__socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
self.__send_buff = []
|
|
self.__rcv_buffer_size = 1024
|
|
self.__rcv_buffer = bytearray(self.__rcv_buffer_size)
|
|
|
|
def connect(self) -> None:
|
|
logger.info("Connecting to server at %s:%d...", self.__host, self.__port)
|
|
while True:
|
|
try:
|
|
self.__socket.connect((self.__host, self.__port))
|
|
break
|
|
except ConnectionRefusedError:
|
|
logger.error(
|
|
"Connection refused. Make sure the server is running and listening on {self.__host}:{self.__port}."
|
|
)
|
|
time.sleep(1.0)
|
|
|
|
logger.info(f"Server connection established to {self.__host}:{self.__port}.")
|
|
|
|
def shutdown(self) -> None:
|
|
try:
|
|
self.__socket.shutdown(socket.SHUT_RDWR)
|
|
except OSError:
|
|
pass
|
|
self.__socket.close()
|
|
|
|
def send_immediate(self, msg: str) -> None:
|
|
"""
|
|
Sends only the desired message, without the buffer
|
|
"""
|
|
try:
|
|
self.__socket.send(
|
|
(len(msg)).to_bytes(4, byteorder="big") + msg.encode()
|
|
) # Add message length in the first 4 bytes
|
|
except BrokenPipeError:
|
|
print("\nError: socket was closed by rcssserver3d!")
|
|
exit()
|
|
|
|
def send(self) -> None:
|
|
"""
|
|
Send all committed messages
|
|
"""
|
|
if not self.__send_buff:
|
|
return
|
|
|
|
if len(select([self.__socket], [], [], 0.0)[0]) != 0:
|
|
logger.debug("Socket is readable while sending; keeping full-duplex command send.")
|
|
|
|
self.send_immediate(("".join(self.__send_buff)))
|
|
self.__send_buff = []
|
|
|
|
def commit(self, msg: str) -> None:
|
|
"""
|
|
Appends message to buffer
|
|
"""
|
|
self.__send_buff.append(msg)
|
|
|
|
def commit_and_send(self, msg: str = "") -> None:
|
|
"""
|
|
Appends a message to buffer and then sends the buffer
|
|
"""
|
|
self.commit(msg)
|
|
self.send()
|
|
|
|
def receive(self):
|
|
|
|
while True:
|
|
|
|
if (
|
|
self.__socket.recv_into(
|
|
self.__rcv_buffer, nbytes=4, flags=socket.MSG_WAITALL
|
|
) != 4
|
|
):
|
|
raise ConnectionResetError
|
|
|
|
msg_size = int.from_bytes(self.__rcv_buffer[:4], byteorder="big", signed=False)
|
|
|
|
if msg_size > self.__rcv_buffer_size:
|
|
self.__rcv_buffer_size = msg_size
|
|
self.__rcv_buffer = bytearray(self.__rcv_buffer_size)
|
|
|
|
if (
|
|
self.__socket.recv_into(
|
|
self.__rcv_buffer, nbytes=msg_size, flags=socket.MSG_WAITALL
|
|
) != msg_size
|
|
):
|
|
raise ConnectionResetError
|
|
|
|
self.world_parser.parse(
|
|
message=self.__rcv_buffer[:msg_size].decode()
|
|
)
|
|
|
|
# 如果socket没有更多数据就退出
|
|
if len(select([self.__socket], [], [], 0.0)[0]) == 0:
|
|
break
|
|
|
|
def commit_beam(self, pos2d: list, rotation: float) -> None:
|
|
assert len(pos2d) == 2
|
|
self.commit(f"(beam {pos2d[0]} {pos2d[1]} {rotation})")
|
|
|
|
def commit_catch(self, angle_deg: float) -> None:
|
|
logger.debug("commit_catch(%s) is a no-op on the current server backend.", angle_deg)
|