mcio-ctrl 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcio-ctrl
3
+ Version: 1.4.0
4
+ Summary: Python interface to connect to the MCio Minecraft mod
5
+ Author: TwoTurtles
6
+ Author-email: TwoTurtles <97465192+twoturtles@users.noreply.github.com>
7
+ License-Expression: MIT
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Dist: pyzmq>=26.2.0
12
+ Requires-Dist: cbor2>=5.6.5
13
+ Requires-Dist: glfw>=2.7.0
14
+ Requires-Dist: pyopengl>=3.1.7
15
+ Requires-Dist: gymnasium>=1.0.0
16
+ Requires-Dist: pillow>=11.0.0
17
+ Requires-Dist: imageio>=2.37.0
18
+ Requires-Dist: imageio-ffmpeg>=0.6.0
19
+ Requires-Dist: minecraft-launcher-lib>=7.1
20
+ Requires-Dist: tqdm>=4.67.1
21
+ Requires-Dist: requests>=2.32.3
22
+ Requires-Dist: ruamel-yaml>=0.18.6
23
+ Requires-Dist: dacite>=1.8.1
24
+ Requires-Dist: nbt>=1.5.1
25
+ Requires-Python: >=3.12
26
+ Project-URL: Changelog, https://github.com/twoturtles/mcio_ctrl/blob/main/CHANGELOG.md
27
+ Project-URL: Issues, https://github.com/twoturtles/mcio_ctrl/issues
28
+ Project-URL: Source, https://github.com/twoturtles/mcio_ctrl
29
+ Description-Content-Type: text/markdown
30
+
31
+ # mcio_ctrl
32
+
33
+ ### [MCio mod](https://github.com/twoturtles/MCio) | [mcio_ctrl](https://github.com/twoturtles/mcio_ctrl) | [Documentation](https://github.com/twoturtles/mcio_ctrl/wiki) | [Discord](https://discord.gg/PBfdc27h4q)
34
+
35
+ `mcio_ctrl` is a comprehensive Python library designed to interface seamlessly with [MCio](https://github.com/twoturtles/MCio), a Minecraft Fabric mod tailored for AI agent development. It includes a versatile [Gymnasium](https://gymnasium.farama.org/) environment, making it ideal for reinforcement learning research and development.
36
+
37
+ ## Key Features
38
+
39
+ * **Simplified Installation and Launching:** API and commands to easily install Minecraft with the MCio mod, create custom worlds, and launch the game directly from Python.
40
+ * **Pre-built Gymnasium Environments:** Offers example environments, including compatibility with MineRL 1.0 actions and observations, all leveraging the robust low-level API.
41
+ * **Customizable Base Environment:** A convenient base class for quickly creating tailored Gymnasium environments suited to specific research needs.
42
+ * **Interactive GUI Support:** Enables human control of Minecraft through the standard Minecraft controls using the MCio backend. (Seamless Human-in-the-loop is planned for future updates.)
43
+ * **Type-Hinting and Development Convenience:** Fully type-hinted for easier integration, improved code clarity, and streamlined development workflows.
44
+ * **BONUS:** Easily [set up VPT and STEVE-1](https://github.com/jxiong21029/mcio-vpt-example) on modern Minecraft with support for [Sodium](https://modrinth.com/mod/sodium)!
45
+
46
+
47
+ ## Quick Links
48
+
49
+ * **Documentation:** Find comprehensive documentation and tutorials on our [Wiki](https://github.com/twoturtles/mcio_ctrl/wiki).
50
+
51
+ * **MCio Mod:**
52
+ * [GitHub Repository](https://github.com/twoturtles/MCio)
53
+ * [Modrinth Project Page](https://modrinth.com/mod/mcio)
54
+
55
+ * **Python Interface (`mcio_ctrl`):**
56
+ * [GitHub Repository](https://github.com/twoturtles/mcio_ctrl)
57
+ * [PyPI Package](https://pypi.org/project/mcio_ctrl/)
@@ -0,0 +1,27 @@
1
+ # mcio_ctrl
2
+
3
+ ### [MCio mod](https://github.com/twoturtles/MCio) | [mcio_ctrl](https://github.com/twoturtles/mcio_ctrl) | [Documentation](https://github.com/twoturtles/mcio_ctrl/wiki) | [Discord](https://discord.gg/PBfdc27h4q)
4
+
5
+ `mcio_ctrl` is a comprehensive Python library designed to interface seamlessly with [MCio](https://github.com/twoturtles/MCio), a Minecraft Fabric mod tailored for AI agent development. It includes a versatile [Gymnasium](https://gymnasium.farama.org/) environment, making it ideal for reinforcement learning research and development.
6
+
7
+ ## Key Features
8
+
9
+ * **Simplified Installation and Launching:** API and commands to easily install Minecraft with the MCio mod, create custom worlds, and launch the game directly from Python.
10
+ * **Pre-built Gymnasium Environments:** Offers example environments, including compatibility with MineRL 1.0 actions and observations, all leveraging the robust low-level API.
11
+ * **Customizable Base Environment:** A convenient base class for quickly creating tailored Gymnasium environments suited to specific research needs.
12
+ * **Interactive GUI Support:** Enables human control of Minecraft through the standard Minecraft controls using the MCio backend. (Seamless Human-in-the-loop is planned for future updates.)
13
+ * **Type-Hinting and Development Convenience:** Fully type-hinted for easier integration, improved code clarity, and streamlined development workflows.
14
+ * **BONUS:** Easily [set up VPT and STEVE-1](https://github.com/jxiong21029/mcio-vpt-example) on modern Minecraft with support for [Sodium](https://modrinth.com/mod/sodium)!
15
+
16
+
17
+ ## Quick Links
18
+
19
+ * **Documentation:** Find comprehensive documentation and tutorials on our [Wiki](https://github.com/twoturtles/mcio_ctrl/wiki).
20
+
21
+ * **MCio Mod:**
22
+ * [GitHub Repository](https://github.com/twoturtles/MCio)
23
+ * [Modrinth Project Page](https://modrinth.com/mod/mcio)
24
+
25
+ * **Python Interface (`mcio_ctrl`):**
26
+ * [GitHub Repository](https://github.com/twoturtles/mcio_ctrl)
27
+ * [PyPI Package](https://pypi.org/project/mcio_ctrl/)
@@ -0,0 +1,59 @@
1
+ [project]
2
+ name = "mcio_ctrl"
3
+ version = "1.4.0"
4
+ description = "Python interface to connect to the MCio Minecraft mod"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "TwoTurtles", email = "97465192+twoturtles@users.noreply.github.com" },
8
+ ]
9
+ license = "MIT"
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "License :: OSI Approved :: MIT License",
13
+ "Operating System :: OS Independent",
14
+ ]
15
+ requires-python = ">=3.12"
16
+ dependencies = [
17
+ "pyzmq>=26.2.0",
18
+ "cbor2>=5.6.5",
19
+ "glfw>=2.7.0",
20
+ "PyOpenGL>=3.1.7",
21
+ "gymnasium>=1.0.0",
22
+ "pillow>=11.0.0",
23
+ "imageio>=2.37.0",
24
+ "imageio-ffmpeg>=0.6.0",
25
+ "minecraft-launcher-lib>=7.1",
26
+ "tqdm>=4.67.1",
27
+ "requests>=2.32.3",
28
+ "ruamel.yaml>=0.18.6",
29
+ "dacite>=1.8.1",
30
+ "NBT>=1.5.1",
31
+ ]
32
+
33
+ [build-system]
34
+ requires = ["uv_build>=0.7.12,<0.8"]
35
+ build-backend = "uv_build"
36
+
37
+ [project.urls]
38
+ Source = "https://github.com/twoturtles/mcio_ctrl"
39
+ Issues = "https://github.com/twoturtles/mcio_ctrl/issues"
40
+ Changelog = "https://github.com/twoturtles/mcio_ctrl/blob/main/CHANGELOG.md"
41
+
42
+ [project.scripts]
43
+ mcio = "mcio_ctrl.scripts.mcio_cmd:main"
44
+
45
+ [dependency-groups]
46
+ dev = [
47
+ "pre-commit>=4.0.1",
48
+ "mypy>=1.13.0",
49
+ "ruff>=0.8.1",
50
+ "black>=24.10.0",
51
+ "pip",
52
+ "pytest>=8.3.4",
53
+ "pytest-mock>=3.14.0",
54
+ "pytest-cov>=6.0.0",
55
+ "isort>=5.13.2",
56
+ ]
57
+
58
+ [tool.isort]
59
+ profile = "black"
@@ -0,0 +1 @@
1
+ __version__ = "1.3.1.dev0"
@@ -0,0 +1,32 @@
1
+ from . import (
2
+ config,
3
+ controller,
4
+ envs,
5
+ gui,
6
+ instance,
7
+ mc_mock,
8
+ mcio_gui,
9
+ network,
10
+ server,
11
+ types,
12
+ util,
13
+ world,
14
+ )
15
+
16
+ __version__ = "1.4.0"
17
+
18
+ __all__ = [
19
+ "__version__",
20
+ "config",
21
+ "controller",
22
+ "envs",
23
+ "gui",
24
+ "instance",
25
+ "mc_mock",
26
+ "mcio_gui",
27
+ "network",
28
+ "server",
29
+ "types",
30
+ "util",
31
+ "world",
32
+ ]
@@ -0,0 +1,78 @@
1
+ """CBOR processing helpers"""
2
+
3
+ import logging
4
+ from dataclasses import fields, is_dataclass
5
+ from typing import Any, Final, TypeVar
6
+
7
+ import cbor2
8
+
9
+ LOG = logging.getLogger(__name__)
10
+
11
+ # Used by MCio and mcio_ctrl to annotate protocol classes
12
+ MCIO_PROTOCOL_TYPE: Final[str] = "__mcio_type__"
13
+
14
+ # Maps protcol class names to the type and vice versa
15
+ _MCIO_NAME_TO_TYPE: dict[str, type] = {}
16
+ _MCIO_TYPE_TO_NAME: dict[type, str] = {}
17
+
18
+
19
+ T = TypeVar("T")
20
+
21
+
22
+ def MCioType(cls: type[T]) -> type[T]:
23
+ """Decorator to register a class as used in the MCio protocol"""
24
+ # Use the Java Jackson style MINIMAL_CLASS name which includes a leading dot.
25
+ name = "." + cls.__name__
26
+ _MCIO_NAME_TO_TYPE[name] = cls
27
+ _MCIO_TYPE_TO_NAME[cls] = name
28
+ return cls
29
+
30
+
31
+ def encode(obj: Any) -> bytes:
32
+ """Encode object to CBOR using MCioType class annotations"""
33
+ return cbor2.dumps(typed_asdict(obj))
34
+
35
+
36
+ def decode(data: bytes) -> Any | None:
37
+ """Decode CBOR using MCioType classes where possible. Returns None on error"""
38
+ try:
39
+ return cbor2.loads(data, object_hook=_object_hook)
40
+ except Exception as e:
41
+ LOG.error(f"CBOR load error: {type(e).__name__}: {e}")
42
+ return None
43
+
44
+
45
+ def _object_hook(decoder: cbor2.CBORDecoder, obj_dict: dict[Any, Any]) -> Any:
46
+ """Used by the CBOR parser. Decodes packet entries into MCioType classes where possible."""
47
+ mcio_type = obj_dict.pop(MCIO_PROTOCOL_TYPE, None)
48
+ if isinstance(mcio_type, str):
49
+ cls = _MCIO_NAME_TO_TYPE.get(mcio_type)
50
+ if cls:
51
+ return cls(**obj_dict)
52
+ else:
53
+ LOG.error(f"Unknown MCioType type: {mcio_type}")
54
+
55
+ # Non MCioType
56
+ return obj_dict
57
+
58
+
59
+ def typed_asdict(obj: Any) -> Any:
60
+ """Like dataclass asdict, but annotates MCioType classes with type info.
61
+ Recursively walks the dataclass.
62
+ """
63
+ if is_dataclass(obj):
64
+ cls = type(obj)
65
+ cls_name = _MCIO_TYPE_TO_NAME.get(cls)
66
+ result = {
67
+ key: typed_asdict(getattr(obj, key))
68
+ for key in (f.name for f in fields(obj))
69
+ }
70
+ if cls_name:
71
+ result[MCIO_PROTOCOL_TYPE] = cls_name
72
+ return result
73
+ elif isinstance(obj, (list, tuple)):
74
+ return [typed_asdict(i) for i in obj]
75
+ elif isinstance(obj, dict):
76
+ return {k: typed_asdict(v) for k, v in obj.items()}
77
+ else:
78
+ return obj
@@ -0,0 +1,118 @@
1
+ """Persistent config - mcio.yaml"""
2
+
3
+ import logging
4
+ import types
5
+ from dataclasses import asdict, dataclass, field
6
+ from io import StringIO
7
+ from pathlib import Path
8
+ from typing import Any, Final, Optional, TypeAlias
9
+
10
+ import dacite
11
+ from ruamel.yaml import YAML
12
+
13
+ LOG = logging.getLogger(__name__)
14
+
15
+ ##
16
+ # Global defines
17
+
18
+ DEFAULT_MCIO_DIR: Final[Path] = Path("~/.mcio/").expanduser()
19
+ DEFAULT_MINECRAFT_VERSION: Final[str] = "1.21.3"
20
+
21
+
22
+ ##
23
+ # Configuration
24
+
25
+ # XXX Consider saving necessary config in each entity's directory
26
+
27
+ CONFIG_FILENAME: Final[str] = "mcio.yaml"
28
+ CONFIG_VERSION: Final[int] = 1
29
+ InstanceName: TypeAlias = str
30
+ WorldName: TypeAlias = str
31
+ MinecraftVersion: TypeAlias = str
32
+
33
+
34
+ @dataclass
35
+ class WorldConfig:
36
+ name: WorldName = ""
37
+ minecraft_version: MinecraftVersion = "" # Save the version that created this world
38
+ seed: str = ""
39
+
40
+
41
+ @dataclass
42
+ class InstanceConfig:
43
+ name: InstanceName = ""
44
+ launch_version: MinecraftVersion = ""
45
+ minecraft_version: MinecraftVersion = ""
46
+ worlds: dict[WorldName, WorldConfig] = field(default_factory=dict)
47
+
48
+
49
+ @dataclass
50
+ class ServerConfig:
51
+ minecraft_version: MinecraftVersion = ""
52
+ jvm_version: str = ""
53
+
54
+
55
+ @dataclass
56
+ class Config:
57
+ config_version: int = CONFIG_VERSION # XXX Eventually check this
58
+ instances: dict[InstanceName, InstanceConfig] = field(default_factory=dict)
59
+ world_storage: dict[WorldName, WorldConfig] = field(default_factory=dict)
60
+ servers: dict[MinecraftVersion, ServerConfig] = field(default_factory=dict)
61
+
62
+ @classmethod
63
+ def from_dict(cls, config_dict: dict[str, Any]) -> Optional["Config"]:
64
+ try:
65
+ rv = dacite.from_dict(data_class=cls, data=config_dict)
66
+ except Exception as e:
67
+ # This means the dict doesn't match Config
68
+ LOG.error(f"Failed to parse config file: {e}")
69
+ return None
70
+ return rv
71
+
72
+ def to_dict(self) -> dict[str, Any]:
73
+ return asdict(self)
74
+
75
+
76
+ class ConfigManager:
77
+ def __init__(self, mcio_dir: Path | str, save: bool = False) -> None:
78
+ """Set save to true to save automatically on exiting"""
79
+ self.save_on_exit = save
80
+ mcio_dir = Path(mcio_dir).expanduser()
81
+ self.config_file = mcio_dir / CONFIG_FILENAME
82
+ self.yaml = YAML(typ="rt")
83
+ self.config: Config = Config()
84
+
85
+ def load(self) -> None:
86
+ if self.config_file.exists():
87
+ with open(self.config_file) as f:
88
+ # load() returns None if the file has no data.
89
+ cfg_dict = self.yaml.load(f) or {}
90
+ self.config = Config.from_dict(cfg_dict) or Config()
91
+ else:
92
+ self.config = Config()
93
+
94
+ def pformat(self) -> str:
95
+ """Pretty print the config"""
96
+ string_stream = StringIO()
97
+ self.yaml.dump(self.config.to_dict(), string_stream)
98
+ return string_stream.getvalue()
99
+
100
+ def save(self) -> None:
101
+ with open(self.config_file, "w") as f:
102
+ self.yaml.dump(self.config.to_dict(), f)
103
+
104
+ def __enter__(self) -> "ConfigManager":
105
+ self.load()
106
+ return self
107
+
108
+ def __exit__(
109
+ self,
110
+ exc_type: type[BaseException] | None,
111
+ exc_value: BaseException | None,
112
+ traceback: types.TracebackType | None,
113
+ ) -> bool | None:
114
+ if exc_type is None:
115
+ # Clean exit
116
+ if self.save_on_exit:
117
+ self.save()
118
+ return None
@@ -0,0 +1,193 @@
1
+ import logging
2
+ import threading
3
+ from typing import Protocol
4
+
5
+ from . import network, types, util
6
+
7
+ LOG = logging.getLogger(__name__)
8
+
9
+
10
+ class ControllerCommon(Protocol):
11
+ """Protocol for the fundamental controller interface shared by sync/async implementations"""
12
+
13
+ _action_sequence_last_sent: int
14
+ _mcio_conn: network._Connection
15
+
16
+ def send_action(self, action: network.ActionPacket) -> None:
17
+ """Send action to minecraft. Automatically sets action.sequence."""
18
+ self._action_sequence_last_sent += 1
19
+ action.sequence = self._action_sequence_last_sent
20
+ self._mcio_conn.send_action(action)
21
+
22
+ def send_stop(self) -> None:
23
+ """Send a stop packet to Minecraft. This should cause Minecraft to cleanly exit."""
24
+ self._mcio_conn.send_stop()
25
+
26
+ def recv_observation(self) -> network.ObservationPacket: ...
27
+ def close(self) -> None: ...
28
+
29
+
30
+ class ControllerSync(ControllerCommon):
31
+ """
32
+ Handles SYNC mode connections to Minecraft.
33
+ Blocks in recv waiting for a new observation.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ *,
39
+ action_port: int | None = None,
40
+ observation_port: int | None = None,
41
+ wait_for_connection: bool = True,
42
+ connection_timeout: float | None = None,
43
+ ):
44
+ self._action_sequence_last_sent = 0
45
+ self._mcio_conn = network._Connection(
46
+ action_port=action_port,
47
+ observation_port=observation_port,
48
+ wait_for_connection=wait_for_connection,
49
+ connection_timeout=connection_timeout,
50
+ )
51
+ self.check_mode = True
52
+
53
+ def recv_observation(
54
+ self, block: bool = True, timeout: float | None = None
55
+ ) -> network.ObservationPacket:
56
+ """Receive observation. Always blocks - ignores block and timeout args"""
57
+ obs = self._mcio_conn.recv_observation(block=True)
58
+ if obs is None:
59
+ # Exiting or packet decode error
60
+ return network.ObservationPacket()
61
+
62
+ if self.check_mode:
63
+ self.check_mode = False
64
+ mode = types.MCioMode.SYNC
65
+ if mode != obs.mode:
66
+ LOG.warning(f"Mode-Mismatch controller={mode} mcio={obs.mode}")
67
+
68
+ return obs
69
+
70
+ def close(self) -> None:
71
+ """Shut down the network connection"""
72
+ self._mcio_conn.close()
73
+
74
+
75
+ class ControllerAsync(ControllerCommon):
76
+ """
77
+ Handles ASYNC mode connections to Minecraft
78
+ """
79
+
80
+ def __init__(
81
+ self,
82
+ *,
83
+ action_port: int | None = None,
84
+ observation_port: int | None = None,
85
+ wait_for_connection: bool = True,
86
+ connection_timeout: float | None = None,
87
+ ):
88
+ self._action_sequence_last_sent = 0
89
+
90
+ self.process_counter = util.TrackPerSecond("ProcessObservationPPS")
91
+ self.queued_counter = util.TrackPerSecond("QueuedActionsPPS")
92
+ self.check_mode = True
93
+
94
+ # Flag to signal observation thread to stop.
95
+ self._running = threading.Event()
96
+ self._running.set()
97
+
98
+ self._observation_queue = util.LatestItemQueue[network.ObservationPacket]()
99
+ self._mcio_conn = network._Connection(
100
+ action_port=action_port,
101
+ observation_port=observation_port,
102
+ wait_for_connection=wait_for_connection,
103
+ connection_timeout=connection_timeout,
104
+ )
105
+
106
+ # Start observation thread
107
+ self._observation_thread = threading.Thread(
108
+ target=self._observation_thread_fn, name="ObservationThread"
109
+ )
110
+ self._observation_thread.daemon = True
111
+ self._observation_thread.start()
112
+
113
+ LOG.info("Controller init complete")
114
+
115
+ def recv_observation(
116
+ self, block: bool = True, timeout: float | None = None
117
+ ) -> network.ObservationPacket:
118
+ """
119
+ Returns the most recently received observation pulling it from the processing queue.
120
+ Block and timeout are like queue.Queue.get().
121
+ Can raise Empty exception if non-blocking or timeout is used.
122
+ """
123
+ # RECV 2
124
+ observation = self._observation_queue.get(block=block, timeout=timeout)
125
+ return observation
126
+
127
+ def send_and_recv_match(
128
+ self, action: network.ActionPacket, max_skip: int | None = 5
129
+ ) -> network.ObservationPacket:
130
+ """Send action to minecraft. Automatically sets action.sequence.
131
+ This will ensure the next observation you receive came after this action.
132
+ Since we're running in async mode, observations may be in flight that occurred
133
+ before Minecraft processed this action.
134
+ max_skip is the maximum number of observations to skip before giving up.
135
+ Because the observations are stored in a LatestItemQueue, there shouldn't be
136
+ many to skip - only observations that were in flight after the action was sent, but
137
+ before Minecraft processed it. Generally we should only hit max_skip if something went
138
+ wrong, like the action was dropped. This could happen, for example, when the agent
139
+ starts before Minecraft.
140
+ """
141
+ self.send_action(action)
142
+ wait_seq = self._action_sequence_last_sent
143
+ n_skip = 0
144
+ while True:
145
+ observation = self._observation_queue.get()
146
+ obs_action_seq = observation.last_action_sequence
147
+ if obs_action_seq >= wait_seq:
148
+ break
149
+ n_skip += 1
150
+ if max_skip is not None and n_skip >= max_skip:
151
+ LOG.warning("Max-Skip")
152
+ break
153
+ LOG.debug(
154
+ f"SKIPPING obs={observation.sequence} last_action={obs_action_seq} < waiting={wait_seq}"
155
+ )
156
+ # print(f"SKIPPING obs={observation.sequence} last_action={obs_action_seq} < waiting={wait_seq}")
157
+ return observation
158
+
159
+ def _observation_thread_fn(self) -> None:
160
+ """Loops. Receives observation packets from minecraft and places on observation_queue"""
161
+ LOG.info("ObservationThread start")
162
+ while self._running.is_set():
163
+ # RECV 1
164
+ # I don't think we'll ever drop here. this is a short loop to recv the packet
165
+ # and put it on the queue to be processed.
166
+ observation = self._mcio_conn.recv_observation(block=True)
167
+ if observation is None:
168
+ continue # Exiting or packet decode error
169
+
170
+ if self.check_mode:
171
+ self.check_mode = False
172
+ mode = types.MCioMode.ASYNC
173
+ if mode != observation.mode:
174
+ LOG.warning(
175
+ f"Mode-Mismatch controller={mode} mcio={observation.mode}"
176
+ )
177
+
178
+ dropped = self._observation_queue.put(observation)
179
+ if dropped:
180
+ # This means the main (processing) thread isn't reading fast enough.
181
+ # The first few are always dropped, presumably as we empty the initial zmq buffer
182
+ # that built up during pause for "slow joiner syndrome".
183
+ # XXX This should not longer happen since we're using push/pull? Change log level?
184
+ LOG.debug("Dropped observation packet from processing queue")
185
+ pass
186
+
187
+ LOG.info("ObservationThread shut down")
188
+
189
+ def close(self) -> None:
190
+ """Shut down the network connection"""
191
+ self._running.clear()
192
+ self._mcio_conn.close()
193
+ self._observation_thread.join()
@@ -0,0 +1,19 @@
1
+ from gymnasium.envs.registration import register
2
+
3
+ from . import base_env, mcio_env, minerl_env
4
+
5
+ __all__ = [
6
+ "base_env",
7
+ "mcio_env",
8
+ "minerl_env",
9
+ ]
10
+
11
+ register(
12
+ id="MCio/MCioEnv-v0",
13
+ entry_point="mcio_ctrl.envs.mcio_env:MCioEnv",
14
+ )
15
+
16
+ register(
17
+ id="MCio/MinerlEnv-v0",
18
+ entry_point="mcio_ctrl.envs.minerl_env:MinerlEnv",
19
+ )