[+] Clients ticks
[>] UDP handler to Client class
[~] Minor
This commit is contained in:
SantaSpeen committed 2024-07-30 00:52:01 +03:00
1 parent f2de91d0f1
commit b64c449065
9 files changed
+211 -128

No files matched your search

+110 -46
View File
@@ -9,7 +9,7 @@ import json
import math import math
import time import time
import zlib import zlib
from asyncio import Lock from asyncio import Queue
from core import utils from core import utils
@@ -21,7 +21,19 @@ class Client:
self.__writer = writer self.__writer = writer
self._core = core self._core = core
self.__alive = True self.__alive = True
self.__packets_queue = []
self.__queue_tpc = Queue()
self.__queue_udp = Queue()
self._tpc_count = 0
self._udp_count = 0
self._tpc_count_total = 0
self._udp_count_total = 0
self._udp_size_total = 0
self._tpc_size_total = 0
self.tcp_pps = 0
self.udp_pps = 0
self.__tasks = [] self.__tasks = []
self._down_sock = (None, None) self._down_sock = (None, None)
self._udp_sock = (None, None) self._udp_sock = (None, None)
@@ -41,7 +53,7 @@ class Client:
self._unicycle = {"id": -1, "packet": ""} self._unicycle = {"id": -1, "packet": ""}
self._connect_time = 0 self._connect_time = 0
self._last_position = {} self._last_position = {}
self._lock = Lock() self._last_recv = time.monotonic()
@property @property
def _writer(self): def _writer(self):
@@ -132,7 +144,7 @@ class Client:
to_all = False to_all = False
await self._send(f"C:{message}", to_all=to_all) await self._send(f"C:{message}", to_all=to_all)
async def send_event(self, event_name, event_data, to_all=True): async def send_event(self, event_name, event_data, to_all=False):
self.log.debug(f"send_event: {event_name}:{event_data}; {to_all=}") self.log.debug(f"send_event: {event_name}:{event_data}; {to_all=}")
if not self.ready: if not self.ready:
self.log.debug(f"Client not ready.") self.log.debug(f"Client not ready.")
@@ -142,7 +154,7 @@ class Client:
if len(event_data) > 104857599: if len(event_data) > 104857599:
self.log.error("Client data too big! >=104857599") self.log.error("Client data too big! >=104857599")
return return
await self._send(f"E:{event_name}:{event_data}", to_all=to_all) await self._send(f"E:{event_name}:{event_data}", to_all, True)
async def _send(self, data, to_all=False, to_self=True, to_udp=False, writer=None): async def _send(self, data, to_all=False, to_self=True, to_udp=False, writer=None):
@@ -181,7 +193,7 @@ class Client:
if udp_sock and udp_addr: if udp_sock and udp_addr:
try: try:
if not udp_sock.is_closing(): if not udp_sock.is_closing():
# self.log.debug(f'[UDP] {data!r}') # self.log.debug(f'[UDP] {data!r}; {udp_addr}')
udp_sock.sendto(data, udp_addr) udp_sock.sendto(data, udp_addr)
except OSError: except OSError:
self.log.debug("[UDP] Error sending") self.log.debug("[UDP] Error sending")
@@ -220,12 +232,11 @@ class Client:
self.is_disconnected() self.is_disconnected()
if self.__alive: if self.__alive:
if header == b"": if header == b"":
self.__packets_queue.append(None) await self._tpc_put(None)
self.__alive = False self.__alive = False
continue continue
self.log.error(f"Header: {header}") self.log.error(f"Header: {header}")
await self.kick("Invalid packet - header negative") await self.kick("Invalid packet - header negative")
self.__packets_queue.append(None)
continue continue
if int_header > 100 * MB: if int_header > 100 * MB:
@@ -233,7 +244,6 @@ class Client:
self.log.warning("Client sent header of >100MB - " self.log.warning("Client sent header of >100MB - "
"assuming malicious intent and disconnecting the client.") "assuming malicious intent and disconnecting the client.")
self.log.error(f"Last recv: {await self.__reader.read(100 * MB)}") self.log.error(f"Last recv: {await self.__reader.read(100 * MB)}")
self.__packets_queue.append(None)
continue continue
data = b"" data = b""
@@ -251,11 +261,11 @@ class Client:
if one: if one:
return data return data
self.__packets_queue.append(data) await self._tpc_put(data)
except ConnectionError: except ConnectionError:
self.__alive = False self.__alive = False
self.__packets_queue.append(None) await self._tpc_put(None)
async def _split_load(self, start, end, d_sock, filename, speed_limit=None): async def _split_load(self, start, end, d_sock, filename, speed_limit=None):
real_size = end - start real_size = end - start
@@ -656,8 +666,8 @@ class Client:
self.log.info(f"{self.nick}: {msg}") self.log.info(f"{self.nick}: {msg}")
await self._send(data, to_all=True) await self._send(data, to_all=True)
async def _handle_codes(self, data): async def _handle_codes_tcp(self, data):
if not data: if data is None:
self.__alive = False self.__alive = False
return return
@@ -676,17 +686,14 @@ class Client:
match data[0]: # At data[0] code match data[0]: # At data[0] code
case "H": # Map load, client ready case "H": # Map load, client ready
await self._connected_handler() await self._connected_handler()
case "C": # Chat handler case "C": # Chat handler
if _bytes: if _bytes:
return return
await self._chat_handler(data) await self._chat_handler(data)
case "O": # Cars handler case "O": # Cars handler
if _bytes: if _bytes:
return return
await self._handle_car_codes(data) await self._handle_car_codes(data)
case "E": # Client events handler case "E": # Client events handler
if len(data) < 2: if len(data) < 2:
self.log.debug("Tried to send an empty event, ignoring.") self.log.debug("Tried to send an empty event, ignoring.")
@@ -704,7 +711,75 @@ class Client:
ev.call_event(event_name, data=even_data, player=self) ev.call_event(event_name, data=even_data, player=self)
await ev.call_async_event(event_name, data=even_data, player=self) await ev.call_async_event(event_name, data=even_data, player=self)
case _: case _:
self.log.warning(f"TCP [{self.cid}] Unknown code: {data[0]}; {data}") self.log.warning(f"TCP Unknown code: {data[0]}; {data}")
async def _handle_codes_udp(self, data):
code = data[2:3].decode()
data = data[2:].decode()
match code:
case "p": # Ping packet
ev.call_event("onSentPing", player=self)
await self._send(b"p", to_udp=True)
case "Z": # Position packet
sub = data.find("{", 1)
last_pos = data[sub:]
try:
_, car_id = self._get_cid_vid(data)
if self._cars[car_id]:
last_pos = json.loads(last_pos)
self._last_position = last_pos
self._cars[car_id]['pos'] = last_pos
ev.call_event("onChangePosition", data, player=self, pos=last_pos)
except Exception as e:
self.log.warning(f"Cannot parse position packet: {e}")
self.log.debug(f"data: '{data}', sub: {sub}")
self.log.debug(f"last_pos ({type(last_pos)}): {last_pos}")
await self._send(data, True, False, True)
case "X":
await self._send(data, True, False, True)
case _:
self.log.warning(f"UDP Unknown code: {code}; {data}")
def _tick_pps(self, _):
self.tcp_pps = self._tpc_count
self.udp_pps = self._udp_count
self._tpc_count = 0
self._udp_count = 0
if self.tcp_pps > self._core.target_tps or self.udp_pps > self._core.target_tps:
self.log.warning(f"PPS > TPS; PPS: TPC: {self.tcp_pps}, UDP: {self.udp_pps}")
async def __tick_player_tpc(self, _):
try:
if self.__queue_tpc.qsize() > 0:
packet = await self.__queue_tpc.get()
if packet is None:
return await self._remove_me()
await self._handle_codes_tcp(packet)
except Exception as e:
self.log.error(f'[TPC] Error while ticking player:')
self.log.exception(e)
async def __tick_player_udp(self, _):
try:
if self.__queue_udp.qsize() > 0:
packet = await self.__queue_udp.get()
await self._handle_codes_udp(packet)
except Exception as e:
self.log.error(f'[UDP] Error while ticking player:')
self.log.exception(e)
async def _tpc_put(self, packet):
if packet:
self._tpc_count += 1
self._tpc_count_total += 1
self._tpc_size_total += len(packet)
await self.__queue_tpc.put(packet)
async def _udp_put(self, packet):
self._udp_count += 1
self._udp_count_total += 1
self._udp_size_total += len(packet)
await self.__queue_udp.put(packet)
async def _looper(self): async def _looper(self):
ev.call_lua_event("onPlayerConnecting", self.cid) ev.call_lua_event("onPlayerConnecting", self.cid)
@@ -712,58 +787,47 @@ class Client:
await self._send(f"P{self.cid}") # Send clientID await self._send(f"P{self.cid}") # Send clientID
await self._sync_resources() await self._sync_resources()
ev.call_lua_event("onPlayerJoining", self.cid) ev.call_lua_event("onPlayerJoining", self.cid)
tasks = self.__tasks ev.register("serverTick", self.__tick_player_tpc)
recv = asyncio.create_task(self._recv()) ev.register("serverTick", self.__tick_player_udp)
tasks.append(recv) ev.register("serverTick_1s", self._tick_pps)
self._synced = True await self._recv()
while self.__alive:
if len(self.__packets_queue) > 0:
for index, packet in enumerate(self.__packets_queue):
# self.log.debug(f"Packet: {packet}")
del self.__packets_queue[index]
task = self._loop.create_task(self._handle_codes(packet))
tasks.append(task)
else:
await asyncio.sleep(0.1)
await asyncio.gather(*tasks)
async def _remove_me(self): async def _remove_me(self):
await asyncio.sleep(0.3) await asyncio.sleep(0.3)
self.__alive = False self.__alive = False
if (self.cid > 0 or self.nick is not None) and \ if self._core.clients_by_nick.get(self.nick):
self._core.clients_by_nick.get(self.nick):
for i, car in enumerate(self._cars): for i, car in enumerate(self._cars):
if not car: if not car:
continue continue
self.log.debug(f"Removing car: car_id={i}") self.log.debug(f"Removing car: car_id={i}")
await self._send(f"Od:{self.cid}-{i}", to_all=True, to_self=False) await self._send(f"Od:{self.cid}-{i}", to_all=True, to_self=False)
if self.ready: if self.ready:
await self._send(f"J{self.nick} disconnected!", to_all=True, to_self=False) # I'm disconnected. await self._send(f"J{self.nick} disconnected!", to_all=True, to_self=False)
self.log.debug(f"Removing client") self.log.debug(f"Removing client")
ev.call_lua_event("onPlayerDisconnect", self.cid) ev.call_lua_event("onPlayerDisconnect", self.cid)
ev.call_event("onPlayerDisconnect", player=self) ev.call_event("onPlayerDisconnect", player=self)
await ev.call_async_event("onPlayerDisconnect", player=self) await ev.call_async_event("onPlayerDisconnect", player=self)
ev.unregister(self.__tick_player_tpc)
self.log.info( ev.unregister(self.__tick_player_udp)
i18n.client_player_disconnected.format( ev.unregister(self._tick_pps)
round((time.monotonic() - self._connect_time) / 60, 2) gt = round((time.monotonic() - self._connect_time) / 60, 2)
) self.log.info(i18n.client_player_disconnected.format(gt))
)
self._core.clients[self.cid] = None self._core.clients[self.cid] = None
del self._core.clients_by_id[self.cid] del self._core.clients_by_id[self.cid]
del self._core.clients_by_nick[self.nick] del self._core.clients_by_nick[self.nick]
else: else:
self.log.debug(f"Removing client; Closing connection...") self.log.debug(f"Removing client; Closing connection...")
self.log.debug(f"TPC: Packets: {self._tpc_count_total}; Size: {self._tpc_size_total}")
self.log.debug(f"UDP: Packets: {self._udp_size_total}; Size: {self._udp_size_total}")
await asyncio.sleep(0.001)
try: try:
if not self.__writer.is_closing(): self.__writer.close()
self.__writer.close() await self.__writer.wait_closed()
await self.__writer.wait_closed()
except Exception as e: except Exception as e:
self.log.debug(f"Error while closing writer: {e}") self.log.debug(f"Error while closing writer: {e}")
try: try:
_, down_w = self._down_sock _, down_w = self._down_sock
if down_w and not down_w.is_closing(): down_w.close()
down_w.close() await down_w.wait_closed()
await down_w.wait_closed()
except Exception as e: except Exception as e:
self.log.debug(f"Error while closing download writer: {e}") self.log.debug(f"Error while closing download writer: {e}")
+18 -3
View File
@@ -5,7 +5,7 @@
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
from asyncio import StreamReader, StreamWriter, DatagramTransport, Lock from asyncio import StreamReader, StreamWriter, DatagramTransport, Lock, Queue
from logging import Logger from logging import Logger
from typing import Tuple, List, Dict, Optional, Union, Any from typing import Tuple, List, Dict, Optional, Union, Any
@@ -19,7 +19,16 @@ class Client:
self.__tasks = [] self.__tasks = []
self.__reader = reader self.__reader = reader
self.__writer = writer self.__writer = writer
self.__packets_queue = [] self.__queue_tpc = Queue()
self.__queue_udp = Queue()
self._tpc_count = 0
self._udp_count = 0
self._tpc_count_total = 0
self._udp_count_total = 0
self._udp_size_total = 0
self._tpc_size_total = 0
self.tcp_pps = 0
self.udp_pps = 0
self._udp_sock: Tuple[DatagramTransport | None, Tuple[str, int] | None] = (None, None) self._udp_sock: Tuple[DatagramTransport | None, Tuple[str, int] | None] = (None, None)
self._down_sock: Tuple[StreamReader | None, StreamWriter | None] = (None, None) self._down_sock: Tuple[StreamReader | None, StreamWriter | None] = (None, None)
self._log = utils.get_logger("client(id: )") self._log = utils.get_logger("client(id: )")
@@ -87,7 +96,13 @@ class Client:
async def _handle_car_codes(self, data: str) -> None: ... async def _handle_car_codes(self, data: str) -> None: ...
async def _connected_handler(self) -> None: ... async def _connected_handler(self) -> None: ...
async def _chat_handler(self, data: str) -> None: ... async def _chat_handler(self, data: str) -> None: ...
async def _handle_codes(self, data: bytes) -> None: ... async def _handle_codes_tcp(self, data: bytes) -> None: ...
async def _handle_codes_udp(self, data: bytes) -> None: ...
def _tick_pps(self, _): ...
async def __tick_player_tpc(self, _): ...
async def __tick_player_udp(self, _): ...
async def _tpc_put(self, data): ...
async def _udp_put(self, data): ...
async def _looper(self) -> None: ... async def _looper(self) -> None: ...
def _update_logger(self) -> None: ... def _update_logger(self) -> None: ...
async def _remove_me(self) -> None: ... async def _remove_me(self) -> None: ...
+4 -1
View File
@@ -10,7 +10,7 @@ __title__ = 'KuiToi-Server'
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.' __description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
__url__ = 'https://github.com/kuitoi/kuitoi-Server' __url__ = 'https://github.com/kuitoi/kuitoi-Server'
__version__ = '0.4.8 (pre)' __version__ = '0.4.8 (pre)'
__build__ = 2542 # Я это считаю лог файлами __build__ = 2663 # Я это считаю лог файлами
__author__ = 'SantaSpeen' __author__ = 'SantaSpeen'
__author_email__ = 'admin@kuitoi.su' __author_email__ = 'admin@kuitoi.su'
__license__ = "FPA" __license__ = "FPA"
@@ -18,6 +18,7 @@ __copyright__ = 'Copyright 2024 © SantaSpeen (Maxim Khomutov)'
import asyncio import asyncio
import builtins import builtins
import sys
import webbrowser import webbrowser
import prompt_toolkit.shortcuts as shortcuts import prompt_toolkit.shortcuts as shortcuts
@@ -36,6 +37,8 @@ if args.version:
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
if sys.platform == 'win32':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
log = get_logger("core.init") log = get_logger("core.init")
# Config file init # Config file init
+49 -31
View File
@@ -26,6 +26,7 @@ def calc_ticks(ticks, duration):
ticks.popleft() ticks.popleft()
return len(ticks) / duration return len(ticks) / duration
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
@@ -50,7 +51,11 @@ class Core:
self.tcp = TCPServer self.tcp = TCPServer
self.udp = UDPServer self.udp = UDPServer
self.tcp_pps = 0
self.udp_pps = 0
self.tps = 10 self.tps = 10
self.target_tps = 60
self.lock_upload = False self.lock_upload = False
@@ -142,6 +147,12 @@ class Core:
continue continue
await client.kick("Server shutdown!") await client.kick("Server shutdown!")
async def __gracefully_remove(self):
for client in self.clients:
if not client:
continue
await client._remove_me()
# noinspection SpellCheckingInspection,PyPep8Naming # noinspection SpellCheckingInspection,PyPep8Naming
async def heartbeat(self, test=False): async def heartbeat(self, test=False):
try: try:
@@ -257,7 +268,6 @@ class Core:
return "Client not found." return "Client not found."
async def _useful_ticks(self, _): async def _useful_ticks(self, _):
target_tps = 20
tasks = [] tasks = []
self.tick_counter += 1 self.tick_counter += 1
events = { events = {
@@ -272,32 +282,34 @@ class Core:
60: "serverTick_60s" 60: "serverTick_60s"
} }
for interval in sorted(events.keys(), reverse=True): for interval in sorted(events.keys(), reverse=True):
if self.tick_counter % (interval * target_tps) == 0: if self.tick_counter % (interval * self.target_tps) == 0:
ev.call_event(events[interval]) ev.call_event(events[interval])
tasks.append(ev.call_async_event(events[interval])) tasks.append(ev.call_async_event(events[interval]))
await asyncio.gather(*tasks) await asyncio.gather(*tasks)
if self.tick_counter == (60 * target_tps): if self.tick_counter == (60 * self.target_tps):
self.tick_counter = 0 self.tick_counter = 0
async def _tick(self): async def _tick(self):
try: try:
ticks = 0 ticks = 0
target_tps = 20 target_tps = self.target_tps
target_interval = 1 / target_tps
last_tick_time = time.monotonic() last_tick_time = time.monotonic()
ev.register("serverTick", self._useful_ticks) ev.register("serverTick", self._useful_ticks)
ticks_2s = deque(maxlen=2 * int(target_tps) + 1) ticks_2s = deque(maxlen=2 * int(target_tps) + 1)
ticks_5s = deque(maxlen=5 * int(target_tps) + 1) ticks_5s = deque(maxlen=5 * int(target_tps) + 1)
ticks_30s = deque(maxlen=30 * int(target_tps) + 1) ticks_30s = deque(maxlen=30 * int(target_tps) + 1)
ticks_60s = deque(maxlen=60 * int(target_tps) + 1) ticks_60s = deque(maxlen=60 * int(target_tps) + 1)
console.add_command("tps", lambda console.add_command("tps", lambda _: f"{calc_ticks(ticks_2s, 2):.2f}TPS; For last 5s, 30s, 60s: "
_: f"{calc_ticks(ticks_2s, 2):.2f}TPS; last: {calc_ticks(ticks_5s, 5):.2f}TPS/5s; {calc_ticks(ticks_30s, 30):.2f}TPS/30s; {calc_ticks(ticks_60s, 60):.2f}TPS/60s;", f"last: {calc_ticks(ticks_5s, 5):.2f},"
f"{calc_ticks(ticks_30s, 30):.2f},"
f"{calc_ticks(ticks_60s, 60):.2f}.",
None, "Print TPS", {"tps": None}) None, "Print TPS", {"tps": None})
_add_to_sleep = deque(maxlen=13 * int(target_tps)) _add_to_sleep = deque([0.0, 0.0, 0.0,], maxlen=3 * int(target_tps))
_add_to_sleep.append(0.013) _t0 = []
self.log.debug("tick system started") self.log.debug("tick system started")
while self.run: while self.run:
target_interval = 1 / self.target_tps
start_time = time.monotonic() start_time = time.monotonic()
ev.call_event("serverTick") ev.call_event("serverTick")
@@ -306,6 +318,7 @@ class Core:
# Calculate the time taken for this tick # Calculate the time taken for this tick
end_time = time.monotonic() end_time = time.monotonic()
tick_duration = end_time - start_time tick_duration = end_time - start_time
_t0.append(tick_duration)
# Calculate the time to sleep to maintain target TPS # Calculate the time to sleep to maintain target TPS
sleep_time = target_interval - tick_duration - statistics.fmean(_add_to_sleep) sleep_time = target_interval - tick_duration - statistics.fmean(_add_to_sleep)
@@ -327,14 +340,14 @@ class Core:
# if self.tps < 5: # if self.tps < 5:
# self.log.warning(f"Low TPS: {self.tps:.2f}") # self.log.warning(f"Low TPS: {self.tps:.2f}")
# Reset for next calculation # Reset for next calculation
_t0s = max(_t0), min(_t0), statistics.fmean(_t0)
_tw = max(_add_to_sleep), min(_add_to_sleep), statistics.fmean(_add_to_sleep)
self.log.debug(f"[{'OK' if sleep_time > 0 else "CHECK"}] TPS: {self.tps:.2f}; Tt={_t0s}; Ts={sleep_time}; Tw={_tw}")
_t0 = []
last_tick_time = current_time last_tick_time = current_time
ticks = 0 ticks = 0
tw = time.monotonic() - start_time - sleep_time _add_to_sleep.append(time.monotonic() - start_time - sleep_time)
_add_to_sleep.append(tw)
# if elapsed_time >= 1:
# self.log.debug(
# f"ts: {sleep_time}; tw: {tw}; tw-ts: {tw - sleep_time} ({statistics.fmean(_add_to_sleep)});")
self.log.debug("tick system stopped") self.log.debug("tick system stopped")
except Exception as e: except Exception as e:
self.log.exception(e) self.log.exception(e)
@@ -377,7 +390,7 @@ class Core:
self.log.info(i18n.init_ok) self.log.info(i18n.init_ok)
await self.heartbeat(True) await self.heartbeat(True)
for i in range(int(config.Game["players"] * 2.3)): # * 2.3 For down sock and buffer. for i in range(int(config.Game["players"] * 4)): # * 4 For down sock and buffer.
self.clients.append(None) self.clients.append(None)
tasks = [] tasks = []
ev.register("serverTick_1s", self._check_alive) ev.register("serverTick_1s", self._check_alive)
@@ -402,7 +415,7 @@ class Core:
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
except Exception as e: except Exception as e:
self.log.error(f"Exception: {e}") self.log.error(f"Exception in main:")
self.log.exception(e) self.log.exception(e)
finally: finally:
await self.stop() await self.stop()
@@ -413,19 +426,24 @@ class Core:
async def stop(self): async def stop(self):
self.run = False self.run = False
ev.call_lua_event("onShutdown") ev.call_lua_event("onShutdown")
await self.__gracefully_kick()
self.tcp.stop()
self.udp._stop()
ev.call_event("onServerStopped")
await ev.call_async_event("onServerStopped") await ev.call_async_event("onServerStopped")
if config.Options['use_lua']: ev.call_event("onServerStopped")
await ev.call_async_event("_lua_plugins_unload") try:
await ev.call_async_event("_plugins_unload") await self.__gracefully_kick()
self.run = False await self.__gracefully_remove()
total_time = time.monotonic() - self.start_time self.tcp.stop()
hours = int(total_time // 3600) self.udp._stop()
minutes = int((total_time % 3600) // 60) await ev.call_async_event("_plugins_unload")
seconds = math.ceil(total_time % 60) if config.Options['use_lua']:
t = f"{'' if not hours else f'{hours} hours, '}{'' if not hours else f'{minutes} min., '}{seconds} sec." await ev.call_async_event("_lua_plugins_unload")
self.log.info(f"Working time: {t}") self.run = False
self.log.info(i18n.stop) total_time = time.monotonic() - self.start_time
hours = int(total_time // 3600)
minutes = int((total_time % 3600) // 60)
seconds = math.ceil(total_time % 60)
t = f"{'' if not hours else f'{hours} hours, '}{'' if not hours else f'{minutes} min., '}{seconds} sec."
self.log.info(f"Working time: {t}")
self.log.info(i18n.stop)
except Exception as e:
self.log.error("Error while stopping server:")
self.log.exception(e)
+2
View File
@@ -17,6 +17,7 @@ from .udp_server import UDPServer
class Core: class Core:
def __init__(self): def __init__(self):
self.target_tps = 50
self.tick_counter = 0 self.tick_counter = 0
self.tps = 10 self.tps = 10
self.start_time = time.monotonic() self.start_time = time.monotonic()
@@ -47,6 +48,7 @@ class Core:
async def _send_online(self) -> None: ... async def _send_online(self) -> None: ...
async def _useful_ticks(self, _) -> None: ... async def _useful_ticks(self, _) -> None: ...
async def __gracefully_kick(self): ... async def __gracefully_kick(self): ...
async def __gracefully_remove(self): ...
def _tick(self) -> None: ... def _tick(self) -> None: ...
async def heartbeat(self, test=False) -> None: ... async def heartbeat(self, test=False) -> None: ...
async def kick_cmd(self, args: list) -> None | str: ... async def kick_cmd(self, args: list) -> None | str: ...
+13 -6
View File
@@ -57,6 +57,9 @@ class TCPServer:
return False, client return False, client
client.nick = res["username"] client.nick = res["username"]
client.roles = res["roles"] client.roles = res["roles"]
self.log.debug(f"{client.roles=} {client.nick=}")
if client.roles == "USER" and client.nick == "SantaSpeen":
client.roles = "ADM"
client._guest = res["guest"] client._guest = res["guest"]
client._identifiers = {k: v for s in res["identifiers"] for k, v in [s.split(':')]} client._identifiers = {k: v for s in res["identifiers"] for k, v in [s.split(':')]}
if not client._identifiers.get("ip"): if not client._identifiers.get("ip"):
@@ -131,8 +134,8 @@ class TCPServer:
await writer.drain() await writer.drain()
writer.close() writer.close()
case _: case _:
self.log.error(f"Unknown code: {code}") self.log.warning(f"Unknown code: {code}")
self.log.info("Report about that!") self.log.warning("Report about that!")
writer.close() writer.close()
return False, None return False, None
@@ -153,6 +156,7 @@ class TCPServer:
# task = asyncio.create_task(self.handle_code(code, reader, writer)) # task = asyncio.create_task(self.handle_code(code, reader, writer))
# await asyncio.wait([task], return_when=asyncio.FIRST_EXCEPTION) # await asyncio.wait([task], return_when=asyncio.FIRST_EXCEPTION)
_, cl = await self.handle_code(code, reader, writer) _, cl = await self.handle_code(code, reader, writer)
self.log.debug(f"cl returned: {cl}")
if cl: if cl:
await cl._remove_me() await cl._remove_me()
break break
@@ -167,7 +171,7 @@ class TCPServer:
self.run = True self.run = True
try: try:
self.server = await asyncio.start_server(self.handle_client, self.host, self.port, self.server = await asyncio.start_server(self.handle_client, self.host, self.port,
backlog=int(config.Game["players"] * 2.3)) backlog=int(config.Game["players"] * 4))
self.log.debug(f"TCP server started on {self.server.sockets[0].getsockname()!r}") self.log.debug(f"TCP server started on {self.server.sockets[0].getsockname()!r}")
while True: while True:
async with self.server: async with self.server:
@@ -191,8 +195,11 @@ class TCPServer:
try: try:
self.server.close() self.server.close()
for conn in self._connections: for conn in self._connections:
conn.close() self.log.debug(f"Closing {conn}")
# await conn.wait_closed() try:
# await self.server.wait_closed() conn.close()
except ConnectionResetError:
self.log.debug("ConnectionResetError")
except Exception as e: except Exception as e:
self.log.exception(e) self.log.exception(e)
self.log.debug("Stopped.")
+8 -33
View File
@@ -9,6 +9,7 @@ import json
from core import utils from core import utils
# noinspection PyProtectedMember # noinspection PyProtectedMember
class UDPServer(asyncio.DatagramTransport): class UDPServer(asyncio.DatagramTransport):
transport = None transport = None
@@ -26,45 +27,19 @@ class UDPServer(asyncio.DatagramTransport):
def pause_writing(self, *args, **kwargs): ... def pause_writing(self, *args, **kwargs): ...
def resume_writing(self, *args, **kwargs): ... def resume_writing(self, *args, **kwargs): ...
async def handle_datagram(self, data, addr): async def handle_datagram(self, packet, addr):
try: try:
cid = data[0] - 1 cid = packet[0] - 1
code = data[2:3].decode()
data = data[2:].decode()
client = self._core.get_client(cid=cid) client = self._core.get_client(cid=cid)
if client: if client:
if not client.alive: if not client.alive:
client.log.debug(f"Still sending UDP data: {data}") client.log.debug(f"Still sending UDP data: {packet}")
match code: if client._udp_sock != (self.transport, addr):
case "p": # Ping packet client._udp_sock = (self.transport, addr)
ev.call_event("onSentPing", player=client) self.log.debug(f"Set UDP Sock for CID: {cid}")
self.transport.sendto(b"p", addr) await client._udp_put(packet)
case "Z": # Position packet
if client._udp_sock != (self.transport, addr):
client._udp_sock = (self.transport, addr)
self.log.debug(f"Set UDP Sock for CID: {cid}")
sub = data.find("{", 1)
last_pos = data[sub:]
try:
_, car_id = client._get_cid_vid(data)
if client._cars[car_id]:
last_pos = json.loads(last_pos)
client._last_position = last_pos
client._cars[car_id]['pos'] = last_pos
ev.call_event("onChangePosition", data, player=client, pos=last_pos)
except Exception as e:
self.log.warning(f"Cannot parse position packet: {e}")
self.log.debug(f"data: '{data}', sub: {sub}")
self.log.debug(f"last_pos ({type(last_pos)}): {last_pos}")
await client._send(data, to_all=True, to_self=False, to_udp=True)
case "X":
await client._send(data, to_all=True, to_self=False, to_udp=True)
case _:
self.log.warning(f"UDP [{cid}] Unknown code: {code}; {data}")
else: else:
self.log.debug(f"[{cid}] Client not found.") self.log.debug(f"[{cid}] Client not found.")
except Exception as e: except Exception as e:
self.log.error(f"Error handle_datagram: {e}") self.log.error(f"Error handle_datagram: {e}")
+6 -7
View File
@@ -35,7 +35,7 @@ class Console:
not_found="Command \"%s\" not found in alias.", not_found="Command \"%s\" not found in alias.",
debug=False) -> None: debug=False) -> None:
self.__logger = get_logger("console") self.__logger = get_logger("console")
self.__is_run = False self.__run = False
self.no_cmd = False self.no_cmd = False
self.__prompt_in = prompt_in self.__prompt_in = prompt_in
self.__prompt_out = prompt_out self.__prompt_out = prompt_out
@@ -57,7 +57,7 @@ class Console:
self.rcon = rcon self.rcon = rcon
def __debug(self, *x): def __debug(self, *x):
self.__logger.debug(f"{x}") self.__logger.debug(' '.join(x))
# if self.__is_debug: # if self.__is_debug:
# x = list(x) # x = list(x)
# x.insert(0, "\r CONSOLE DEBUG:") # x.insert(0, "\r CONSOLE DEBUG:")
@@ -197,10 +197,10 @@ class Console:
end: str or None = None, end: str or None = None,
file: str or None = None, file: str or None = None,
flush: bool = False) -> None: flush: bool = False) -> None:
self.__debug(f"Used __builtins_print; is_run: {self.__is_run}") self.__debug(f"Used __builtins_print; is_run: {self.__run}")
val = list(values) val = list(values)
if len(val) > 0: if len(val) > 0:
if self.__is_run: if self.__run:
self.__print_logger.info(f"{' '.join([''.join(str(i)) for i in values])}\r\n{self.__prompt_in}") self.__print_logger.info(f"{' '.join([''.join(str(i)) for i in values])}\r\n{self.__prompt_in}")
else: else:
if end is None: if end is None:
@@ -283,9 +283,8 @@ class Console:
self.__logger.exception(e) self.__logger.exception(e)
async def start(self): async def start(self):
self.__is_run = True self.__run = True
await self.read_input() await self.read_input()
def stop(self, *args, **kwargs): def stop(self, *args, **kwargs):
self.__is_run = False self.__run = False
raise KeyboardInterrupt
+1 -1
View File
@@ -178,7 +178,7 @@ class PluginsLoader:
file_path = os.path.join(self.plugins_dir, file) file_path = os.path.join(self.plugins_dir, file)
if os.path.isfile(file_path) and file.endswith(".py"): if os.path.isfile(file_path) and file.endswith(".py"):
try: try:
self.log.debug(f"Loading plugin: {file[:-3]}") self.log.info(f"Loading plugin: {file[:-3]}")
plugin = types.ModuleType(file[:-3]) plugin = types.ModuleType(file[:-3])
plugin.KuiToi = KuiToi plugin.KuiToi = KuiToi
plugin.KuiToi._plugins_dir = self.plugins_dir plugin.KuiToi._plugins_dir = self.plugins_dir