Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
AI-WAIFU committed Apr 20, 2023
2 parents e8a3e3a + bad091a commit 3edde8c
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 52 deletions.
74 changes: 36 additions & 38 deletions minetester/minetest_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import psutil
import zmq
from minetester.utils import (KEY_MAP, pack_pb_action, start_minetest_client,
start_minetest_server, unpack_pb_obs)
start_minetest_server, unpack_pb_obs, start_xserver)


class Minetest(gym.Env):
Expand All @@ -36,15 +36,20 @@ def __init__(
clientmods: List[str] = [],
servermods: List[str] = [],
config_dict: Dict[str, Any] = {},
xvfb_headless: bool = False,
sync_port: Optional[int] = None,
sync_dtime: Optional[float] = None,
headless: bool = False,
start_xvfb: bool = False,
x_display: Optional[int] = None,
):
self.unique_env_id = str(uuid.uuid4())

# Graphics settings
self.xvfb_headless = xvfb_headless
self.headless = headless
self.display_size = display_size
self.fov_y = fov
self.fov_x = self.fov_y * self.display_size[0] / self.display_size[1]

# Define action and observation space
self.max_mouse_move_x = self.display_size[0]
self.max_mouse_move_y = self.display_size[1]
Expand Down Expand Up @@ -86,11 +91,14 @@ def __init__(
"mouse_cursor_white_16x16.png",
)

# Regenerate and clean world if no custom world provided
# Regenerate and clean world and config if no custom ones provided
self.reset_world = self.world_dir is None

# Clean config if no custom config provided
self.clean_config = self.config_path is None
# If no custom world / config provided set folder / path based on UUID
if self.world_dir is None:
self.world_dir = os.path.join(self.root_dir, self.unique_env_id)
if self.config_path is None:
self.config_path = os.path.join(self.root_dir, f"{self.unique_env_id}.conf")

# Whether to start minetest server and client
self.start_minetest = start_minetest
Expand All @@ -116,7 +124,6 @@ def __init__(
self.render_img = None

# Seed the environment
self.unique_env_id = str(uuid.uuid4()) # fallback UUID when no seed is provided
if seed is not None:
self.seed(seed)

Expand Down Expand Up @@ -147,6 +154,17 @@ def __init__(
self.config_dict = config_dict
self._write_config()

# Start X server virtual frame buffer
self.default_display = x_display or 0
if "DISPLAY" in os.environ:
self.default_display = int(os.environ["DISPLAY"].split(":")[1])
self.x_display = x_display or self.default_display
self.start_xvfb = start_xvfb and self.headless
self.xserver_process = None
if self.start_xvfb:
self.x_display = x_display or self.default_display + 4
self.xserver_process = start_xserver(self.x_display, self.display_size)

def _enable_clientmods(self):
clientmods_folder = os.path.realpath(
os.path.join(os.path.dirname(self.minetest_executable), "../clientmods"),
Expand All @@ -173,7 +191,6 @@ def _enable_servermods(self):
if not os.path.exists(servermods_folder):
raise RuntimeError(f"Server mods must be located at {servermods_folder}!")
# Create world_dir/worldmods folder
self._check_world_dir()
worldmods_folder = os.path.join(self.world_dir, "worldmods")
os.makedirs(worldmods_folder, exist_ok=True)
# Copy server mods to world_dir/worldmods
Expand Down Expand Up @@ -203,10 +220,13 @@ def _reset_minetest(self):
f"{{}}_{reset_timestamp}_{self.unique_env_id}.log",
)

# (Re)start Minetest server
# Close Mintest processes
if self.server_process:
self.server_process.kill()
if self.client_process:
self.client_process.kill()

# (Re)start Minetest server
self.server_process = start_minetest_server(
self.minetest_executable,
self.config_path,
Expand All @@ -219,22 +239,16 @@ def _reset_minetest(self):
)

# (Re)start Minetest client
if self.client_process:
self.client_process.kill()
if self.xvfb_headless:
# kill running xvfb processes
for proc in psutil.process_iter():
if proc.name() in ["Xvfb"]:
proc.kill()
os.environ["DISPLAY"] = f":{self.x_display}"
self.client_process = start_minetest_client(
self.minetest_executable,
self.config_path,
log_path,
self.env_port,
self.server_port,
self.cursor_image_path,
xvfb_headless=self.xvfb_headless,
sync_port=self.sync_port,
headless=self.headless,
)

def _check_world_dir(self):
Expand All @@ -245,7 +259,6 @@ def _check_world_dir(self):
)

def _delete_world(self):
self._check_world_dir()
if os.path.exists(self.world_dir):
shutil.rmtree(self.world_dir)

Expand All @@ -257,7 +270,6 @@ def _check_config_path(self):
)

def _delete_config(self):
self._check_config_path()
if os.path.exists(self.config_path):
os.remove(self.config_path)

Expand Down Expand Up @@ -298,27 +310,16 @@ def _write_config(self):
config_file.write(f"fov = {self.fov_y}\n")

# Seed the map generator
if self.seed:
config_file.write(f"fixed_map_seed = {self.seed}\n")
if self.the_seed:
config_file.write(f"fixed_map_seed = {self.the_seed}\n")

# Set from custom config dict
# TODO enable overwriting of default settings
for key, value in self.config_dict.items():
config_file.write(f"{key} = {value}\n")

def seed(self, seed: int):
self.seed = seed

# Create UUID from seed
rnd = random.Random()
rnd.seed(self.seed)
self.unique_env_id = str(uuid.UUID(int=rnd.getrandbits(128), version=4))

# If not set manually, world and config paths are based on UUID
if self.world_dir is None:
self.world_dir = os.path.join(self.root_dir, self.unique_env_id)
if self.config_path is None:
self.config_path = os.path.join(self.root_dir, f"{self.unique_env_id}.conf")
self.the_seed = seed

def reset(self):
if self.start_minetest:
Expand Down Expand Up @@ -399,11 +400,8 @@ def close(self):
self.client_process.kill()
if self.server_process is not None:
self.server_process.kill()
if self.xvfb_headless:
# kill remaining xvfb and minetest processes
for proc in psutil.process_iter():
if proc.name() in ["Xvfb", "minetest"]:
proc.kill()
if self.xserver_process is not None:
self.xserver_process.terminate()
if self.reset_world:
self._delete_world()
if self.clean_config:
Expand Down
4 changes: 3 additions & 1 deletion minetester/scripts/test_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
env = Minetest(
seed=42,
start_minetest=True,
xvfb_headless=True,
sync_port=30010,
sync_dtime=0.05,
headless=True,
start_xvfb=True,
clientmods=["random_v0"],
)

render = True
Expand Down
22 changes: 18 additions & 4 deletions minetester/scripts/test_loop_parallel.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import random
from typing import Any, Dict, Optional

from gym.wrappers import TimeLimit
from minetester import Minetest
from minetester.utils import start_xserver
from stable_baselines3.common.vec_env import DummyVecEnv, SubprocVecEnv

if __name__ == "__main__":
Expand All @@ -24,15 +26,21 @@ def _init():
seed=seed + rank,
**env_kwargs,
)
env = TimeLimit(env, max_episode_steps=max_steps)
env = TimeLimit(env, max_episode_steps=int(max_steps * random.random()))
return env

return _init

# Env settings
seed = 42
max_steps = 100
env_kwargs = {"display_size": (600, 400), "fov": 72}
x_display = 4
env_kwargs = {
"display_size": (600, 400),
"fov": 72,
"headless": True,
"x_display": x_display
}

# Create a vectorized environment
num_envs = 2 # Number of envs to use (<= number of avail. cpus)
Expand All @@ -44,14 +52,20 @@ def _init():
],
)

# Start X server
xserver = start_xserver(x_display)

# Start loop
render = False
render = True
obs = venv.reset()
done = [False] * num_envs
while sum(done) != num_envs:
step = 0
while step < max_steps:
print(f"Elapsed steps: {venv.get_attr('_elapsed_steps')}")
actions = [venv.action_space.sample() for _ in range(num_envs)]
obs, rew, done, info = venv.step(actions)
if render:
venv.render()
step += 1
venv.close()
xserver.terminate()
27 changes: 20 additions & 7 deletions minetester/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import subprocess
from typing import Any, Dict
from typing import Any, Dict, Tuple

import numpy as np
from minetester.proto import objects_pb2 as pb_objects
Expand Down Expand Up @@ -117,8 +117,8 @@ def start_minetest_client(
server_port: int = 30000,
cursor_img: str = "cursors/mouse_cursor_white_16x16.png",
client_name: str = "MinetestAgent",
xvfb_headless: bool = False,
sync_port: int = None,
headless: bool = False,
):
cmd = [
minetest_path,
Expand All @@ -139,10 +139,7 @@ def start_minetest_client(
"--config",
config_path,
]
if xvfb_headless:
# hide window
cmd.insert(0, "-a") # allow restarts
cmd.insert(0, "xvfb-run")
if headless:
# don't render to screen
cmd.append("--headless")
if cursor_img:
Expand All @@ -154,4 +151,20 @@ def start_minetest_client(
stderr_file = log_path.format("client_stderr")
with open(stdout_file, "w") as out, open(stderr_file, "w") as err:
client_process = subprocess.Popen(cmd, stdout=out, stderr=err)
return client_process
return client_process


def start_xserver(
display_idx: int = 1,
display_size: Tuple[int, int] = (1024, 600),
display_depth: int = 24,
):
cmd = [
"Xvfb",
f":{display_idx}",
"-screen",
"0", # screennum param
f"{display_size[0]}x{display_size[1]}x{display_depth}",
]
xserver_process = subprocess.Popen(cmd)
return xserver_process
2 changes: 1 addition & 1 deletion scripts/compile_proto.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/bin/bash
protoc -I=../proto/ --python_out=../minetests/proto --cpp_out=../src ../proto/*.proto
protoc -I=../proto/ --python_out=../minetester/proto --cpp_out=../src ../proto/*.proto
1 change: 0 additions & 1 deletion src/client/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1389,7 +1389,6 @@ void Game::run()
if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
showPauseMenu();
}

}
}

Expand Down

0 comments on commit 3edde8c

Please sign in to comment.