import asyncio import importlib import betterproto from game_server.net.kcp import Kcp from game_server.net.packet import NetPacket from utils.logger import Info,Error,Warn from rail_proto import cmd from rail_proto import lib as protos from game_server.game.player.player_manager import PlayerManager from game_server.dummy import dummyprotolist import traceback from utils.time import cur_timestamp_ms class PlayerSession: def __init__(self, transport, session_id, client_addr, db): self.transport = transport self.session_id = session_id self.client_addr = client_addr self.kcp = Kcp(session_id, self.send_output) self.kcp.set_wndsize(sndwnd=256, rcvwnd=256) self.kcp.set_nodelay(1, 5, 2, 0) self.is_destroyed = False self.db = db self.pending_notifies = list() self.player = PlayerManager() self.active = False self.last_received = asyncio.get_event_loop().time() self.connect_time_ms = cur_timestamp_ms() def update_last_received(self): self.last_received = asyncio.get_event_loop().time() def is_timeout(self, timeout=15): return asyncio.get_event_loop().time() - self.last_received > timeout def pending_notify(self, data: betterproto.Message, delay=0): """ This can be used to queue packet to be sent after response inside a handler """ self.pending_notifies.append((data, delay)) async def notify(self, data: betterproto.Message): msg_name = data.__class__.__name__ cmd_id = getattr(cmd.CmdID, msg_name, None) if not cmd_id: Warn(f"Server tried to send notify with unsupported message: {msg_name}") return response_packet = NetPacket.from_message(cmd_id, data) await self.send(response_packet) def send_output(self, data): self.transport.sendto(data, self.client_addr) async def consume(self, data): self.kcp.input(data) self.kcp.update(self.time()) self.kcp.flush() while True: packet = self.kcp.recv() if packet is None: break await self.handle_packet(packet) #self.kcp.update(self.time()) async def handle_packet(self, packet): net_packet = NetPacket.from_bytes(packet) cmd_id = net_packet.cmd_type request_name = cmd.get_key_by_value(cmd_id) if not request_name: Warn( f"Request doesn't have registered message_id: {cmd_id}" ) return if request_name[:-5] in dummyprotolist: dummy_cmd_id = getattr(cmd.CmdID, f"{request_name[:-5]}ScRsp", None) dummy_response = NetPacket.from_message(dummy_cmd_id, b'') await self.send(dummy_response) return try: try: req: betterproto.Message = getattr(protos, request_name)() req.parse(net_packet.body) except Exception: req = betterproto.Message() try: handle_result: betterproto.Message = await importlib.import_module( f"game_server.handlers.{request_name}" ).handle(self, req) if not handle_result: return except ModuleNotFoundError: Error(f"Unhandled request {request_name}") return except Exception: Error(f"Handler {request_name} returns error.") traceback.print_exc() return Info(f"Received cmd: {request_name}({cmd_id})") response_name = handle_result.__class__.__name__ cmd_type = getattr(cmd.CmdID, response_name, None) if not cmd_type: Warn( f"Server tried to send response with unsupported message: {response_name}" ) return response_packet = NetPacket.from_message(cmd_type, handle_result) await self.send(response_packet) asyncio.create_task(self.send_pending_notifies( self.pending_notifies.copy() )) self.pending_notifies.clear() except: pass async def send_pending_notifies( self, pending_notifies: list[tuple[betterproto.Message, int]] ): for notify, delay in pending_notifies: if delay > 0: await asyncio.sleep(delay) await self.notify(notify) async def send(self, packet): self.kcp.send(packet.to_bytes()) self.kcp.flush() cmd_id = packet.cmd_type request_name = cmd.get_key_by_value(cmd_id) Info(f"Sent cmd: {request_name}({cmd_id})") def time(self): return (cur_timestamp_ms()) - self.connect_time_ms