Skip to content

Commit

Permalink
Merge pull request #170 from tdameros/pong-server-docker
Browse files Browse the repository at this point in the history
Pong servers can now run in docker
  • Loading branch information
V-Fries authored Feb 5, 2024
2 parents 03042c7 + 809a704 commit 6cd0183
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 88 deletions.
7 changes: 3 additions & 4 deletions front/src/threejs/Scene/Ball.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export class Ball {

if (this.#handleCollisions(travel,
matchBoundingBox,
this.#movement.x < 0. ? leftPaddle : rightPaddle,
timeDelta)) {
this.#movement.x < 0. ? leftPaddle : rightPaddle,
timeDelta)) {
this.#threeJSGroup.position.sub(radiusCompensator);
}
}
Expand Down Expand Up @@ -90,8 +90,7 @@ export class Ball {
this.#movement.y = -this.#movement.y * this.#acceleration;
}
this.#threeJSGroup.position
.add(this.#movement.clone()
.multiplyScalar(timeDelta * (1. - closestT)));
.add(this.#movement.clone().multiplyScalar(timeDelta * (1. - closestT)));
return true;
}

Expand Down
15 changes: 14 additions & 1 deletion pong_server/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,17 @@ WORKDIR /app
COPY requirements.txt /app/

# Installez les dépendances
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

RUN apt update -y && \
apt upgrade -y && \
apt install -y lsof && \
rm -rf /var/lib/apt/lists/*

ARG PONG_GAME_CREATOR_PORT
ARG PONG_GAME_SERVERS_MIN_PORT
ARG PONG_GAME_SERVERS_MAX_PORT

ENV PONG_GAME_CREATOR_PORT=${PONG_GAME_CREATOR_PORT}
ENV PONG_GAME_SERVERS_MIN_PORT=${PONG_GAME_SERVERS_MIN_PORT}
ENV PONG_GAME_SERVERS_MAX_PORT=${PONG_GAME_SERVERS_MAX_PORT}
15 changes: 12 additions & 3 deletions pong_server/docker/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@ services:
pong-server:
networks:
- transcendence
ports:
- '${PONG_GAME_CREATOR_PORT}:${PONG_GAME_CREATOR_PORT}'
- '${PONG_GAME_SERVERS_MIN_PORT}-${PONG_GAME_SERVERS_MAX_PORT}:${PONG_GAME_SERVERS_MIN_PORT}-${PONG_GAME_SERVERS_MAX_PORT}'

build:
args:
- PONG_GAME_CREATOR_PORT=${PONG_GAME_CREATOR_PORT}
- PONG_GAME_SERVERS_MIN_PORT=${PONG_GAME_SERVERS_MIN_PORT}
- PONG_GAME_SERVERS_MAX_PORT=${PONG_GAME_SERVERS_MAX_PORT}
context: .
dockerfile: Dockerfile
command: python3 /app/src/main.py
volumes:
- pong_server_code:/app/src/
expose:
- "4242"

command: sh -c 'cd /app/ &&
python3 -m src.redirection_server'

restart: on-failure
1 change: 1 addition & 0 deletions pong_server/rerun.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ while true; do
kill -9 $(pgrep '[P]ython')
sleep 1
clear
`sed 's/^/export /' ../.env`
python -m unittest discover test
find . -name '*.py' | entr -z -d python3 -m src.redirection_server
done
50 changes: 38 additions & 12 deletions pong_server/src/game_server/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import os
import sys

import socketio
Expand All @@ -7,7 +8,7 @@

from src.game_server.Clock import Clock
from src.game_server.Game import Game
from src.game_server.print_server_uri import print_server_uri
from src.game_server.get_server_uri import get_server_uri
from src.game_server.update_player_movement_and_position import \
update_player_direction_and_position
from src.shared_code.emit import emit
Expand Down Expand Up @@ -86,14 +87,27 @@ async def wait_for_all_players_to_join():


async def background_task():
await print_server_uri(sio)
try:
pid = os.getpid()
server_uri = await get_server_uri(sio, pid)
print(f'uri: {server_uri}')
sys.stdout.flush()
""" Do not use logging()! This should always be printed as the redirection
server will read it """

await wait_for_all_players_to_join()
logging.info(f'Game Server({os.getpid()}) started with uri {server_uri}')

clock = Clock()
while True:
await game.get_scene().update(sio, clock.get_delta())
await sio.sleep(0.01)
await wait_for_all_players_to_join()

clock = Clock()
while True:
await game.get_scene().update(sio, clock.get_delta())
await sio.sleep(0.01)
except Exception as e:
print(f'Error: in background_task: {e}')
""" Do not use logging! This should always be printed as the game
creator will read it """
exit(2)


# The app arguments is not used but is required by
Expand All @@ -105,11 +119,23 @@ async def start_background_task(app):
app.on_startup.append(start_background_task)
if __name__ == '__main__':
try:
setup_logging()
setup_logging(f'Game Server({os.getpid()}): ')
game = Game(sys.argv[1:])
# web.run_app(app, port=4242)
web.run_app(app, host='localhost', port=0, access_log=None)

min_port = int(os.getenv('PONG_GAME_SERVERS_MIN_PORT'))
max_port = int(os.getenv('PONG_GAME_SERVERS_MAX_PORT'))

for port in range(min_port, max_port + 1):
try:
web.run_app(app, host='0.0.0.0', port=port, access_log=None)
exit(0)
except Exception:
continue

raise Exception('Could not find an available port')

except Exception as e:
print(f'Error: {e}')
""" Do not use logging! This should always be printed as the redirection
server will read it """
""" Do not use logging! This should always be printed as the game
creator will read it """
exit(1)
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
import os
import subprocess
import sys


def get_server_uri():
command = (f'lsof -a -p {os.getpid()} -i6'
async def get_server_uri(sio, pid):
await sio.sleep(0.1)
""" The async sleep is here so that the server starts before
this function is executed """

command = (f'lsof -a -p {pid} -i4'
f" | awk '{{print $9}}'"
f' | tail -n +2')
""" gets all open ipv6 sockets for current process
""" gets all open ipv4 sockets for current process
| gets the column with the uri of the socket
| removes the first line which only contains the name
of the column """

port: str = subprocess.check_output(command, shell=True).decode()
if len(port) == 0:
raise Exception('No open ipv6 sockets found')
return f'http://{port if port[-1] != '\n' else port[:-1]}'


async def print_server_uri(sio):
await sio.sleep(0.1)
""" The async sleep is here so that the server starts before
this function is executed """

print(f'uri: {get_server_uri()}')
""" Do not use logging()! This should always be printed as the redirection
server will read it """
sys.stdout.flush()
raise Exception('No open ipv4 sockets found')
return f'http://localhost{port if port[-1] != '\n' else port[1:-1]}'
# TODO this only works on localhost
44 changes: 2 additions & 42 deletions pong_server/src/redirection_server/Game.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,9 @@
import logging
import subprocess
import threading

import src.shared_code.settings as settings


class _GameServerLogger(threading.Thread):
def __init__(self, process: subprocess.Popen, server_uri: str):
super().__init__(daemon=True)
self._process: subprocess.Popen = process
self._uri = server_uri

def run(self):
while self._process.poll() is None:
line = self._process.stderr.readline()
if len(line) > 0:
print(f'\tServer({self._uri}): '
f'{line[:-1] if line[-1] == '\n' else line}')
remaining_output = self._process.communicate()[0]
for line in remaining_output.splitlines():
if len(line) > 0:
print(f'\tServer({self._uri}): '
f'{line[:-1] if line[-1] == '\n' else line}')
logging.info(f'\tServer({self._uri}) has stopped')


class Game(object):
def __init__(self, clients: list[str]):
self._clients: list[str] = clients
Expand All @@ -34,12 +13,10 @@ def create_server(self):
""" Can Raise Exception """

process: subprocess.Popen = self._start_server()

output: list[str] = []
while process.poll() is None:
line = process.stdout.readline()
if self._parse_subprocess_line(line):
self._start_game_server_logger(process, self._uri)
return
if settings.LOG_LEVEL != settings.NO_LOGS:
output.append(line)
Expand All @@ -50,18 +27,17 @@ def create_server(self):
if settings.LOG_LEVEL != settings.NO_LOGS:
output.append(line)

Game._print_program_output_on_error(output)

raise Exception('Error creating game server: undefined error')

def _start_server(self) -> subprocess.Popen:
""" Can Raise Exception """

try:
command = ['python3', '-m', 'src.game_server']
for player in self._clients:
command.append(player)
return subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
except Exception as e:
raise Exception(f'Failed to run command to start new game '
Expand All @@ -84,22 +60,6 @@ def _parse_subprocess_line(self, line):

return False

@staticmethod
def _start_game_server_logger(process: subprocess.Popen, server_uri: str):
if settings.LOG_LEVEL == settings.NO_LOGS:
return
game_server_logger = _GameServerLogger(process, server_uri)
game_server_logger.start()

@staticmethod
def _print_program_output_on_error(output: list[str]):
if settings.LOG_LEVEL == settings.NO_LOGS:
return
logging.error('\tError creating a new server, output:')
for line in output:
if len(line) > 0:
logging.error(f'\t\t{line[:-1] if line[-1] == '\n' else line}')

def was_server_created(self) -> bool:
return self._uri is not None

Expand Down
9 changes: 6 additions & 3 deletions pong_server/src/redirection_server/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import os

import socketio
from aiohttp import web
Expand All @@ -10,7 +11,7 @@
from src.shared_code.get_query_string import get_query_string
from src.shared_code.setup_logging import setup_logging

sio = socketio.AsyncServer(cors_allowed_origins=['http://localhost:5173'])
sio = socketio.AsyncServer(cors_allowed_origins='*')
app = web.Application()
sio.attach(app)

Expand Down Expand Up @@ -111,5 +112,7 @@ async def start_background_task(app):
app.on_startup.append(start_background_task)
if __name__ == '__main__':
setup_logging()
# web.run_app(app, port=4242)
web.run_app(app, host='localhost', port=4242, access_log=None)
web.run_app(app,
host='0.0.0.0',
port=int(os.getenv('PONG_GAME_CREATOR_PORT')),
access_log=None)
10 changes: 5 additions & 5 deletions pong_server/src/shared_code/setup_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import src.shared_code.settings as settings


def create_log_formatter():
def create_log_formatter(prefix: str):
return ColoredFormatter(
"%(log_color)s%(levelname)-8s %(asctime)s:%(reset)s %(message)s",
datefmt="%Hh:%Mm:%Ss",
f'{prefix}%(log_color)s%(levelname)-8s %(asctime)s:%(reset)s %(message)s',
datefmt='%Hh:%Mm:%Ss',
reset=True,
log_colors={
'DEBUG': 'cyan',
Expand All @@ -22,8 +22,8 @@ def create_log_formatter():
)


def setup_logging():
formatter = create_log_formatter()
def setup_logging(prefix: str = ''):
formatter = create_log_formatter(prefix)

handler = logging.StreamHandler()
handler.setFormatter(formatter)
Expand Down

0 comments on commit 6cd0183

Please sign in to comment.