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
56 changes: 29 additions & 27 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ jobs:
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
set -x
sudo apt-get install -y xvfb libglu1-mesa x11-utils
sudo apt-get update
sudo apt-get install -y xvfb libglu1-mesa libegl1-mesa-dev x11-utils
uv sync --all-extras
unzip -o -P ${{ secrets.ROM_PASSWORD }} uncompressed_ROMs.zip
uv run python -m stable_retro.import "uncompressed ROMs"
Expand All @@ -81,31 +82,31 @@ jobs:
run: |
uv run python -c "import stable_retro; games = stable_retro.data.list_games(); print(f'Total games: {len(games)}'); assert len(games) > 60, 'ROM import failed'"

- name: Install MacOS test and package dependencies
if: ${{ matrix.os == 'macos-latest' }}
run: |
set -x
# XQuartz not needed - rendering tests are skipped on macOS (SKIP_RENDER=True)
# macOS doesn't support headless rendering (osmesa/egl) - only Linux does
brew install swig libzip
uv sync --all-extras
# Fix a bug in retro.data where it tries to load an inexistent version file
# Only applies to Python 3.10 where stable-retro is installed
RETRO_DIR="/Users/runner/work/plangym/plangym/.venv/lib/python${{ matrix.python-version }}/site-packages/retro"
if [ -d "$RETRO_DIR" ] && [ ! -f "$RETRO_DIR/VERSION.txt" ]; then
echo "0.9.1" > "$RETRO_DIR/VERSION.txt"
fi
- name: Import Retro ROMs (macOS Intel)
if: ${{ matrix.os == 'macos-latest' && runner.arch == 'X64' }}
run: |
set -x
unzip -o -P ${{ secrets.ROM_PASSWORD }} uncompressed_ROMs.zip
uv run python -m stable_retro.import "uncompressed ROMs"

- name: Verify ROM import success (macOS Intel)
if: ${{ matrix.os == 'macos-latest' && runner.arch == 'X64' }}
run: |
uv run python -c "import stable_retro; games = stable_retro.data.list_games(); print(f'Total games: {len(games)}'); assert len(games) > 60, 'ROM import failed'"
- name: Install MacOS test and package dependencies
if: ${{ matrix.os == 'macos-latest' }}
run: |
set -x
# XQuartz not needed - rendering tests are skipped on macOS (SKIP_RENDER=True)
# macOS doesn't support headless rendering (osmesa/egl) - only Linux does
brew install swig libzip
uv sync --all-extras
# Fix a bug in retro.data where it tries to load an inexistent version file
# Only applies to Python 3.10 where stable-retro is installed
RETRO_DIR="/Users/runner/work/plangym/plangym/.venv/lib/python${{ matrix.python-version }}/site-packages/retro"
if [ -d "$RETRO_DIR" ] && [ ! -f "$RETRO_DIR/VERSION.txt" ]; then
echo "0.9.1" > "$RETRO_DIR/VERSION.txt"
fi
- name: Import Retro ROMs (macOS Intel)
if: ${{ matrix.os == 'macos-latest' && runner.arch == 'X64' }}
run: |
set -x
unzip -o -P ${{ secrets.ROM_PASSWORD }} uncompressed_ROMs.zip
uv run python -m stable_retro.import "uncompressed ROMs"
- name: Verify ROM import success (macOS Intel)
if: ${{ matrix.os == 'macos-latest' && runner.arch == 'X64' }}
run: |
uv run python -c "import stable_retro; games = stable_retro.data.list_games(); print(f'Total games: {len(games)}'); assert len(games) > 60, 'ROM import failed'"

- name: Run Pytest on MacOS
if: ${{ matrix.os == 'macos-latest' }}
Expand Down Expand Up @@ -202,7 +203,8 @@ jobs:
UV_SYSTEM_PYTHON: 1
run: |
set -x
sudo apt-get install -y xvfb libglu1-mesa x11-utils
sudo apt-get update
sudo apt-get install -y xvfb libglu1-mesa libegl1-mesa-dev x11-utils
uv lock
uv pip install -r pyproject.toml --all-extras
uv pip install dist/*.whl
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ FROM ubuntu:22.04
RUN apt-get update && \
apt-get install -y --no-install-suggests --no-install-recommends \
make cmake git xvfb build-essential curl ssh wget ca-certificates swig \
libegl1-mesa-dev libglu1-mesa libgl1-mesa-glx \
python3 python3-dev && \
wget -O - https://bootstrap.pypa.io/get-pip.py | python3

Expand Down
23 changes: 14 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ ROM_PASSWORD ?= "NO_PASSWORD"
VERSION ?= latest
MUJOCO_PATH?=~/.mujoco

.PHONY: install-system-deps
install-system-deps:
sudo apt-get update
sudo apt-get install -y xvfb libglu1-mesa libegl1-mesa-dev libgl1-mesa-glx x11-utils

.PHONY: install-mujoco
install-mujoco:
mkdir ${MUJOCO_PATH}
Expand Down Expand Up @@ -73,24 +78,24 @@ test: test-doctest test-parallel test-singlecore test-ray

.PHONY: test-parallel
test-parallel:
PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_CLASSIC_CONTROL=1 SKIP_RENDER=True \
DISPLAY= MUJOCO_GL=egl PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_CLASSIC_CONTROL=1 SKIP_RENDER=True \
RAY_ENABLE_UV_RUN_RUNTIME_ENV=0 RAY_RUNTIME_ENV_CREATE_WORKING_DIR=0 \
uv run pytest -n $n -s -o log_cli=true -o log_cli_level=info \
--ignore=tests/vectorization/test_ray.py tests

.PHONY: test-ray
test-ray:
RAY_ENABLE_UV_RUN_RUNTIME_ENV=0 RAY_RUNTIME_ENV_CREATE_WORKING_DIR=0 MUJOCO_GL=egl \
DISPLAY= RAY_ENABLE_UV_RUN_RUNTIME_ENV=0 RAY_RUNTIME_ENV_CREATE_WORKING_DIR=0 MUJOCO_GL=egl \
uv run pytest -s -o log_cli=true -o log_cli_level=info tests/vectorization/test_ray.py

.PHONY: test-singlecore
test-singlecore:
PYTEST_XDIST_AUTO_NUM_WORKERS=1 PYVIRTUALDISPLAY_DISPLAYFD=0 \
DISPLAY= MUJOCO_GL=egl PYTEST_XDIST_AUTO_NUM_WORKERS=1 PYVIRTUALDISPLAY_DISPLAYFD=0 \
uv run pytest -s -o log_cli=true -o log_cli_level=info tests/control/test_classic_control.py

.PHONY: test-doctest
test-doctest:
PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_CLASSIC_CONTROL=1 \
DISPLAY= MUJOCO_GL=egl PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_CLASSIC_CONTROL=1 \
uv run pytest --doctest-modules -n $n -s -o log_cli=true -o log_cli_level=info src

# ============ Code Coverage ============
Expand All @@ -103,35 +108,35 @@ codecov-parallel: codecov-parallel-1 codecov-parallel-2 codecov-parallel-3 codec

.PHONY: codecov-parallel-1
codecov-parallel-1:
PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_CLASSIC_CONTROL=1 SKIP_RENDER=True \
DISPLAY= MUJOCO_GL=egl PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_CLASSIC_CONTROL=1 SKIP_RENDER=True \
uv run pytest -n $n -s -o log_cli=true -o log_cli_level=info \
--cov=./ --cov-report=xml:coverage_parallel_1.xml --cov-config=pyproject.toml \
tests/test_core.py tests/test_registry.py tests/test_utils.py

.PHONY: codecov-parallel-2
codecov-parallel-2:
PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_CLASSIC_CONTROL=1 SKIP_RENDER=True \
DISPLAY= MUJOCO_GL=egl PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_CLASSIC_CONTROL=1 SKIP_RENDER=True \
uv run pytest -n $n -s -o log_cli=true -o log_cli_level=info \
--cov=./ --cov-report=xml:coverage_parallel_2.xml --cov-config=pyproject.toml \
tests/videogames

.PHONY: codecov-parallel-3
codecov-parallel-3:
PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_CLASSIC_CONTROL=1 SKIP_RENDER=True \
DISPLAY= MUJOCO_GL=egl PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_CLASSIC_CONTROL=1 SKIP_RENDER=True \
uv run pytest -n $n -s -o log_cli=true -o log_cli_level=info \
--cov=./ --cov-report=xml:coverage_parallel_3.xml --cov-config=pyproject.toml \
tests/control

.PHONY: codecov-vectorization
codecov-vectorization:
PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_CLASSIC_CONTROL=1 SKIP_RENDER=True \
DISPLAY= MUJOCO_GL=egl PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_CLASSIC_CONTROL=1 SKIP_RENDER=True \
uv run pytest -n 0 -s -o log_cli=true -o log_cli_level=info \
--cov=./ --cov-report=xml:coverage_vectorization.xml --cov-config=pyproject.toml \
tests/vectorization

.PHONY: codecov-singlecore
codecov-singlecore:
PYTEST_XDIST_AUTO_NUM_WORKERS=1 PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_RENDER=True \
DISPLAY= MUJOCO_GL=egl PYTEST_XDIST_AUTO_NUM_WORKERS=1 PYVIRTUALDISPLAY_DISPLAYFD=0 SKIP_RENDER=True \
uv run pytest --doctest-modules -s -o log_cli=true -o log_cli_level=info \
--cov=./ --cov-report=xml --cov-config=pyproject.toml \
tests/control/test_classic_control.py
Expand Down
2 changes: 1 addition & 1 deletion Makefile.docker
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,5 @@ install-python-libs:
install-env-deps:
apt-get update
apt-get install -y --no-install-suggests --no-install-recommends \
libglfw3 libglew-dev libgl1-mesa-glx libosmesa6 xvfb swig
libglfw3 libglew-dev libgl1-mesa-glx libegl1-mesa-dev libosmesa6 xvfb swig

12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,12 @@
<summary><strong>Ubuntu / Debian</strong></summary>

```bash
# Install all system dependencies for headless rendering (EGL, GLU, X11)
make install-system-deps

# Or manually:
sudo apt-get update
sudo apt-get install -y xvfb libglu1-mesa x11-utils
sudo apt-get install -y xvfb libglu1-mesa libegl1-mesa-dev libgl1-mesa-glx x11-utils
```

For NES environments (nes-py):
Expand Down Expand Up @@ -91,8 +95,12 @@ fi
<summary><strong>WSL2 (Windows)</strong></summary>

```bash
# Install all system dependencies for headless rendering (EGL, GLU, X11)
make install-system-deps

# Or manually:
sudo apt-get update
sudo apt-get install -y xvfb libglu1-mesa x11-utils
sudo apt-get install -y xvfb libglu1-mesa libegl1-mesa-dev libgl1-mesa-glx x11-utils
```

For GUI rendering, install an X server on Windows (e.g., VcXsrv) or use headless mode.
Expand Down
33 changes: 25 additions & 8 deletions src/plangym/control/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
"""Module that contains environments representing control tasks."""

from plangym.control.balloon import BalloonEnv
from plangym.control.box_2d import Box2DEnv
from plangym.control.classic_control import ClassicControl
from plangym.control.dm_control import DMControlEnv
from plangym.control.lunar_lander import LunarLander
from plangym.control.mujoco import MujocoEnv
"""Module that contains environments representing control tasks.

Imports are lazy so that missing optional dependencies (e.g. mujoco, dm-control)
do not prevent importing unrelated environment classes.
"""

import importlib

_SUBMODULES = {
"BalloonEnv": "plangym.control.balloon",
"Box2DEnv": "plangym.control.box_2d",
"ClassicControl": "plangym.control.classic_control",
"DMControlEnv": "plangym.control.dm_control",
"LunarLander": "plangym.control.lunar_lander",
"MujocoEnv": "plangym.control.mujoco",
}

__all__ = list(_SUBMODULES)


def __getattr__(name: str):
if name in _SUBMODULES:
module = importlib.import_module(_SUBMODULES[name])
return getattr(module, name)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

Check warning on line 25 in src/plangym/control/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/plangym/control/__init__.py#L25

Added line #L25 was not covered by tests
41 changes: 31 additions & 10 deletions src/plangym/environment_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,40 @@
"Walker2d-v5",
]

try:
import retro.data
_RETRO = None
_DM_CONTROL = None

RETRO = retro.data.list_games()
except Exception: # pragma: no cover
RETRO = []

try:
from dm_control import suite
def _load_retro():
global _RETRO
if _RETRO is None:
try:
import retro.data

DM_CONTROL = list(suite.ALL_TASKS)
except (ImportError, OSError, AttributeError): # pragma: no cover
DM_CONTROL = []
_RETRO = retro.data.list_games()

Check warning on line 56 in src/plangym/environment_names.py

View check run for this annotation

Codecov / codecov/patch

src/plangym/environment_names.py#L56

Added line #L56 was not covered by tests
except Exception: # pragma: no cover
_RETRO = []
return _RETRO


def _load_dm_control():
global _DM_CONTROL
if _DM_CONTROL is None:
try:
from dm_control import suite

_DM_CONTROL = list(suite.ALL_TASKS)
except (ImportError, OSError, AttributeError): # pragma: no cover
_DM_CONTROL = []
return _DM_CONTROL


def __getattr__(name: str):
if name == "RETRO":
return _load_retro()
if name == "DM_CONTROL":
return _load_dm_control()
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


ATARI = [
Expand Down
4 changes: 3 additions & 1 deletion src/plangym/registry.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Functionality for instantiating the environment by passing the environment id."""

from plangym.environment_names import ATARI, BOX_2D, CLASSIC_CONTROL, DM_CONTROL, MUJOCO, RETRO
from plangym.environment_names import ATARI, BOX_2D, CLASSIC_CONTROL, MUJOCO


def get_planenv_class(name, domain_name, state):
"""Return the class corresponding to the environment name."""
from plangym.environment_names import DM_CONTROL, RETRO

# if name == "MinimalPacman-v0":
# return MinimalPacman
# elif name == "MinimalPong-v0":
Expand Down
2 changes: 1 addition & 1 deletion src/plangym/scripts/import_retro_roms.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
try:
import retro.data
except Exception:
sys.exit(0)
retro = None

flogging.setup()
logger = logging.getLogger("import-roms")
Expand Down
36 changes: 27 additions & 9 deletions src/plangym/videogames/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
"""Module that contains environments representing video games.

Imports are lazy so that missing optional dependencies (e.g. ale-py, stable-retro)
do not prevent importing unrelated environment classes.

.. note::
**Python Version Compatibility**:

- ``RetroEnv``: Requires ``stable-retro``, only available on Python 3.10.
"""

import importlib

from plangym import warn_import_error
from plangym.videogames.atari import AtariEnv
from plangym.videogames.montezuma import MontezumaEnv
from plangym.videogames.nes import MarioEnv

try:
from plangym.videogames.retro import RetroEnv
except ImportError:
RetroEnv = None
warn_import_error("RetroEnv", "stable-retro is only available on Python 3.10.")

_SUBMODULES = {
"AtariEnv": "plangym.videogames.atari",
"MontezumaEnv": "plangym.videogames.montezuma",
"MarioEnv": "plangym.videogames.nes",
"RetroEnv": "plangym.videogames.retro",
}

__all__ = list(_SUBMODULES)


def __getattr__(name: str):
if name in _SUBMODULES:
try:
module = importlib.import_module(_SUBMODULES[name])
return getattr(module, name)
except ImportError:

Check warning on line 31 in src/plangym/videogames/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/plangym/videogames/__init__.py#L31

Added line #L31 was not covered by tests
if name == "RetroEnv":
warn_import_error("RetroEnv", "stable-retro is only available on Python 3.10.")
return None
raise
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

Check warning on line 36 in src/plangym/videogames/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/plangym/videogames/__init__.py#L33-L36

Added lines #L33 - L36 were not covered by tests
Loading
Loading