-
-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0073162
commit 71b52d3
Showing
13 changed files
with
779 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .registered_listener import RegisteredListener, RegisteredEntityListener | ||
from .server_id import ServerID |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from typing import Coroutine | ||
|
||
|
||
class RegisteredListener: | ||
def __init__( | ||
self, | ||
listener_id: str, | ||
coroutine: Coroutine, | ||
) -> None: | ||
self.listener_id = listener_id | ||
self._coroutine = coroutine | ||
|
||
def get_coro(self): | ||
return self._coroutine | ||
|
||
def __eq__(self, other) -> bool: | ||
if not isinstance(other, RegisteredListener): | ||
return False | ||
|
||
return ( | ||
self.listener_id == other.listener_id | ||
and self._coroutine == other.get_coro() | ||
) | ||
|
||
def __hash__(self): | ||
return hash((self.listener_id, self._coroutine)) | ||
|
||
|
||
class RegisteredEntityListener(RegisteredListener): | ||
def __init__( | ||
self, | ||
listener_id: str, | ||
coroutine: Coroutine, | ||
entity_type: int, | ||
) -> None: | ||
super().__init__(listener_id, coroutine) | ||
self.entity_type = entity_type | ||
|
||
def get_entity_type(self): | ||
return self.entity_type | ||
|
||
def __eq__(self, other) -> bool: | ||
if not isinstance(other, RegisteredEntityListener): | ||
return False | ||
|
||
return super().__eq__(other) and self.listener_id == other.listener_id | ||
|
||
def __hash__(self): | ||
return hash((self.listener_id, self._coroutine, self.entity_type)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
class ServerID: | ||
def __init__(self, ip: str, port: str, player_id: int, player_token: int) -> None: | ||
self.ip = ip | ||
self.port = port | ||
self.player_id = player_id | ||
self.player_token = player_token | ||
|
||
def __str__(self) -> str: | ||
return f"{self.ip}:{self.port} {self.player_id} {self.player_token}" | ||
|
||
def get_server_string(self) -> str: | ||
return f"{self.ip}:{self.port}" | ||
|
||
def __hash__(self): | ||
return hash(self.__str__()) | ||
|
||
def __eq__(self, o: object) -> bool: | ||
if not isinstance(o, ServerID): | ||
return False | ||
|
||
return ( | ||
self.ip == o.ip | ||
and self.port == o.port | ||
and self.player_id == o.player_id | ||
and self.player_token == o.player_token | ||
) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .camera_manager import CameraManager | ||
from .camera_constants import CameraMovementOptions, MovementControls | ||
from .structures import CameraInfo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from collections import defaultdict | ||
from typing import Set, Dict | ||
from ..identification import RegisteredListener, RegisteredEntityListener, ServerID | ||
|
||
|
||
class HandlerList: | ||
def __init__(self) -> None: | ||
self._handlers: Dict[ServerID, Set[RegisteredListener]] = defaultdict(set) | ||
|
||
def unregister(self, listener: RegisteredListener, server_id: ServerID) -> None: | ||
self._handlers[server_id].remove(listener) | ||
|
||
def register(self, listener: RegisteredListener, server_id: ServerID) -> None: | ||
self._handlers[server_id].add(listener) | ||
|
||
def has(self, listener: RegisteredListener, server_id: ServerID) -> bool: | ||
return listener in self._handlers[server_id] | ||
|
||
def unregister_all(self) -> None: | ||
self._handlers.clear() | ||
|
||
def get_handlers( | ||
self, server_id: ServerID | ||
) -> Set[RegisteredListener]: | ||
return self._handlers.get(server_id, set()) | ||
|
||
|
||
class EntityHandlerList(HandlerList): | ||
def __init__(self) -> None: | ||
super().__init__() | ||
self._handlers: Dict[ServerID, Dict[str, Set[RegisteredEntityListener]]] = ( | ||
defaultdict(dict) | ||
) | ||
|
||
def unregister(self, listener: RegisteredEntityListener, server_id: ServerID) -> None: | ||
if listener.listener_id in self._handlers.get(server_id): | ||
self._handlers.get(server_id).get(listener.listener_id).remove(listener) | ||
|
||
def register(self, listener: RegisteredEntityListener, server_id: ServerID) -> None: | ||
if server_id not in self._handlers: | ||
self._handlers[server_id] = defaultdict(set) | ||
|
||
if listener.listener_id not in self._handlers.get(server_id): | ||
self._handlers.get(server_id)[listener.listener_id] = set() | ||
|
||
self._handlers.get(server_id).get(listener.listener_id).add(listener) | ||
|
||
def has(self, listener: RegisteredEntityListener, server_id: ServerID) -> bool: | ||
if server_id in self._handlers and listener.listener_id in self._handlers.get( | ||
server_id | ||
): | ||
return listener in self._handlers.get(server_id).get(listener.listener_id) | ||
|
||
return False | ||
|
||
def unregister_all(self) -> None: | ||
self._handlers.clear() | ||
|
||
def get_handlers( | ||
self, server_id: ServerID | ||
) -> Dict[str, Set[RegisteredEntityListener]]: | ||
return self._handlers.get(server_id, dict()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import math | ||
import time | ||
import asyncio | ||
from typing import Dict | ||
|
||
from ...exceptions.exceptions import RateLimitError | ||
from ...identification import ServerID | ||
|
||
|
||
class TokenBucket: | ||
def __init__( | ||
self, current: float, maximum: float, refresh_rate: float, refresh_amount: float | ||
) -> None: | ||
self.current = current | ||
self.max = maximum | ||
self.refresh_rate = refresh_rate | ||
self.refresh_amount = refresh_amount | ||
self.last_update = time.time() | ||
self.refresh_per_second = self.refresh_amount / self.refresh_rate | ||
|
||
def can_consume(self, amount) -> bool: | ||
if (self.current - amount) >= 0: | ||
return True | ||
|
||
return False | ||
|
||
def consume(self, amount: int = 1) -> None: | ||
self.current -= amount | ||
|
||
def refresh(self) -> None: | ||
time_now = time.time() | ||
|
||
time_delta = time_now - self.last_update | ||
self.last_update = time_now | ||
|
||
self.current = min([self.current + time_delta * self.refresh_amount, self.max]) | ||
|
||
|
||
class RateLimiter: | ||
SERVER_LIMIT = 50 | ||
SERVER_REFRESH_AMOUNT = 15 | ||
|
||
@classmethod | ||
def default(cls) -> "RateLimiter": | ||
""" | ||
Returns a default rate limiter with 3 tokens per second | ||
""" | ||
return cls() | ||
|
||
def __init__(self) -> None: | ||
self.socket_buckets: Dict[ServerID, TokenBucket] = {} | ||
self.server_buckets: Dict[str, TokenBucket] = {} | ||
self.lock = asyncio.Lock() | ||
|
||
def add_socket( | ||
self, | ||
server_id: ServerID, | ||
current: float, | ||
maximum: float, | ||
refresh_rate: float, | ||
refresh_amount: float, | ||
) -> None: | ||
self.socket_buckets[server_id] = TokenBucket( | ||
current, maximum, refresh_rate, refresh_amount | ||
) | ||
if server_id.get_server_string() not in self.server_buckets: | ||
self.server_buckets[server_id.get_server_string()] = TokenBucket( | ||
self.SERVER_LIMIT, self.SERVER_LIMIT, 1, self.SERVER_REFRESH_AMOUNT | ||
) | ||
|
||
async def can_consume(self, server_id: ServerID, amount: int = 1) -> bool: | ||
""" | ||
Returns whether the user can consume the amount of tokens provided | ||
""" | ||
async with self.lock: | ||
can_consume = True | ||
|
||
for bucket in [ | ||
self.socket_buckets.get(server_id), | ||
self.server_buckets.get(server_id.get_server_string()), | ||
]: | ||
bucket.refresh() | ||
if not bucket.can_consume(amount): | ||
can_consume = False | ||
|
||
return can_consume | ||
|
||
async def consume(self, server_id: ServerID, amount: int = 1) -> None: | ||
""" | ||
Consumes an amount of tokens from the bucket. You should first check to see whether it is possible with can_consume | ||
""" | ||
async with self.lock: | ||
for bucket in [ | ||
self.socket_buckets.get(server_id), | ||
self.server_buckets.get(server_id.get_server_string()), | ||
]: | ||
bucket.refresh() | ||
if not bucket.can_consume(amount): | ||
self.lock.release() | ||
raise RateLimitError("Not Enough Tokens") | ||
bucket.consume(amount) | ||
|
||
async def get_estimated_delay_time( | ||
self, server_id: ServerID, target_cost: int | ||
) -> float: | ||
""" | ||
Returns how long until the amount of tokens needed will be available | ||
""" | ||
async with self.lock: | ||
delay = 0 | ||
for bucket in [ | ||
self.socket_buckets.get(server_id), | ||
self.server_buckets.get(server_id.get_server_string()), | ||
]: | ||
val = ( | ||
math.ceil( | ||
( | ||
((target_cost - bucket.current) / bucket.refresh_per_second) | ||
+ 0.1 | ||
) | ||
* 100 | ||
) | ||
/ 100 | ||
) | ||
if val > delay: | ||
delay = val | ||
return delay | ||
|
||
async def remove(self, server_id: ServerID) -> None: | ||
""" | ||
Removes the limiter | ||
""" | ||
async with self.lock: | ||
del self.socket_buckets[server_id] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .ws import RustWebsocket |
Oops, something went wrong.