diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..427aef1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.idea/.gitignore +.idea/misc.xml +.idea/modules.xml +.idea/sample-playmaker-server-python-thrift.iml +.idea/vcs.xml +.idea/inspectionProfiles/profiles_settings.xml +.idea/inspectionProfiles/Project_Default.xml +venv/ +.venv/ +utils/__pycache__/PFProcessServer.cpython-310.pyc +soccer/__pycache__/ +scripts/proxy +scripts/rcssserver +__pycache__/ diff --git a/check_requirements.py b/check_requirements.py new file mode 100644 index 0000000..7c0047b --- /dev/null +++ b/check_requirements.py @@ -0,0 +1,20 @@ + +import pkg_resources +import sys + +def check_requirements(requirements_file='requirements.txt'): + with open(requirements_file, 'r') as file: + requirements = file.readlines() + + for requirement in requirements: + requirement = requirement.strip() + try: + pkg_resources.require(requirement) + except pkg_resources.VersionConflict as e: + print(f"WARNING: {str(e)}") + except pkg_resources.DistributionNotFound as e: + print(f"ERROR: {str(e)}") + sys.exit(1) + +if __name__ == "__main__": + check_requirements() diff --git a/scripts/download-proxy.sh b/scripts/download-proxy.sh new file mode 100755 index 0000000..58bbba8 --- /dev/null +++ b/scripts/download-proxy.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +# check proxy directory exists, if exists, remove it +if [ -d proxy ]; then + echo "proxy directory exists, remove it" + rm -rf proxy +fi + +mkdir proxy + +cd proxy + +# Check if curl exists +if command -v curl >/dev/null 2>&1; then + echo "curl is installed." +else + echo "curl is not installed. Please install it." + exit 1 +fi + +# Check if get exists +if command -v wget >/dev/null 2>&1; then + echo "wget is installed." +else + echo "wget is not installed. Please install it." + exit 1 +fi + +# download soccer simulation proxy +wget $(curl -s "https://api.github.com/repos/clsframework/soccer-simulation-proxy/releases/latest" | grep -oP '"browser_download_url": "\K[^"]*' | grep "soccer-simulation-proxy.tar.gz") + +tar -xvf soccer-simulation-proxy.tar.gz + +mv soccer-simulation-proxy/* . + +rm -rf soccer-simulation-proxy + +rm soccer-simulation-proxy.tar.gz diff --git a/scripts/download-rcssserver.sh b/scripts/download-rcssserver.sh new file mode 100755 index 0000000..3696619 --- /dev/null +++ b/scripts/download-rcssserver.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +# check rcssserver directory exists, if exists, remove it +if [ -d rcssserver ]; then + echo "rcssserver directory exists, remove it" + rm -rf rcssserver +fi + +mkdir rcssserver + +cd rcssserver + +# Check if curl exists +if command -v curl >/dev/null 2>&1; then + echo "curl is installed." +else + echo "curl is not installed. Please install it." + exit 1 +fi + +# Check if get exists +if command -v wget >/dev/null 2>&1; then + echo "wget is installed." +else + echo "wget is not installed. Please install it." + exit 1 +fi + +# download soccer simulation server App Image +wget $(curl -s https://api.github.com/repos/clsframework/rcssserver/releases/latest | grep -oP '"browser_download_url": "\K(.*rcssserver-x86_64-.*\.AppImage)' | head -n 1) + +# check download is successful +if [ ! -f *.AppImage ]; then + echo "Download failed" + exit 1 +fi + +mv rcssserver-x86_64-*.AppImage rcssserver + +chmod +x rcssserver \ No newline at end of file diff --git a/server.py b/server.py index 71db0ee..7138511 100644 --- a/server.py +++ b/server.py @@ -14,6 +14,8 @@ from multiprocessing import Manager, Lock import logging from pyrusgeom.vector_2d import Vector2D +import argparse + logging.basicConfig(level=logging.DEBUG) @@ -161,4 +163,7 @@ def serve(port): if __name__ == '__main__': - serve(50051) + parser = argparse.ArgumentParser(description='Run play maker server') + parser.add_argument('-p', '--g-port', required=False, help='The port of the server', default=50051) + args = parser.parse_args() + serve(args.g_port) diff --git a/start-team.py b/start-team.py new file mode 100644 index 0000000..4ec7f4f --- /dev/null +++ b/start-team.py @@ -0,0 +1,85 @@ +import subprocess +import os +import signal +import threading +import logging +import argparse +import check_requirements + + +# Set up logging +logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') + +def run_server_script(args): + # Start the server.py script as a new process group + process = subprocess.Popen( + ['python3', 'server.py', '--g-port', args.g_port], + preexec_fn=os.setsid, # Create a new session and set the process group ID + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT # Capture stderr and redirect it to stdout + ) + return process + +def run_start_script(args): + # Start the start.sh script in its own directory as a new process group + process = subprocess.Popen( + ['bash', 'start.sh', '-t', args.team_name, '--g-port', args.g_port], + cwd='scripts/proxy', # Corrected directory to where start.sh is located + preexec_fn=os.setsid, # Create a new session and set the process group ID + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT # Capture stderr and redirect it to stdout + ) + return process + +def stream_output(process, prefix): + # Stream output from the process and log it with a prefix + for line in iter(process.stdout.readline, b''): + logging.debug(f'{prefix} {line.decode().strip()}') + process.stdout.close() + +def kill_process_group(process): + try: + os.killpg(os.getpgid(process.pid), signal.SIGTERM) # Send SIGTERM to the process group + except ProcessLookupError: + pass # The process might have already exited + +if __name__ == "__main__": + # Set up argument parsing + parser = argparse.ArgumentParser(description='Run server and team scripts.') + parser.add_argument('-t', '--team_name', required=False, help='The name of the team', default='CLS') + parser.add_argument('--g-port', required=False, help='The port of the server', default='50051') + args = parser.parse_args() + + try: + # Check Python requirements + logging.debug("Checking Python requirements...") + check_requirements.check_requirements() + + # Run the server.py script first + server_process = run_server_script(args) + logging.debug(f"Started server.py process with PID: {server_process.pid}") + + # Run the start.sh script after server.py with the given arguments + start_process = run_start_script(args) + logging.debug(f"Started start.sh process with PID: {start_process.pid} with team name {args=}") + + # Monitor both processes and log their outputs + server_thread = threading.Thread(target=stream_output, args=(server_process, 'server:')) + start_thread = threading.Thread(target=stream_output, args=(start_process, 'team:')) + + server_thread.start() + start_thread.start() + + # Wait for both threads to finish + server_thread.join() + start_thread.join() + + except KeyboardInterrupt: + logging.debug("Interrupted! Killing all processes.") + kill_process_group(server_process) + kill_process_group(start_process) + + finally: + # Ensure all processes are killed on exit + kill_process_group(server_process) + kill_process_group(start_process) diff --git a/start-team.sh b/start-team.sh new file mode 100755 index 0000000..5492057 --- /dev/null +++ b/start-team.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +# Ensure the script exits if any command fails +set -e +# check scripts/proxy directory does not exist, raise error +if [! -d scripts/proxy ]; then + echo "scripts/proxy directory does not exist" + exit 1 +fi + +team_name="CLS" +g_port=50051 + +# help function +usage() { + echo "Usage: $0 [options]" + echo "Options:" + echo " -t team_name: specify team name" + echo " --g-port GRPC PORT - specifies grpc port (default: 50051)" + exit 1 +} + +while [ $# -gt 0 ] +do + case $1 in + -t) + team_name=$2 + shift + ;; + --g-port) + g_port=$2 + shift + ;; + *) + echo 1>&2 + echo "invalid option \"${1}\"." 1>&2 + echo 1>&2 + usage + exit 1 + ;; + esac + + shift 1 +done + +# Check Python requirements +echo "Checking Python requirements..." +python3 check_requirements.py + +# Start server.py in the background +echo "Starting server.py..." +python3 server.py --g-port $g_port & +server_pid=$! + +# Function to kill server and team processes on exit +cleanup() { + echo "Cleaning up..." + kill $server_pid + kill $start_pid +} + +# Trap the exit signal to cleanup processes +trap cleanup EXIT + +# Wait a moment to ensure the server has started (optional) +sleep 2 + +# Start start.sh script in the correct directory with arguments +echo "Starting start.sh with team name: $team_name and ..." +cd scripts/proxy +bash start.sh -t "$team_name" --g-port $g_port & +start_pid=$! + +# Wait for both background processes to finish +wait $server_pid +wait $start_pid