151 lines
5.2 KiB
Python
151 lines
5.2 KiB
Python
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
|
|
from utils.config import Config
|
|
|
|
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()
|
|
self.ignore_log_packets = [
|
|
cmd.CmdID.SceneEntityMoveScRsp,cmd.CmdID.SceneEntityMoveCsReq,
|
|
cmd.CmdID.PlayerHeartBeatCsReq,cmd.CmdID.PlayerHeartBeatScRsp,
|
|
]
|
|
|
|
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:
|
|
if Config.PacketLog:
|
|
Error(f"Unhandled request {request_name}")
|
|
return
|
|
except Exception:
|
|
Error(f"Handler {request_name} returns error.")
|
|
traceback.print_exc()
|
|
return
|
|
|
|
if Config.PacketLog and cmd_id not in self.ignore_log_packets:
|
|
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)
|
|
if Config.PacketLog and cmd_id not in self.ignore_log_packets:
|
|
Info(f"Sent cmd: {request_name}({cmd_id})")
|
|
|
|
def time(self):
|
|
return (cur_timestamp_ms()) - self.connect_time_ms
|