Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for multiple player ratings #486

Merged
merged 15 commits into from
Sep 6, 2019
2 changes: 2 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ humanize = "*"
pytest = "*"
pytest-mock = "*"
pytest-cov = "*"
pytest-asyncio = "*"
coveralls = "*"
mock = "*"
vulture = "*"
asynctest = "*"

[requires]
python_version = "3.6"
484 changes: 90 additions & 394 deletions Pipfile.lock

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,20 @@ def signal_handler(_sig, _frame):
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)

engine_fut = asyncio.ensure_future(
server.db.connect_engine(
database = server.db.FAFDatabase(loop)
db_fut = asyncio.ensure_future(
database.connect(
host=DB_SERVER,
port=int(DB_PORT),
user=DB_LOGIN,
password=DB_PASSWORD,
maxsize=10,
db=DB_NAME,
loop=loop
)
)
engine = loop.run_until_complete(engine_fut)
loop.run_until_complete(db_fut)

players_online = PlayerService()
players_online = PlayerService(database)

twilio_nts = None
if TWILIO_ACCOUNT_SID:
Expand All @@ -92,15 +92,16 @@ def signal_handler(_sig, _frame):
event_service, achievement_service
)

games = GameService(players_online, game_stats_service)
ladder_service = LadderService(games)
games = GameService(database, players_online, game_stats_service)
ladder_service = LadderService(database, games)

ctrl_server = loop.run_until_complete(
server.run_control_server(loop, players_online, games)
)

lobby_server = server.run_lobby_server(
address=('', 8001),
database=database,
geoip_service=GeoIpService(),
player_service=players_online,
games=games,
Expand All @@ -117,8 +118,7 @@ def signal_handler(_sig, _frame):
ladder_service.shutdown_queues()

# Close DB connections
engine.close()
loop.run_until_complete(engine.wait_closed())
loop.run_until_complete(database.close())

loop.close()

Expand Down
3 changes: 3 additions & 0 deletions server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import aiomeasures

from server.db import FAFDatabase
from . import config as config
from .games.game import GameState, VisibilityState
from .stats.game_stats_service import GameStatsService
Expand Down Expand Up @@ -81,6 +82,7 @@ def encode_queues(queues):

def run_lobby_server(
address: (str, int),
database: FAFDatabase,
player_service: PlayerService,
games: GameService,
loop,
Expand Down Expand Up @@ -137,6 +139,7 @@ def ping_broadcast():

def make_connection() -> LobbyConnection:
return LobbyConnection(
database=database,
geoip=geoip_service,
games=games,
nts_client=nts_client,
Expand Down
36 changes: 0 additions & 36 deletions server/abc/base_game.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from abc import ABCMeta, abstractmethod
from enum import Enum, IntEnum, unique


Expand All @@ -14,38 +13,3 @@ class GameConnectionState(Enum):
class InitMode(IntEnum):
NORMAL_LOBBY = 0
AUTO_LOBBY = 1


class BaseGame:
__metaclass__ = ABCMeta

@abstractmethod
async def on_game_end(self):
pass # pragma: no cover

async def rate_game(self):
pass # pragma: no cover

@property
@abstractmethod
def init_mode(self):
"""
The initialization mode to use for the Game
:rtype InitMode
:return:
"""
pass # pragma: no cover

@abstractmethod
def teams(self):
"""
A dictionary of lists representing teams

It is of the form:
>>> {
>>> 1: [Player(1), Player(2)],
>>> 2: [Player(3), Player(4)]
>>> }
:return:
"""
pass # pragma: no cover
53 changes: 0 additions & 53 deletions server/abc/base_player.py

This file was deleted.

53 changes: 27 additions & 26 deletions server/db/__init__.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
from aiomysql.sa import create_engine

engine = None

class FAFDatabase:
def __init__(self, loop):
self._loop = loop
self.engine = None

def set_engine(engine_):
"""
Set the globally used engine to the given argument
"""
global engine
engine = engine_
async def connect(self, host='localhost', port=3306, user='root',
password='', db='faf_test', minsize=1, maxsize=1,
echo=True):
if self.engine is not None:
raise ValueError("DB is already connected!")
self.engine = await create_engine(
host=host,
port=port,
user=user,
password=password,
db=db,
autocommit=True,
loop=self._loop,
minsize=minsize,
maxsize=maxsize,
echo=echo
)

async def close(self):
if self.engine is None:
return

async def connect_engine(
loop, host='localhost', port=3306, user='root', password='', db='faf_test',
minsize=1, maxsize=1, echo=True
):
engine = await create_engine(
host=host,
port=port,
user=user,
password=password,
db=db,
autocommit=True,
loop=loop,
minsize=minsize,
maxsize=maxsize,
echo=echo
)

set_engine(engine)
return engine
self.engine.close()
await self.engine.wait_closed()
self.engine = None
10 changes: 6 additions & 4 deletions server/game_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Dict, List, Optional, Union, ValuesView

import aiocron
import server.db as db
from server.db import FAFDatabase
from server import GameState, VisibilityState
from server.decorators import with_logger
from server.games import CoopGame, CustomGame, FeaturedMod, LadderGame
Expand All @@ -16,7 +16,8 @@ class GameService:
"""
Utility class for maintaining lifecycle of games
"""
def __init__(self, player_service, game_stats_service):
def __init__(self, database: FAFDatabase, player_service, game_stats_service):
self._db = database
self._dirty_games = set()
self._dirty_queues = set()
self.player_service = player_service
Expand Down Expand Up @@ -45,7 +46,7 @@ def __init__(self, player_service, game_stats_service):
self._update_cron = aiocron.crontab('*/10 * * * *', func=self.update_data)

async def initialise_game_counter(self):
async with db.engine.acquire() as conn:
async with self._db.engine.acquire() as conn:
# InnoDB, unusually, doesn't allow insertion of values greater than the next expected
# value into an auto_increment field. We'd like to do that, because we no longer insert
# games into the database when they don't start, so game ids aren't contiguous (as
Expand All @@ -66,7 +67,7 @@ async def update_data(self):
Loads from the database the mostly-constant things that it doesn't make sense to query every
time we need, but which can in principle change over time.
"""
async with db.engine.acquire() as conn:
async with self._db.engine.acquire() as conn:
result = await conn.execute("SELECT `id`, `gamemod`, `name`, description, publish, `order` FROM game_featuredMods")

async for row in result:
Expand Down Expand Up @@ -126,6 +127,7 @@ def create_game(
"""
game_id = self.create_uid()
args = {
"database": self._db,
"id_": game_id,
"host": host,
"name": name,
Expand Down
17 changes: 9 additions & 8 deletions server/gameconnection.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio

import server.db as db
from server.db import FAFDatabase
from sqlalchemy import text, select

from .abc.base_game import GameConnectionState
Expand All @@ -23,6 +23,7 @@ class GameConnection(GpgNetServerProtocol):

def __init__(
self,
database: FAFDatabase,
game: Game,
player: Player,
protocol: QDataStreamProtocol,
Expand All @@ -34,6 +35,7 @@ def __init__(
Construct a new GameConnection
"""
super().__init__()
self._db = database
self._logger.debug('GameConnection initializing')

self.protocol = protocol
Expand Down Expand Up @@ -211,7 +213,7 @@ async def handle_game_mods(self, mode, args):
elif mode == "uids":
uids = str(args).split()
self.game.mods = {uid: "Unknown sim mod" for uid in uids}
async with db.engine.acquire() as conn:
async with self._db.engine.acquire() as conn:
result = await conn.execute(
text("SELECT `uid`, `name` from `table_mod` WHERE `uid` in :ids"),
ids=tuple(uids))
Expand Down Expand Up @@ -267,7 +269,7 @@ async def handle_operation_complete(self, army, secondary, delta):
return

secondary, delta = int(secondary), str(delta)
async with db.engine.acquire() as conn:
async with self._db.engine.acquire() as conn:
# FIXME: Resolve used map earlier than this
result = await conn.execute(
"SELECT `id` FROM `coop_map` WHERE `filename` = %s",
Expand Down Expand Up @@ -301,9 +303,8 @@ async def handle_teamkill_report(self, gametime, reporter_id, reporter_name, tea
:param teamkiller_id: teamkiller id
:param teamkiller_name: teamkiller nickname - Used as a failsafe in case ID is wrong
"""

async with db.engine.acquire() as conn:


async with self._db.engine.acquire() as conn:
"""
Sometime the game sends a wrong ID - but a correct player name
We need to make sure the player ID is correct before pursuing
Expand Down Expand Up @@ -379,7 +380,7 @@ async def handle_teamkill_happened(self, gametime, victim_id, victim_name, teamk
self._logger.debug("Ignoring teamkill for AI player")
return

async with db.engine.acquire() as conn:
async with self._db.engine.acquire() as conn:
await conn.execute(
""" INSERT INTO `teamkills` (`teamkiller`, `victim`, `game_id`, `gametime`)
VALUES (%s, %s, %s, %s)""",
Expand Down Expand Up @@ -435,7 +436,7 @@ async def handle_game_state(self, state):
await self.game.launch()

if len(self.game.mods.keys()) > 0:
async with db.engine.acquire() as conn:
async with self._db.engine.acquire() as conn:
uids = list(self.game.mods.keys())
await conn.execute(text(
""" UPDATE mod_stats s JOIN mod_version v ON v.mod_id = s.mod_id
Expand Down
6 changes: 4 additions & 2 deletions server/games/custom_game.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import time

from .game import Game, ValidityState
from server.rating import RatingType
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now this makes sense, but it won't anymore once you finish multiple rating support :P

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want something else than an enum? Is it supposed to be populated from the db?


from server.abc.base_game import InitMode
from server.decorators import with_logger

Expand All @@ -14,5 +16,5 @@ async def rate_game(self):
if not self.enforce_rating and time.time() - self.launched_at < limit:
await self.mark_invalid(ValidityState.TOO_SHORT)
if self.validity == ValidityState.VALID:
new_ratings = self.compute_rating(rating='global')
await self.persist_rating_change_stats(new_ratings, rating='global')
new_ratings = self.compute_rating(RatingType.GLOBAL)
await self.persist_rating_change_stats(new_ratings, RatingType.GLOBAL)
Loading