diff --git a/axuy/misc.py b/axuy/misc.py index 5d9aa9d..bc98b0d 100644 --- a/axuy/misc.py +++ b/axuy/misc.py @@ -19,6 +19,7 @@ from itertools import (chain, combinations_with_replacement, permutations, product) from random import choices, shuffle +from typing import Iterator, List, Tuple import numpy from pkg_resources import resource_filename @@ -30,17 +31,17 @@ SPACE = numpy.load(resource_filename('axuy', 'map.npy')) COLORS = tuple(map(numpy.float32, permutations((1.0, 0.5, 0.0)))) -def abspath(resource_name): +def abspath(resource_name) -> str: """Return a true filesystem path for the specified resource.""" return resource_filename('axuy', resource_name) -def color(code, value): +def color(code, value) -> numpy.float32: """Return NumPy float32 array of RGB colors from color name.""" return COLORS[code] * (value + 1) * 0.5 -def mapidgen(replacement=False): +def mapidgen(replacement=False) -> List[int, ...]: """Return a randomly generated map ID.""" mapid = list(range(48)) if replacement: return choices(mapid, k=48) @@ -59,7 +60,7 @@ def mapgen(mapid): return space -def neighbors(x, y, z): +def neighbors(x, y, z) -> Iterator[Tuple[int, int, int]]: """Return a generator of coordinates of images point (x, y, z) in neighbor universes. """ @@ -71,7 +72,7 @@ def norm(vector): return numpy.sqrt(numpy.sum(vector**2)) -def normalized(*vector): +def normalized(*vector) -> numpy.float32: """Return normalized vector as a NumPy array of float32.""" v = numpy.float32(vector) if not v.any(): return v @@ -95,7 +96,7 @@ def nine(x) -> int: return int(x % 9) -def placeable(space, x, y, z, r): +def placeable(space, x, y, z, r) -> bool: """Return whether a sphere of radius r can be placed at (x, y, z) in given space.""" return not any(space[i][j][k] for i, j, k in product( diff --git a/axuy/peer.py b/axuy/peer.py index f581975..7e8b1b3 100644 --- a/axuy/peer.py +++ b/axuy/peer.py @@ -24,6 +24,7 @@ from pickle import dumps, loads from queue import Empty, Queue from socket import socket, SOCK_DGRAM, SOL_SOCKET, SO_REUSEADDR from threading import Thread +from typing import Iterator, Tuple from .misc import mapgen, mapidgen from .pico import Picobot @@ -32,7 +33,29 @@ from .view import ConfigReader, View class Peer: """Axuy peer. - TODO: Documentation + + Parameters + ---------- + config : ConfigReader + Networking and other configurations. + + Attributes + ---------- + sock : socket + UDP socket for exchanging instantaneous states with other peers. + addr : Tuple[str, int] + Own's address. + q : Queue[Tuple[bytes, Tuple[str, int]]] + Queue of (data, addr), where addr is the address of the peer + who sent the raw data. + peers : List[Tuple[str, int], ...] + Addresses of connected peers. + space : numpy.ndarray of shape (12, 12, 9) of bools + 3D array of occupied space. + pico : Picobot + Protagonist. + view : View + World representation and renderer. """ def __init__(self, config): @@ -61,6 +84,19 @@ class Peer: """Peer status.""" return self.view.is_running + @property + def ready(self) -> Iterator[Tuple[bytes, Tuple[str, int]]]: + """Iterator of (data, addr) that can be used without waiting, + where addr is the address of the peer who sent the data. + """ + while True: + try: + yield self.q.get_nowait() + except Empty: + break + else: + self.q.task_done() + def serve(self, mapid): """Initiate other peers.""" with socket() as server: # TCP server @@ -76,21 +112,17 @@ class Peer: def pull(self): """Receive other peers' state.""" while self.is_running: self.q.put(self.sock.recvfrom(1<<16)) + while not self.q.empty(): + self.q.get() + self.q.task_done() def update(self): """Update the local states and send them to other peers.""" - while True: - try: - data, addr = self.q.get_nowait() - except Empty: - break - else: - health, pos, rot, shards = loads(data) - if addr not in self.view.picos: - self.peers.append(addr) - self.view.add_pico(addr) - self.view.picos[addr].sync(health, pos, rot, shards) - self.q.task_done() + for data, addr in self.ready: + if addr not in self.view.picos: + self.peers.append(addr) + self.view.add_pico(addr) + self.view.picos[addr].sync(*loads(data)) self.view.update() pico = self.pico @@ -102,9 +134,6 @@ class Peer: def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): - while not self.q.empty(): - self.q.get() - self.q.task_done() self.q.join() self.sock.close() self.view.close() diff --git a/axuy/pico.py b/axuy/pico.py index 23e415f..9795fe4 100644 --- a/axuy/pico.py +++ b/axuy/pico.py @@ -51,7 +51,7 @@ class Shard: Parameters ---------- - address : (str, int) + address : Tuple[str, int] IP address (host, port). space : np.ndarray of shape (12, 12, 9) of bools 3D array of occupied space. @@ -64,7 +64,7 @@ class Shard: Attributes ---------- - addr : (str, int) + addr : Tuple[str, int] IP address (host, port). power : int Relative destructive power. @@ -83,7 +83,7 @@ class Shard: self.rot = rotation @property - def pos(self): + def pos(self) -> np.float32: """Position in a NumPy array.""" return np.float32([self.x, self.y, self.z]) @@ -95,7 +95,7 @@ class Shard: self.z = z % 9 @property - def forward(self): + def forward(self) -> np.float32: """Direction in a NumPy array.""" return self.rot[-1] @@ -139,7 +139,7 @@ class Picobot: Parameters ---------- - address : (str, int) + address : Tuple[str, int] IP address (host, port). space : np.ndarray of shape (12, 12, 9) of bools 3D array of occupied space. @@ -152,7 +152,7 @@ class Picobot: Attributes ---------- - addr : (str, int) + addr : Tuple[str, int] IP address (host, port). space : np.ndarray of shape (12, 12, 9) of bools 3D array of occupied space. @@ -162,7 +162,7 @@ class Picobot: Position. rot : np.ndarray of shape (3, 3) of np.float32 Rotational matrix. - shards : dict of Shard + shards : Dict[int, Shard] Active shards. recoil_u : np.ndarray of length 3 of np.float32 Recoil direction (unit vector). @@ -201,7 +201,7 @@ class Picobot: return self.health < 0 @property - def forward(self): + def forward(self) -> np.float32: """Direction in a NumPy array.""" return self.rot[-1] diff --git a/axuy/view.py b/axuy/view.py index 0724a69..bde4c16 100644 --- a/axuy/view.py +++ b/axuy/view.py @@ -91,13 +91,13 @@ class ConfigReader: Port to bind the peer to. seeder : str Address of the peer that created the map. - size : (int, int) + size : Tuple[int, int] GLFW window resolution. vsync : bool Vertical synchronization. zmlvl : float Zoom level. - key, mouse : dict of (str, int) + key, mouse : Dict[str, int] Input control. mouspeed : float Relative camera rotational speed. @@ -192,30 +192,30 @@ class View: Parameters ---------- - address : (str, int) + address : Tuple[str, int] IP address (host, port). camera : Picobot Protagonist whose view is the camera. space : np.ndarray of shape (12, 12, 9) of bools 3D array of occupied space. - size : (int, int) + size : Tuple[int, int] GLFW window resolution. vsync : bool Vertical synchronization. - ctl : dict of (str, int) + ctl : Dict[str, int] Input control. Attributes ---------- - addr : (str, int) + addr : Tuple[str, int] IP address (host, port). space : np.ndarray of shape (12, 12, 9) of bools 3D array of occupied space. camera : Picobot Protagonist whose view is the camera. - picos : dict of (address, Picobot) + picos : Dict[Tuple[str, int], Picobot] Enemies characters. - colors : dict of (address, str) + colors : Dict[Tuple[str, int], str] Color names of enemies. window : GLFW window zmlvl : float @@ -253,7 +253,7 @@ class View: Frame buffers for bloom-effect post-processing. last_time : float timestamp in seconds of the previous frame. - fpses : deque of floats + fpses : Deque[float, ...] FPS during the last 5 seconds to display the average. """