Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 70 additions & 17 deletions rlbot/managers/match.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os
import stat
from pathlib import Path
from time import sleep

Expand All @@ -7,7 +9,7 @@
from rlbot.interface import RLBOT_SERVER_IP, RLBOT_SERVER_PORT, SocketRelay
from rlbot.utils import fill_desired_game_state, gateway
from rlbot.utils.logging import DEFAULT_LOGGER
from rlbot.utils.os_detector import MAIN_EXECUTABLE_NAME
from rlbot.utils.os_detector import CURRENT_OS, OS, RLBOT_SERVER_NAME


class MatchManager:
Expand All @@ -21,13 +23,16 @@ class MatchManager:
rlbot_server_port = RLBOT_SERVER_PORT
initialized = False

def __init__(
self,
main_executable_path: Path | None = None,
main_executable_name: str = MAIN_EXECUTABLE_NAME,
):
self.main_executable_path = main_executable_path
self.main_executable_name = main_executable_name
def __init__(self, rlbot_server_path: Path | None = None):
"""
Initialize a MatchManager.
Args:
rlbot_server_path: The path to the RLBotServer executable. The path is used to launch the server
if it is not already running. If set to None, the RLBotServer in the current working directory will
be used, and otherwise the globally installed RLBotServer will be used.
"""

self.rlbot_server_path = rlbot_server_path

self.rlbot_interface: SocketRelay = SocketRelay("")
self.rlbot_interface.packet_handlers.append(self._packet_reporter)
Expand All @@ -41,27 +46,75 @@ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
def ensure_server_started(self):
"""
Ensures that RLBotServer is running, starting it if it is not.
If no path to an RLBotServer executable was passed during initialization of the MatchManager,
the function will use the RLBotServer executable in the current working directory, if any, or
otherwise the global installed RLBotServer will be used, if any.
"""

exe_name = (
self.rlbot_server_path.stem
if self.rlbot_server_path is not None and self.rlbot_server_path.is_file()
else RLBOT_SERVER_NAME
)
self.rlbot_server_process, self.rlbot_server_port = gateway.find_server_process(
self.main_executable_name
exe_name
)
if self.rlbot_server_process is not None:
self.logger.info("Already have %s running!", self.main_executable_name)
self.logger.info("%s is already running!", exe_name)
return

if self.main_executable_path is None:
self.main_executable_path = Path.cwd()
if self.rlbot_server_path is None:
# Look in cwd or localappdata
path = Path.cwd() / RLBOT_SERVER_NAME
if not path.exists() and CURRENT_OS == OS.WINDOWS:
self.logger.debug(
f"Could not find RLBotServer in cwd ('{path.parent}'), trying %localappdata% instead."
)
path = (
Path(os.environ.get("LOCALAPPDATA"))
/ "RLBot5"
/ "bin"
/ RLBOT_SERVER_NAME
)
if not path.exists():
raise FileNotFoundError(
"Unable to find RLBotServer in the current working directory "
"or in the default installation location. "
"Is your antivirus messing you up? Check "
"https://github.com/RLBot/RLBot/wiki/Antivirus-Notes."
)
else:
# User specified path
path = self.rlbot_server_path
if path.exists() and path.is_dir():
path = path / RLBOT_SERVER_NAME
if not path.exists():
raise FileNotFoundError(
f"Unable to find RLBotServer at the specified path '{path}'."
)

if path is None or not os.access(path, os.F_OK):
raise FileNotFoundError(
f"Unable to find RLBotServer at '{path}'. "
"Is your antivirus messing you up? Check "
"https://github.com/RLBot/RLBot/wiki/Antivirus-Notes."
)

if not os.access(path, os.X_OK):
os.chmod(path, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)

rlbot_server_process, self.rlbot_server_port = gateway.launch(
self.main_executable_path,
self.main_executable_name,
)
if not os.access(path, os.X_OK):
raise PermissionError(
"Unable to execute RLBotServer due to file permissions! Is your antivirus messing you up? "
f"Check https://github.com/RLBot/RLBot/wiki/Antivirus-Notes. The exact path is '{path}'"
)

rlbot_server_process, self.rlbot_server_port = gateway.launch(path)
self.rlbot_server_process = psutil.Process(rlbot_server_process.pid)

self.logger.info(
"Started %s with process id %s",
self.main_executable_name,
path,
self.rlbot_server_process.pid,
)

Expand Down
62 changes: 19 additions & 43 deletions rlbot/utils/gateway.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import os
import socket
import stat
import subprocess
from pathlib import Path

Expand All @@ -14,21 +12,21 @@
import shlex


def find_main_executable_path(
main_executable_path: Path, main_executable_name: str
) -> tuple[Path, Path | None]:
main_executable_path = main_executable_path.absolute().resolve()
def find_file(base_dir: Path, file_name: str) -> Path | None:
"""
Looks for a file called `file_name` in the given `base_dir` directory and its subdirectories.
Returns the path to the file, or None if it was not found.
"""

# check if the path is directly to the main executable
if main_executable_path.is_file():
return main_executable_path.parent, main_executable_path
base_dir = base_dir.absolute().resolve()
assert base_dir.exists() and base_dir.is_dir(), f"'{base_dir}' is not a directory!"

# search subdirectories for the main executable
for path in main_executable_path.glob(f"**/{main_executable_name}"):
# Search subdirectories for the file
for path in base_dir.glob(f"**/{file_name}"):
if path.is_file():
return path.parent, path
return path

return main_executable_path, None
return None


def is_port_accessible(port: int):
Expand All @@ -51,47 +49,25 @@ def find_open_server_port() -> int:
)


def launch(
main_executable_path: Path, main_executable_name: str
) -> tuple[subprocess.Popen[bytes], int]:
directory, path = find_main_executable_path(
main_executable_path, main_executable_name
)

if path is None or not os.access(path, os.F_OK):
raise FileNotFoundError(
f"Unable to find RLBotServer at '{main_executable_path}'. "
"Is your antivirus messing you up? Check "
"https://github.com/RLBot/RLBot/wiki/Antivirus-Notes."
)

if not os.access(path, os.X_OK):
os.chmod(path, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)

if not os.access(path, os.X_OK):
raise PermissionError(
"Unable to execute RLBotServer due to file permissions! Is your antivirus messing you up? "
f"Check https://github.com/RLBot/RLBot/wiki/Antivirus-Notes. The exact path is {path}"
)

def launch(exe_path: Path) -> tuple[subprocess.Popen[bytes], int]:
port = find_open_server_port()

if CURRENT_OS == "Windows":
args = [str(path), str(port)]
args = [str(exe_path), str(port)]
else:
args = f"{shlex.quote(path.as_posix())} {port}" # on Unix, when shell=True, args must be a string for flags to reach the executable
DEFAULT_LOGGER.info("Launching RLBotServer with via %s", args)
args = f"{shlex.quote(exe_path.as_posix())} {port}" # on Unix, when shell=True, args must be a string for flags to reach the executable
DEFAULT_LOGGER.info("Launching RLBotServer via %s", args)

return subprocess.Popen(args, shell=True, cwd=directory), port
return subprocess.Popen(args, shell=True, cwd=exe_path.parent), port


def find_server_process(
main_executable_name: str,
exe_name: str,
) -> tuple[psutil.Process | None, int]:
logger = DEFAULT_LOGGER
for proc in psutil.process_iter():
try:
if proc.name() != main_executable_name:
if proc.name() != exe_name:
continue

args = proc.cmdline()
Expand All @@ -106,7 +82,7 @@ def find_server_process(
except Exception as e:
logger.error(
"Failed to read the name of a process while hunting for %s: %s",
main_executable_name,
exe_name,
e,
)

Expand Down
6 changes: 3 additions & 3 deletions rlbot/utils/os_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ class OS(IntEnum):

match platform.system():
case "Windows":
MAIN_EXECUTABLE_NAME = "RLBotServer.exe"
RLBOT_SERVER_NAME = "RLBotServer.exe"
CURRENT_OS = OS.WINDOWS
case "Linux":
MAIN_EXECUTABLE_NAME = "RLBotServer"
RLBOT_SERVER_NAME = "RLBotServer"
CURRENT_OS = OS.LINUX
case _ as unknown_os:
from rlbot.utils.logging import get_logger

MAIN_EXECUTABLE_NAME = ""
RLBOT_SERVER_NAME = ""
CURRENT_OS = OS.UNKNOWN

get_logger("os_detector").warning("Unknown OS: %s", unknown_os)
2 changes: 1 addition & 1 deletion rlbot/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2.0.0-beta.51"
__version__ = "2.0.0-beta.52"
8 changes: 6 additions & 2 deletions tests/many_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
from rlbot import flat
from rlbot.config import load_player_config
from rlbot.managers import MatchManager
from rlbot.utils.gateway import find_file
from rlbot.utils.os_detector import RLBOT_SERVER_NAME

DIR = Path(__file__).parent

BOT_PATH = DIR / "atba/atba.bot.toml"
RLBOT_SERVER_FOLDER = DIR / "../../core/RLBotCS/bin/Release/"
RLBOT_SERVER_PATH = find_file(
DIR / "../../core/RLBotCS/bin/Release/", RLBOT_SERVER_NAME
)

num_comms = set()

Expand All @@ -20,7 +24,7 @@ def handle_match_comm(comm: flat.MatchComm):


if __name__ == "__main__":
match_manager = MatchManager(RLBOT_SERVER_FOLDER)
match_manager = MatchManager(RLBOT_SERVER_PATH)
match_manager.rlbot_interface.match_comm_handlers.append(handle_match_comm)
match_manager.ensure_server_started()
match_manager.connect_and_run(
Expand Down
8 changes: 6 additions & 2 deletions tests/run_forever.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@
from rlbot import flat
from rlbot.config import load_player_config
from rlbot.managers import MatchManager
from rlbot.utils.gateway import find_file
from rlbot.utils.maps import GAME_MAP_TO_UPK, STANDARD_MAPS
from rlbot.utils.os_detector import RLBOT_SERVER_NAME

DIR = Path(__file__).parent

BOT_PATH = DIR / "atba/atba.bot.toml"
RLBOT_SERVER_FOLDER = DIR / "../../core/RLBotCS/bin/Release/"
RLBOT_SERVER_PATH = find_file(
DIR / "../../core/RLBotCS/bin/Release/", RLBOT_SERVER_NAME
)

if __name__ == "__main__":
match_manager = MatchManager(RLBOT_SERVER_FOLDER)
match_manager = MatchManager(RLBOT_SERVER_PATH)

current_map = -1

Expand Down
8 changes: 6 additions & 2 deletions tests/run_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@

from rlbot import flat
from rlbot.managers import MatchManager
from rlbot.utils.gateway import find_file
from rlbot.utils.os_detector import RLBOT_SERVER_NAME

DIR = Path(__file__).parent

MATCH_CONFIG_PATH = DIR / "human_vs_atba.toml"
RLBOT_SERVER_FOLDER = DIR / "../../core/RLBotCS/bin/Release/"
RLBOT_SERVER_PATH = find_file(
DIR / "../../core/RLBotCS/bin/Release/", RLBOT_SERVER_NAME
)

if __name__ == "__main__":
with MatchManager(RLBOT_SERVER_FOLDER) as man:
with MatchManager(RLBOT_SERVER_PATH) as man:
man.start_match(MATCH_CONFIG_PATH)
assert man.packet is not None

Expand Down