Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into switch-to-hatchling
Browse files Browse the repository at this point in the history
  • Loading branch information
CoolCat467 committed Oct 20, 2024
2 parents cc0a1b6 + caee991 commit 7bed24b
Show file tree
Hide file tree
Showing 20 changed files with 822 additions and 329 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/autodeps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:

# apply newer versions' formatting
- name: Black
run: black src/trio
run: black src/checkers

- name: uv
run: |
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ repos:
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
rev: v0.7.0
hooks:
- id: ruff
types: [file]
Expand Down
2 changes: 0 additions & 2 deletions computer_players/MiniMax_AI.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@

T = TypeVar("T")

PORT = 31613

# Player:
# 0 = False = Person = MIN = 0, 2
# 1 = True = AI (Us) = MAX = 1, 3
Expand Down
46 changes: 29 additions & 17 deletions computer_players/machine_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import sys
from abc import ABCMeta, abstractmethod
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING

import trio

Expand All @@ -20,6 +22,9 @@
)
from checkers.state import Action, Pos, State

if TYPE_CHECKING:
from collections.abc import AsyncGenerator

if sys.version_info < (3, 11):
from exceptiongroup import BaseExceptionGroup

Expand Down Expand Up @@ -140,12 +145,14 @@ def __init__(self, remote_state_class: type[RemoteState]) -> None:

self.running = True

self.add_components(
(
remote_state_class(),
GameClient("game_client"),
),
)
self.add_component(remote_state_class())

@asynccontextmanager
async def client_with_block(self) -> AsyncGenerator[GameClient, None]:
"""Add client temporarily with `with` block, ensuring closure."""
async with GameClient("game_client") as client:
with self.temporary_component(client):
yield client

def bind_handlers(self) -> None:
"""Register client event handlers."""
Expand Down Expand Up @@ -182,14 +189,21 @@ async def run_client(
)
client = MachineClient(remote_state_class)
with event_manager.temporary_component(client):
await event_manager.raise_event(
Event("client_connect", (host, port)),
)
print(f"Connected to server {host}:{port}")
while client.running: # noqa: ASYNC110
# Wait so backlog things happen
await trio.sleep(1)
print(f"Disconnected from server {host}:{port}")
async with client.client_with_block():
await event_manager.raise_event(
Event("client_connect", (host, port)),
)
print(f"Connected to server {host}:{port}")
try:
while client.running: # noqa: ASYNC110
# Wait so backlog things happen
await trio.sleep(1)
except KeyboardInterrupt:
print("Shutting down client from keyboard interrupt.")
await event_manager.raise_event(
Event("network_stop", None),
)
print(f"Disconnected from server {host}:{port}")
client.unbind_components()
connected.remove((host, port))

Expand Down Expand Up @@ -225,13 +239,11 @@ async def run_clients_in_local_servers(
)
await trio.sleep(1)
except BaseExceptionGroup as exc:
caught = False
for ex in exc.exceptions:
if isinstance(ex, KeyboardInterrupt):
print("Shutting down from keyboard interrupt.")
caught = True
break
if not caught:
else:
raise


Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ dependencies = [
"pygame~=2.6.0",
"typing_extensions>=4.12.2",
"mypy_extensions>=1.0.0",
"trio~=0.26.2",
"trio~=0.27.0",
"cryptography>=43.0.0",
"exceptiongroup; python_version < '3.11'",
]
Expand Down Expand Up @@ -97,7 +97,6 @@ disable_all_dunder_policy = true

[tool.black]
line-length = 79
target-version = ['py312']

[tool.ruff]
line-length = 79
Expand All @@ -117,12 +116,16 @@ select = [
"EXE", # flake8-executable
"F", # pyflakes
"FA", # flake8-future-annotations
"FLY", # flynt
"FURB", # refurb
"I", # isort
"ICN", # flake8-import-conventions
"N", # pep8-naming
"PIE", # flake8-pie
"PT", # flake8-pytest-style
"PYI", # flake8-pyi
"Q", # flake8-quotes
"R", # Refactor
"RET", # flake8-return
"RUF", # Ruff-specific rules
"S", # flake8-bandit
Expand Down
20 changes: 17 additions & 3 deletions src/checkers/base_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ async def write_varint(self, value: int, /) -> None:
"""Write a 32-bit signed integer in a variable length format.
For more information about variable length format check :meth:`._write_varuint`.
Raises ValueError if value is outside of the range of a 32-bit signed integer.
"""
val = to_twos_complement(value, bits=32)
await self._write_varuint(val, max_bits=32)
Expand All @@ -182,12 +184,17 @@ async def write_varlong(self, value: int, /) -> None:
"""Write a 64-bit signed integer in a variable length format.
For more information about variable length format check :meth:`._write_varuint`.
Raises ValueError if value is outside of the range of a 64-bit signed integer.
"""
val = to_twos_complement(value, bits=64)
await self._write_varuint(val, max_bits=64)

async def write_bytearray(self, data: bytes, /) -> None:
"""Write an arbitrary sequence of bytes, prefixed with a varint of it's size."""
"""Write an arbitrary sequence of bytes, prefixed with a varint of it's size.
Raises ValueError if length is is outside of the range of a 32-bit signed integer.
"""
await self.write_varint(len(data))
await self.write(data)

Expand Down Expand Up @@ -321,6 +328,8 @@ def write_varint(self, value: int, /) -> None:
"""Write a 32-bit signed integer in a variable length format.
For more information about variable length format check :meth:`._write_varuint`.
Raises ValueError if length is is outside of the range of a 32-bit signed integer.
"""
val = to_twos_complement(value, bits=32)
self._write_varuint(val, max_bits=32)
Expand All @@ -329,12 +338,17 @@ def write_varlong(self, value: int, /) -> None:
"""Write a 64-bit signed integer in a variable length format.
For more information about variable length format check :meth:`._write_varuint` docstring.
Raises ValueError if length is is outside of the range of a 64-bit signed integer.
"""
val = to_twos_complement(value, bits=64)
self._write_varuint(val, max_bits=64)

def write_bytearray(self, data: bytes, /) -> None:
"""Write an arbitrary sequence of bytes, prefixed with a varint of it's size."""
"""Write an arbitrary sequence of bytes, prefixed with a varint of it's size.
Raises ValueError if length is is outside of the range of a 32-bit signed integer.
"""
self.write_varint(len(data))
self.write(data)

Expand Down Expand Up @@ -429,7 +443,7 @@ async def _read_varuint(self, *, max_bits: int | None = None) -> int:
This is a standard way of transmitting ints, and it allows smaller numbers to take less bytes.
Reading will be limited up to integer values of ``max_bits`` bits, and trying to read bigger values will rase
an :exc:`IOError`. Note that setting ``max_bits`` to for example 32 bits doesn't mean that at most 4 bytes
an :exc:`OSError`. Note that setting ``max_bits`` to for example 32 bits doesn't mean that at most 4 bytes
will be read, in this case we would actually read at most 5 bytes, due to the variable encoding overhead.
Varints send bytes where 7 least significant bits are value bits, and the most significant bit is continuation
Expand Down
76 changes: 57 additions & 19 deletions src/checkers/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
__version__ = "0.0.0"

import struct
import time
import traceback
from typing import TYPE_CHECKING

import trio

from checkers import network
from checkers.base_io import StructFormat
from checkers.buffer import Buffer
from checkers.component import Event
Expand All @@ -39,10 +41,6 @@
encrypt_token_and_secret,
generate_shared_secret,
)
from checkers.network import (
NetworkStreamNotConnectedError,
NetworkTimeoutError,
)
from checkers.network_shared import (
ADVERTISEMENT_IP,
ADVERTISEMENT_PORT,
Expand Down Expand Up @@ -108,7 +106,7 @@ async def read_advertisements(
trio.socket.IP_ADD_MEMBERSHIP,
mreq,
)
else:
else: # IPv6
mreq = group_bin + struct.pack("@I", 0)
udp_socket.setsockopt(
trio.socket.IPPROTO_IPV6,
Expand Down Expand Up @@ -181,7 +179,7 @@ def __init__(self, name: str) -> None:
cbe = ClientBoundEvents
self.register_read_network_events(
{
cbe.callback_ping: "callback_ping->client",
cbe.callback_ping: "server->callback_ping",
cbe.create_piece: "server->create_piece",
cbe.select_piece: "server->select_piece",
cbe.create_tile: "server->create_tile",
Expand All @@ -206,7 +204,7 @@ def bind_handlers(self) -> None:
super().bind_handlers()
self.register_handlers(
{
# "callback_ping->client": self.print_callback_ping,
"server->callback_ping": self.read_callback_ping,
"gameboard_piece_clicked": self.write_piece_click,
"gameboard_tile_clicked": self.write_tile_click,
"server->create_piece": self.read_create_piece,
Expand Down Expand Up @@ -249,7 +247,22 @@ async def raise_disconnect(self, message: str) -> None:
assert self.not_connected

async def handle_read_event(self) -> None:
"""Raise events from server."""
"""Raise events from server.
Can raise following exceptions:
RuntimeError - Unhandled packet id
network.NetworkStreamNotConnectedError - Network stream is not connected
OSError - Stopped responding
trio.BrokenResourceError - Something is wrong and stream is broken
Shouldn't happen with write lock but still:
trio.BusyResourceError - Another task is already writing data
Handled exceptions:
trio.ClosedResourceError - Stream is closed or another task closes stream
network.NetworkTimeoutError - Timeout
network.NetworkEOFError - Server closed connection
"""
##print(f"{self.__class__.__name__}[{self.name}]: handle_read_event")
if not self.manager_exists:
return
Expand All @@ -260,34 +273,43 @@ async def handle_read_event(self) -> None:
# print("handle_read_event start")
event = await self.read_event()
except trio.ClosedResourceError:
self.running = False
await self.close()
print("Client side socket closed from another task.")
print(f"[{self.name}] Socket closed from another task.")
return
except NetworkTimeoutError as exc:
except network.NetworkTimeoutError as exc:
if self.running:
self.running = False
print(f"[{self.name}] NetworkTimeoutError")
await self.close()
traceback.print_exception(exc)
await self.raise_disconnect(
"Failed to read event from server.",
)
return
except NetworkStreamNotConnectedError as exc:
except network.NetworkStreamNotConnectedError as exc:
self.running = False
print(f"[{self.name}] NetworkStreamNotConnectedError")
traceback.print_exception(exc)
await self.close()
assert self.not_connected
raise
else:
await self.raise_event(event)
## await self.raise_event(
## Event(f"client[{self.name}]_read_event", None),
## )
except network.NetworkEOFError:
self.running = False
print(f"[{self.name}] NetworkEOFError")
await self.close()
await self.raise_disconnect(
"Server closed connection.",
)
return

await self.raise_event(event)

async def handle_client_connect(
self,
event: Event[tuple[str, int]],
) -> None:
"""Have client connect to address specified in event."""
print("handle_client_connect event fired")
if self.connect_event_lock.locked():
raise RuntimeError("2nd client connect fired!")
async with self.connect_event_lock:
Expand All @@ -310,10 +332,25 @@ async def handle_client_connect(
await self.raise_event(
Event("client_connection_closed", None),
)

else:
print(
"manager does not exist, cannot send client connection closed event.",
)
return
await self.raise_disconnect("Error connecting to server.")

async def read_callback_ping(self, event: Event[bytearray]) -> None:
"""Read callback_ping event from server."""
ns = int.from_bytes(event.data)
now = int(time.time() * 1e9)
difference = now - ns

# print(f'{difference / 1e9 = } seconds')

await self.raise_event(
Event("callback_ping", difference),
)

async def read_create_piece(self, event: Event[bytearray]) -> None:
"""Read create_piece event from server."""
buffer = Buffer(event.data)
Expand Down Expand Up @@ -360,7 +397,7 @@ async def write_piece_click(self, event: Event[tuple[Pos, int]]) -> None:

buffer = Buffer()
write_position(buffer, piece_position)
buffer.write_value(StructFormat.UINT, piece_type)
# buffer.write_value(StructFormat.UINT, piece_type)

await self.write_event(Event("select_piece->server", buffer))

Expand Down Expand Up @@ -510,6 +547,7 @@ async def handle_network_stop(self, event: Event[None]) -> None:
"""Send EOF if connected and close socket."""
if self.not_connected:
return
self.running = False
try:
await self.send_eof()
finally:
Expand Down
Loading

0 comments on commit 7bed24b

Please sign in to comment.