From 8b780c2590157a4545e2738b6773badeddfe51d0 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Mon, 20 Jan 2025 20:26:11 +0900 Subject: [PATCH 01/91] =?UTF-8?q?=E2=9C=A8=20Add=20boost=20ipc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clangd | 4 +- .vscode/settings.json | 2 +- .../MinecraftEnv/src/main/cpp/CMakeLists.txt | 25 +++++++++-- .../MinecraftEnv/src/main/cpp/boost_ipc.cpp | 44 +++++++++++++++++++ 4 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp diff --git a/.clangd b/.clangd index c308252f..a1589371 100644 --- a/.clangd +++ b/.clangd @@ -7,7 +7,7 @@ CompileFlags: --- If: - PathMatch: /src/MinecraftEnv/src/main/cpp/.* + PathMatch: /src/craftground/MinecraftEnv/src/main/cpp/.* CompileFlags: - CompilationDatabase: /src/MinecraftEnv/build \ No newline at end of file + CompilationDatabase: /src/craftground/MinecraftEnv \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 4ea0c53e..b0caada2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,7 @@ "C_Cpp.default.compileCommands": "${workspaceFolder}/src/MinecraftEnv/compile_commands.json", "clangd.arguments": [ "--background-index", - "--compile-commands-dir=${workspaceFolder}/src/MinecraftEnv/" + "--compile-commands-dir=${workspaceFolder}/src/craftground/MinecraftEnv/" ], "java.compile.nullAnalysis.mode": "automatic", } \ No newline at end of file diff --git a/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt b/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt index 9b174de5..36f903b1 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt +++ b/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.11) project(framebuffer_capturer) # Set the C++ standard @@ -68,12 +68,31 @@ else() message(STATUS "CUDA is not available") endif() +# Find Boost +set(BOOST_INCLUDE_LIBRARIES interprocess thread system) +set(BOOST_ENABLE_CMAKE ON) + +set(FETCHCONTENT_QUIET FALSE) +FetchContent_Declare( + Boost + GIT_REPOSITORY https://github.com/boostorg/boost.git + GIT_TAG boost-1.87.0 + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE +) +FetchContent_MakeAvailable(Boost) + +message(STATUS "Boost is now available") # Add your source files here -set(SOURCE_FILES +set( + SOURCE_FILES framebuffer_capturer.cpp + boost_ipc.cpp ) +message(STATUS "SOURCE_FILES=${SOURCE_FILES}") + if(APPLE) list(APPEND SOURCE_FILES framebuffer_capturer_apple.mm) endif() @@ -91,7 +110,7 @@ if(CRAFGROUND_NATIVE_DEBUG) endif() # Link with JNI and OpenGL libraries -target_link_libraries(native-lib ${JNI_LIBRARIES} ${OPENGL_LIBRARIES} glm::glm) +target_link_libraries(native-lib ${JNI_LIBRARIES} ${OPENGL_LIBRARIES} glm::glm Boost::system Boost::thread Boost::interprocess) if(PNG_FOUND) target_link_libraries(native-lib ${PNG_LIBRARIES} ${ZLIB_LIBRARIES}) diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp new file mode 100644 index 00000000..2c88c421 --- /dev/null +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +using namespace boost::interprocess; + +struct SharedData { + interprocess_mutex mutex; + interprocess_condition condition; + bool ready; // Data ready flag + char data[256]; // Message buffer +}; + +int main() { + // Shared memory + shared_memory_object::remove("PythonJavaSharedMemory"); + managed_shared_memory sharedMemory(create_only, "PythonJavaSharedMemory", 1024); + + // Shared memory data + SharedData* sharedData = sharedMemory.construct("SharedData")(); + sharedData->ready = false; + + while (true) { + // Wait for Python to send a message + std::unique_lock lock(sharedData->mutex); + sharedData->condition.wait(lock, [&] { return sharedData->ready; }); + + // Read the message from Python + std::cout << "Received from Python: " << sharedData->data << std::endl; + + // Send a message to Python + std::strcpy(sharedData->data, "Acknowledged from Java"); + sharedData->ready = false; + + // Notify Python that the message has been sent + sharedData->condition.notify_one(); + } + + // Cleanup + shared_memory_object::remove("PythonJavaSharedMemory"); + return 0; +} From e04a7053b55f18740fd6330bc926bebd9f1386ad Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Mon, 20 Jan 2025 22:11:06 +0900 Subject: [PATCH 02/91] =?UTF-8?q?=E2=9C=A8=20Add=20boost=20ipc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 21 +++- src/cpp/ipc.cpp | 45 +++++++ src/cpp/ipc.h | 18 +++ src/cpp/ipc_boost.cpp | 114 ++++++++++++++++++ src/cpp/ipc_boost.hpp | 43 +++++++ .../MinecraftEnv/src/main/cpp/boost_ipc.cpp | 94 ++++++++++++--- src/craftground/environment/__init__.py | 0 .../{ => environment}/environment.py | 18 +-- src/craftground/environment/ipc_boost.py | 0 9 files changed, 323 insertions(+), 30 deletions(-) create mode 100644 src/cpp/ipc_boost.cpp create mode 100644 src/cpp/ipc_boost.hpp create mode 100644 src/craftground/environment/__init__.py rename src/craftground/{ => environment}/environment.py (99%) create mode 100644 src/craftground/environment/ipc_boost.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 51929679..5657cb9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.15...3.29) project(craftground LANGUAGES CXX) include(GNUInstallDirs) +include(FetchContent) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_CXX_STANDARD 17) @@ -67,7 +68,7 @@ else() endif() # Collect source files for the module -set(CRAFTGROUND_PY_SOURCES src/cpp/ipc.cpp) +set(CRAFTGROUND_PY_SOURCES src/cpp/ipc.cpp src/cpp/ipc_boost.cpp) if(APPLE) # Add Apple-specific source files @@ -86,9 +87,25 @@ elseif(CUDAToolkit_FOUND) set(CRAFTGROUND_PY_COMPILE_OPTIONS -DHAS_CUDA) endif() +# Find Boost +message(STATUS "Installing Boost") +set(BOOST_INCLUDE_LIBRARIES interprocess thread system) +set(BOOST_ENABLE_CMAKE ON) + +set(FETCHCONTENT_QUIET FALSE) +FetchContent_Declare( + Boost + GIT_REPOSITORY https://github.com/boostorg/boost.git + GIT_TAG boost-1.87.0 + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE +) +FetchContent_MakeAvailable(Boost) + +message(STATUS "Boost is now available") # Add the module pybind11_add_module(craftground_native ${CRAFTGROUND_PY_SOURCES}) - +target_link_libraries(craftground_native PRIVATE Boost::system Boost::thread Boost::interprocess) if(APPLE) if(Torch_FOUND) target_include_directories(craftground_native PRIVATE "${TORCH_INCLUDE_DIRS}") diff --git a/src/cpp/ipc.cpp b/src/cpp/ipc.cpp index ed814043..d9473c63 100644 --- a/src/cpp/ipc.cpp +++ b/src/cpp/ipc.cpp @@ -58,10 +58,55 @@ py::capsule mtl_tensor_from_cuda_mem_handle( } #endif +#include "ipc_boost.hpp" + +void initialize_shared_memory( + const char *memory_name, + const char *management_memory_name, + const char *initial_data, + size_t data_size, + size_t action_size +) { + create_shared_memory_impl( + memory_name, management_memory_name, initial_data, data_size, action_size + ); +} + +void write_to_shared_memory( + const char *memory_name, + const char *data, + const size_t data_size +) { + write_to_shared_memory_impl( + memory_name, data, data_size + ); +} + +py::bytes read_from_shared_memory( + const char *memory_name, + const char *management_memory_name +) { + size_t data_size = 0; + const char *data = read_from_shared_memory_impl( + memory_name, management_memory_name, data_size + ); + return py::bytes(data, data_size); +} + +void destroy_shared_memory(const char *memory_name) { + destroy_shared_memory_impl(memory_name); +} + + + PYBIND11_MODULE(craftground_native, m) { m.doc() = "Craftground Native Module"; m.def("initialize_from_mach_port", &initialize_from_mach_port); m.def("mtl_tensor_from_cuda_mem_handle", &mtl_tensor_from_cuda_mem_handle); + m.def("initialize_shared_memory", &initialize_shared_memory); + m.def("write_to_shared_memory", &write_to_shared_memory); + m.def("read_from_shared_memory", &read_from_shared_memory); + m.def("destroy_shared_memory", &destroy_shared_memory); #ifdef VERSION_INFO m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); diff --git a/src/cpp/ipc.h b/src/cpp/ipc.h index 505ed413..2606df29 100644 --- a/src/cpp/ipc.h +++ b/src/cpp/ipc.h @@ -1,11 +1,29 @@ #ifndef __IPC_H__ #define __IPC_H__ +#include namespace py = pybind11; py::object initialize_from_mach_port(unsigned int machPort, int width, int height); py::capsule mtl_tensor_from_cuda_mem_handle( const char *cuda_ipc_handle, int width, int height ); +void initialize_shared_memory( + const char *memory_name, + const char *management_memory_name, + const char *initial_data, + size_t data_size, + size_t action_size +); + +void write_to_shared_memory( + const char *memory_name, const char *data, const size_t data_size +); + +py::bytes read_from_shared_memory( + const char *memory_name, const char *management_memory_name +); + +void destroy_shared_memory(const char *memory_name); #endif \ No newline at end of file diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp new file mode 100644 index 00000000..4bcb687f --- /dev/null +++ b/src/cpp/ipc_boost.cpp @@ -0,0 +1,114 @@ +#include "ipc_boost.hpp" +#include "boost/interprocess/shared_memory_object.hpp" + +// Create shared memory and write initial environment data +void create_shared_memory_impl( + const std::string &initial_memory_name, + const std::string &synchronization_memory_name, + const std::string &action_memory_name, + const char *initial_data, + size_t data_size, + size_t action_size +) { + shared_memory_object::remove(initial_memory_name.c_str()); + managed_shared_memory sharedMemory( + create_only, initial_memory_name.c_str(), sizeof(SharedDataHeader) + data_size + ); + void *addr = sharedMemory.allocate(sizeof(SharedDataHeader) + data_size); + + auto *header = new (addr) SharedDataHeader(); + header->ready = false; + + char *data_start = + reinterpret_cast(header) + sizeof(SharedDataHeader); + std::memcpy(data_start, initial_data, data_size); + header->size = data_size; + + header->ready = true; + // Java will remove the initial environment shared memory + + // Create management shared memory (fixed size, the size field ) + shared_memory_object::remove(synchronization_memory_name.c_str()); + managed_shared_memory sharedMemoryManagement( + create_only, + synchronization_memory_name.c_str(), + sizeof(SharedDataHeader) + ); + void *addrManagement = + sharedMemoryManagement.allocate(sizeof(SharedDataHeader)); + auto *headerManagement = new (addrManagement) SharedDataHeader(); + std::unique_lock lockManagement(headerManagement->mutex); + headerManagement->ready = false; + headerManagement->size = 0; + headerManagement->ready = true; + + // Allocate shared memory for action + shared_memory_object::remove(action_memory_name.c_str()); + managed_shared_memory sharedMemoryAction( + create_only, action_memory_name.c_str(), sizeof(SharedDataHeader) + action_size + ); + void *addrAction = sharedMemoryAction.allocate(sizeof(SharedDataHeader) + action_size); + auto *headerAction = new (addrAction) SharedDataHeader(); + std::unique_lock lockAction(headerAction->mutex); + headerAction->ready = false; + headerAction->size = action_size; + headerAction->ready = true; +} + +// Write action to shared memory +void write_to_shared_memory_impl( + const std::string &action_memory_name, + const char *data, + const size_t action_size +) { + managed_shared_memory sharedMemory(open_only, action_memory_name.c_str()); + void *addr = sharedMemory.get_address(); + auto *header = reinterpret_cast(addr); + + std::unique_lock lock(header->mutex); + + char *data_start = + reinterpret_cast(header) + sizeof(SharedDataHeader); + std::memcpy(data_start, data, action_size); + + header->ready = true; + header->condition.notify_one(); +} + +// Read observation from shared memory +const char* read_from_shared_memory_impl( + const std::string &memory_name, + const std::string &synchronization_memory_name, + size_t &data_size +) { + // Synchronize with Java using synchronization shared memory + managed_shared_memory synchronizationSharedMemory( + open_only, synchronization_memory_name.c_str() + ); + void *addrManagement = synchronizationSharedMemory.get_address(); + auto *headerSynchronization = + reinterpret_cast(addrManagement); + std::unique_lock lockManagement( + headerSynchronization->mutex + ); + headerSynchronization->condition.wait(lockManagement, [&] { + return headerSynchronization->ready; + }); + + // Read the observation from shared memory + managed_shared_memory sharedMemory(open_only, memory_name.c_str()); + void *addr = sharedMemory.get_address(); + auto *header = reinterpret_cast(addr); + // Read the message from Java + char *data_start = + reinterpret_cast(header) + sizeof(SharedDataHeader); + header->ready = false; + + data_size = header->size; + return data_start; +} + +// Destroy shared memory +void destroy_shared_memory_impl(const std::string &memory_name) { + shared_memory_object::remove(memory_name.c_str()); +} diff --git a/src/cpp/ipc_boost.hpp b/src/cpp/ipc_boost.hpp new file mode 100644 index 00000000..05d9c2c7 --- /dev/null +++ b/src/cpp/ipc_boost.hpp @@ -0,0 +1,43 @@ +#ifndef SHARED_MEMORY_UTILS_HPP +#define SHARED_MEMORY_UTILS_HPP + +#include +#include +#include +#include +#include + +using namespace boost::interprocess; + +struct SharedDataHeader { + interprocess_mutex mutex; + interprocess_condition condition; + size_t size; + bool ready; +}; +// Message follows the header + +void create_shared_memory_impl( + const std::string &memory_name, + const std::string &management_memory_name, + const char *initial_data, + size_t data_size, + size_t action_size +); + +void write_to_shared_memory_impl( + const std::string &memory_name, + const char *data, + const size_t data_size +); + +const char *read_from_shared_memory_impl( + const std::string &memory_name, + const std::string &management_memory_name, + size_t &data_size +); + +// remove shared memory +void destroy_shared_memory_impl(const std::string &memory_name); + +#endif // SHARED_MEMORY_UTILS_HPP diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index 2c88c421..8afe333e 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -1,3 +1,4 @@ +#include "boost/interprocess/shared_memory_object.hpp" #include #include #include @@ -6,36 +7,91 @@ using namespace boost::interprocess; -struct SharedData { +// explicit structure of the object is handled using protobuf +struct SharedDataHeader { interprocess_mutex mutex; interprocess_condition condition; - bool ready; // Data ready flag - char data[256]; // Message buffer + bool ready; // Data ready flag }; -int main() { - // Shared memory - shared_memory_object::remove("PythonJavaSharedMemory"); - managed_shared_memory sharedMemory(create_only, "PythonJavaSharedMemory", 1024); +// char data[8]; // Message buffer (Acutally, protobuf object size, which can +// // be calcluated using protobuf) +// // TODO: use buffer size of protobuf object, not 256 + +// 1. Receive the initial environment message from Python (Python -> Java) +// 2. Send observation to python (Java -> Python) +// 3. Receive action from python (Python -> Java) +// Prepare Shared memory +void loop( + std::string initial_environment_memory_name, + std::string memory_name, + int initial_environment_size, + int observation_size, + int action_size +) { + // Prepare initial environment shared memory + shared_memory_object::remove(initial_environment_memory_name.c_str()); + managed_shared_memory sharedMemoryInitialEnvironment( + create_only, + initial_environment_memory_name.c_str(), + sizeof(SharedDataHeader) + initial_environment_size + ); + + // Read the initial environment + SharedDataHeader *sharedDataInitialEnvironment = + sharedMemoryInitialEnvironment.construct("SharedDataInitialEnvironment")(); + sharedDataInitialEnvironment->ready = false; + + sharedDataInitialEnvironment->condition.notify_one(); + + + + shared_memory_object::remove(memory_name.c_str()); + managed_shared_memory sharedMemory( + create_only, + memory_name.c_str(), + 1024 // TODO: Size of protobufdata sizes + ); - // Shared memory data - SharedData* sharedData = sharedMemory.construct("SharedData")(); + // Shared memory data for initial environment message + SharedData *sharedData = sharedMemory.construct("SharedData")(); sharedData->ready = false; + // Shared memory data for observation message + SharedData *sharedDataObs = + sharedMemory.construct("SharedDataObs")(); + sharedDataObs->ready = false; + // Shared memory data for action message + SharedData *sharedDataAction = + sharedMemory.construct("SharedDataAction")(); + sharedDataAction->ready = false; + + // 1. Receive the initial environment message from Python (Python -> Java) + + // Wait for Python to send a message + std::unique_lock lock(sharedData->mutex); + sharedData->condition.wait(lock, [&] { return sharedData->ready; }); + + // Read the message from Python + std::cout << "Received from Python: " << sharedData->data << std::endl; + + // Do something with the initial configuration message while (true) { + // Send the observation to Python + std::strcpy(sharedDataObs->data, "Observation from Java"); + sharedDataObs->ready = true; + // Notify Python that the observation has been sent + sharedDataObs->condition.notify_one(); + // Wait for Python to send a message - std::unique_lock lock(sharedData->mutex); - sharedData->condition.wait(lock, [&] { return sharedData->ready; }); + std::unique_lock lock(sharedDataAction->mutex); + sharedDataAction->condition.wait(lock, [&] { + return sharedDataAction->ready; + }); // Read the message from Python - std::cout << "Received from Python: " << sharedData->data << std::endl; - - // Send a message to Python - std::strcpy(sharedData->data, "Acknowledged from Java"); - sharedData->ready = false; - - // Notify Python that the message has been sent - sharedData->condition.notify_one(); + std::cout << "Received Action from Python: " << sharedDataAction->data + << std::endl; } // Cleanup diff --git a/src/craftground/environment/__init__.py b/src/craftground/environment/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/craftground/environment.py b/src/craftground/environment/environment.py similarity index 99% rename from src/craftground/environment.py rename to src/craftground/environment/environment.py index 8f552fed..3478328f 100644 --- a/src/craftground/environment.py +++ b/src/craftground/environment/environment.py @@ -17,20 +17,20 @@ from gymnasium.core import ActType, ObsType, RenderFrame import torch -from .action_space import ActionSpace -from .buffered_socket import BufferedSocket -from .csv_logger import CsvLogger, LogBackend -from .font import get_font -from .initial_environment_config import InitialEnvironmentConfig -from .minecraft import ( +from ..action_space import ActionSpace +from ..buffered_socket import BufferedSocket +from ..csv_logger import CsvLogger, LogBackend +from ..font import get_font +from ..initial_environment_config import InitialEnvironmentConfig +from ..minecraft import ( wait_for_server, send_fastreset2, send_action_and_commands, send_exit, ) -from .print_with_time import print_with_time -from .proto import observation_space_pb2 -from .screen_encoding_modes import ScreenEncodingMode +from ..print_with_time import print_with_time +from ..proto import observation_space_pb2 +from ..screen_encoding_modes import ScreenEncodingMode class ActionSpaceVersion(Enum): diff --git a/src/craftground/environment/ipc_boost.py b/src/craftground/environment/ipc_boost.py new file mode 100644 index 00000000..e69de29b From 66d52c85a610a86bdfc7f8ac8f59530d9181df5a Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Mon, 20 Jan 2025 22:22:49 +0900 Subject: [PATCH 03/91] =?UTF-8?q?=E2=9C=A8=20Implement=20python=20binding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/ipc_boost.py | 50 ++++ src/craftground/proto/action_space_pb2.py | 4 +- src/craftground/proto/action_space_pb2.pyi | 56 ++++ .../proto/initial_environment_pb2.py | 4 +- .../proto/initial_environment_pb2.pyi | 102 +++++++ .../proto/observation_space_pb2.py | 4 +- .../proto/observation_space_pb2.pyi | 253 ++++++++++++++++++ 7 files changed, 467 insertions(+), 6 deletions(-) create mode 100644 src/craftground/proto/action_space_pb2.pyi create mode 100644 src/craftground/proto/initial_environment_pb2.pyi create mode 100644 src/craftground/proto/observation_space_pb2.pyi diff --git a/src/craftground/environment/ipc_boost.py b/src/craftground/environment/ipc_boost.py index e69de29b..ef52ee87 100644 --- a/src/craftground/environment/ipc_boost.py +++ b/src/craftground/environment/ipc_boost.py @@ -0,0 +1,50 @@ +from craftground_native import ( # noqa + initialize_shared_memory, # noqa + write_to_shared_memory, # noqa + read_from_shared_memory, # noqa + destroy_shared_memory, # noqa +) +from proto.action_space_pb2 import ActionSpaceMessageV2 +from proto.initial_environment_pb2 import InitialEnvironmentMessage + + +class IPCBoost: + def __init__(self, port: str, initial_environment: InitialEnvironmentMessage): + self.port = port + self.initial_environment_shared_memory_name = ( + f"craftground_{port}_initial_environment" + ) + self.action_shared_memory_name = f"craftground_{port}_action" + self.observation_shared_memory_name = f"craftground_{port}_observation" + self.synchronization_shared_memory_name = f"craftground_{port}_synchronization" + + initial_environment_bytes: bytes = initial_environment.SerializeToString() + + # Get the length of the action space message + dummy_action: ActionSpaceMessageV2 = ActionSpaceMessageV2() + dummy_action_bytes: bytes = dummy_action.SerializeToString() + initialize_shared_memory( + self.initial_environment_shared_memory_name, + self.synchronization_shared_memory_name, + initial_environment_bytes, + len(initial_environment_bytes), + len(dummy_action_bytes), + ) + + def write_action(self, action: ActionSpaceMessageV2): + action_bytes: bytes = action.SerializeToString() + write_to_shared_memory( + self.action_shared_memory_name, action_bytes, len(action_bytes) + ) + + def read(self) -> bytes: + return read_from_shared_memory( + self.observation_shared_memory_name, self.synchronization_shared_memory_name + ) + + def destroy(self): + destroy_shared_memory(self.action_shared_memory_name) + destroy_shared_memory(self.observation_shared_memory_name) + destroy_shared_memory(self.synchronization_shared_memory_name) + # Java destroys the initial environment shared memory + # destroy_shared_memory(self.initial_environment_shared_memory_name) diff --git a/src/craftground/proto/action_space_pb2.py b/src/craftground/proto/action_space_pb2.py index 4788ecfe..d44d0125 100644 --- a/src/craftground/proto/action_space_pb2.py +++ b/src/craftground/proto/action_space_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: proto/action_space.proto -# Protobuf Python Version: 5.29.1 +# Protobuf Python Version: 5.29.2 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -13,7 +13,7 @@ _runtime_version.Domain.PUBLIC, 5, 29, - 1, + 2, '', 'proto/action_space.proto' ) diff --git a/src/craftground/proto/action_space_pb2.pyi b/src/craftground/proto/action_space_pb2.pyi new file mode 100644 index 00000000..58a9ca48 --- /dev/null +++ b/src/craftground/proto/action_space_pb2.pyi @@ -0,0 +1,56 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Optional as _Optional + +DESCRIPTOR: _descriptor.FileDescriptor + +class ActionSpaceMessageV2(_message.Message): + __slots__ = ("attack", "back", "forward", "jump", "left", "right", "sneak", "sprint", "use", "drop", "inventory", "hotbar_1", "hotbar_2", "hotbar_3", "hotbar_4", "hotbar_5", "hotbar_6", "hotbar_7", "hotbar_8", "hotbar_9", "camera_pitch", "camera_yaw", "commands") + ATTACK_FIELD_NUMBER: _ClassVar[int] + BACK_FIELD_NUMBER: _ClassVar[int] + FORWARD_FIELD_NUMBER: _ClassVar[int] + JUMP_FIELD_NUMBER: _ClassVar[int] + LEFT_FIELD_NUMBER: _ClassVar[int] + RIGHT_FIELD_NUMBER: _ClassVar[int] + SNEAK_FIELD_NUMBER: _ClassVar[int] + SPRINT_FIELD_NUMBER: _ClassVar[int] + USE_FIELD_NUMBER: _ClassVar[int] + DROP_FIELD_NUMBER: _ClassVar[int] + INVENTORY_FIELD_NUMBER: _ClassVar[int] + HOTBAR_1_FIELD_NUMBER: _ClassVar[int] + HOTBAR_2_FIELD_NUMBER: _ClassVar[int] + HOTBAR_3_FIELD_NUMBER: _ClassVar[int] + HOTBAR_4_FIELD_NUMBER: _ClassVar[int] + HOTBAR_5_FIELD_NUMBER: _ClassVar[int] + HOTBAR_6_FIELD_NUMBER: _ClassVar[int] + HOTBAR_7_FIELD_NUMBER: _ClassVar[int] + HOTBAR_8_FIELD_NUMBER: _ClassVar[int] + HOTBAR_9_FIELD_NUMBER: _ClassVar[int] + CAMERA_PITCH_FIELD_NUMBER: _ClassVar[int] + CAMERA_YAW_FIELD_NUMBER: _ClassVar[int] + COMMANDS_FIELD_NUMBER: _ClassVar[int] + attack: bool + back: bool + forward: bool + jump: bool + left: bool + right: bool + sneak: bool + sprint: bool + use: bool + drop: bool + inventory: bool + hotbar_1: bool + hotbar_2: bool + hotbar_3: bool + hotbar_4: bool + hotbar_5: bool + hotbar_6: bool + hotbar_7: bool + hotbar_8: bool + hotbar_9: bool + camera_pitch: float + camera_yaw: float + commands: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, attack: bool = ..., back: bool = ..., forward: bool = ..., jump: bool = ..., left: bool = ..., right: bool = ..., sneak: bool = ..., sprint: bool = ..., use: bool = ..., drop: bool = ..., inventory: bool = ..., hotbar_1: bool = ..., hotbar_2: bool = ..., hotbar_3: bool = ..., hotbar_4: bool = ..., hotbar_5: bool = ..., hotbar_6: bool = ..., hotbar_7: bool = ..., hotbar_8: bool = ..., hotbar_9: bool = ..., camera_pitch: _Optional[float] = ..., camera_yaw: _Optional[float] = ..., commands: _Optional[_Iterable[str]] = ...) -> None: ... diff --git a/src/craftground/proto/initial_environment_pb2.py b/src/craftground/proto/initial_environment_pb2.py index 979b355b..fa75e2ba 100644 --- a/src/craftground/proto/initial_environment_pb2.py +++ b/src/craftground/proto/initial_environment_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: proto/initial_environment.proto -# Protobuf Python Version: 5.29.1 +# Protobuf Python Version: 5.29.2 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -13,7 +13,7 @@ _runtime_version.Domain.PUBLIC, 5, 29, - 1, + 2, '', 'proto/initial_environment.proto' ) diff --git a/src/craftground/proto/initial_environment_pb2.pyi b/src/craftground/proto/initial_environment_pb2.pyi new file mode 100644 index 00000000..761eef26 --- /dev/null +++ b/src/craftground/proto/initial_environment_pb2.pyi @@ -0,0 +1,102 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class GameMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + SURVIVAL: _ClassVar[GameMode] + HARDCORE: _ClassVar[GameMode] + CREATIVE: _ClassVar[GameMode] + +class Difficulty(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PEACEFUL: _ClassVar[Difficulty] + EASY: _ClassVar[Difficulty] + NORMAL: _ClassVar[Difficulty] + HARD: _ClassVar[Difficulty] + +class WorldType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + DEFAULT: _ClassVar[WorldType] + SUPERFLAT: _ClassVar[WorldType] + LARGE_BIOMES: _ClassVar[WorldType] + AMPLIFIED: _ClassVar[WorldType] + SINGLE_BIOME: _ClassVar[WorldType] +SURVIVAL: GameMode +HARDCORE: GameMode +CREATIVE: GameMode +PEACEFUL: Difficulty +EASY: Difficulty +NORMAL: Difficulty +HARD: Difficulty +DEFAULT: WorldType +SUPERFLAT: WorldType +LARGE_BIOMES: WorldType +AMPLIFIED: WorldType +SINGLE_BIOME: WorldType + +class InitialEnvironmentMessage(_message.Message): + __slots__ = ("imageSizeX", "imageSizeY", "gamemode", "difficulty", "worldType", "worldTypeArgs", "seed", "generate_structures", "bonus_chest", "datapackPaths", "initialExtraCommands", "killedStatKeys", "minedStatKeys", "miscStatKeys", "surroundingEntityDistances", "hudHidden", "render_distance", "simulation_distance", "eye_distance", "structurePaths", "no_fov_effect", "request_raycast", "screen_encoding_mode", "requiresSurroundingBlocks", "level_display_name_to_play", "fov", "requiresBiomeInfo", "requiresHeightmap", "python_pid") + IMAGESIZEX_FIELD_NUMBER: _ClassVar[int] + IMAGESIZEY_FIELD_NUMBER: _ClassVar[int] + GAMEMODE_FIELD_NUMBER: _ClassVar[int] + DIFFICULTY_FIELD_NUMBER: _ClassVar[int] + WORLDTYPE_FIELD_NUMBER: _ClassVar[int] + WORLDTYPEARGS_FIELD_NUMBER: _ClassVar[int] + SEED_FIELD_NUMBER: _ClassVar[int] + GENERATE_STRUCTURES_FIELD_NUMBER: _ClassVar[int] + BONUS_CHEST_FIELD_NUMBER: _ClassVar[int] + DATAPACKPATHS_FIELD_NUMBER: _ClassVar[int] + INITIALEXTRACOMMANDS_FIELD_NUMBER: _ClassVar[int] + KILLEDSTATKEYS_FIELD_NUMBER: _ClassVar[int] + MINEDSTATKEYS_FIELD_NUMBER: _ClassVar[int] + MISCSTATKEYS_FIELD_NUMBER: _ClassVar[int] + SURROUNDINGENTITYDISTANCES_FIELD_NUMBER: _ClassVar[int] + HUDHIDDEN_FIELD_NUMBER: _ClassVar[int] + RENDER_DISTANCE_FIELD_NUMBER: _ClassVar[int] + SIMULATION_DISTANCE_FIELD_NUMBER: _ClassVar[int] + EYE_DISTANCE_FIELD_NUMBER: _ClassVar[int] + STRUCTUREPATHS_FIELD_NUMBER: _ClassVar[int] + NO_FOV_EFFECT_FIELD_NUMBER: _ClassVar[int] + REQUEST_RAYCAST_FIELD_NUMBER: _ClassVar[int] + SCREEN_ENCODING_MODE_FIELD_NUMBER: _ClassVar[int] + REQUIRESSURROUNDINGBLOCKS_FIELD_NUMBER: _ClassVar[int] + LEVEL_DISPLAY_NAME_TO_PLAY_FIELD_NUMBER: _ClassVar[int] + FOV_FIELD_NUMBER: _ClassVar[int] + REQUIRESBIOMEINFO_FIELD_NUMBER: _ClassVar[int] + REQUIRESHEIGHTMAP_FIELD_NUMBER: _ClassVar[int] + PYTHON_PID_FIELD_NUMBER: _ClassVar[int] + imageSizeX: int + imageSizeY: int + gamemode: GameMode + difficulty: Difficulty + worldType: WorldType + worldTypeArgs: str + seed: str + generate_structures: bool + bonus_chest: bool + datapackPaths: _containers.RepeatedScalarFieldContainer[str] + initialExtraCommands: _containers.RepeatedScalarFieldContainer[str] + killedStatKeys: _containers.RepeatedScalarFieldContainer[str] + minedStatKeys: _containers.RepeatedScalarFieldContainer[str] + miscStatKeys: _containers.RepeatedScalarFieldContainer[str] + surroundingEntityDistances: _containers.RepeatedScalarFieldContainer[int] + hudHidden: bool + render_distance: int + simulation_distance: int + eye_distance: float + structurePaths: _containers.RepeatedScalarFieldContainer[str] + no_fov_effect: bool + request_raycast: bool + screen_encoding_mode: int + requiresSurroundingBlocks: bool + level_display_name_to_play: str + fov: float + requiresBiomeInfo: bool + requiresHeightmap: bool + python_pid: int + def __init__(self, imageSizeX: _Optional[int] = ..., imageSizeY: _Optional[int] = ..., gamemode: _Optional[_Union[GameMode, str]] = ..., difficulty: _Optional[_Union[Difficulty, str]] = ..., worldType: _Optional[_Union[WorldType, str]] = ..., worldTypeArgs: _Optional[str] = ..., seed: _Optional[str] = ..., generate_structures: bool = ..., bonus_chest: bool = ..., datapackPaths: _Optional[_Iterable[str]] = ..., initialExtraCommands: _Optional[_Iterable[str]] = ..., killedStatKeys: _Optional[_Iterable[str]] = ..., minedStatKeys: _Optional[_Iterable[str]] = ..., miscStatKeys: _Optional[_Iterable[str]] = ..., surroundingEntityDistances: _Optional[_Iterable[int]] = ..., hudHidden: bool = ..., render_distance: _Optional[int] = ..., simulation_distance: _Optional[int] = ..., eye_distance: _Optional[float] = ..., structurePaths: _Optional[_Iterable[str]] = ..., no_fov_effect: bool = ..., request_raycast: bool = ..., screen_encoding_mode: _Optional[int] = ..., requiresSurroundingBlocks: bool = ..., level_display_name_to_play: _Optional[str] = ..., fov: _Optional[float] = ..., requiresBiomeInfo: bool = ..., requiresHeightmap: bool = ..., python_pid: _Optional[int] = ...) -> None: ... diff --git a/src/craftground/proto/observation_space_pb2.py b/src/craftground/proto/observation_space_pb2.py index 0bb6877c..9ce1a799 100644 --- a/src/craftground/proto/observation_space_pb2.py +++ b/src/craftground/proto/observation_space_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: proto/observation_space.proto -# Protobuf Python Version: 5.29.1 +# Protobuf Python Version: 5.29.2 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -13,7 +13,7 @@ _runtime_version.Domain.PUBLIC, 5, 29, - 1, + 2, '', 'proto/observation_space.proto' ) diff --git a/src/craftground/proto/observation_space_pb2.pyi b/src/craftground/proto/observation_space_pb2.pyi new file mode 100644 index 00000000..b9a91c51 --- /dev/null +++ b/src/craftground/proto/observation_space_pb2.pyi @@ -0,0 +1,253 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class ItemStack(_message.Message): + __slots__ = ("raw_id", "translation_key", "count", "durability", "max_durability") + RAW_ID_FIELD_NUMBER: _ClassVar[int] + TRANSLATION_KEY_FIELD_NUMBER: _ClassVar[int] + COUNT_FIELD_NUMBER: _ClassVar[int] + DURABILITY_FIELD_NUMBER: _ClassVar[int] + MAX_DURABILITY_FIELD_NUMBER: _ClassVar[int] + raw_id: int + translation_key: str + count: int + durability: int + max_durability: int + def __init__(self, raw_id: _Optional[int] = ..., translation_key: _Optional[str] = ..., count: _Optional[int] = ..., durability: _Optional[int] = ..., max_durability: _Optional[int] = ...) -> None: ... + +class BlockInfo(_message.Message): + __slots__ = ("x", "y", "z", "translation_key") + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + TRANSLATION_KEY_FIELD_NUMBER: _ClassVar[int] + x: int + y: int + z: int + translation_key: str + def __init__(self, x: _Optional[int] = ..., y: _Optional[int] = ..., z: _Optional[int] = ..., translation_key: _Optional[str] = ...) -> None: ... + +class EntityInfo(_message.Message): + __slots__ = ("unique_name", "translation_key", "x", "y", "z", "yaw", "pitch", "health") + UNIQUE_NAME_FIELD_NUMBER: _ClassVar[int] + TRANSLATION_KEY_FIELD_NUMBER: _ClassVar[int] + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + YAW_FIELD_NUMBER: _ClassVar[int] + PITCH_FIELD_NUMBER: _ClassVar[int] + HEALTH_FIELD_NUMBER: _ClassVar[int] + unique_name: str + translation_key: str + x: float + y: float + z: float + yaw: float + pitch: float + health: float + def __init__(self, unique_name: _Optional[str] = ..., translation_key: _Optional[str] = ..., x: _Optional[float] = ..., y: _Optional[float] = ..., z: _Optional[float] = ..., yaw: _Optional[float] = ..., pitch: _Optional[float] = ..., health: _Optional[float] = ...) -> None: ... + +class HitResult(_message.Message): + __slots__ = ("type", "target_block", "target_entity") + class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + MISS: _ClassVar[HitResult.Type] + BLOCK: _ClassVar[HitResult.Type] + ENTITY: _ClassVar[HitResult.Type] + MISS: HitResult.Type + BLOCK: HitResult.Type + ENTITY: HitResult.Type + TYPE_FIELD_NUMBER: _ClassVar[int] + TARGET_BLOCK_FIELD_NUMBER: _ClassVar[int] + TARGET_ENTITY_FIELD_NUMBER: _ClassVar[int] + type: HitResult.Type + target_block: BlockInfo + target_entity: EntityInfo + def __init__(self, type: _Optional[_Union[HitResult.Type, str]] = ..., target_block: _Optional[_Union[BlockInfo, _Mapping]] = ..., target_entity: _Optional[_Union[EntityInfo, _Mapping]] = ...) -> None: ... + +class StatusEffect(_message.Message): + __slots__ = ("translation_key", "duration", "amplifier") + TRANSLATION_KEY_FIELD_NUMBER: _ClassVar[int] + DURATION_FIELD_NUMBER: _ClassVar[int] + AMPLIFIER_FIELD_NUMBER: _ClassVar[int] + translation_key: str + duration: int + amplifier: int + def __init__(self, translation_key: _Optional[str] = ..., duration: _Optional[int] = ..., amplifier: _Optional[int] = ...) -> None: ... + +class SoundEntry(_message.Message): + __slots__ = ("translate_key", "age", "x", "y", "z") + TRANSLATE_KEY_FIELD_NUMBER: _ClassVar[int] + AGE_FIELD_NUMBER: _ClassVar[int] + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + translate_key: str + age: int + x: float + y: float + z: float + def __init__(self, translate_key: _Optional[str] = ..., age: _Optional[int] = ..., x: _Optional[float] = ..., y: _Optional[float] = ..., z: _Optional[float] = ...) -> None: ... + +class EntitiesWithinDistance(_message.Message): + __slots__ = ("entities",) + ENTITIES_FIELD_NUMBER: _ClassVar[int] + entities: _containers.RepeatedCompositeFieldContainer[EntityInfo] + def __init__(self, entities: _Optional[_Iterable[_Union[EntityInfo, _Mapping]]] = ...) -> None: ... + +class ChatMessageInfo(_message.Message): + __slots__ = ("added_time", "message", "indicator") + ADDED_TIME_FIELD_NUMBER: _ClassVar[int] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + INDICATOR_FIELD_NUMBER: _ClassVar[int] + added_time: int + message: str + indicator: str + def __init__(self, added_time: _Optional[int] = ..., message: _Optional[str] = ..., indicator: _Optional[str] = ...) -> None: ... + +class BiomeInfo(_message.Message): + __slots__ = ("biome_name", "center_x", "center_y", "center_z") + BIOME_NAME_FIELD_NUMBER: _ClassVar[int] + CENTER_X_FIELD_NUMBER: _ClassVar[int] + CENTER_Y_FIELD_NUMBER: _ClassVar[int] + CENTER_Z_FIELD_NUMBER: _ClassVar[int] + biome_name: str + center_x: int + center_y: int + center_z: int + def __init__(self, biome_name: _Optional[str] = ..., center_x: _Optional[int] = ..., center_y: _Optional[int] = ..., center_z: _Optional[int] = ...) -> None: ... + +class NearbyBiome(_message.Message): + __slots__ = ("biome_name", "x", "y", "z") + BIOME_NAME_FIELD_NUMBER: _ClassVar[int] + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + biome_name: str + x: int + y: int + z: int + def __init__(self, biome_name: _Optional[str] = ..., x: _Optional[int] = ..., y: _Optional[int] = ..., z: _Optional[int] = ...) -> None: ... + +class HeightInfo(_message.Message): + __slots__ = ("x", "z", "height", "block_name") + X_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + HEIGHT_FIELD_NUMBER: _ClassVar[int] + BLOCK_NAME_FIELD_NUMBER: _ClassVar[int] + x: int + z: int + height: int + block_name: str + def __init__(self, x: _Optional[int] = ..., z: _Optional[int] = ..., height: _Optional[int] = ..., block_name: _Optional[str] = ...) -> None: ... + +class ObservationSpaceMessage(_message.Message): + __slots__ = ("image", "x", "y", "z", "yaw", "pitch", "health", "food_level", "saturation_level", "is_dead", "inventory", "raycast_result", "sound_subtitles", "status_effects", "killed_statistics", "mined_statistics", "misc_statistics", "visible_entities", "surrounding_entities", "bobber_thrown", "experience", "world_time", "last_death_message", "image_2", "surrounding_blocks", "eye_in_block", "suffocating", "chat_messages", "biome_info", "nearby_biomes", "submerged_in_water", "is_in_lava", "submerged_in_lava", "height_info", "is_on_ground", "is_touching_water", "ipc_handle") + class KilledStatisticsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: int + def __init__(self, key: _Optional[str] = ..., value: _Optional[int] = ...) -> None: ... + class MinedStatisticsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: int + def __init__(self, key: _Optional[str] = ..., value: _Optional[int] = ...) -> None: ... + class MiscStatisticsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: int + def __init__(self, key: _Optional[str] = ..., value: _Optional[int] = ...) -> None: ... + class SurroundingEntitiesEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: int + value: EntitiesWithinDistance + def __init__(self, key: _Optional[int] = ..., value: _Optional[_Union[EntitiesWithinDistance, _Mapping]] = ...) -> None: ... + IMAGE_FIELD_NUMBER: _ClassVar[int] + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + Z_FIELD_NUMBER: _ClassVar[int] + YAW_FIELD_NUMBER: _ClassVar[int] + PITCH_FIELD_NUMBER: _ClassVar[int] + HEALTH_FIELD_NUMBER: _ClassVar[int] + FOOD_LEVEL_FIELD_NUMBER: _ClassVar[int] + SATURATION_LEVEL_FIELD_NUMBER: _ClassVar[int] + IS_DEAD_FIELD_NUMBER: _ClassVar[int] + INVENTORY_FIELD_NUMBER: _ClassVar[int] + RAYCAST_RESULT_FIELD_NUMBER: _ClassVar[int] + SOUND_SUBTITLES_FIELD_NUMBER: _ClassVar[int] + STATUS_EFFECTS_FIELD_NUMBER: _ClassVar[int] + KILLED_STATISTICS_FIELD_NUMBER: _ClassVar[int] + MINED_STATISTICS_FIELD_NUMBER: _ClassVar[int] + MISC_STATISTICS_FIELD_NUMBER: _ClassVar[int] + VISIBLE_ENTITIES_FIELD_NUMBER: _ClassVar[int] + SURROUNDING_ENTITIES_FIELD_NUMBER: _ClassVar[int] + BOBBER_THROWN_FIELD_NUMBER: _ClassVar[int] + EXPERIENCE_FIELD_NUMBER: _ClassVar[int] + WORLD_TIME_FIELD_NUMBER: _ClassVar[int] + LAST_DEATH_MESSAGE_FIELD_NUMBER: _ClassVar[int] + IMAGE_2_FIELD_NUMBER: _ClassVar[int] + SURROUNDING_BLOCKS_FIELD_NUMBER: _ClassVar[int] + EYE_IN_BLOCK_FIELD_NUMBER: _ClassVar[int] + SUFFOCATING_FIELD_NUMBER: _ClassVar[int] + CHAT_MESSAGES_FIELD_NUMBER: _ClassVar[int] + BIOME_INFO_FIELD_NUMBER: _ClassVar[int] + NEARBY_BIOMES_FIELD_NUMBER: _ClassVar[int] + SUBMERGED_IN_WATER_FIELD_NUMBER: _ClassVar[int] + IS_IN_LAVA_FIELD_NUMBER: _ClassVar[int] + SUBMERGED_IN_LAVA_FIELD_NUMBER: _ClassVar[int] + HEIGHT_INFO_FIELD_NUMBER: _ClassVar[int] + IS_ON_GROUND_FIELD_NUMBER: _ClassVar[int] + IS_TOUCHING_WATER_FIELD_NUMBER: _ClassVar[int] + IPC_HANDLE_FIELD_NUMBER: _ClassVar[int] + image: bytes + x: float + y: float + z: float + yaw: float + pitch: float + health: float + food_level: float + saturation_level: float + is_dead: bool + inventory: _containers.RepeatedCompositeFieldContainer[ItemStack] + raycast_result: HitResult + sound_subtitles: _containers.RepeatedCompositeFieldContainer[SoundEntry] + status_effects: _containers.RepeatedCompositeFieldContainer[StatusEffect] + killed_statistics: _containers.ScalarMap[str, int] + mined_statistics: _containers.ScalarMap[str, int] + misc_statistics: _containers.ScalarMap[str, int] + visible_entities: _containers.RepeatedCompositeFieldContainer[EntityInfo] + surrounding_entities: _containers.MessageMap[int, EntitiesWithinDistance] + bobber_thrown: bool + experience: int + world_time: int + last_death_message: str + image_2: bytes + surrounding_blocks: _containers.RepeatedCompositeFieldContainer[BlockInfo] + eye_in_block: bool + suffocating: bool + chat_messages: _containers.RepeatedCompositeFieldContainer[ChatMessageInfo] + biome_info: BiomeInfo + nearby_biomes: _containers.RepeatedCompositeFieldContainer[NearbyBiome] + submerged_in_water: bool + is_in_lava: bool + submerged_in_lava: bool + height_info: _containers.RepeatedCompositeFieldContainer[HeightInfo] + is_on_ground: bool + is_touching_water: bool + ipc_handle: bytes + def __init__(self, image: _Optional[bytes] = ..., x: _Optional[float] = ..., y: _Optional[float] = ..., z: _Optional[float] = ..., yaw: _Optional[float] = ..., pitch: _Optional[float] = ..., health: _Optional[float] = ..., food_level: _Optional[float] = ..., saturation_level: _Optional[float] = ..., is_dead: bool = ..., inventory: _Optional[_Iterable[_Union[ItemStack, _Mapping]]] = ..., raycast_result: _Optional[_Union[HitResult, _Mapping]] = ..., sound_subtitles: _Optional[_Iterable[_Union[SoundEntry, _Mapping]]] = ..., status_effects: _Optional[_Iterable[_Union[StatusEffect, _Mapping]]] = ..., killed_statistics: _Optional[_Mapping[str, int]] = ..., mined_statistics: _Optional[_Mapping[str, int]] = ..., misc_statistics: _Optional[_Mapping[str, int]] = ..., visible_entities: _Optional[_Iterable[_Union[EntityInfo, _Mapping]]] = ..., surrounding_entities: _Optional[_Mapping[int, EntitiesWithinDistance]] = ..., bobber_thrown: bool = ..., experience: _Optional[int] = ..., world_time: _Optional[int] = ..., last_death_message: _Optional[str] = ..., image_2: _Optional[bytes] = ..., surrounding_blocks: _Optional[_Iterable[_Union[BlockInfo, _Mapping]]] = ..., eye_in_block: bool = ..., suffocating: bool = ..., chat_messages: _Optional[_Iterable[_Union[ChatMessageInfo, _Mapping]]] = ..., biome_info: _Optional[_Union[BiomeInfo, _Mapping]] = ..., nearby_biomes: _Optional[_Iterable[_Union[NearbyBiome, _Mapping]]] = ..., submerged_in_water: bool = ..., is_in_lava: bool = ..., submerged_in_lava: bool = ..., height_info: _Optional[_Iterable[_Union[HeightInfo, _Mapping]]] = ..., is_on_ground: bool = ..., is_touching_water: bool = ..., ipc_handle: _Optional[bytes] = ...) -> None: ... From 1288e1393ed314632be1dce4f8c22989e5092a64 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Mon, 20 Jan 2025 23:30:49 +0900 Subject: [PATCH 04/91] =?UTF-8?q?=E2=9C=A8=20Add=20boost=20interface=20for?= =?UTF-8?q?=20java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_boost.cpp | 44 ++- .../MinecraftEnv/src/main/cpp/boost_ipc.cpp | 251 ++++++++++++------ 2 files changed, 196 insertions(+), 99 deletions(-) diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 4bcb687f..e837a274 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -27,20 +27,18 @@ void create_shared_memory_impl( header->ready = true; // Java will remove the initial environment shared memory - // Create management shared memory (fixed size, the size field ) + // Create synchronization shared memory (fixed size, the size field ) shared_memory_object::remove(synchronization_memory_name.c_str()); - managed_shared_memory sharedMemoryManagement( + managed_shared_memory sharedMemorySynchronization( create_only, synchronization_memory_name.c_str(), sizeof(SharedDataHeader) ); - void *addrManagement = - sharedMemoryManagement.allocate(sizeof(SharedDataHeader)); - auto *headerManagement = new (addrManagement) SharedDataHeader(); - std::unique_lock lockManagement(headerManagement->mutex); - headerManagement->ready = false; - headerManagement->size = 0; - headerManagement->ready = true; + void *addrSyncrhonization = + sharedMemorySynchronization.allocate(sizeof(SharedDataHeader)); + auto *headerSynchronization = new (addrSyncrhonization) SharedDataHeader(); + headerSynchronization->size = 0; + headerSynchronization->ready = true; // Allocate shared memory for action shared_memory_object::remove(action_memory_name.c_str()); @@ -49,8 +47,6 @@ void create_shared_memory_impl( ); void *addrAction = sharedMemoryAction.allocate(sizeof(SharedDataHeader) + action_size); auto *headerAction = new (addrAction) SharedDataHeader(); - std::unique_lock lockAction(headerAction->mutex); - headerAction->ready = false; headerAction->size = action_size; headerAction->ready = true; } @@ -61,18 +57,17 @@ void write_to_shared_memory_impl( const char *data, const size_t action_size ) { - managed_shared_memory sharedMemory(open_only, action_memory_name.c_str()); - void *addr = sharedMemory.get_address(); - auto *header = reinterpret_cast(addr); - - std::unique_lock lock(header->mutex); + managed_shared_memory actionMemory(open_only, action_memory_name.c_str()); + void *addr = actionMemory.get_address(); + auto *actionHeader = reinterpret_cast(addr); + std::unique_lock actionLock(actionHeader->mutex); char *data_start = - reinterpret_cast(header) + sizeof(SharedDataHeader); + reinterpret_cast(actionHeader) + sizeof(SharedDataHeader); std::memcpy(data_start, data, action_size); - - header->ready = true; - header->condition.notify_one(); + actionHeader->ready = true; + actionHeader->condition.notify_one(); + actionLock.unlock(); } // Read observation from shared memory @@ -85,13 +80,13 @@ const char* read_from_shared_memory_impl( managed_shared_memory synchronizationSharedMemory( open_only, synchronization_memory_name.c_str() ); - void *addrManagement = synchronizationSharedMemory.get_address(); + void *addrSynchronization = synchronizationSharedMemory.get_address(); auto *headerSynchronization = - reinterpret_cast(addrManagement); - std::unique_lock lockManagement( + reinterpret_cast(addrSynchronization); + std::unique_lock lockSynchronization( headerSynchronization->mutex ); - headerSynchronization->condition.wait(lockManagement, [&] { + headerSynchronization->condition.wait(lockSynchronization, [&] { return headerSynchronization->ready; }); @@ -103,6 +98,7 @@ const char* read_from_shared_memory_impl( char *data_start = reinterpret_cast(header) + sizeof(SharedDataHeader); header->ready = false; + lockSynchronization.unlock(); data_size = header->size; return data_start; diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index 8afe333e..6c15edf5 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -1,9 +1,9 @@ -#include "boost/interprocess/shared_memory_object.hpp" +#include #include #include #include #include -#include +#include using namespace boost::interprocess; @@ -11,90 +11,191 @@ using namespace boost::interprocess; struct SharedDataHeader { interprocess_mutex mutex; interprocess_condition condition; - bool ready; // Data ready flag + size_t size; + bool ready; }; - -// char data[8]; // Message buffer (Acutally, protobuf object size, which can -// // be calcluated using protobuf) -// // TODO: use buffer size of protobuf object, not 256 - -// 1. Receive the initial environment message from Python (Python -> Java) -// 2. Send observation to python (Java -> Python) -// 3. Receive action from python (Python -> Java) -// Prepare Shared memory -void loop( - std::string initial_environment_memory_name, - std::string memory_name, - int initial_environment_size, - int observation_size, - int action_size +// Message follows the header + +// Returns ByteArray object containing the initial environment message +jobject read_initial_environment( + JNIEnv *env, + jclass clazz, + const std::string &initial_environment_memory_name, + size_t &data_size ) { - // Prepare initial environment shared memory - shared_memory_object::remove(initial_environment_memory_name.c_str()); managed_shared_memory sharedMemoryInitialEnvironment( - create_only, - initial_environment_memory_name.c_str(), - sizeof(SharedDataHeader) + initial_environment_size + open_only, initial_environment_memory_name.c_str() + ); + void *addrInitialEnvironment = sharedMemoryInitialEnvironment.get_address(); + auto *headerInitialEnvironment = + reinterpret_cast(addrInitialEnvironment); + // Read the initial environment message + char *data_startInitialEnvironment = + reinterpret_cast(headerInitialEnvironment) + + sizeof(SharedDataHeader); + data_size = headerInitialEnvironment->size; + + jbyteArray byteArray = env->NewByteArray(data_size); + if (byteArray == nullptr || env->ExceptionCheck()) { + return nullptr; + } + env->SetByteArrayRegion( + byteArray, + 0, + data_size, + reinterpret_cast(data_startInitialEnvironment) ); + // Delete the shared memory + shared_memory_object::remove(initial_environment_memory_name.c_str()); + return byteArray; +} - // Read the initial environment - SharedDataHeader *sharedDataInitialEnvironment = - sharedMemoryInitialEnvironment.construct("SharedDataInitialEnvironment")(); - sharedDataInitialEnvironment->ready = false; +jbyteArray read_action( + JNIEnv *env, + jclass clazz, + const std::string &action_memory_name, + jbyteArray data +) { + managed_shared_memory actionSharedMemory( + open_only, action_memory_name.c_str() + ); + void *addr = actionSharedMemory.get_address(); + auto *actionHeader = reinterpret_cast(addr); - sharedDataInitialEnvironment->condition.notify_one(); + std::unique_lock actionLock(actionHeader->mutex); + actionHeader->condition.wait(actionLock, [&] { + return actionHeader->ready; + }); + if (data == nullptr) { + data = env->NewByteArray(actionHeader->size); + } + if (data == nullptr || env->ExceptionCheck()) { + return nullptr; + } + // Read the action message + char *data_start = + reinterpret_cast(actionHeader) + sizeof(SharedDataHeader); + env->SetByteArrayRegion( + data, 0, actionHeader->size, reinterpret_cast(data_start) + ); + actionHeader->ready = false; + actionLock.unlock(); + return data; +} +void write_observation( + const std::string &observation_memory_name, + const std::string &synchronization_memory_name, + const char *data, + const size_t observation_size +) { + managed_shared_memory synchronizationSharedMemory( + open_only, synchronization_memory_name.c_str() + ); + void *addrSynchronization = synchronizationSharedMemory.get_address(); + auto *headerSynchronization = + reinterpret_cast(addrSynchronization); + std::unique_lock lockSynchronization( + headerSynchronization->mutex + ); + headerSynchronization->ready = false; - shared_memory_object::remove(memory_name.c_str()); - managed_shared_memory sharedMemory( - create_only, - memory_name.c_str(), - 1024 // TODO: Size of protobufdata sizes + managed_shared_memory observationSharedMemory( + open_only, observation_memory_name.c_str() ); - // Shared memory data for initial environment message - SharedData *sharedData = sharedMemory.construct("SharedData")(); - sharedData->ready = false; - // Shared memory data for observation message - SharedData *sharedDataObs = - sharedMemory.construct("SharedDataObs")(); - sharedDataObs->ready = false; - // Shared memory data for action message - SharedData *sharedDataAction = - sharedMemory.construct("SharedDataAction")(); - sharedDataAction->ready = false; - - // 1. Receive the initial environment message from Python (Python -> Java) - - // Wait for Python to send a message - std::unique_lock lock(sharedData->mutex); - sharedData->condition.wait(lock, [&] { return sharedData->ready; }); - - // Read the message from Python - std::cout << "Received from Python: " << sharedData->data << std::endl; - - // Do something with the initial configuration message - - while (true) { - // Send the observation to Python - std::strcpy(sharedDataObs->data, "Observation from Java"); - sharedDataObs->ready = true; - // Notify Python that the observation has been sent - sharedDataObs->condition.notify_one(); - - // Wait for Python to send a message - std::unique_lock lock(sharedDataAction->mutex); - sharedDataAction->condition.wait(lock, [&] { - return sharedDataAction->ready; - }); - - // Read the message from Python - std::cout << "Received Action from Python: " << sharedDataAction->data - << std::endl; + // Resize the shared memory if needed + const size_t currentSize = observationSharedMemory.get_size(); + const size_t requiredSize = observation_size + sizeof(SharedDataHeader); + if (currentSize < requiredSize) { + observationSharedMemory.grow( + observation_memory_name.c_str(), (requiredSize - currentSize) + ); } - // Cleanup - shared_memory_object::remove("PythonJavaSharedMemory"); - return 0; + // Write the observation to shared memory + void *observationAddr = observationSharedMemory.get_address(); + auto *observationHeader = + reinterpret_cast(observationAddr); + char *data_start = + reinterpret_cast(observationHeader) + sizeof(SharedDataHeader); + std::memcpy(data_start, data, observation_size); + observationHeader->size = observation_size; + observationHeader->ready = true; + // mutex, condition of observationHeader SHOULD NOT BE USED. Use + // synchronizationSharedMemory instead + + // Notify Python that the observation is ready + headerSynchronization->ready = true; + headerSynchronization->condition.notify_one(); } + +extern "C" JNIEXPORT jobject JNICALL +Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readInitialEnvironment( + JNIEnv *env, + jclass clazz, + jstring initial_environment_memory_name, + jlong data_size // size_t +) { + const char *initial_environment_memory_name_cstr = + env->GetStringUTFChars(initial_environment_memory_name, nullptr); + size_t data_size_ = static_cast(data_size); + jobject result = read_initial_environment( + env, clazz, initial_environment_memory_name_cstr, data_size_ + ); + env->ReleaseStringUTFChars( + initial_environment_memory_name, initial_environment_memory_name_cstr + ); + return result; +} + +// fun readAction(action_memory_name: String, action_data: ByteArray?): ByteArray +extern "C" JNIEXPORT jbyteArray JNICALL +Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readAction( + JNIEnv *env, + jclass clazz, + jstring action_memory_name, + jbyteArray action_data +) { + const char *action_memory_name_cstr = + env->GetStringUTFChars(action_memory_name, nullptr); + + size_t data_size = 0; + jbyteArray data = + read_action(env, clazz, action_memory_name_cstr, action_data); + env->ReleaseStringUTFChars(action_memory_name, action_memory_name_cstr); + return data; +} + +// fun writeObservation( +// observation_memory_name: String, +// synchronization_memory_name: String, +// observation_data: ByteArray +// ) +extern "C" JNIEXPORT void JNICALL +Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_writeObservation( + JNIEnv *env, + jclass clazz, + jstring observation_memory_name, + jstring synchronization_memory_name, + jbyteArray observation_data +) { + const char *observation_memory_name_cstr = + env->GetStringUTFChars(observation_memory_name, nullptr); + const char *synchronization_memory_name_cstr = + env->GetStringUTFChars(synchronization_memory_name, nullptr); + jbyte *observation_data_ptr = env->GetByteArrayElements(observation_data, nullptr); + jsize observation_data_size = env->GetArrayLength(observation_data); + + write_observation( + observation_memory_name_cstr, + synchronization_memory_name_cstr, + reinterpret_cast(observation_data_ptr), + static_cast(observation_data_size) + ); + + env->ReleaseStringUTFChars(observation_memory_name, observation_memory_name_cstr); + env->ReleaseStringUTFChars(synchronization_memory_name, synchronization_memory_name_cstr); + env->ReleaseByteArrayElements(observation_data, observation_data_ptr, JNI_ABORT); +} \ No newline at end of file From 34d9d635b91f7e99a8817513963241c2ddf92772 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Mon, 20 Jan 2025 23:43:10 +0900 Subject: [PATCH 05/91] =?UTF-8?q?=E2=9C=A8=20Add=20mmap=20kotlin=20bind?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MinecraftEnv/src/main/cpp/boost_ipc.cpp | 16 ++++++-------- .../minecraftenv/FramebufferCapturer.kt | 22 +++++++++++++++++++ .../kyhsgeekcode/minecraftenv/MessageIO.kt | 10 ++++----- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index 6c15edf5..99e1146e 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -21,7 +22,6 @@ jobject read_initial_environment( JNIEnv *env, jclass clazz, const std::string &initial_environment_memory_name, - size_t &data_size ) { managed_shared_memory sharedMemoryInitialEnvironment( open_only, initial_environment_memory_name.c_str() @@ -33,7 +33,7 @@ jobject read_initial_environment( char *data_startInitialEnvironment = reinterpret_cast(headerInitialEnvironment) + sizeof(SharedDataHeader); - data_size = headerInitialEnvironment->size; + size_t data_size = headerInitialEnvironment->size; jbyteArray byteArray = env->NewByteArray(data_size); if (byteArray == nullptr || env->ExceptionCheck()) { @@ -132,17 +132,15 @@ void write_observation( } extern "C" JNIEXPORT jobject JNICALL -Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readInitialEnvironment( +Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readInitialEnvironmentImpl( JNIEnv *env, jclass clazz, - jstring initial_environment_memory_name, - jlong data_size // size_t + jstring initial_environment_memory_name ) { const char *initial_environment_memory_name_cstr = env->GetStringUTFChars(initial_environment_memory_name, nullptr); - size_t data_size_ = static_cast(data_size); jobject result = read_initial_environment( - env, clazz, initial_environment_memory_name_cstr, data_size_ + env, clazz, initial_environment_memory_name_cstr ); env->ReleaseStringUTFChars( initial_environment_memory_name, initial_environment_memory_name_cstr @@ -152,7 +150,7 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readInitialEnvironment( // fun readAction(action_memory_name: String, action_data: ByteArray?): ByteArray extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readAction( +Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readActionImpl( JNIEnv *env, jclass clazz, jstring action_memory_name, @@ -174,7 +172,7 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readAction( // observation_data: ByteArray // ) extern "C" JNIEXPORT void JNICALL -Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_writeObservation( +Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_writeObservationImpl( JNIEnv *env, jclass clazz, jstring observation_memory_name, diff --git a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt index d3ac938b..fb85bb56 100644 --- a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt +++ b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt @@ -1,6 +1,9 @@ package com.kyhsgeekcode.minecraftenv import com.google.protobuf.ByteString +import com.kyhsgeekcode.minecraftenv.proto.InitialEnvironment +import com.kyhsgeekcode.minecraftenv.proto.ActionSpace +import com.kyhsgeekcode.minecraftenv.proto.ObservationSpace import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL30 @@ -143,4 +146,23 @@ object FramebufferCapturer { private var hasInitializedGLEW: Boolean = false var ipcHandle: ByteString = ByteString.EMPTY private set + + private var actionBuffer: ByteArray? = null + + external fun readInitialEnvironmentImpl(initialEnvironmentMemoryName: String): ByteArray + external fun readActionImpl(actionMemoryName: String, actionData: ByteArray?): ByteArray + external fun writeObservationImpl(observationMemoryName: String, synchronizationMemoryName: String, observationData: ByteArray) + + fun readInitialEnvironment(initialEnvironmentMemoryName: String): InitialEnvironment.InitialEnvironmentMessage { + return InitialEnvironment.InitialEnvironmentMessage.parseFrom(readInitialEnvironmentImpl(initialEnvironmentMemoryName)) + } + + fun readAction(actionMemoryName: String): ActionSpace.ActionSpaceMessageV2 { + actionBuffer = readActionImpl(actionMemoryName, actionBuffer) + return ActionSpace.ActionSpaceMessageV2.parseFrom(actionBuffer) + } + + fun writeObservation(observationMemoryName: String, synchronizationMemoryName: String, observationData: ObservationSpace.ObservationSpaceMessage) { + writeObservationImpl(observationMemoryName, synchronizationMemoryName, observationData.toByteArray()) + } } diff --git a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt index fd3531e2..1cb0e953 100644 --- a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt +++ b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt @@ -118,13 +118,13 @@ class DomainSocketMessageIO( } } - override fun writeObservation(observationSpace: ObservationSpace.ObservationSpaceMessage) { - printWithTime("Writing observation with size ${observationSpace.serializedSize}") - val bufferSize = 4 + observationSpace.serializedSize + override fun writeObservation(observation: ObservationSpace.ObservationSpaceMessage) { + printWithTime("Writing observation with size ${observation.serializedSize}") + val bufferSize = 4 + observation.serializedSize val buffer = ByteBuffer.allocate(bufferSize).order(ByteOrder.LITTLE_ENDIAN) - buffer.putInt(observationSpace.serializedSize) + buffer.putInt(observation.serializedSize) val byteArrayOutputStream = ByteArrayOutputStream() - observationSpace.writeTo(byteArrayOutputStream) + observation.writeTo(byteArrayOutputStream) buffer.put(byteArrayOutputStream.toByteArray()) // to read mode buffer.flip() From 0f1524b61893e77eaf1b5e4f3b659e51bc88d9f4 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Mon, 20 Jan 2025 23:51:49 +0900 Subject: [PATCH 06/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20mutex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clang-format-ignore | 6 +++++- src/cpp/ipc_boost.cpp | 2 +- src/cpp/ipc_boost.hpp | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.clang-format-ignore b/.clang-format-ignore index 7a1e7ce7..6925839c 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -6,6 +6,7 @@ venv/* .pytest_cache/* .vscode/* build/* +./build/* craftground.egg-info/* docs/* figures/* @@ -13,4 +14,7 @@ paper/* poster/* pybind11/* src/craftground.egg-info/* -src/proto/* \ No newline at end of file +src/proto/* +build/_deps/* +./build/_deps/* +src/craftground/MinecraftEnv/_deps/* \ No newline at end of file diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index e837a274..bd0f4e91 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -7,7 +7,7 @@ void create_shared_memory_impl( const std::string &synchronization_memory_name, const std::string &action_memory_name, const char *initial_data, - size_t data_size, + size_t data_size, size_t action_size ) { shared_memory_object::remove(initial_memory_name.c_str()); diff --git a/src/cpp/ipc_boost.hpp b/src/cpp/ipc_boost.hpp index 05d9c2c7..bfc3815f 100644 --- a/src/cpp/ipc_boost.hpp +++ b/src/cpp/ipc_boost.hpp @@ -1,6 +1,7 @@ #ifndef SHARED_MEMORY_UTILS_HPP #define SHARED_MEMORY_UTILS_HPP +#include #include #include #include From 4a29c2dba15936e75ff43854ff923e20fb72f5bf Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 00:00:46 +0900 Subject: [PATCH 07/91] =?UTF-8?q?=F0=9F=8E=A8=20Format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev_tools.sh | 6 ++++ src/cpp/ipc.cpp | 19 ++++++------- src/cpp/ipc_boost.cpp | 17 +++++++---- .../MinecraftEnv/src/main/cpp/boost_ipc.cpp | 22 +++++++++------ .../minecraftenv/FramebufferCapturer.kt | 28 +++++++++++++------ 5 files changed, 59 insertions(+), 33 deletions(-) create mode 100644 dev_tools.sh diff --git a/dev_tools.sh b/dev_tools.sh new file mode 100644 index 00000000..6c04d6e6 --- /dev/null +++ b/dev_tools.sh @@ -0,0 +1,6 @@ +format_code() { + # Get a list of tracked files matching the desired extensions + git ls-files -- '*.h' '*.cpp' '*.mm' | xargs clang-format -i + git ls-files -- '*.java' -z | xargs -0 -P 4 google-java-format -i + ktlint '!**/com/kyhsgeekcode/minecraftenv/proto/**' --format +} diff --git a/src/cpp/ipc.cpp b/src/cpp/ipc.cpp index d9473c63..1045410a 100644 --- a/src/cpp/ipc.cpp +++ b/src/cpp/ipc.cpp @@ -68,23 +68,22 @@ void initialize_shared_memory( size_t action_size ) { create_shared_memory_impl( - memory_name, management_memory_name, initial_data, data_size, action_size + memory_name, + management_memory_name, + initial_data, + data_size, + action_size ); } void write_to_shared_memory( - const char *memory_name, - const char *data, - const size_t data_size + const char *memory_name, const char *data, const size_t data_size ) { - write_to_shared_memory_impl( - memory_name, data, data_size - ); + write_to_shared_memory_impl(memory_name, data, data_size); } py::bytes read_from_shared_memory( - const char *memory_name, - const char *management_memory_name + const char *memory_name, const char *management_memory_name ) { size_t data_size = 0; const char *data = read_from_shared_memory_impl( @@ -97,8 +96,6 @@ void destroy_shared_memory(const char *memory_name) { destroy_shared_memory_impl(memory_name); } - - PYBIND11_MODULE(craftground_native, m) { m.doc() = "Craftground Native Module"; m.def("initialize_from_mach_port", &initialize_from_mach_port); diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index bd0f4e91..9044ce93 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -7,12 +7,14 @@ void create_shared_memory_impl( const std::string &synchronization_memory_name, const std::string &action_memory_name, const char *initial_data, - size_t data_size, + size_t data_size, size_t action_size ) { shared_memory_object::remove(initial_memory_name.c_str()); managed_shared_memory sharedMemory( - create_only, initial_memory_name.c_str(), sizeof(SharedDataHeader) + data_size + create_only, + initial_memory_name.c_str(), + sizeof(SharedDataHeader) + data_size ); void *addr = sharedMemory.allocate(sizeof(SharedDataHeader) + data_size); @@ -43,9 +45,12 @@ void create_shared_memory_impl( // Allocate shared memory for action shared_memory_object::remove(action_memory_name.c_str()); managed_shared_memory sharedMemoryAction( - create_only, action_memory_name.c_str(), sizeof(SharedDataHeader) + action_size + create_only, + action_memory_name.c_str(), + sizeof(SharedDataHeader) + action_size ); - void *addrAction = sharedMemoryAction.allocate(sizeof(SharedDataHeader) + action_size); + void *addrAction = + sharedMemoryAction.allocate(sizeof(SharedDataHeader) + action_size); auto *headerAction = new (addrAction) SharedDataHeader(); headerAction->size = action_size; headerAction->ready = true; @@ -71,7 +76,7 @@ void write_to_shared_memory_impl( } // Read observation from shared memory -const char* read_from_shared_memory_impl( +const char *read_from_shared_memory_impl( const std::string &memory_name, const std::string &synchronization_memory_name, size_t &data_size @@ -89,7 +94,7 @@ const char* read_from_shared_memory_impl( headerSynchronization->condition.wait(lockSynchronization, [&] { return headerSynchronization->ready; }); - + // Read the observation from shared memory managed_shared_memory sharedMemory(open_only, memory_name.c_str()); void *addr = sharedMemory.get_address(); diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index 99e1146e..f72695fe 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -133,9 +133,7 @@ void write_observation( extern "C" JNIEXPORT jobject JNICALL Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readInitialEnvironmentImpl( - JNIEnv *env, - jclass clazz, - jstring initial_environment_memory_name + JNIEnv *env, jclass clazz, jstring initial_environment_memory_name ) { const char *initial_environment_memory_name_cstr = env->GetStringUTFChars(initial_environment_memory_name, nullptr); @@ -148,7 +146,8 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readInitialEnvironmentImp return result; } -// fun readAction(action_memory_name: String, action_data: ByteArray?): ByteArray +// fun readAction(action_memory_name: String, action_data: ByteArray?): +// ByteArray extern "C" JNIEXPORT jbyteArray JNICALL Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readActionImpl( JNIEnv *env, @@ -183,7 +182,8 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_writeObservationImpl( env->GetStringUTFChars(observation_memory_name, nullptr); const char *synchronization_memory_name_cstr = env->GetStringUTFChars(synchronization_memory_name, nullptr); - jbyte *observation_data_ptr = env->GetByteArrayElements(observation_data, nullptr); + jbyte *observation_data_ptr = + env->GetByteArrayElements(observation_data, nullptr); jsize observation_data_size = env->GetArrayLength(observation_data); write_observation( @@ -193,7 +193,13 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_writeObservationImpl( static_cast(observation_data_size) ); - env->ReleaseStringUTFChars(observation_memory_name, observation_memory_name_cstr); - env->ReleaseStringUTFChars(synchronization_memory_name, synchronization_memory_name_cstr); - env->ReleaseByteArrayElements(observation_data, observation_data_ptr, JNI_ABORT); + env->ReleaseStringUTFChars( + observation_memory_name, observation_memory_name_cstr + ); + env->ReleaseStringUTFChars( + synchronization_memory_name, synchronization_memory_name_cstr + ); + env->ReleaseByteArrayElements( + observation_data, observation_data_ptr, JNI_ABORT + ); } \ No newline at end of file diff --git a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt index fb85bb56..55ed1a49 100644 --- a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt +++ b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt @@ -1,8 +1,8 @@ package com.kyhsgeekcode.minecraftenv import com.google.protobuf.ByteString -import com.kyhsgeekcode.minecraftenv.proto.InitialEnvironment import com.kyhsgeekcode.minecraftenv.proto.ActionSpace +import com.kyhsgeekcode.minecraftenv.proto.InitialEnvironment import com.kyhsgeekcode.minecraftenv.proto.ObservationSpace import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL30 @@ -150,19 +150,31 @@ object FramebufferCapturer { private var actionBuffer: ByteArray? = null external fun readInitialEnvironmentImpl(initialEnvironmentMemoryName: String): ByteArray - external fun readActionImpl(actionMemoryName: String, actionData: ByteArray?): ByteArray - external fun writeObservationImpl(observationMemoryName: String, synchronizationMemoryName: String, observationData: ByteArray) - fun readInitialEnvironment(initialEnvironmentMemoryName: String): InitialEnvironment.InitialEnvironmentMessage { - return InitialEnvironment.InitialEnvironmentMessage.parseFrom(readInitialEnvironmentImpl(initialEnvironmentMemoryName)) - } - + external fun readActionImpl( + actionMemoryName: String, + actionData: ByteArray?, + ): ByteArray + + external fun writeObservationImpl( + observationMemoryName: String, + synchronizationMemoryName: String, + observationData: ByteArray, + ) + + fun readInitialEnvironment(initialEnvironmentMemoryName: String): InitialEnvironment.InitialEnvironmentMessage = + InitialEnvironment.InitialEnvironmentMessage.parseFrom(readInitialEnvironmentImpl(initialEnvironmentMemoryName)) + fun readAction(actionMemoryName: String): ActionSpace.ActionSpaceMessageV2 { actionBuffer = readActionImpl(actionMemoryName, actionBuffer) return ActionSpace.ActionSpaceMessageV2.parseFrom(actionBuffer) } - fun writeObservation(observationMemoryName: String, synchronizationMemoryName: String, observationData: ObservationSpace.ObservationSpaceMessage) { + fun writeObservation( + observationMemoryName: String, + synchronizationMemoryName: String, + observationData: ObservationSpace.ObservationSpaceMessage, + ) { writeObservationImpl(observationMemoryName, synchronizationMemoryName, observationData.toByteArray()) } } From d40327737a789ca79084d52059ae5a545e0912db Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 00:03:59 +0900 Subject: [PATCH 08/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20c++?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 + docs/develop.md | 4 ++-- src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5657cb9f..6bba581d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,6 +106,7 @@ message(STATUS "Boost is now available") # Add the module pybind11_add_module(craftground_native ${CRAFTGROUND_PY_SOURCES}) target_link_libraries(craftground_native PRIVATE Boost::system Boost::thread Boost::interprocess) +target_include_directories(craftground_native PRIVATE ${Boost_INCLUDE_DIRS}) if(APPLE) if(Torch_FOUND) target_include_directories(craftground_native PRIVATE "${TORCH_INCLUDE_DIRS}") diff --git a/docs/develop.md b/docs/develop.md index bc5706af..1b9e9ba7 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -19,9 +19,9 @@ sudo ln -s /usr/bin/clang-format-19 /usr/bin/clang-format ## Run formatters ```bash -find . \( -iname '*.h' -o -iname '*.cpp' -o -iname '*.mm' \) | xargs clang-format -i +git ls-files -- '*.h' '*.cpp' '*.mm' | xargs clang-format -i +git ls-files -- '*.java' -z | xargs -0 -P 4 google-java-format -i ktlint '!**/com/kyhsgeekcode/minecraftenv/proto/**' --format -find . -name '*.java' -print0 | xargs -0 -P 4 google-java-format -i ``` # Managing proto files diff --git a/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt b/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt index 36f903b1..e9f89739 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt +++ b/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.11) project(framebuffer_capturer) # Set the C++ standard -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL) set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC) @@ -111,6 +111,7 @@ endif() # Link with JNI and OpenGL libraries target_link_libraries(native-lib ${JNI_LIBRARIES} ${OPENGL_LIBRARIES} glm::glm Boost::system Boost::thread Boost::interprocess) +target_include_directories(native-lib PRIVATE ${Boost_INCLUDE_DIRS}) if(PNG_FOUND) target_link_libraries(native-lib ${PNG_LIBRARIES} ${ZLIB_LIBRARIES}) From 18883274ee4c624e9fdc4334d29c58ba526135f4 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 00:08:02 +0900 Subject: [PATCH 09/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20comma?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index f72695fe..0ff9c18b 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -21,7 +21,7 @@ struct SharedDataHeader { jobject read_initial_environment( JNIEnv *env, jclass clazz, - const std::string &initial_environment_memory_name, + const std::string &initial_environment_memory_name ) { managed_shared_memory sharedMemoryInitialEnvironment( open_only, initial_environment_memory_name.c_str() From 2a600bfc1a49d7aa07d60d5719d0bc5b622d4ae2 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 00:17:34 +0900 Subject: [PATCH 10/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20mutex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index 0ff9c18b..d5d3df6d 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -5,6 +5,7 @@ #include #include #include +#include using namespace boost::interprocess; From 23d40a02d6d155f1f18754bcf6e7b0a50d3b3c08 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 00:19:18 +0900 Subject: [PATCH 11/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20boost=20includes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bba581d..b0a2bce3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,7 +134,7 @@ elseif(CUDAToolkit_FOUND) endif() -target_include_directories(craftground_native PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp) +target_include_directories(craftground_native PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp ${Boost_INCLUDE_DIRS}) target_compile_options(craftground_native PRIVATE ${CRAFTGROUND_PY_COMPILE_OPTIONS}) From 593beb4c442ab5e4e84ef7d780fd2de2b0ae696f Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 00:27:04 +0900 Subject: [PATCH 12/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20boost=20config=20(in?= =?UTF-8?q?terprocess=5Ffwd.hpp)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- src/cpp/ipc_boost.cpp | 2 +- src/cpp/ipc_boost.hpp | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0a2bce3..6bba581d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,7 +134,7 @@ elseif(CUDAToolkit_FOUND) endif() -target_include_directories(craftground_native PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp ${Boost_INCLUDE_DIRS}) +target_include_directories(craftground_native PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp) target_compile_options(craftground_native PRIVATE ${CRAFTGROUND_PY_COMPILE_OPTIONS}) diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 9044ce93..57e3ed44 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -1,5 +1,5 @@ #include "ipc_boost.hpp" -#include "boost/interprocess/shared_memory_object.hpp" +#include // Create shared memory and write initial environment data void create_shared_memory_impl( diff --git a/src/cpp/ipc_boost.hpp b/src/cpp/ipc_boost.hpp index bfc3815f..5d610fb3 100644 --- a/src/cpp/ipc_boost.hpp +++ b/src/cpp/ipc_boost.hpp @@ -1,10 +1,11 @@ #ifndef SHARED_MEMORY_UTILS_HPP #define SHARED_MEMORY_UTILS_HPP -#include +#include #include #include #include +#include #include #include From ed7c81e0b818f0a8bbc8d7046a0c81919fcd4c9e Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 00:33:00 +0900 Subject: [PATCH 13/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20test=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bba581d..29b2f094 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,16 +173,18 @@ if(BUILD_TESTS) target_include_directories(craftground PUBLIC ${CUDAToolkit_INCLUDE_DIRS}) if(WIN32) target_link_libraries( - craftground_native PRIVATE + craftground PRIVATE ${CUDAToolkit_LIBRARY_DIR}/cudart.lib # ${CUDAToolkit_LIBRARY_DIR}/cudart_static.lib ${CUDAToolkit_LIBRARY_DIR}/cuda.lib ) else() # On Linux, use the CUDA targets provided by CMake - target_link_libraries(craftground_native PRIVATE CUDA::cudart CUDA::cudart_static) + target_link_libraries(craftground PRIVATE CUDA::cudart CUDA::cudart_static) endif() endif() + target_link_libraries(craftground PRIVATE Boost::system Boost::thread Boost::interprocess) + target_include_directories(craftground PRIVATE ${Boost_INCLUDE_DIRS}) target_link_options(craftground PRIVATE "-Wl,--whole-archive" "-Wl,--no-whole-archive") target_include_directories(craftground PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp) From f0776de1cb99d14b21e8995ebb4abc0f22dc03c4 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 00:41:05 +0900 Subject: [PATCH 14/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20link?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc.cpp | 6 ++++-- src/cpp/ipc_boost.hpp | 5 +++-- src/craftground/environment/ipc_boost.py | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/cpp/ipc.cpp b/src/cpp/ipc.cpp index 1045410a..01a2d0db 100644 --- a/src/cpp/ipc.cpp +++ b/src/cpp/ipc.cpp @@ -62,14 +62,16 @@ py::capsule mtl_tensor_from_cuda_mem_handle( void initialize_shared_memory( const char *memory_name, - const char *management_memory_name, + const char *synchronization_memory_name, + const char *action_memory_name, const char *initial_data, size_t data_size, size_t action_size ) { create_shared_memory_impl( memory_name, - management_memory_name, + synchronization_memory_name, + action_memory_name, initial_data, data_size, action_size diff --git a/src/cpp/ipc_boost.hpp b/src/cpp/ipc_boost.hpp index 5d610fb3..a32894f9 100644 --- a/src/cpp/ipc_boost.hpp +++ b/src/cpp/ipc_boost.hpp @@ -20,8 +20,9 @@ struct SharedDataHeader { // Message follows the header void create_shared_memory_impl( - const std::string &memory_name, - const std::string &management_memory_name, + const std::string &initial_memory_name, + const std::string &synchronization_memory_name, + const std::string &action_memory_name, const char *initial_data, size_t data_size, size_t action_size diff --git a/src/craftground/environment/ipc_boost.py b/src/craftground/environment/ipc_boost.py index ef52ee87..a56d0b68 100644 --- a/src/craftground/environment/ipc_boost.py +++ b/src/craftground/environment/ipc_boost.py @@ -26,6 +26,7 @@ def __init__(self, port: str, initial_environment: InitialEnvironmentMessage): initialize_shared_memory( self.initial_environment_shared_memory_name, self.synchronization_shared_memory_name, + self.action_shared_memory_name, initial_environment_bytes, len(initial_environment_bytes), len(dummy_action_bytes), From e6999c017aea0231e6036bf348fc292f9762a0ac Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 00:45:47 +0900 Subject: [PATCH 15/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20ipc=20python=20bindi?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cpp/ipc.h b/src/cpp/ipc.h index 2606df29..578a1815 100644 --- a/src/cpp/ipc.h +++ b/src/cpp/ipc.h @@ -10,7 +10,8 @@ py::capsule mtl_tensor_from_cuda_mem_handle( ); void initialize_shared_memory( const char *memory_name, - const char *management_memory_name, + const char *synchronization_memory_name, + const char *action_memory_name, const char *initial_data, size_t data_size, size_t action_size From 9dd499264cfb3c255af93bbfa579df92d7eca923 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 01:10:33 +0900 Subject: [PATCH 16/91] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/csv_logger.py | 25 ++ src/craftground/environment/action_space.py | 139 +++++++ src/craftground/environment/communication.py | 0 src/craftground/environment/environment.py | 353 +----------------- .../environment/observation_converter.py | 283 ++++++++++++++ .../environment/observation_space.py | 207 ++++++++++ src/craftground/screen_encoding_modes.py | 1 + 7 files changed, 672 insertions(+), 336 deletions(-) create mode 100644 src/craftground/environment/action_space.py create mode 100644 src/craftground/environment/communication.py create mode 100644 src/craftground/environment/observation_converter.py create mode 100644 src/craftground/environment/observation_space.py diff --git a/src/craftground/csv_logger.py b/src/craftground/csv_logger.py index fccc352d..a89294c3 100644 --- a/src/craftground/csv_logger.py +++ b/src/craftground/csv_logger.py @@ -1,3 +1,4 @@ +from contextlib import contextmanager from datetime import datetime from enum import Enum @@ -56,3 +57,27 @@ def profile_end(self, tag): def close(self): if self.backend in [LogBackend.CSV, LogBackend.BOTH]: self.file.close() + + @contextmanager + def profile(self, tag): + """Context manager for profiling.""" + if not self.profile: + yield + return + + time_str = datetime.now().strftime("%H:%M:%S.%f") + if self.backend in [LogBackend.CSV, LogBackend.BOTH]: + self.file.write(f"{time_str}, start, {tag}\n") + self.file.flush() + if self.backend in [LogBackend.CONSOLE, LogBackend.BOTH]: + print(f"{time_str}: start: {tag}") + + try: + yield + finally: + time_str = datetime.now().strftime("%H:%M:%S.%f") + if self.backend in [LogBackend.CONSOLE, LogBackend.BOTH]: + print(f"{time_str}: end: {tag}") + if self.backend in [LogBackend.CSV, LogBackend.BOTH]: + self.file.write(f"{time_str}, end, {tag}\n") + self.file.flush() diff --git a/src/craftground/environment/action_space.py b/src/craftground/environment/action_space.py new file mode 100644 index 00000000..d6e0108e --- /dev/null +++ b/src/craftground/environment/action_space.py @@ -0,0 +1,139 @@ +from enum import Enum +from gymnasium.core import ActType +from typing import Dict, Union +import gymnasium as gym +import numpy as np + +from action_space import ActionSpace + + +class ActionSpaceVersion(Enum): + V1_MINEDOJO = 1 + V2_MINERL_HUMAN = 2 + + +def translate_action_to_v2(action: ActType) -> Dict[str, Union[bool, float]]: + translated_action = { + "attack": action[5] == 3, + "back": action[0] == 2, + "forward": action[0] == 1, + "jump": action[2] == 1, + "left": action[1] == 1, + "right": action[1] == 2, + "sneak": action[2] == 2, + "sprint": action[2] == 3, + "use": action[5] == 1, + "drop": action[5] == 2, + "inventory": False, + } + for i in range(1, 10): + translated_action[f"hotbar.{i}"] = False + + translated_action["camera_pitch"] = action[3] * 15 - 180.0 + translated_action["camera_yaw"] = action[4] * 15 - 180.0 + + return translated_action + + +def action_to_symbol(action) -> str: # noqa: C901 + res = "" + if action[0] == 1: + res += "↑" + elif action[0] == 2: + res += "↓" + if action[1] == 1: + res += "←" + elif action[1] == 2: + res += "→" + if action[2] == 1: + res += "jump" # "⤴" + elif action[2] == 2: + res += "sneak" # "⤵" + elif action[2] == 3: + res += "sprint" # "⚡" + if action[3] > 12: # pitch up + res += "⤒" + elif action[3] < 12: # pitch down + res += "⤓" + if action[4] > 12: # yaw right + res += "⏭" + elif action[4] < 12: # yaw left + res += "⏮" + if action[5] == 1: # use + res += "use" # "⚒" + elif action[5] == 2: # drop + res += "drop" # "🤮" + elif action[5] == 3: # attack + res += "attack" # "⚔" + return res + + +def action_v2_to_symbol(action_v2: Dict[str, Union[int, float]]) -> str: # noqa: C901 + res = "" + + if action_v2.get("forward") == 1: + res += "↑" + if action_v2.get("backward") == 1: + res += "↓" + if action_v2.get("left") == 1: + res += "←" + if action_v2.get("right") == 1: + res += "→" + if action_v2.get("jump") == 1: + res += "JMP" + if action_v2.get("sneak") == 1: + res += "SNK" + if action_v2.get("sprint") == 1: + res += "SPRT" + if action_v2.get("attack") == 1: + res += "ATK" + if action_v2.get("use") == 1: + res += "USE" + if action_v2.get("drop") == 1: + res += "Q" + if action_v2.get("inventory") == 1: + res += "I" + + for i in range(1, 10): + if action_v2.get(f"hotbar.{i}") == 1: + res += f"hotbar.{i}" + + return res + + +def declare_action_space(action_space_version: ActionSpaceVersion) -> gym.spaces.Space: + if action_space_version == ActionSpaceVersion.V1_MINEDOJO: + return ActionSpace(6) + elif action_space_version == ActionSpaceVersion.V2_MINERL_HUMAN: + return gym.spaces.Dict( + { + "attack": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "back": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "forward": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "jump": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "left": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "right": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "sneak": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "sprint": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "use": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "drop": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "inventory": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "hotbar.1": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "hotbar.2": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "hotbar.3": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "hotbar.4": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "hotbar.5": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "hotbar.6": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "hotbar.7": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "hotbar.8": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "hotbar.9": gym.spaces.Discrete(2), # 0 or 1 (boolean) + "camera": gym.spaces.Box( + low=np.array([-180, -180]), + high=np.array([180, 180]), + dtype=np.float32, + ), + # Camera pitch/yaw between -180 and 180 degrees + } + ) + else: + raise ValueError(f"Unknown action space version: {action_space_version}") diff --git a/src/craftground/environment/communication.py b/src/craftground/environment/communication.py new file mode 100644 index 00000000..e69de29b diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 3478328f..e75ddab7 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -17,6 +17,15 @@ from gymnasium.core import ActType, ObsType, RenderFrame import torch +from environment.action_space import ( + ActionSpaceVersion, + action_to_symbol, + action_v2_to_symbol, + declare_action_space, + translate_action_to_v2, +) +from environment.observation_space import declare_observation_space + from ..action_space import ActionSpace from ..buffered_socket import BufferedSocket from ..csv_logger import CsvLogger, LogBackend @@ -33,11 +42,6 @@ from ..screen_encoding_modes import ScreenEncodingMode -class ActionSpaceVersion(Enum): - V1_MINEDOJO = 1 - V2_MINERL_HUMAN = 2 - - class ObservationTensorType(Enum): NONE = 0 CUDA_DLPACK = 1 @@ -67,243 +71,9 @@ def __init__( profile: bool = False, ): self.action_space_version = action_space_version - if action_space_version == ActionSpaceVersion.V1_MINEDOJO: - self.action_space = ActionSpace(6) - elif action_space_version == ActionSpaceVersion.V2_MINERL_HUMAN: - self.action_space = gym.spaces.Dict( - { - "attack": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "back": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "forward": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "jump": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "left": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "right": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "sneak": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "sprint": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "use": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "drop": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "inventory": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.1": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.2": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.3": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.4": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.5": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.6": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.7": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.8": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.9": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "camera": gym.spaces.Box( - low=np.array([-180, -180]), - high=np.array([180, 180]), - dtype=np.float32, - ), - # Camera pitch/yaw between -180 and 180 degrees - } - ) - else: - raise ValueError(f"Unknown action space version: {action_space_version}") - entity_info_space = gym.spaces.Dict( - { - "unique_name": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "translation_key": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "x": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.float64, - ), - "y": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.float64, - ), - "z": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.float64, - ), - "yaw": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.float64, - ), - "pitch": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.float64, - ), - "health": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.float64, - ), - } - ) - sound_entry_space = gym.spaces.Dict( - { - "translate_key": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32 - ), - "x": spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64), - "y": spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64), - "z": spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64), - "age": spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32), - } - ) - entities_within_distance_space = gym.spaces.Sequence(entity_info_space) - status_effect_space = gym.spaces.Dict( - { - "translation_key": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32 - ), - "amplifier": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32 - ), - "duration": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32 - ), - } - ) - self.observation_space = gym.spaces.Dict( - { - "obs": spaces.Dict( - { - "image": spaces.Box( - low=0, - high=255, - shape=(initial_env.imageSizeY, initial_env.imageSizeX, 3), - dtype=np.uint8, - ), - "position": spaces.Box( - low=-np.inf, high=np.inf, shape=(3,), dtype=np.float64 - ), - "yaw": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64 - ), - "pitch": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64 - ), - "health": spaces.Box( - low=0, high=np.inf, shape=(1,), dtype=np.float64 - ), - "food_level": spaces.Box( - low=0, high=np.inf, shape=(1,), dtype=np.float64 - ), - "saturation_level": spaces.Box( - low=0, high=np.inf, shape=(1,), dtype=np.float64 - ), - "is_dead": spaces.Discrete(2), - "inventory": spaces.Sequence( - spaces.Dict( - { - "raw_id": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "translation_key": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "count": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "durability": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "max_durability": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - } - ), - ), - "raycast_result": spaces.Dict( - { - "type": spaces.Discrete(3), - "target_block": spaces.Dict( - { - "x": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "y": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "z": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "translation_key": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - } - ), - "target_entity": entity_info_space, - } - ), - "sound_subtitles": spaces.Sequence(sound_entry_space), - "status_effects": spaces.Sequence(status_effect_space), - "killed_statistics": spaces.Dict(), - "mined_statistics": spaces.Dict(), - "misc_statistics": spaces.Dict(), - "visible_entities": spaces.Sequence(entity_info_space), - "surrounding_entities": entities_within_distance_space, # This is actually - "bobber_thrown": spaces.Discrete(2), - "experience": spaces.Box( - low=0, high=np.inf, shape=(1,), dtype=np.int32 - ), - "world_time": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.int64 - ), - "last_death_message": spaces.Text( - min_length=0, max_length=1000 - ), - "image_2": spaces.Box( - low=0, - high=255, - shape=(initial_env.imageSizeY, initial_env.imageSizeX, 3), - dtype=np.uint8, - ), - } - ), - } + self.action_space = declare_action_space(action_space_version) + self.observation_space = declare_observation_space( + initial_env.imageSizeX, initial_env.imageSizeY ) self.initial_env = initial_env self.use_terminate = use_terminate @@ -400,9 +170,8 @@ def reset( self.csv_logger.profile_end("read_response") self.csv_logger.log(f"Got response with size {siz}") - self.csv_logger.profile_start("convert_observation") - rgb_1, img_1 = self.convert_observation(res.image, res) - self.csv_logger.profile_end("convert_observation") + with self.csv_logger.profile("convert_observation"): + rgb_1, img_1 = self.convert_observation(res.image, res) rgb_2 = None img_2 = None if res.image_2 is not None and res.image_2 != b"": @@ -696,35 +465,13 @@ def send_initial_env(self): self.sock.send(struct.pack(" Dict[str, Union[bool, float]]: - translated_action = { - "attack": action[5] == 3, - "back": action[0] == 2, - "forward": action[0] == 1, - "jump": action[2] == 1, - "left": action[1] == 1, - "right": action[1] == 2, - "sneak": action[2] == 2, - "sprint": action[2] == 3, - "use": action[5] == 1, - "drop": action[5] == 2, - "inventory": False, - } - for i in range(1, 10): - translated_action[f"hotbar.{i}"] = False - - translated_action["camera_pitch"] = action[3] * 15 - 180.0 - translated_action["camera_yaw"] = action[4] * 15 - 180.0 - - return translated_action - def step(self, action: ActType) -> Tuple[ObsType, float, bool, bool, dict]: # send the action self.last_action = action self.csv_logger.profile_start("send_action_and_commands") # Translate the action v1 to v2 if self.action_space_version == ActionSpaceVersion.V1_MINEDOJO: - translated_action = self.translate_action_to_v2(action) + translated_action = translate_action_to_v2(action) else: translated_action = action send_action_and_commands( @@ -819,9 +566,9 @@ def render(self) -> Union[RenderFrame, List[RenderFrame], None]: self.csv_logger.profile_start("render_action") draw = ImageDraw.Draw(last_image) if self.action_space_version == ActionSpaceVersion.V1_MINEDOJO: - text = self.action_to_symbol(self.last_action) + text = action_to_symbol(self.last_action) elif self.action_space_version == ActionSpaceVersion.V2_MINERL_HUMAN: - text = self.action_v2_to_symbol(self.last_action) + text = action_v2_to_symbol(self.last_action) else: raise ValueError( f"Unknown action space version {self.action_space_version}" @@ -836,72 +583,6 @@ def render(self) -> Union[RenderFrame, List[RenderFrame], None]: else: return last_rgb_frame - def action_to_symbol(self, action) -> str: # noqa: C901 - res = "" - if action[0] == 1: - res += "↑" - elif action[0] == 2: - res += "↓" - if action[1] == 1: - res += "←" - elif action[1] == 2: - res += "→" - if action[2] == 1: - res += "jump" # "⤴" - elif action[2] == 2: - res += "sneak" # "⤵" - elif action[2] == 3: - res += "sprint" # "⚡" - if action[3] > 12: # pitch up - res += "⤒" - elif action[3] < 12: # pitch down - res += "⤓" - if action[4] > 12: # yaw right - res += "⏭" - elif action[4] < 12: # yaw left - res += "⏮" - if action[5] == 1: # use - res += "use" # "⚒" - elif action[5] == 2: # drop - res += "drop" # "🤮" - elif action[5] == 3: # attack - res += "attack" # "⚔" - return res - - def action_v2_to_symbol( # noqa: C901 - self, action_v2: Dict[str, Union[int, float]] - ) -> str: - res = "" - - if action_v2.get("forward") == 1: - res += "↑" - if action_v2.get("backward") == 1: - res += "↓" - if action_v2.get("left") == 1: - res += "←" - if action_v2.get("right") == 1: - res += "→" - if action_v2.get("jump") == 1: - res += "JMP" - if action_v2.get("sneak") == 1: - res += "SNK" - if action_v2.get("sprint") == 1: - res += "SPRT" - if action_v2.get("attack") == 1: - res += "ATK" - if action_v2.get("use") == 1: - res += "USE" - if action_v2.get("drop") == 1: - res += "Q" - if action_v2.get("inventory") == 1: - res += "I" - - for i in range(1, 10): - if action_v2.get(f"hotbar.{i}") == 1: - res += f"hotbar.{i}" - - return res - @property def render_mode(self) -> Optional[str]: return "rgb_array" diff --git a/src/craftground/environment/observation_converter.py b/src/craftground/environment/observation_converter.py new file mode 100644 index 00000000..5302a4a2 --- /dev/null +++ b/src/craftground/environment/observation_converter.py @@ -0,0 +1,283 @@ +from enum import Enum +import io +from typing import TYPE_CHECKING, Optional, Tuple, Union + +from environment.action_space import ActionSpaceVersion +from font import get_font +from print_with_time import print_with_time +from proto.observation_space_pb2 import ObservationSpaceMessage +from screen_encoding_modes import ScreenEncodingMode +import numpy as np +from PIL import Image, ImageDraw + + +class ObservationTensorType(Enum): + NONE = 0 + CUDA_DLPACK = 1 + APPLE_TENSOR = 2 + JAX_NP = 3 + + +if TYPE_CHECKING: + import torch + import jax.numpy as jnp + + TorchArrayType = torch.Tensor + JaxArrayType = jnp.ndarray +else: + try: + import torch + + TorchArrayType = torch.Tensor + except ImportError: + TorchArrayType = None + try: + import jax + import jax.numpy as jnp + + JaxArrayType = jnp.ndarray + except ImportError: + JaxArrayType = None +ImageOutputType = Union[np.ndarray, "TorchArrayType", "JaxArrayType"] + + +class ObservationConverter: + def __init__( + self, + output_type: ScreenEncodingMode, + is_binocular: bool = False, + render_action: bool = False, + ) -> None: + self.output_type = output_type + self.internal_type = ObservationTensorType.NONE + self.last_observations = [None, None] + self.last_images = [None, None] + self.is_binocular = is_binocular + self.render_alternating_eyes_counter = 0 + self.render_action = render_action + + def convert( + self, observation: ObservationSpaceMessage + ) -> Tuple[Optional[ImageOutputType]]: + if self.output_type == ScreenEncodingMode.PNG: + obs_1, obs_2 = None, None + img_1, img_2 = None, None + obs_1, img_1 = self.convert_png_observation(observation.image) + if self.is_binocular: + obs_2, img_2 = self.convert_png_observation(observation.image_2) + self.last_observations[0], self.last_observations[1] = obs_1, obs_2 + self.last_images[0], self.last_images[1] = img_1, img_2 + return (obs_1, obs_2) + elif self.output_type == ScreenEncodingMode.RAW: + obs_1, obs_2 = None, None + img_1, img_2 = None, None + obs_1 = self.convert_raw_observation(observation.image) + if self.is_binocular: + obs_2 = self.convert_raw_observation(observation.image_2) + self.last_observations[0], self.last_observations[1] = obs_1, obs_2 + return (obs_1, obs_2) + elif self.output_type == ScreenEncodingMode.ZEROCOPY: + if self.is_binocular: + raise ValueError("Zerocopy mode does not support binocular vision") + if self.internal_type == ObservationTensorType.NONE: + self.initialize_zerocopy(observation.ipc_handle) + if self.internal_type == ObservationTensorType.APPLE_TENSOR: + obs_1 = self.last_observations[0].clone()[:, :, [2, 1, 0]].flip(0) + return (obs_1, None) + elif self.internal_type == ObservationTensorType.CUDA_DLPACK: + obs_1 = self.observation_tensors[0].clone()[:, :, :3].flip(0) + return (obs_1, None) + else: + raise ValueError( + f"Invalid internal type for output {self.output_type}: {self.internal_type}" + ) + + elif self.output_type == ScreenEncodingMode.JAX: + if self.is_binocular: + raise ValueError("JAX mode does not support binocular vision") + if self.internal_type == ObservationTensorType.JAX_NP: + return (self.last_observations[0], None) + else: + pass + return self.convert_jax_zerocopy(observation) + else: + raise ValueError(f"Unknown output type: {self.output_type}") + + # when self.render_mode is None: no render is computed + # when self.render_mode is "human": render returns None, envrionment is already being rendered on the screen + # when self.render_mode is "rgb_array": render returns the image to be rendered + # when self.render_mode is "ansi": render returns the text to be rendered + # when self.render_mode is "rgb_array_list": render returns a list of images to be rendered + # when self.render_mode is "rgb_tensor": render returns a torch tensor to be rendered + def render(self): + # select last_image and last_frame + if self.render_mode is None: + return None + if self.render_mode == "human": + # do not render anything + return None + if self.render_mode == "ansi": + raise ValueError("Rendering mode ansi not supported") + if self.render_mode == "rgb_array_list": + raise ValueError("Rendering mode rgb_array_list not supported") + + if self.render_alternating_eyes: + last_image = self.last_images[self.render_alternating_eyes_counter] + last_rgb_frame = self.last_observations[ + self.render_alternating_eyes_counter + ] + self.render_alternating_eyes_counter = ( + 1 - self.render_alternating_eyes_counter + ) + else: + last_image = self.last_images[0] + last_rgb_frame = self.last_observations[0] + if last_image is None and last_rgb_frame is None: + return None + + if isinstance(last_rgb_frame, TorchArrayType) and ( + self.render_mode != "rgb_array_tensor" or self.render_action + ): + # drop the alpha channel and convert to numpy array + last_rgb_frame = last_rgb_frame.cpu().numpy() + if isinstance(last_rgb_frame, JaxArrayType): + last_rgb_frame = jax.device_get(last_rgb_frame) # type: ignore + + # last_rgb_frame: np.ndarray or torch.Tensor + # last_image: PIL.Image.Image or None + if self.render_action and self.last_action: + if last_image is None: + # it is inevitable to convert the tensor to numpy array + last_image = Image.fromarray(last_rgb_frame) + with self.csv_logger.profile("render_action"): + draw = ImageDraw.Draw(last_image) + if self.action_space_version == ActionSpaceVersion.V1_MINEDOJO: + text = self.action_to_symbol(self.last_action) + elif self.action_space_version == ActionSpaceVersion.V2_MINERL_HUMAN: + text = self.action_v2_to_symbol(self.last_action) + else: + raise ValueError( + f"Unknown action space version {self.action_space_version}" + ) + position = (0, 0) + font = get_font() + font_size = 8 + color = (255, 0, 0) + draw.text(position, text, font=font, font_size=font_size, fill=color) + return np.array(last_image) + else: + return last_rgb_frame + + def convert_png_observation( + self, image_bytes: bytes + ) -> Tuple[np.ndarray, Image.Image]: + # decode png byte array to numpy array + # Create a BytesIO object from the byte array + with self.csv_logger.profile("convert_observation/decode_png"): + bytes_io = io.BytesIO(image_bytes) + # Use PIL to open the image from the BytesIO object + img = Image.open(bytes_io).convert("RGB") + # Flip y axis + img = img.transpose(Image.FLIP_TOP_BOTTOM) + + with self.csv_logger.profile("convert_observation/convert_to_numpy"): + # Convert the PIL image to a numpy array + last_rgb_frame = np.array(img) + arr = np.transpose(last_rgb_frame, (2, 1, 0)) + rgb_array_or_tensor = arr.astype(np.uint8) + + return rgb_array_or_tensor, img + + def convert_raw_observation(self, image_bytes: bytes) -> np.ndarray: + # decode raw byte array to numpy array + with self.csv_logger.profile("convert_observation/decode_raw"): + last_rgb_frame = np.frombuffer(image_bytes, dtype=np.uint8).reshape( + (self.initial_env.imageSizeY, self.initial_env.imageSizeX, 3) + ) + # Flip y axis using np + last_rgb_frame = np.flip(last_rgb_frame, axis=0) + rgb_array_or_tensor = last_rgb_frame + # arr = np.transpose(last_rgb_frame, (2, 1, 0)) # channels, width, height + return rgb_array_or_tensor + + def initialize_zerocopy(self, ipc_handle: bytes): + import torch + from .craftground_native import initialize_from_mach_port # type: ignore + from .craftground_native import mtl_tensor_from_cuda_mem_handle # type: ignore + + if len(ipc_handle) == 0: + raise ValueError("No ipc handle found.") + if len(ipc_handle) == 4: + mach_port = int.from_bytes(ipc_handle, byteorder="little", signed=False) + print_with_time(f"{mach_port=}") + apple_dl_tensor = initialize_from_mach_port( + mach_port, self.initial_env.imageSizeX, self.initial_env.imageSizeY + ) + if apple_dl_tensor is None: + raise ValueError(f"Failed to initialize from mach port {mach_port}.") + # image_tensor = torch.utils.dlpack.from_dlpack(apple_dl_tensor) + rgb_array_or_tensor = apple_dl_tensor + print(rgb_array_or_tensor.shape) + print(rgb_array_or_tensor.dtype) + print(rgb_array_or_tensor.device) + self.last_observations[0] = rgb_array_or_tensor + # drop alpha, flip y axis, and clone + self.observation_tensor_type = ObservationTensorType.APPLE_TENSOR + else: + import torch.utils.dlpack + + cuda_dl_tensor = mtl_tensor_from_cuda_mem_handle( + ipc_handle, + self.initial_env.imageSizeX, + self.initial_env.imageSizeY, + ) + if not cuda_dl_tensor: + raise ValueError("Invalid DLPack capsule: None") + rgb_array_or_tensor = torch.utils.dlpack.from_dlpack(cuda_dl_tensor) + print(rgb_array_or_tensor.shape) + print(rgb_array_or_tensor.dtype) + print(rgb_array_or_tensor.device) + print(f"{rgb_array_or_tensor.data_ptr()=}\n\n") + self.last_observations[0] = rgb_array_or_tensor + # drop alpha, flip y axis, and clone + self.observation_tensor_type = ObservationTensorType.CUDA_DLPACK + + def convert_jax_observation(self, ipc_handle: bytes) -> "JaxArrayType": + import jax.numpy as jnp + from .craftground_native import mtl_dlpack_from_mach_port # type: ignore + from .craftground_native import mtl_tensor_from_cuda_mem_handle # type: ignore + + if len(ipc_handle) == 0: + raise ValueError("No ipc handle found.") + if len(ipc_handle) == 4: + mach_port = int.from_bytes(ipc_handle, byteorder="little", signed=False) + print_with_time(f"{mach_port=}") + dlpack_capsule = mtl_dlpack_from_mach_port( + mach_port, self.initial_env.imageSizeX, self.initial_env.imageSizeY + ) + if not dlpack_capsule: + raise ValueError(f"Failed to initialize from mach port {ipc_handle}.") + jax_image = jnp.from_dlpack(cuda_dlpack) + # image_tensor = torch.utils.dlpack.from_dlpack(apple_dl_tensor) + rgb_array_or_tensor = jax_image + print(rgb_array_or_tensor.shape) + print(rgb_array_or_tensor.dtype) + print(rgb_array_or_tensor.device()) + self.observation_tensors[0] = rgb_array_or_tensor + # drop alpha, flip y axis, and clone + rgb_array_or_tensor = rgb_array_or_tensor.clone()[:, :, [2, 1, 0]].flip(0) + self.observation_tensor_type = ObservationTensorType.JAX_NP + return rgb_array_or_tensor + else: + cuda_dlpack = mtl_tensor_from_cuda_mem_handle( + ipc_handle, + self.initial_env.imageSizeX, + self.initial_env.imageSizeY, + ) + if not cuda_dlpack: + raise ValueError("Invalid DLPack capsule: None") + jax_image = jnp.from_dlpack(cuda_dlpack) + rgb_array_or_tensor = jax_image + rgb_array_or_tensor = rgb_array_or_tensor.clone()[:, :, [2, 1, 0]].flip(0) + self.observation_tensor_type = ObservationTensorType.JAX_NP + return jax_image, None diff --git a/src/craftground/environment/observation_space.py b/src/craftground/environment/observation_space.py new file mode 100644 index 00000000..3a4e5af7 --- /dev/null +++ b/src/craftground/environment/observation_space.py @@ -0,0 +1,207 @@ +import gymnasium as gym +import numpy as np +from gymnasium import spaces + + +def declare_observation_space(image_width: int, image_height: int) -> gym.spaces.Dict: + entity_info_space = gym.spaces.Dict( + { + "unique_name": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.int32, + ), + "translation_key": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.int32, + ), + "x": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.float64, + ), + "y": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.float64, + ), + "z": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.float64, + ), + "yaw": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.float64, + ), + "pitch": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.float64, + ), + "health": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.float64, + ), + } + ) + sound_entry_space = gym.spaces.Dict( + { + "translate_key": spaces.Box( + low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32 + ), + "x": spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64), + "y": spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64), + "z": spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64), + "age": spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32), + } + ) + entities_within_distance_space = gym.spaces.Sequence(entity_info_space) + status_effect_space = gym.spaces.Dict( + { + "translation_key": spaces.Box( + low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32 + ), + "amplifier": spaces.Box( + low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32 + ), + "duration": spaces.Box( + low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32 + ), + } + ) + return gym.spaces.Dict( + { + "obs": spaces.Dict( + { + "image": spaces.Box( + low=0, + high=255, + shape=(image_height, image_width, 3), + dtype=np.uint8, + ), + "position": spaces.Box( + low=-np.inf, high=np.inf, shape=(3,), dtype=np.float64 + ), + "yaw": spaces.Box( + low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64 + ), + "pitch": spaces.Box( + low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64 + ), + "health": spaces.Box( + low=0, high=np.inf, shape=(1,), dtype=np.float64 + ), + "food_level": spaces.Box( + low=0, high=np.inf, shape=(1,), dtype=np.float64 + ), + "saturation_level": spaces.Box( + low=0, high=np.inf, shape=(1,), dtype=np.float64 + ), + "is_dead": spaces.Discrete(2), + "inventory": spaces.Sequence( + spaces.Dict( + { + "raw_id": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.int32, + ), + "translation_key": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.int32, + ), + "count": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.int32, + ), + "durability": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.int32, + ), + "max_durability": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.int32, + ), + } + ), + ), + "raycast_result": spaces.Dict( + { + "type": spaces.Discrete(3), + "target_block": spaces.Dict( + { + "x": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.int32, + ), + "y": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.int32, + ), + "z": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.int32, + ), + "translation_key": spaces.Box( + low=-np.inf, + high=np.inf, + shape=(1,), + dtype=np.int32, + ), + } + ), + "target_entity": entity_info_space, + } + ), + "sound_subtitles": spaces.Sequence(sound_entry_space), + "status_effects": spaces.Sequence(status_effect_space), + "killed_statistics": spaces.Dict(), + "mined_statistics": spaces.Dict(), + "misc_statistics": spaces.Dict(), + "visible_entities": spaces.Sequence(entity_info_space), + "surrounding_entities": entities_within_distance_space, # This is actually + "bobber_thrown": spaces.Discrete(2), + "experience": spaces.Box( + low=0, high=np.inf, shape=(1,), dtype=np.int32 + ), + "world_time": spaces.Box( + low=-np.inf, high=np.inf, shape=(1,), dtype=np.int64 + ), + "last_death_message": spaces.Text(min_length=0, max_length=1000), + "image_2": spaces.Box( + low=0, + high=255, + shape=(image_height, image_width, 3), + dtype=np.uint8, + ), + } + ), + } + ) diff --git a/src/craftground/screen_encoding_modes.py b/src/craftground/screen_encoding_modes.py index 92cfae00..ea41c64a 100644 --- a/src/craftground/screen_encoding_modes.py +++ b/src/craftground/screen_encoding_modes.py @@ -5,3 +5,4 @@ class ScreenEncodingMode(Enum): RAW = 0 PNG = 1 ZEROCOPY = 2 + JAX = 3 From 4570cf748afa25fff8f42e54b65b0994e37cdea4 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 01:31:06 +0900 Subject: [PATCH 17/91] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20More=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/action_space.py | 33 ----- src/craftground/environment/action_space.py | 32 ++++- .../{ipc_boost.py => boost_ipc.py} | 3 +- src/craftground/environment/environment.py | 9 ++ src/craftground/environment/ipc_interface.py | 19 +++ src/craftground/environment/socket_ipc.py | 121 ++++++++++++++++++ 6 files changed, 182 insertions(+), 35 deletions(-) delete mode 100644 src/craftground/action_space.py rename src/craftground/environment/{ipc_boost.py => boost_ipc.py} (96%) create mode 100644 src/craftground/environment/ipc_interface.py create mode 100644 src/craftground/environment/socket_ipc.py diff --git a/src/craftground/action_space.py b/src/craftground/action_space.py deleted file mode 100644 index 01f0e532..00000000 --- a/src/craftground/action_space.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Optional - -import gymnasium as gym -import numpy as np - - -class ActionSpace(gym.spaces.Discrete): - def __init__(self, n): - super(ActionSpace, self).__init__(n) - - def sample(self, mask: Optional[np.ndarray] = None) -> int: - super(ActionSpace, self).sample() - return np.random.randint(self.n) - - def contains(self, x): - return x in range(self.n) - - def __repr__(self): - return "MyActionSpace(%d)" % self.n - - def __eq__(self, other): - return self.n == other.n - - -class MultiActionSpace(gym.spaces.MultiDiscrete): - def __init__(self, nvec: list[int]): - super(MultiActionSpace, self).__init__(nvec) - - # def sample(self, mask: Optional[np.ndarray] = None) -> NDArray[np.integer[Any]]: - # return super(MultiActionSpace, self).sample() - - def __repr__(self): - return "MultiActionSpace(%d)" % self.nvec diff --git a/src/craftground/environment/action_space.py b/src/craftground/environment/action_space.py index d6e0108e..a1fc2e38 100644 --- a/src/craftground/environment/action_space.py +++ b/src/craftground/environment/action_space.py @@ -1,6 +1,6 @@ from enum import Enum from gymnasium.core import ActType -from typing import Dict, Union +from typing import Dict, List, Union import gymnasium as gym import numpy as np @@ -12,6 +12,36 @@ class ActionSpaceVersion(Enum): V2_MINERL_HUMAN = 2 +def no_op() -> List[int]: + r = [0] * 8 + r[3] = 12 + r[4] = 12 + return r + + +def no_op_v2() -> Dict[str, Union[bool, float]]: + noop_dict = {} + for bool_key in [ + "attack", + "back", + "forward", + "jump", + "left", + "right", + "sneak", + "sprint", + "use", + "drop", + "inventory", + ]: + noop_dict[bool_key] = False + for i in range(1, 10): + noop_dict[f"hotbar.{i}"] = False + noop_dict["camera_pitch"] = 0.0 + noop_dict["camera_yaw"] = 0.0 + return noop_dict + + def translate_action_to_v2(action: ActType) -> Dict[str, Union[bool, float]]: translated_action = { "attack": action[5] == 3, diff --git a/src/craftground/environment/ipc_boost.py b/src/craftground/environment/boost_ipc.py similarity index 96% rename from src/craftground/environment/ipc_boost.py rename to src/craftground/environment/boost_ipc.py index a56d0b68..c08c44e7 100644 --- a/src/craftground/environment/ipc_boost.py +++ b/src/craftground/environment/boost_ipc.py @@ -4,11 +4,12 @@ read_from_shared_memory, # noqa destroy_shared_memory, # noqa ) +from environment.ipc_interface import IPCInterface from proto.action_space_pb2 import ActionSpaceMessageV2 from proto.initial_environment_pb2 import InitialEnvironmentMessage -class IPCBoost: +class BoostIPC(IPCInterface): def __init__(self, port: str, initial_environment: InitialEnvironmentMessage): self.port = port self.initial_environment_shared_memory_name = ( diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index e75ddab7..43ba22fe 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -57,6 +57,7 @@ def __init__( env_path=None, port=8000, find_free_port: bool = True, + use_shared_memory: bool = False, render_action: bool = False, render_alternating_eyes: bool = False, use_terminate: bool = False, @@ -101,6 +102,7 @@ def __init__( self.find_free_port = find_free_port self.queued_commands = [] self.process = None + self.use_shared_memory = use_shared_memory if env_path is None: self.env_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), @@ -114,6 +116,13 @@ def __init__( backend=LogBackend.BOTH if verbose_python else LogBackend.NONE, ) + if self.use_shared_memory: + from .boost_ipc import BoostIPC # type: ignore + + self.ipc = BoostIPC(str(port), initial_env) + else: + self.ipc = None + # in case when using zerocopy self.observation_tensors = [None, None] self.observation_tensor_type = ObservationTensorType.NONE diff --git a/src/craftground/environment/ipc_interface.py b/src/craftground/environment/ipc_interface.py new file mode 100644 index 00000000..d3c0f18f --- /dev/null +++ b/src/craftground/environment/ipc_interface.py @@ -0,0 +1,19 @@ +from abc import ABC, abstractmethod + +from proto.action_space_pb2 import ActionSpaceMessageV2 +from proto.initial_environment_pb2 import InitialEnvironmentMessage +from proto.observation_space_pb2 import ObservationSpaceMessage + + +class IPCInterface(ABC): + @abstractmethod + def send_action(self, message: ActionSpaceMessageV2): + pass + + @abstractmethod + def read_observation(self) -> ObservationSpaceMessage: + pass + + @abstractmethod + def send_initial_environment(self, message: InitialEnvironmentMessage): + pass diff --git a/src/craftground/environment/socket_ipc.py b/src/craftground/environment/socket_ipc.py new file mode 100644 index 00000000..f447b9ae --- /dev/null +++ b/src/craftground/environment/socket_ipc.py @@ -0,0 +1,121 @@ +import os +import signal +import struct +from typing import List, Optional, Tuple + +import psutil +from csv_logger import CsvLogger +from environment.ipc_interface import IPCInterface +from minecraft import action_v2_dict_to_message +from print_with_time import print_with_time +from proto.action_space_pb2 import ActionSpaceMessageV2 +from proto.initial_environment_pb2 import InitialEnvironmentMessage +from proto.observation_space_pb2 import ObservationSpaceMessage + + +class SocketIPC(IPCInterface): + def __init__(self, logger: CsvLogger, port: int, find_free_port: bool = False): + self.logger = logger + self.find_free_port = find_free_port + self.remove_orphan_java_processes() + self.port = self.check_port(port) + + def check_port(self, port: int) -> int: + if os.name == "nt": + while True: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + if s.connect_ex(("127.0.0.1", port)) == 0: # The port is in use + if self.find_free_port: + print( + f"[Warning]: Port {port} is already in use. Trying another port." + ) + port += 1 + else: + raise ConnectionError( + f"Port {port} is already in use. Please choose another port." + ) + else: + return port + else: + socket_path = f"/tmp/minecraftrl_{port}.sock" + if os.path.exists(socket_path): + if self.find_free_port: + print( + f"[Warning]: Socket file {socket_path} already exists. Trying another port." + ) + while os.path.exists(socket_path): + port += 1 + socket_path = f"/tmp/minecraftrl_{port}.sock" + print(f"Using port {socket_path}") + return port + else: + raise FileExistsError( + f"Socket file {socket_path} already exists. Please choose another port." + ) + + def send_initial_environment(self, initial_env: InitialEnvironmentMessage): + v = initial_env.SerializeToString() + self.sock.send(struct.pack(" ObservationSpaceMessage: + data_len_bytes = self.buffered_socket.read(4, True) + data_len = struct.unpack(" Date: Tue, 21 Jan 2025 01:54:29 +0900 Subject: [PATCH 18/91] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/action_space.py | 30 ++ src/craftground/environment/boost_ipc.py | 2 +- src/craftground/environment/environment.py | 353 +++---------------- src/craftground/environment/ipc_interface.py | 45 +++ src/craftground/environment/socket_ipc.py | 2 + src/craftground/minecraft.py | 59 +--- 6 files changed, 137 insertions(+), 354 deletions(-) diff --git a/src/craftground/environment/action_space.py b/src/craftground/environment/action_space.py index a1fc2e38..98928f56 100644 --- a/src/craftground/environment/action_space.py +++ b/src/craftground/environment/action_space.py @@ -5,6 +5,7 @@ import numpy as np from action_space import ActionSpace +from proto.action_space_pb2 import ActionSpaceMessageV2 class ActionSpaceVersion(Enum): @@ -65,6 +66,35 @@ def translate_action_to_v2(action: ActType) -> Dict[str, Union[bool, float]]: return translated_action +def action_v2_dict_to_message( + action_v2: Dict[str, Union[bool, float]] +) -> ActionSpaceMessageV2: + action_space = ActionSpaceMessageV2() + action_space.attack = action_v2["attack"] + action_space.back = action_v2["back"] + action_space.forward = action_v2["forward"] + action_space.jump = action_v2["jump"] + action_space.left = action_v2["left"] + action_space.right = action_v2["right"] + action_space.sneak = action_v2["sneak"] + action_space.sprint = action_v2["sprint"] + action_space.use = action_v2["use"] + action_space.drop = action_v2["drop"] + action_space.inventory = action_v2["inventory"] + action_space.hotbar_1 = action_v2["hotbar.1"] + action_space.hotbar_2 = action_v2["hotbar.2"] + action_space.hotbar_3 = action_v2["hotbar.3"] + action_space.hotbar_4 = action_v2["hotbar.4"] + action_space.hotbar_5 = action_v2["hotbar.5"] + action_space.hotbar_6 = action_v2["hotbar.6"] + action_space.hotbar_7 = action_v2["hotbar.7"] + action_space.hotbar_8 = action_v2["hotbar.8"] + action_space.hotbar_9 = action_v2["hotbar.9"] + action_space.camera_pitch = action_v2["camera_pitch"] + action_space.camera_yaw = action_v2["camera_yaw"] + return action_space + + def action_to_symbol(action) -> str: # noqa: C901 res = "" if action[0] == 1: diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index c08c44e7..500c2dcd 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -39,7 +39,7 @@ def write_action(self, action: ActionSpaceMessageV2): self.action_shared_memory_name, action_bytes, len(action_bytes) ) - def read(self) -> bytes: + def read_observation(self) -> bytes: return read_from_shared_memory( self.observation_shared_memory_name, self.synchronization_shared_memory_name ) diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 43ba22fe..2e868c34 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -24,9 +24,10 @@ declare_action_space, translate_action_to_v2, ) +from environment.observation_converter import ObservationConverter from environment.observation_space import declare_observation_space +from environment.socket_ipc import SocketIPC -from ..action_space import ActionSpace from ..buffered_socket import BufferedSocket from ..csv_logger import CsvLogger, LogBackend from ..font import get_font @@ -121,7 +122,7 @@ def __init__( self.ipc = BoostIPC(str(port), initial_env) else: - self.ipc = None + self.ipc = SocketIPC(self.csv_logger, port, find_free_port) # in case when using zerocopy self.observation_tensors = [None, None] @@ -137,6 +138,12 @@ def __init__( " If this error happens in macOS, please report it to the developers." ) + self.observation_converter = ObservationConverter( + self.initial_env.screen_encoding_mode, + self.initial_env.eye_distance > 0, + self.render_action, + ) + def reset( self, *, @@ -167,182 +174,29 @@ def reset( ld_preload=self.ld_preload, ) else: - self.csv_logger.profile_start("fast_reset") - send_fastreset2(self.sock, extra_commands) - self.csv_logger.profile_end("fast_reset") + with self.csv_logger.profile("fast_reset"): + send_fastreset2(self.sock, extra_commands) self.csv_logger.log("Sent fast reset") - if self.verbose_python: - print_with_time("Sent fast reset") - self.csv_logger.log("Reading response...") - self.csv_logger.profile_start("read_response") - siz, res = self.read_one_observation() - self.csv_logger.profile_end("read_response") - - self.csv_logger.log(f"Got response with size {siz}") + + with self.csv_logger.profile("read_response"): + res = self.ipc.read_observation() + + final_obs = self.convert_observation_v2(res) + return final_obs, final_obs + + def convert_observation_v2(self, res): with self.csv_logger.profile("convert_observation"): - rgb_1, img_1 = self.convert_observation(res.image, res) - rgb_2 = None - img_2 = None - if res.image_2 is not None and res.image_2 != b"": - rgb_2, img_2 = self.convert_observation(res.image_2, res) + rgb_1, rgb_2 = self.observation_converter.convert(res) + self.queued_commands = [] res.yaw = ((res.yaw + 180) % 360) - 180 final_obs: Dict[str, Union[np.ndarray, torch.Tensor, Any]] = { - "obs": res, - "rgb": rgb_1, + "full": res, + "pov": rgb_1, } - self.last_images = [img_1, img_2] - self.last_rgb_frames = [rgb_1, rgb_2] if rgb_2 is not None: - final_obs["rgb_2"] = rgb_2 - return final_obs, final_obs - - """ - returns: - - numpy array or torch Tensor of the image - - PIL image (optional) - - numpy array of the last_rgb_frame - """ - - def convert_observation( - self, image_bytes: bytes, res: ObsType - ) -> Tuple[Union[np.ndarray, torch.Tensor], Optional[Image.Image]]: - if self.encoding_mode == ScreenEncodingMode.PNG: - # decode png byte array to numpy array - # Create a BytesIO object from the byte array - self.csv_logger.profile_start("convert_observation/decode_png") - bytes_io = io.BytesIO(image_bytes) - # Use PIL to open the image from the BytesIO object - img = Image.open(bytes_io).convert("RGB") - # Flip y axis - img = img.transpose(Image.FLIP_TOP_BOTTOM) - self.csv_logger.profile_end("convert_observation/decode_png") - self.csv_logger.profile_start("convert_observation/convert_to_numpy") - # Convert the PIL image to a numpy array - last_rgb_frame = np.array(img) - arr = np.transpose(last_rgb_frame, (2, 1, 0)) - rgb_array_or_tensor = arr.astype(np.uint8) - self.csv_logger.profile_end("convert_observation/convert_to_numpy") - elif self.encoding_mode == ScreenEncodingMode.RAW: - # decode raw byte array to numpy array - self.csv_logger.profile_start("convert_observation/decode_raw") - last_rgb_frame = np.frombuffer(image_bytes, dtype=np.uint8).reshape( - (self.initial_env.imageSizeY, self.initial_env.imageSizeX, 3) - ) - # Flip y axis using np - # last_rgb_frame = np.transpose(last_rgb_frame, (1, 0, 2)) - last_rgb_frame = np.flip(last_rgb_frame, axis=0) - rgb_array_or_tensor = last_rgb_frame - # arr = np.transpose(last_rgb_frame, (2, 1, 0)) # channels, width, height - img = None - self.csv_logger.profile_end("convert_observation/decode_raw") - elif self.encoding_mode == ScreenEncodingMode.ZEROCOPY: - import torch - - if self.initial_env.eye_distance > 0: # binocular vision - # TODO: Handle binocular vision - if ( - self.observation_tensors[0] is not None - and self.observation_tensors[1] is not None - ): - # already intialized - # drop alpha, flip y axis, and clone - if ( - self.observation_tensor_type - == ObservationTensorType.APPLE_TENSOR - ): - return ( - self.observation_tensors[0] - .clone()[:, :, [2, 1, 0]] - .flip(0), - None, - ) - elif ( - self.observation_tensor_type - == ObservationTensorType.CUDA_DLPACK - ): - return ( - self.observation_tensors[0].clone()[:, :, :3].flip(0), - None, - ) - else: - if self.observation_tensors[0] is not None: - # already intialized - # drop alpha, flip y axis, and clone - if ( - self.observation_tensor_type - == ObservationTensorType.APPLE_TENSOR - ): - return ( - self.observation_tensors[0] - .clone()[:, :, [2, 1, 0]] - .flip(0), - None, - ) - elif ( - self.observation_tensor_type - == ObservationTensorType.CUDA_DLPACK - ): - return ( - self.observation_tensors[0].clone()[:, :, :3].flip(0), - None, - ) - - from .craftground_native import initialize_from_mach_port # type: ignore - from .craftground_native import mtl_tensor_from_cuda_mem_handle # type: ignore - - if len(res.ipc_handle) == 0: - raise ValueError("No ipc handle found.") - if len(res.ipc_handle) == 4: - mach_port = int.from_bytes( - res.ipc_handle, byteorder="little", signed=False - ) - print(f"{mach_port=}") - apple_dl_tensor = initialize_from_mach_port( - mach_port, self.initial_env.imageSizeX, self.initial_env.imageSizeY - ) - if apple_dl_tensor is not None: - # image_tensor = torch.utils.dlpack.from_dlpack(apple_dl_tensor) - rgb_array_or_tensor = apple_dl_tensor - print(rgb_array_or_tensor.shape) - print(rgb_array_or_tensor.dtype) - print(rgb_array_or_tensor.device) - self.observation_tensors[0] = rgb_array_or_tensor - # drop alpha, flip y axis, and clone - rgb_array_or_tensor = rgb_array_or_tensor.clone()[ - :, :, [2, 1, 0] - ].flip(0) - self.observation_tensor_type = ObservationTensorType.APPLE_TENSOR - else: - raise ValueError( - f"Failed to initialize from mach port {res.ipc_handle}." - ) - else: - import torch.utils.dlpack - - cuda_dl_tensor = mtl_tensor_from_cuda_mem_handle( - res.ipc_handle, - self.initial_env.imageSizeX, - self.initial_env.imageSizeY, - ) - if not cuda_dl_tensor: - raise ValueError("Invalid DLPack capsule: None") - rgb_array_or_tensor = torch.utils.dlpack.from_dlpack(cuda_dl_tensor) - print(rgb_array_or_tensor.shape) - print(rgb_array_or_tensor.dtype) - print(rgb_array_or_tensor.device) - print(f"{rgb_array_or_tensor.data_ptr()=}\n\n") - self.observation_tensors[0] = rgb_array_or_tensor - # drop alpha, flip y axis, and clone - rgb_array_or_tensor = ( - self.observation_tensors[0].clone()[:, :, :3].flip(0) - ) - self.observation_tensor_type = ObservationTensorType.CUDA_DLPACK - img = None - - else: - raise ValueError(f"Unknown encoding mode: {self.encoding_mode}") - return rgb_array_or_tensor, img + final_obs["pov_2"] = rgb_2 + return final_obs def start_server( self, @@ -351,36 +205,10 @@ def start_server( track_native_memory: bool, ld_preload: Optional[str], ): - self.remove_orphan_java_processes() + self.remove_orphan_java_processes() # TODO # Check if a file exists - if os.name == "nt": - while True: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - if s.connect_ex(("127.0.0.1", port)) == 0: # The port is in use - if self.find_free_port: - print( - f"[Warning]: Port {port} is already in use. Trying another port." - ) - port += 1 - else: - raise ConnectionError( - f"Port {port} is already in use. Please choose another port." - ) - else: - socket_path = f"/tmp/minecraftrl_{port}.sock" - if os.path.exists(socket_path): - if self.find_free_port: - print( - f"[Warning]: Socket file {socket_path} already exists. Trying another port." - ) - while os.path.exists(socket_path): - port += 1 - socket_path = f"/tmp/minecraftrl_{port}.sock" - print(f"Using port {socket_path}") - else: - raise FileExistsError( - f"Socket file {socket_path} already exists. Please choose another port." - ) + + # Prepare command TODO my_env = os.environ.copy() my_env["PORT"] = str(port) my_env["VERBOSE"] = str(int(self.verbose_jvm)) @@ -404,7 +232,9 @@ def start_server( cmd = f"vglrun {cmd}" if ld_preload: my_env["LD_PRELOAD"] = ld_preload - print(f"{cmd=}") + self.csv_logger.log(f"Starting server with command: {cmd}") + + # Launch the server self.process = subprocess.Popen( cmd, cwd=self.env_path, @@ -412,13 +242,17 @@ def start_server( stdout=subprocess.DEVNULL if not self.verbose_gradle else None, env=my_env, ) + + # TODO: socket specific sock: socket.socket = wait_for_server(port) self.sock = sock - self.send_initial_env() + + self.ipc.send_initial_environment( + self.initial_env.to_initial_environment_message() + ) + + # TODO: socket specific self.buffered_socket = BufferedSocket(self.sock) - # self.json_socket.send_json_as_base64(self.initial_env.to_dict()) - if self.verbose_python: - print_with_time("Sent initial environment") self.csv_logger.log("Sent initial environment") def update_override_resolutions(self, options_txt_path): @@ -477,47 +311,27 @@ def send_initial_env(self): def step(self, action: ActType) -> Tuple[ObsType, float, bool, bool, dict]: # send the action self.last_action = action - self.csv_logger.profile_start("send_action_and_commands") - # Translate the action v1 to v2 - if self.action_space_version == ActionSpaceVersion.V1_MINEDOJO: - translated_action = translate_action_to_v2(action) - else: - translated_action = action - send_action_and_commands( - self.sock, - translated_action, - commands=self.queued_commands, - verbose=self.verbose_python, - ) - self.csv_logger.profile_end("send_action_and_commands") + with self.csv_logger.profile("send_action_and_commands"): + # Translate the action v1 to v2 + if self.action_space_version == ActionSpaceVersion.V1_MINEDOJO: + translated_action = translate_action_to_v2(action) + else: + translated_action = action + + self.ipc.send_action(translated_action, self.queued_commands) + send_action_and_commands( + self.sock, + translated_action, + commands=self.queued_commands, + verbose=self.verbose_python, + ) self.queued_commands.clear() # read the response - if self.verbose_python: - print_with_time("Sent action and reading response...") self.csv_logger.log("Sent action and reading response...") - self.csv_logger.profile_start("read_response") - siz, res = self.read_one_observation() - self.csv_logger.profile_end("read_response") - if self.verbose_python: - print_with_time("Read observation...") + with self.csv_logger.profile("read_response"): + siz, res = self.read_one_observation() self.csv_logger.log("Read observation...") - self.csv_logger.profile_start("convert_observation") - rgb_1, img_1 = self.convert_observation(res.image, res) - self.csv_logger.profile_end("convert_observation") - rgb_2 = None - img_2 = None - frame_2 = None - if res.image_2 is not None and res.image_2 != b"": - rgb_2, img_2 = self.convert_observation(res.image_2, res) - final_obs = { - "obs": res, - "rgb": rgb_1, - } - res.yaw = ((res.yaw + 180) % 360) - 180 - self.last_images = [img_1, img_2] - self.last_rgb_frames = [rgb_1, rgb_2] - if rgb_2 is not None: - final_obs["rgb_2"] = rgb_2 + final_obs = self.convert_observation_v2(res) reward = 0 # Initialize reward to zero done = False # Initialize done flag to False @@ -537,60 +351,7 @@ def step(self, action: ActType) -> Tuple[ObsType, float, bool, bool, dict]: # when self.render_mode is "rgb_array_list": render returns a list of images to be rendered # when self.render_mode is "rgb_tensor": render returns a torch tensor to be rendered def render(self) -> Union[RenderFrame, List[RenderFrame], None]: - # print("Rendering...") - # select last_image and last_frame - if self.render_mode is None: - return None - if self.render_mode == "human": - # do not render anything - return None - if self.render_mode == "ansi": - raise ValueError("Rendering mode ansi not supported") - if self.render_mode == "rgb_array_list": - raise ValueError("Rendering mode rgb_array_list not supported") - - if self.render_alternating_eyes: - last_image = self.last_images[self.render_alternating_eyes_counter] - last_rgb_frame = self.last_rgb_frames[self.render_alternating_eyes_counter] - self.render_alternating_eyes_counter = ( - 1 - self.render_alternating_eyes_counter - ) - else: - last_image = self.last_images[0] - last_rgb_frame = self.last_rgb_frames[0] - if last_image is None and last_rgb_frame is None: - return None - - if isinstance(last_rgb_frame, torch.Tensor) and ( - self.render_mode != "rgb_array_tensor" or self.render_action - ): - # drop the alpha channel and convert to numpy array - last_rgb_frame = last_rgb_frame.cpu().numpy() - # last_rgb_frame: np.ndarray or torch.Tensor - # last_image: PIL.Image.Image or None - if self.render_action and self.last_action: - if last_image is None: - # it is inevitable to convert the tensor to numpy array - last_image = Image.fromarray(last_rgb_frame) - self.csv_logger.profile_start("render_action") - draw = ImageDraw.Draw(last_image) - if self.action_space_version == ActionSpaceVersion.V1_MINEDOJO: - text = action_to_symbol(self.last_action) - elif self.action_space_version == ActionSpaceVersion.V2_MINERL_HUMAN: - text = action_v2_to_symbol(self.last_action) - else: - raise ValueError( - f"Unknown action space version {self.action_space_version}" - ) - position = (0, 0) - font = get_font() - font_size = 8 - color = (255, 0, 0) - draw.text(position, text, font=font, font_size=font_size, fill=color) - self.csv_logger.profile_end("render_action") - return np.array(last_image) - else: - return last_rgb_frame + return self.observation_converter.render() @property def render_mode(self) -> Optional[str]: diff --git a/src/craftground/environment/ipc_interface.py b/src/craftground/environment/ipc_interface.py index d3c0f18f..67a14ee9 100644 --- a/src/craftground/environment/ipc_interface.py +++ b/src/craftground/environment/ipc_interface.py @@ -1,4 +1,6 @@ from abc import ABC, abstractmethod +import os +import subprocess from proto.action_space_pb2 import ActionSpaceMessageV2 from proto.initial_environment_pb2 import InitialEnvironmentMessage @@ -6,6 +8,8 @@ class IPCInterface(ABC): + port: int + @abstractmethod def send_action(self, message: ActionSpaceMessageV2): pass @@ -17,3 +21,44 @@ def read_observation(self) -> ObservationSpaceMessage: @abstractmethod def send_initial_environment(self, message: InitialEnvironmentMessage): pass + + def start_server( + self, + env_path, + track_native_memory: bool, + verbose_jvm: bool, + use_vglrun, + ld_preload, + options_txt_path, + verbose_gradle: bool = False, + ): + my_env = os.environ.copy() + my_env["PORT"] = str(self.port) + my_env["VERBOSE"] = str(int(verbose_jvm)) + if track_native_memory: + my_env["CRAFTGROUND_JVM_NATIVE_TRACKING"] = "detail" + if self.native_debug: + my_env["CRAFGROUND_NATIVE_DEBUG"] = "True" + # configure permission of the gradlew + gradlew_path = os.path.join(env_path, "gradlew") + if not os.access(gradlew_path, os.X_OK): + os.chmod(gradlew_path, 0o755) + # update image settings of options.txt if exists + if options_txt_path is not None: + if os.path.exists(options_txt_path): + pass + # self.update_override_resolutions(options_txt_path) + + cmd = f"./gradlew runClient -w --no-daemon" # --args="--width {self.initial_env.imageSizeX} --height {self.initial_env.imageSizeY}"' + if use_vglrun: + cmd = f"vglrun {cmd}" + if ld_preload: + my_env["LD_PRELOAD"] = ld_preload + print(f"{cmd=}") + self.process = subprocess.Popen( + cmd, + cwd=self.env_path, + shell=True, + stdout=subprocess.DEVNULL if not verbose_gradle else None, + env=my_env, + ) diff --git a/src/craftground/environment/socket_ipc.py b/src/craftground/environment/socket_ipc.py index f447b9ae..106fd05f 100644 --- a/src/craftground/environment/socket_ipc.py +++ b/src/craftground/environment/socket_ipc.py @@ -72,11 +72,13 @@ def send_action( self.logger.log("Sent action and commands") def read_observation(self) -> ObservationSpaceMessage: + self.logger.log("Reading response...") data_len_bytes = self.buffered_socket.read(4, True) data_len = struct.unpack(" socket.socket: time.sleep(1) -def no_op() -> List[int]: - r = [0] * 8 - r[3] = 12 - r[4] = 12 - return r - - -def no_op_v2() -> Dict[str, Union[bool, float]]: - noop_dict = {} - for bool_key in [ - "attack", - "back", - "forward", - "jump", - "left", - "right", - "sneak", - "sprint", - "use", - "drop", - "inventory", - ]: - noop_dict[bool_key] = False - for i in range(1, 10): - noop_dict[f"hotbar.{i}"] = False - noop_dict["camera_pitch"] = 0.0 - noop_dict["camera_yaw"] = 0.0 - return noop_dict - - def send_commands(sock: socket.socket, commands: List[str]): # print("Sending command") action_space = action_v2_dict_to_message(no_op_v2()) @@ -98,33 +70,6 @@ def send_action_and_commands( print_with_time("Sent actions and commands") -def action_v2_dict_to_message(action_v2): - action_space = action_space_pb2.ActionSpaceMessageV2() - action_space.attack = action_v2["attack"] - action_space.back = action_v2["back"] - action_space.forward = action_v2["forward"] - action_space.jump = action_v2["jump"] - action_space.left = action_v2["left"] - action_space.right = action_v2["right"] - action_space.sneak = action_v2["sneak"] - action_space.sprint = action_v2["sprint"] - action_space.use = action_v2["use"] - action_space.drop = action_v2["drop"] - action_space.inventory = action_v2["inventory"] - action_space.hotbar_1 = action_v2["hotbar.1"] - action_space.hotbar_2 = action_v2["hotbar.2"] - action_space.hotbar_3 = action_v2["hotbar.3"] - action_space.hotbar_4 = action_v2["hotbar.4"] - action_space.hotbar_5 = action_v2["hotbar.5"] - action_space.hotbar_6 = action_v2["hotbar.6"] - action_space.hotbar_7 = action_v2["hotbar.7"] - action_space.hotbar_8 = action_v2["hotbar.8"] - action_space.hotbar_9 = action_v2["hotbar.9"] - action_space.camera_pitch = action_v2["camera_pitch"] - action_space.camera_yaw = action_v2["camera_yaw"] - return action_space - - def send_fastreset2(sock: socket.socket, extra_commands: List[str] = None): extra_cmd_str = "" if extra_commands is not None: From 3fb0bbf04ddeb3babead97abdcabd4b533809678 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 10:36:47 +0900 Subject: [PATCH 19/91] =?UTF-8?q?=E2=9C=A8=20Automatically=20find=20port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc.cpp | 18 ++++------ src/cpp/ipc.h | 10 +++--- src/cpp/ipc_boost.cpp | 45 +++++++++++++++++++++--- src/cpp/ipc_boost.hpp | 9 +++-- src/craftground/environment/boost_ipc.py | 5 +-- 5 files changed, 58 insertions(+), 29 deletions(-) diff --git a/src/cpp/ipc.cpp b/src/cpp/ipc.cpp index 01a2d0db..09309515 100644 --- a/src/cpp/ipc.cpp +++ b/src/cpp/ipc.cpp @@ -60,21 +60,15 @@ py::capsule mtl_tensor_from_cuda_mem_handle( #include "ipc_boost.hpp" -void initialize_shared_memory( - const char *memory_name, - const char *synchronization_memory_name, - const char *action_memory_name, +int initialize_shared_memory( + int port, const char *initial_data, size_t data_size, - size_t action_size + size_t action_size, + bool find_free_port ) { - create_shared_memory_impl( - memory_name, - synchronization_memory_name, - action_memory_name, - initial_data, - data_size, - action_size + return create_shared_memory_impl( + port, initial_data, data_size, action_size, find_free_port ); } diff --git a/src/cpp/ipc.h b/src/cpp/ipc.h index 578a1815..2ce2ff13 100644 --- a/src/cpp/ipc.h +++ b/src/cpp/ipc.h @@ -8,13 +8,13 @@ initialize_from_mach_port(unsigned int machPort, int width, int height); py::capsule mtl_tensor_from_cuda_mem_handle( const char *cuda_ipc_handle, int width, int height ); -void initialize_shared_memory( - const char *memory_name, - const char *synchronization_memory_name, - const char *action_memory_name, + +int initialize_shared_memory( + int port, const char *initial_data, size_t data_size, - size_t action_size + size_t action_size, + bool find_free_port ); void write_to_shared_memory( diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 57e3ed44..4e741c24 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -1,15 +1,48 @@ #include "ipc_boost.hpp" #include +#include +#include + +bool shared_memory_exists(const std::string &name) { + try { + // Try to open the shared memory object + shared_memory_object shm(open_only, name.c_str(), read_only); + return true; // The shared memory exists + } catch (const interprocess_exception &e) { + // The shared memory does not exist + return false; + } +} // Create shared memory and write initial environment data -void create_shared_memory_impl( - const std::string &initial_memory_name, - const std::string &synchronization_memory_name, - const std::string &action_memory_name, +int create_shared_memory_impl( + int port, const char *initial_data, size_t data_size, - size_t action_size + size_t action_size, + bool find_free_port ) { + std::string initial_memory_name; + std::string synchronization_memory_name; + std::string action_memory_name; + bool found_free_port = false; + do { + initial_memory_name = "craftground_" + std::to_string(port) + "_action"; + synchronization_memory_name = "craftground_" + std::to_string(port) + "_synchronization"; + action_memory_name = "craftground_" + std::to_string(port) + "_action"; + if (shared_memory_exists(initial_memory_name)) { + if (find_free_port) { + port++; + continue; + } else { + throw std::runtime_error( + "Shared memory " + initial_memory_name + " already exists" + ); + } + } + found_free_port = true; + } while (!found_free_port); + shared_memory_object::remove(initial_memory_name.c_str()); managed_shared_memory sharedMemory( create_only, @@ -54,6 +87,8 @@ void create_shared_memory_impl( auto *headerAction = new (addrAction) SharedDataHeader(); headerAction->size = action_size; headerAction->ready = true; + + return port; } // Write action to shared memory diff --git a/src/cpp/ipc_boost.hpp b/src/cpp/ipc_boost.hpp index a32894f9..928387b7 100644 --- a/src/cpp/ipc_boost.hpp +++ b/src/cpp/ipc_boost.hpp @@ -19,13 +19,12 @@ struct SharedDataHeader { }; // Message follows the header -void create_shared_memory_impl( - const std::string &initial_memory_name, - const std::string &synchronization_memory_name, - const std::string &action_memory_name, +int create_shared_memory_impl( + int port, const char *initial_data, size_t data_size, - size_t action_size + size_t action_size, + bool find_free_port ); void write_to_shared_memory_impl( diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index 500c2dcd..ae01d03f 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -10,7 +10,7 @@ class BoostIPC(IPCInterface): - def __init__(self, port: str, initial_environment: InitialEnvironmentMessage): + def __init__(self, port: str, find_free_port: bool, initial_environment: InitialEnvironmentMessage): self.port = port self.initial_environment_shared_memory_name = ( f"craftground_{port}_initial_environment" @@ -24,13 +24,14 @@ def __init__(self, port: str, initial_environment: InitialEnvironmentMessage): # Get the length of the action space message dummy_action: ActionSpaceMessageV2 = ActionSpaceMessageV2() dummy_action_bytes: bytes = dummy_action.SerializeToString() - initialize_shared_memory( + self.port = initialize_shared_memory( self.initial_environment_shared_memory_name, self.synchronization_shared_memory_name, self.action_shared_memory_name, initial_environment_bytes, len(initial_environment_bytes), len(dummy_action_bytes), + find_free_port, ) def write_action(self, action: ActionSpaceMessageV2): From f98bc7f2ab74995a3510f849dc06661ec7719c13 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 11:07:56 +0900 Subject: [PATCH 20/91] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/boost_ipc.py | 30 +++-- src/craftground/environment/environment.py | 108 ++---------------- src/craftground/environment/ipc_interface.py | 31 +++++ .../environment/observation_converter.py | 17 +++ src/craftground/environment/socket_ipc.py | 13 ++- 5 files changed, 86 insertions(+), 113 deletions(-) diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index ae01d03f..6553a92f 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -10,29 +10,31 @@ class BoostIPC(IPCInterface): - def __init__(self, port: str, find_free_port: bool, initial_environment: InitialEnvironmentMessage): + def __init__( + self, + port: str, + find_free_port: bool, + initial_environment: InitialEnvironmentMessage, + ): self.port = port - self.initial_environment_shared_memory_name = ( - f"craftground_{port}_initial_environment" - ) - self.action_shared_memory_name = f"craftground_{port}_action" - self.observation_shared_memory_name = f"craftground_{port}_observation" - self.synchronization_shared_memory_name = f"craftground_{port}_synchronization" - initial_environment_bytes: bytes = initial_environment.SerializeToString() # Get the length of the action space message dummy_action: ActionSpaceMessageV2 = ActionSpaceMessageV2() dummy_action_bytes: bytes = dummy_action.SerializeToString() self.port = initialize_shared_memory( - self.initial_environment_shared_memory_name, - self.synchronization_shared_memory_name, - self.action_shared_memory_name, + self.port, initial_environment_bytes, len(initial_environment_bytes), len(dummy_action_bytes), find_free_port, ) + self.initial_environment_shared_memory_name = ( + f"craftground_{port}_initial_environment" + ) + self.action_shared_memory_name = f"craftground_{port}_action" + self.observation_shared_memory_name = f"craftground_{port}_observation" + self.synchronization_shared_memory_name = f"craftground_{port}_synchronization" def write_action(self, action: ActionSpaceMessageV2): action_bytes: bytes = action.SerializeToString() @@ -51,3 +53,9 @@ def destroy(self): destroy_shared_memory(self.synchronization_shared_memory_name) # Java destroys the initial environment shared memory # destroy_shared_memory(self.initial_environment_shared_memory_name) + + def is_alive(self) -> bool: + return True + + def __del__(self): + self.destroy() diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 2e868c34..11f647fb 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -1,4 +1,3 @@ -import io import os import re import signal @@ -6,21 +5,15 @@ import struct import subprocess from enum import Enum -from time import sleep from typing import Tuple, Optional, Union, List, Any, Dict import gymnasium as gym import numpy as np -import psutil -from PIL import Image, ImageDraw -from gymnasium import spaces from gymnasium.core import ActType, ObsType, RenderFrame import torch from environment.action_space import ( ActionSpaceVersion, - action_to_symbol, - action_v2_to_symbol, declare_action_space, translate_action_to_v2, ) @@ -30,17 +23,12 @@ from ..buffered_socket import BufferedSocket from ..csv_logger import CsvLogger, LogBackend -from ..font import get_font from ..initial_environment_config import InitialEnvironmentConfig from ..minecraft import ( wait_for_server, - send_fastreset2, - send_action_and_commands, send_exit, ) -from ..print_with_time import print_with_time from ..proto import observation_space_pb2 -from ..screen_encoding_modes import ScreenEncodingMode class ObservationTensorType(Enum): @@ -99,7 +87,6 @@ def __init__( self.render_alternating_eyes = render_alternating_eyes self.render_alternating_eyes_counter = 0 - self.port = port self.find_free_port = find_free_port self.queued_commands = [] self.process = None @@ -128,16 +115,6 @@ def __init__( self.observation_tensors = [None, None] self.observation_tensor_type = ObservationTensorType.NONE - if initial_env.screen_encoding_mode == ScreenEncodingMode.ZEROCOPY: - try: - from .craftground_native import initialize_from_mach_port # type: ignore - from .craftground_native import mtl_tensor_from_cuda_mem_handle # type: ignore - except ImportError: - raise ImportError( - "To use zerocopy encoding mode, please install the craftground[cuda] package on linux or windows." - " If this error happens in macOS, please report it to the developers." - ) - self.observation_converter = ObservationConverter( self.initial_env.screen_encoding_mode, self.initial_env.eye_distance > 0, @@ -154,33 +131,11 @@ def reset( options = {} fast_reset = options.get("fast_reset", True) extra_commands = options.get("extra_commands", []) - if not self.sock: # first time - self.start_server( - port=self.port, - use_vglrun=self.use_vglrun, - track_native_memory=self.track_native_memory, - ld_preload=self.ld_preload, - ) - else: - if not fast_reset: - self.sock.close() - self.terminate() - # wait for server death and restart server - sleep(5) - self.start_server( - port=self.port, - use_vglrun=self.use_vglrun, - track_native_memory=self.track_native_memory, - ld_preload=self.ld_preload, - ) - else: - with self.csv_logger.profile("fast_reset"): - send_fastreset2(self.sock, extra_commands) - self.csv_logger.log("Sent fast reset") + + self.ipc.ensure_alive(fast_reset, extra_commands, seed=seed) with self.csv_logger.profile("read_response"): res = self.ipc.read_observation() - final_obs = self.convert_observation_v2(res) return final_obs, final_obs @@ -319,12 +274,6 @@ def step(self, action: ActType) -> Tuple[ObsType, float, bool, bool, dict]: translated_action = action self.ipc.send_action(translated_action, self.queued_commands) - send_action_and_commands( - self.sock, - translated_action, - commands=self.queued_commands, - verbose=self.verbose_python, - ) self.queued_commands.clear() # read the response self.csv_logger.log("Sent action and reading response...") @@ -370,6 +319,7 @@ def add_commands(self, commands: List[str]): self.queued_commands.extend(commands) def terminate(self): + self.ipc.close() if self.sock is not None: send_exit(self.sock) self.sock.close() @@ -387,55 +337,17 @@ def terminate(self): print("Child process already terminated") print("Terminated the java process") - def remove_orphan_java_processes(self): # noqa: C901 - print("Removing orphan Java processes...") - target_directory = "/tmp" - file_pattern = "minecraftrl_" - file_usage = {} - no_such_processes = 0 - access_denied_processes = 0 - for proc in psutil.process_iter(["pid", "name"]): - try: - for file in proc.open_files(): - if ( - file.path.startswith(target_directory) - and file_pattern in file.path - ): - if file.path not in file_usage: - file_usage[file.path] = [] - file_usage[file.path].append(proc.info) - except psutil.NoSuchProcess: - no_such_processes += 1 - continue - except psutil.AccessDenied: - access_denied_processes += 1 - continue - except Exception as e: - print(f"Error: {e}") - continue - - for file_path, processes in file_usage.items(): - if all(proc["name"].lower() == "java" for proc in processes): - for proc in processes: - os.kill(proc["pid"], signal.SIGTERM) - print(f"Killed Java process {proc['pid']} using file {file_path}") - os.remove(file_path) - print(f"Removed {file_path}") - print( - f"Removed orphan Java processes: {access_denied_processes} access denied, {no_such_processes} no such process" - ) - - # Copy or symlink the save file to the returned folder @staticmethod - def get_env_save_path() -> str: + def get_env_base_path() -> str: current_file = os.path.abspath(__file__) current_dir = os.path.dirname(current_file) - env_dir = os.path.join(current_dir, "MinecraftEnv", "run", "saves") + env_dir = os.path.join(current_dir, "MinecraftEnv", "run") return env_dir + @staticmethod + def get_env_save_path() -> str: + return os.path.join(CraftGroundEnvironment.get_env_base_path(), "saves") + @staticmethod def get_env_option_path() -> str: - current_file = os.path.abspath(__file__) - current_dir = os.path.dirname(current_file) - options_txt = os.path.join(current_dir, "MinecraftEnv", "run", "options.txt") - return options_txt + return os.path.join(CraftGroundEnvironment.get_env_base_path(), "options.txt") diff --git a/src/craftground/environment/ipc_interface.py b/src/craftground/environment/ipc_interface.py index 67a14ee9..5c4501a6 100644 --- a/src/craftground/environment/ipc_interface.py +++ b/src/craftground/environment/ipc_interface.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod import os import subprocess +from time import sleep from proto.action_space_pb2 import ActionSpaceMessageV2 from proto.initial_environment_pb2 import InitialEnvironmentMessage @@ -22,6 +23,36 @@ def read_observation(self) -> ObservationSpaceMessage: def send_initial_environment(self, message: InitialEnvironmentMessage): pass + @abstractmethod + def is_alive(self) -> bool: + pass + + @abstractmethod + def destroy(self): + pass + + def ensure_alive(self, fast_reset, extra_commands, seed): + if not self.is_alive(): # first time + self.start_server( + port=self.port, + use_vglrun=self.use_vglrun, + track_native_memory=self.track_native_memory, + ld_preload=self.ld_preload, + seed=seed, + ) + elif not fast_reset: + self.destroy() + self.start_server( + port=self.port, + use_vglrun=self.use_vglrun, + track_native_memory=self.track_native_memory, + ld_preload=self.ld_preload, + ) + else: + with self.csv_logger.profile("fast_reset"): + self.send_fastreset2(self.sock, extra_commands) + self.csv_logger.log("Sent fast reset") + def start_server( self, env_path, diff --git a/src/craftground/environment/observation_converter.py b/src/craftground/environment/observation_converter.py index 5302a4a2..0def502d 100644 --- a/src/craftground/environment/observation_converter.py +++ b/src/craftground/environment/observation_converter.py @@ -56,6 +56,23 @@ def __init__( self.render_alternating_eyes_counter = 0 self.render_action = render_action + if output_type == ScreenEncodingMode.ZEROCOPY: + try: + from .craftground_native import initialize_from_mach_port # type: ignore + from .craftground_native import mtl_tensor_from_cuda_mem_handle # type: ignore + except ImportError: + raise ImportError( + "To use zerocopy encoding mode, please install the craftground[cuda] package on linux or windows." + " If this error happens in macOS, please report it to the developers." + ) + if output_type == ScreenEncodingMode.JAX: + try: + import jax # type: ignore + except ImportError: + raise ImportError( + "To use JAX encoding mode, please install the craftground[jax] package." + ) + def convert( self, observation: ObservationSpaceMessage ) -> Tuple[Optional[ImageOutputType]]: diff --git a/src/craftground/environment/socket_ipc.py b/src/craftground/environment/socket_ipc.py index 106fd05f..c081451b 100644 --- a/src/craftground/environment/socket_ipc.py +++ b/src/craftground/environment/socket_ipc.py @@ -1,13 +1,12 @@ import os import signal import struct -from typing import List, Optional, Tuple +from time import sleep +from typing import List, Optional import psutil from csv_logger import CsvLogger from environment.ipc_interface import IPCInterface -from minecraft import action_v2_dict_to_message -from print_with_time import print_with_time from proto.action_space_pb2 import ActionSpaceMessageV2 from proto.initial_environment_pb2 import InitialEnvironmentMessage from proto.observation_space_pb2 import ObservationSpaceMessage @@ -81,8 +80,14 @@ def read_observation(self) -> ObservationSpaceMessage: self.logger.log(f"Got response with size {data_len}") return observation_space + def is_alive(self) -> bool: + return self.sock is not None + def destroy(self): - pass + self.sock.close() + self.terminate() + # wait for server death and restart server + sleep(5) def remove_orphan_java_processes(self): # noqa: C901 self.logger.log("Removing orphan Java processes...") From ae0786bb64578fa60e1ca8d9344a8cd9f5ff66b5 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 11:12:03 +0900 Subject: [PATCH 21/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_boost.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 4e741c24..2befc470 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -1,6 +1,5 @@ #include "ipc_boost.hpp" #include -#include #include bool shared_memory_exists(const std::string &name) { @@ -28,7 +27,8 @@ int create_shared_memory_impl( bool found_free_port = false; do { initial_memory_name = "craftground_" + std::to_string(port) + "_action"; - synchronization_memory_name = "craftground_" + std::to_string(port) + "_synchronization"; + synchronization_memory_name = + "craftground_" + std::to_string(port) + "_synchronization"; action_memory_name = "craftground_" + std::to_string(port) + "_action"; if (shared_memory_exists(initial_memory_name)) { if (find_free_port) { @@ -42,7 +42,7 @@ int create_shared_memory_impl( } found_free_port = true; } while (!found_free_port); - + shared_memory_object::remove(initial_memory_name.c_str()); managed_shared_memory sharedMemory( create_only, @@ -87,7 +87,7 @@ int create_shared_memory_impl( auto *headerAction = new (addrAction) SharedDataHeader(); headerAction->size = action_size; headerAction->ready = true; - + return port; } From c4370d9c858d615137ce7788bba903adf226a051 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 11:20:32 +0900 Subject: [PATCH 22/91] =?UTF-8?q?=F0=9F=A7=AA=20Add=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/environment.py | 6 +- src/craftground/environment/socket_ipc.py | 68 +++++++++- src/craftground/minecraft.py | 85 ------------- tests/python/unit/README.md | 3 + tests/python/unit/test_environment.py | 139 +++++++++++++++++++++ 5 files changed, 210 insertions(+), 91 deletions(-) delete mode 100644 src/craftground/minecraft.py create mode 100644 tests/python/unit/README.md create mode 100644 tests/python/unit/test_environment.py diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 11f647fb..3c0f3af0 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -24,10 +24,6 @@ from ..buffered_socket import BufferedSocket from ..csv_logger import CsvLogger, LogBackend from ..initial_environment_config import InitialEnvironmentConfig -from ..minecraft import ( - wait_for_server, - send_exit, -) from ..proto import observation_space_pb2 @@ -199,7 +195,7 @@ def start_server( ) # TODO: socket specific - sock: socket.socket = wait_for_server(port) + sock: socket.socket = self.ipc.wait_for_server(port) self.sock = sock self.ipc.send_initial_environment( diff --git a/src/craftground/environment/socket_ipc.py b/src/craftground/environment/socket_ipc.py index c081451b..acb90547 100644 --- a/src/craftground/environment/socket_ipc.py +++ b/src/craftground/environment/socket_ipc.py @@ -2,14 +2,16 @@ import signal import struct from time import sleep -from typing import List, Optional +from typing import Dict, List, Optional, Union import psutil from csv_logger import CsvLogger +from environment.action_space import action_v2_dict_to_message, no_op_v2 from environment.ipc_interface import IPCInterface from proto.action_space_pb2 import ActionSpaceMessageV2 from proto.initial_environment_pb2 import InitialEnvironmentMessage from proto.observation_space_pb2 import ObservationSpaceMessage +import socket class SocketIPC(IPCInterface): @@ -126,3 +128,67 @@ def remove_orphan_java_processes(self): # noqa: C901 print( f"Removed orphan Java processes: {access_denied_processes} access denied, {no_such_processes} no such process" ) + + def send_commands(self, commands: List[str]): + # print("Sending command") + action_space = action_v2_dict_to_message(no_op_v2()) + action_space.commands.extend(commands) + v = action_space.SerializeToString() + self.sock.send(struct.pack(" socket.socket: + wait_time = 1 + next_output = 1 # 3 7 15 31 63 127 255 seconds passed + + while True: + try: + if os.name == "nt": + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(("127.0.0.1", port)) + s.settimeout(30) + return s + else: + socket_path = f"/tmp/minecraftrl_{port}.sock" + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(socket_path) + # s.connect(("127.0.0.1", port)) + s.settimeout(30) + return s + except (ConnectionRefusedError, FileNotFoundError): + if wait_time == next_output: + print( + f"Waiting for server on port {port}...", + ) + next_output *= 2 + if next_output > 1024: + raise Exception("Server not started within 1024 seconds") + wait_time += 1 + time.sleep(1) diff --git a/src/craftground/minecraft.py b/src/craftground/minecraft.py deleted file mode 100644 index 898cdc7f..00000000 --- a/src/craftground/minecraft.py +++ /dev/null @@ -1,85 +0,0 @@ -import os -import socket -import struct - -# import pdb -import time -from typing import List, Dict, Union - -from environment.action_space import action_v2_dict_to_message, no_op_v2 - -from .print_with_time import print_with_time -from .proto import action_space_pb2 - - -def wait_for_server(port: int) -> socket.socket: - wait_time = 1 - next_output = 1 # 3 7 15 31 63 127 255 seconds passed - - while True: - try: - if os.name == "nt": - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect(("127.0.0.1", port)) - s.settimeout(30) - return s - else: - socket_path = f"/tmp/minecraftrl_{port}.sock" - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - s.connect(socket_path) - # s.connect(("127.0.0.1", port)) - s.settimeout(30) - return s - except (ConnectionRefusedError, FileNotFoundError): - if wait_time == next_output: - print( - f"Waiting for server on port {port}...", - ) - next_output *= 2 - if next_output > 1024: - raise Exception("Server not started within 1024 seconds") - wait_time += 1 - time.sleep(1) - - -def send_commands(sock: socket.socket, commands: List[str]): - # print("Sending command") - action_space = action_v2_dict_to_message(no_op_v2()) - action_space.commands.extend(commands) - v = action_space.SerializeToString() - sock.send(struct.pack(" Date: Tue, 21 Jan 2025 11:49:37 +0900 Subject: [PATCH 23/91] =?UTF-8?q?=F0=9F=A7=AA=20Fix=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/action_space.md | 2 +- src/craftground/environment/action_space.py | 4 ++-- src/craftground/environment/environment.py | 8 ++++---- src/craftground/initial_environment_config.py | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/action_space.md b/docs/action_space.md index e01bbbe1..bd410a39 100644 --- a/docs/action_space.md +++ b/docs/action_space.md @@ -8,7 +8,7 @@ The action space is a list of integers, and each indices represent the following - `0`: 0: no-op, 1: Move forward, 2: Move backward - `1`: 0: no-op, 1: Strafe right, 2: Strafe left -- `2`: 0: no-op, 1: Jump +- `2`: 0: no-op, 1: Jump, 2: Sneak, 3: Sprint - `3`: Camera delta pitch (0: -180, 24:180) - `4`: Camera delta yaw (-180: 180) - `5`: 0: no-op, 1: Use 2: Drop 3: Attack 4: Craft 5: Equip 6: Place 7: Destroy diff --git a/src/craftground/environment/action_space.py b/src/craftground/environment/action_space.py index 98928f56..97ce8e75 100644 --- a/src/craftground/environment/action_space.py +++ b/src/craftground/environment/action_space.py @@ -4,7 +4,6 @@ import gymnasium as gym import numpy as np -from action_space import ActionSpace from proto.action_space_pb2 import ActionSpaceMessageV2 @@ -163,7 +162,8 @@ def action_v2_to_symbol(action_v2: Dict[str, Union[int, float]]) -> str: # noqa def declare_action_space(action_space_version: ActionSpaceVersion) -> gym.spaces.Space: if action_space_version == ActionSpaceVersion.V1_MINEDOJO: - return ActionSpace(6) + # Same as the action space used in MineDojo + return gym.spaces.MultiDiscrete([3, 3, 4, 25, 25, 8, 244, 36]) elif action_space_version == ActionSpaceVersion.V2_MINERL_HUMAN: return gym.spaces.Dict( { diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 3c0f3af0..f5f17eea 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -21,10 +21,10 @@ from environment.observation_space import declare_observation_space from environment.socket_ipc import SocketIPC -from ..buffered_socket import BufferedSocket -from ..csv_logger import CsvLogger, LogBackend -from ..initial_environment_config import InitialEnvironmentConfig -from ..proto import observation_space_pb2 +from buffered_socket import BufferedSocket +from csv_logger import CsvLogger, LogBackend +from initial_environment_config import InitialEnvironmentConfig +from proto import observation_space_pb2 class ObservationTensorType(Enum): diff --git a/src/craftground/initial_environment_config.py b/src/craftground/initial_environment_config.py index 9d9667fe..86c2f2ff 100644 --- a/src/craftground/initial_environment_config.py +++ b/src/craftground/initial_environment_config.py @@ -3,8 +3,8 @@ import os from typing import List, Tuple, Optional -from .proto import initial_environment_pb2 -from .screen_encoding_modes import ScreenEncodingMode +from proto import initial_environment_pb2 +from screen_encoding_modes import ScreenEncodingMode class GameMode(Enum): From 223e3a4b535346bea5847bce50cc1f75239d87d9 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 15:52:04 +0900 Subject: [PATCH 24/91] =?UTF-8?q?=E2=9C=85=20Anyway=20pass=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/MinecraftEnv/.gitignore | 1 + src/craftground/environment/environment.py | 112 +++++++++--------- src/craftground/environment/ipc_interface.py | 4 - .../environment/observation_converter.py | 4 +- src/craftground/environment/socket_ipc.py | 43 ++++--- tests/python/unit/test_environment.py | 60 +++------- 6 files changed, 104 insertions(+), 120 deletions(-) diff --git a/src/craftground/MinecraftEnv/.gitignore b/src/craftground/MinecraftEnv/.gitignore index 6dffa360..be9c0b8b 100644 --- a/src/craftground/MinecraftEnv/.gitignore +++ b/src/craftground/MinecraftEnv/.gitignore @@ -124,3 +124,4 @@ CMakeFiles/ compile_commands.json _deps/ bin/ +.kotlin/ diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index f5f17eea..e7e356c7 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -1,7 +1,7 @@ import os import re +import shutil import signal -import socket import struct import subprocess from enum import Enum @@ -21,7 +21,6 @@ from environment.observation_space import declare_observation_space from environment.socket_ipc import SocketIPC -from buffered_socket import BufferedSocket from csv_logger import CsvLogger, LogBackend from initial_environment_config import InitialEnvironmentConfig from proto import observation_space_pb2 @@ -75,6 +74,7 @@ def __init__( self.last_images: List[Union[np.ndarray, torch.Tensor, None]] = [None, None] self.last_action = None self.render_action = render_action + self.verbose = verbose self.verbose_python = verbose_python self.verbose_gradle = verbose_gradle @@ -89,12 +89,18 @@ def __init__( self.use_shared_memory = use_shared_memory if env_path is None: self.env_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "MinecraftEnv", ) else: self.env_path = env_path - self.csv_logger = CsvLogger( + gradle_path = shutil.which("gradlew", path=self.env_path) + if gradle_path is None: + raise FileNotFoundError( + f"eXecutable gradlew not found in {self.env_path}. Please provide the correct path to the environment." + ) + + self.logger = CsvLogger( "py_log.csv", profile=profile, backend=LogBackend.BOTH if verbose_python else LogBackend.NONE, @@ -105,11 +111,12 @@ def __init__( self.ipc = BoostIPC(str(port), initial_env) else: - self.ipc = SocketIPC(self.csv_logger, port, find_free_port) - - # in case when using zerocopy - self.observation_tensors = [None, None] - self.observation_tensor_type = ObservationTensorType.NONE + self.ipc = SocketIPC( + self.logger, + self.initial_env.to_initial_environment_message(), + port, + find_free_port, + ) self.observation_converter = ObservationConverter( self.initial_env.screen_encoding_mode, @@ -128,15 +135,15 @@ def reset( fast_reset = options.get("fast_reset", True) extra_commands = options.get("extra_commands", []) - self.ipc.ensure_alive(fast_reset, extra_commands, seed=seed) + self.ensure_alive(fast_reset, extra_commands, seed) - with self.csv_logger.profile("read_response"): + with self.logger.profile("read_response"): res = self.ipc.read_observation() final_obs = self.convert_observation_v2(res) return final_obs, final_obs def convert_observation_v2(self, res): - with self.csv_logger.profile("convert_observation"): + with self.logger.profile("convert_observation"): rgb_1, rgb_2 = self.observation_converter.convert(res) self.queued_commands = [] @@ -149,6 +156,32 @@ def convert_observation_v2(self, res): final_obs["pov_2"] = rgb_2 return final_obs + @property + def is_alive(self) -> bool: + if self.process is None: + return False + exit_code = self.process.poll() + if exit_code is not None: + self.logger.log(f"Java process exited with code {exit_code}") + return False + + # (alive and fast_reset) -> send fast reset + # (alive and not fast_reset) -> destroy and start server + # (not alive) -> start server + def ensure_alive(self, fast_reset, extra_commands, seed): + if self.is_alive: + if fast_reset: + self.ipc.send_fastreset2(self.sock, extra_commands) + return + else: + self.terminate() + self.start_server( + port=self.ipc.port, + use_vglrun=self.use_vglrun, + track_native_memory=self.track_native_memory, + ld_preload=self.ld_preload, + ) + def start_server( self, port: int, @@ -156,9 +189,8 @@ def start_server( track_native_memory: bool, ld_preload: Optional[str], ): - self.remove_orphan_java_processes() # TODO - # Check if a file exists - + # Remove orphan java processes + self.ipc.remove_orphan_java_processes() # Prepare command TODO my_env = os.environ.copy() my_env["PORT"] = str(port) @@ -183,7 +215,7 @@ def start_server( cmd = f"vglrun {cmd}" if ld_preload: my_env["LD_PRELOAD"] = ld_preload - self.csv_logger.log(f"Starting server with command: {cmd}") + self.logger.log(f"Starting server with command: {cmd}") # Launch the server self.process = subprocess.Popen( @@ -194,17 +226,7 @@ def start_server( env=my_env, ) - # TODO: socket specific - sock: socket.socket = self.ipc.wait_for_server(port) - self.sock = sock - - self.ipc.send_initial_environment( - self.initial_env.to_initial_environment_message() - ) - - # TODO: socket specific - self.buffered_socket = BufferedSocket(self.sock) - self.csv_logger.log("Sent initial environment") + self.ipc.start_communication() def update_override_resolutions(self, options_txt_path): with open(options_txt_path, "r") as file: @@ -237,32 +259,10 @@ def update_override_resolutions(self, options_txt_path): f"Updated {options_txt_path} to {self.initial_env.imageSizeX}x{self.initial_env.imageSizeY}" ) - def read_one_observation(self) -> Tuple[int, ObsType]: - # print("Reading observation size...") - data_len_bytes = self.buffered_socket.read(4, True) - # print("Reading observation...") - data_len = struct.unpack(" Tuple[ObsType, float, bool, bool, dict]: # send the action self.last_action = action - with self.csv_logger.profile("send_action_and_commands"): + with self.logger.profile("send_action_and_commands"): # Translate the action v1 to v2 if self.action_space_version == ActionSpaceVersion.V1_MINEDOJO: translated_action = translate_action_to_v2(action) @@ -272,10 +272,10 @@ def step(self, action: ActType) -> Tuple[ObsType, float, bool, bool, dict]: self.ipc.send_action(translated_action, self.queued_commands) self.queued_commands.clear() # read the response - self.csv_logger.log("Sent action and reading response...") - with self.csv_logger.profile("read_response"): - siz, res = self.read_one_observation() - self.csv_logger.log("Read observation...") + self.logger.log("Sent action and reading response...") + with self.logger.profile("read_response"): + res = self.ipc.read_observation() + self.logger.log("Read observation...") final_obs = self.convert_observation_v2(res) reward = 0 # Initialize reward to zero @@ -315,9 +315,9 @@ def add_commands(self, commands: List[str]): self.queued_commands.extend(commands) def terminate(self): - self.ipc.close() + self.ipc.destroy() if self.sock is not None: - send_exit(self.sock) + self.ipc.send_exit(self.sock) self.sock.close() self.sock = None print("Terminated the java process") diff --git a/src/craftground/environment/ipc_interface.py b/src/craftground/environment/ipc_interface.py index 5c4501a6..1dfa2861 100644 --- a/src/craftground/environment/ipc_interface.py +++ b/src/craftground/environment/ipc_interface.py @@ -19,10 +19,6 @@ def send_action(self, message: ActionSpaceMessageV2): def read_observation(self) -> ObservationSpaceMessage: pass - @abstractmethod - def send_initial_environment(self, message: InitialEnvironmentMessage): - pass - @abstractmethod def is_alive(self) -> bool: pass diff --git a/src/craftground/environment/observation_converter.py b/src/craftground/environment/observation_converter.py index 0def502d..162edade 100644 --- a/src/craftground/environment/observation_converter.py +++ b/src/craftground/environment/observation_converter.py @@ -102,7 +102,7 @@ def convert( obs_1 = self.last_observations[0].clone()[:, :, [2, 1, 0]].flip(0) return (obs_1, None) elif self.internal_type == ObservationTensorType.CUDA_DLPACK: - obs_1 = self.observation_tensors[0].clone()[:, :, :3].flip(0) + obs_1 = self.last_observations[0].clone()[:, :, :3].flip(0) return (obs_1, None) else: raise ValueError( @@ -280,7 +280,7 @@ def convert_jax_observation(self, ipc_handle: bytes) -> "JaxArrayType": print(rgb_array_or_tensor.shape) print(rgb_array_or_tensor.dtype) print(rgb_array_or_tensor.device()) - self.observation_tensors[0] = rgb_array_or_tensor + self.last_observations[0] = rgb_array_or_tensor # drop alpha, flip y axis, and clone rgb_array_or_tensor = rgb_array_or_tensor.clone()[:, :, [2, 1, 0]].flip(0) self.observation_tensor_type = ObservationTensorType.JAX_NP diff --git a/src/craftground/environment/socket_ipc.py b/src/craftground/environment/socket_ipc.py index acb90547..eb8bd7f3 100644 --- a/src/craftground/environment/socket_ipc.py +++ b/src/craftground/environment/socket_ipc.py @@ -1,10 +1,11 @@ import os import signal import struct -from time import sleep +from time import sleep, time from typing import Dict, List, Optional, Union import psutil +from buffered_socket import BufferedSocket from csv_logger import CsvLogger from environment.action_space import action_v2_dict_to_message, no_op_v2 from environment.ipc_interface import IPCInterface @@ -15,11 +16,19 @@ class SocketIPC(IPCInterface): - def __init__(self, logger: CsvLogger, port: int, find_free_port: bool = False): + def __init__( + self, + logger: CsvLogger, + initial_environment: InitialEnvironmentMessage, + port: int, + find_free_port: bool = False, + ): self.logger = logger self.find_free_port = find_free_port self.remove_orphan_java_processes() self.port = self.check_port(port) + self.sock = None + self.initial_environment = initial_environment def check_port(self, port: int) -> int: if os.name == "nt": @@ -54,7 +63,7 @@ def check_port(self, port: int) -> int: f"Socket file {socket_path} already exists. Please choose another port." ) - def send_initial_environment(self, initial_env: InitialEnvironmentMessage): + def _send_initial_environment(self, initial_env: InitialEnvironmentMessage): v = initial_env.SerializeToString() self.sock.send(struct.pack(" bool: return self.sock is not None def destroy(self): - self.sock.close() - self.terminate() - # wait for server death and restart server - sleep(5) + if self.sock: + self.sock.close() + self.sock = None def remove_orphan_java_processes(self): # noqa: C901 self.logger.log("Removing orphan Java processes...") @@ -164,28 +172,35 @@ def send_respawn2(self): def send_exit(self): self.send_commands(["exit"]) - def wait_for_server(port: int) -> socket.socket: + def start_communication(self): + self._connect_server() + self.buffered_socket = BufferedSocket(self.sock) + self._send_initial_environment(self.initial_environment) + self.logger.log("Sent initial environment") + + def _connect_server(self): wait_time = 1 next_output = 1 # 3 7 15 31 63 127 255 seconds passed - while True: try: if os.name == "nt": s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect(("127.0.0.1", port)) + s.connect(("127.0.0.1", self.port)) s.settimeout(30) - return s + self.sock = s + return else: - socket_path = f"/tmp/minecraftrl_{port}.sock" + socket_path = f"/tmp/minecraftrl_{self.port}.sock" s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect(socket_path) # s.connect(("127.0.0.1", port)) s.settimeout(30) - return s + self.sock = s + return except (ConnectionRefusedError, FileNotFoundError): if wait_time == next_output: print( - f"Waiting for server on port {port}...", + f"Waiting for server on port {self.port}...", ) next_output *= 2 if next_output > 1024: diff --git a/tests/python/unit/test_environment.py b/tests/python/unit/test_environment.py index 1ed8d3b4..f8b7b788 100644 --- a/tests/python/unit/test_environment.py +++ b/tests/python/unit/test_environment.py @@ -1,4 +1,5 @@ import signal +import struct import pytest from unittest.mock import MagicMock, patch @@ -6,14 +7,15 @@ from environment.observation_converter import ObservationConverter from environment.socket_ipc import SocketIPC from initial_environment_config import InitialEnvironmentConfig +from screen_encoding_modes import ScreenEncodingMode @pytest.fixture def mock_initial_env(): return InitialEnvironmentConfig( - imageSizeX=1280, - imageSizeY=720, - screen_encoding_mode="rgb", + image_width=640, + image_height=360, + screen_encoding_mode=ScreenEncodingMode.RAW, eye_distance=0, ) @@ -23,11 +25,13 @@ def environment(mock_initial_env): return CraftGroundEnvironment(initial_env=mock_initial_env) -@patch("environment.craftground_environment.SocketIPC") +@patch("environment.environment.SocketIPC") def test_initialize_environment(mock_ipc_class, mock_initial_env): - mock_ipc_instance = MagicMock() + mock_ipc_instance = MagicMock(spec=SocketIPC) mock_ipc_class.return_value = mock_ipc_instance + assert mock_ipc_instance is not None + print(mock_initial_env) env = CraftGroundEnvironment(initial_env=mock_initial_env) assert env.initial_env == mock_initial_env @@ -38,34 +42,20 @@ def test_initialize_environment(mock_ipc_class, mock_initial_env): assert env.sock is None +@patch("socket.socket") @patch("subprocess.Popen") -def test_start_server(mock_popen, environment): +def test_start_server(mock_popen, mock_socket, environment): mock_process = MagicMock() mock_popen.return_value = mock_process - - environment.start_server( - port=8000, use_vglrun=False, track_native_memory=False, ld_preload=None - ) - - assert mock_popen.called - assert environment.process is not None - - -@patch("socket.socket") -def test_socket_connection(mock_socket, environment): mock_sock_instance = MagicMock() mock_socket.return_value = mock_sock_instance - mock_sock_instance.connect.return_value = True - - environment.ipc.wait_for_server = MagicMock(return_value=mock_sock_instance) - environment.start_server( port=8000, use_vglrun=False, track_native_memory=False, ld_preload=None ) - assert environment.sock is not None - assert environment.sock == mock_sock_instance + assert mock_popen.called + assert environment.process is not None @patch("builtins.open", new_callable=MagicMock) @@ -82,25 +72,7 @@ def test_update_override_resolutions(mock_open, environment): mock_file.write.assert_called() -@patch("environment.craftground_environment.BufferedSocket") -def test_read_one_observation(mock_buffered_socket, environment): - mock_buffered_socket_instance = MagicMock() - mock_buffered_socket.return_value = mock_buffered_socket_instance - - mock_buffered_socket_instance.read.side_effect = [ - struct.pack(" Date: Tue, 21 Jan 2025 16:28:13 +0900 Subject: [PATCH 25/91] =?UTF-8?q?=E2=9C=85=20Pass=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/develop.md | 7 +++++++ src/craftground/environment/boost_ipc.py | 13 ++++++++++--- src/craftground/environment/environment.py | 12 ++---------- src/craftground/environment/socket_ipc.py | 3 ++- tests/python/unit/test_environment.py | 22 +++++++++++++++++++--- 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/docs/develop.md b/docs/develop.md index 1b9e9ba7..3dadf14e 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -74,3 +74,10 @@ coverage run --source=src/craftground -m pytest tests/python/unit/ coverage report ``` +## python test +```bash +cd build +cmake .. +cmake --build . +PYTHONPATH=./build:src/craftground coverage run --source=src/craftground -m pytest tests/python/unit/ +``` \ No newline at end of file diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index 6553a92f..2477544d 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -1,12 +1,19 @@ +from environment.ipc_interface import IPCInterface +from proto.action_space_pb2 import ActionSpaceMessageV2 +from proto.initial_environment_pb2 import InitialEnvironmentMessage + +# Torch should be imported first before craftground_native to avoid segfaults +try: + import torch # noqa +except ImportError: + pass + from craftground_native import ( # noqa initialize_shared_memory, # noqa write_to_shared_memory, # noqa read_from_shared_memory, # noqa destroy_shared_memory, # noqa ) -from environment.ipc_interface import IPCInterface -from proto.action_space_pb2 import ActionSpaceMessageV2 -from proto.initial_environment_pb2 import InitialEnvironmentMessage class BoostIPC(IPCInterface): diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index e7e356c7..e9bf97c5 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -2,7 +2,6 @@ import re import shutil import signal -import struct import subprocess from enum import Enum from typing import Tuple, Optional, Union, List, Any, Dict @@ -23,7 +22,6 @@ from csv_logger import CsvLogger, LogBackend from initial_environment_config import InitialEnvironmentConfig -from proto import observation_space_pb2 class ObservationTensorType(Enum): @@ -68,8 +66,6 @@ def __init__( self.track_native_memory = track_native_memory self.ld_preload = ld_preload self.encoding_mode = initial_env.screen_encoding_mode - self.sock = None - self.buffered_socket = None self.last_rgb_frames: List[Union[np.ndarray, torch.Tensor, None]] = [None, None] self.last_images: List[Union[np.ndarray, torch.Tensor, None]] = [None, None] self.last_action = None @@ -85,6 +81,7 @@ def __init__( self.render_alternating_eyes_counter = 0 self.find_free_port = find_free_port self.queued_commands = [] + self.process = None self.use_shared_memory = use_shared_memory if env_path is None: @@ -171,7 +168,7 @@ def is_alive(self) -> bool: def ensure_alive(self, fast_reset, extra_commands, seed): if self.is_alive: if fast_reset: - self.ipc.send_fastreset2(self.sock, extra_commands) + self.ipc.send_fastreset2(extra_commands) return else: self.terminate() @@ -316,11 +313,6 @@ def add_commands(self, commands: List[str]): def terminate(self): self.ipc.destroy() - if self.sock is not None: - self.ipc.send_exit(self.sock) - self.sock.close() - self.sock = None - print("Terminated the java process") pid = self.process.pid if self.process else None # wait for the pid to exit try: diff --git a/src/craftground/environment/socket_ipc.py b/src/craftground/environment/socket_ipc.py index eb8bd7f3..b0d7829a 100644 --- a/src/craftground/environment/socket_ipc.py +++ b/src/craftground/environment/socket_ipc.py @@ -1,7 +1,7 @@ import os import signal import struct -from time import sleep, time +from time import time from typing import Dict, List, Optional, Union import psutil @@ -96,6 +96,7 @@ def is_alive(self) -> bool: def destroy(self): if self.sock: + self.send_exit(self.sock) self.sock.close() self.sock = None diff --git a/tests/python/unit/test_environment.py b/tests/python/unit/test_environment.py index f8b7b788..0fefebec 100644 --- a/tests/python/unit/test_environment.py +++ b/tests/python/unit/test_environment.py @@ -1,13 +1,14 @@ import signal -import struct import pytest from unittest.mock import MagicMock, patch + from environment.environment import CraftGroundEnvironment from environment.observation_converter import ObservationConverter from environment.socket_ipc import SocketIPC from initial_environment_config import InitialEnvironmentConfig from screen_encoding_modes import ScreenEncodingMode +from environment.boost_ipc import BoostIPC @pytest.fixture @@ -32,14 +33,29 @@ def test_initialize_environment(mock_ipc_class, mock_initial_env): assert mock_ipc_instance is not None print(mock_initial_env) - env = CraftGroundEnvironment(initial_env=mock_initial_env) + env = CraftGroundEnvironment(initial_env=mock_initial_env, use_shared_memory=False) assert env.initial_env == mock_initial_env assert env.action_space is not None assert env.observation_space is not None assert isinstance(env.observation_converter, ObservationConverter) assert isinstance(env.ipc, SocketIPC) - assert env.sock is None + + +@patch("environment.boost_ipc.BoostIPC") +def test_initialize_environment(mock_ipc_class, mock_initial_env): + mock_ipc_instance = MagicMock(spec=BoostIPC) + mock_ipc_class.return_value = mock_ipc_instance + + assert mock_ipc_instance is not None + print(mock_initial_env) + env = CraftGroundEnvironment(initial_env=mock_initial_env, use_shared_memory=True) + + assert env.initial_env == mock_initial_env + assert env.action_space is not None + assert env.observation_space is not None + assert isinstance(env.observation_converter, ObservationConverter) + assert isinstance(env.ipc, BoostIPC) @patch("socket.socket") From 8b0d83c52fa461358f22fa2cc6b806bd1af94a2a Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 16:29:23 +0900 Subject: [PATCH 26/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20observation=5Fconver?= =?UTF-8?q?ter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/observation_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/craftground/environment/observation_converter.py b/src/craftground/environment/observation_converter.py index 162edade..44a809f6 100644 --- a/src/craftground/environment/observation_converter.py +++ b/src/craftground/environment/observation_converter.py @@ -274,7 +274,7 @@ def convert_jax_observation(self, ipc_handle: bytes) -> "JaxArrayType": ) if not dlpack_capsule: raise ValueError(f"Failed to initialize from mach port {ipc_handle}.") - jax_image = jnp.from_dlpack(cuda_dlpack) + jax_image = jnp.from_dlpack(dlpack_capsule) # image_tensor = torch.utils.dlpack.from_dlpack(apple_dl_tensor) rgb_array_or_tensor = jax_image print(rgb_array_or_tensor.shape) From 145a7df70bdeb62b6b8e8647178cfeff770c1f5a Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 16:32:19 +0900 Subject: [PATCH 27/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20python=20ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/python-ci.yml | 4 +++- docs/develop.md | 7 +------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index b2442ad2..e247c2f8 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -38,7 +38,9 @@ jobs: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | - coverage run --source=src/craftground -m pytest tests/python/unit/ + # build cpp extension first + mkdir build && cd build && cmake .. && cmake --build . && cd .. + PYTHONPATH=./build:src/craftground coverage run --source=src/craftground -m pytest tests/python/unit/ - name: coverage run: | diff --git a/docs/develop.md b/docs/develop.md index 3dadf14e..034ed68d 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -70,14 +70,9 @@ cd src/craftground/MinecraftEnv ## Python unit test with coverage ```bash python -m pip install coverage pytest -coverage run --source=src/craftground -m pytest tests/python/unit/ -coverage report -``` - -## python test -```bash cd build cmake .. cmake --build . PYTHONPATH=./build:src/craftground coverage run --source=src/craftground -m pytest tests/python/unit/ +coverage report ``` \ No newline at end of file From 09c0ef58bc845a7dcc41610abfbaaef461e51331 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 18:49:56 +0900 Subject: [PATCH 28/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20ci=20torch=20depende?= =?UTF-8?q?ncies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/python-ci.yml | 2 +- pyproject.toml | 106 ++++++++++++--------- src/craftground/environment/environment.py | 10 +- 3 files changed, 68 insertions(+), 50 deletions(-) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index e247c2f8..dcc65f67 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -28,7 +28,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install flake8 pytest flake8-pyproject coverage + python -m pip install .[test] if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | diff --git a/pyproject.toml b/pyproject.toml index 50e44e3f..87a7e391 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,38 +1,55 @@ [build-system] +build-backend = "scikit_build_core.build" requires = [ - "scikit-build-core", - "pybind11", - "setuptools>=42", - "wheel", - "cmake>=3.12", - "ninja", # Optional, for Ninja builds + "scikit-build-core", + "pybind11", + "setuptools>=42", + "wheel", + "cmake>=3.12", + "ninja", # Optional, for Ninja builds ] -build-backend = "scikit_build_core.build" [project] -name = "craftground" -version = "2.6.1" -description = "Lightweight Minecraft Environment for Reinforcement Learning" -readme = "README.md" -license = { file = "LICENSE" } authors = [ - { name = "yhs0602", email = "jourhyang123@gmail.com" } + {name = "yhs0602", email = "jourhyang123@gmail.com"}, ] -keywords = ["minecraft", "reinforcement learning", "environment"] classifiers = [ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", ] +description = "Lightweight Minecraft Environment for Reinforcement Learning" +keywords = ["minecraft", "reinforcement learning", "environment"] +license = {file = "LICENSE"} +name = "craftground" +readme = "README.md" +version = "2.6.1" dependencies = [ - "gymnasium", - "Pillow", - "numpy", - "protobuf>=5.29.1", - "typing_extensions", - "psutil", - "torch", + "gymnasium", + "Pillow", + "numpy", + "protobuf>=5.29.1", + "typing_extensions", + "psutil", + "torch", +] + +[project.optional-dependencies] +test = [ + "flake8", + "pytest", + "flake8-pyproject", + "coverage", +] + +jax_cuda = [ + "jax[cuda]", +] + +jax_metal = [ + "jax", + "jax-metal", ] [project.urls] @@ -42,12 +59,10 @@ Tracker = "https://github.com/yhs0602/CraftGround/issues" [tool.cibuildwheel.macos] repair-wheel-command = "" # Disable repair wheel command on macOS - [tool.scikit-build] wheel.expand-macos-universal-tags = true [tool.black] -force-exclude = 'pybind11' extend-exclude = ''' /( \.git @@ -60,34 +75,35 @@ extend-exclude = ''' | src/craftground/MinecraftEnv )/ ''' +force-exclude = 'pybind11' line-length = 88 [tool.flake8] -max-line-length = 120 exclude = [ - ".git", - "__pycache__", - "venv", - "src/cpp", - "build", - "dist", - "pybind11", - "src/craftground/MinecraftEnv", - "src/craftground/proto", + ".git", + "__pycache__", + "venv", + "src/cpp", + "build", + "dist", + "pybind11", + "src/craftground/MinecraftEnv", + "src/craftground/proto", ] +max-line-length = 120 # Use extend-ignore to add to already ignored checks which are anti-patterns like W503. extend-ignore = [ - # PEP 8 recommends to treat : in slices as a binary operator with the lowest priority, and to leave an equal - # amount of space on either side, except if a parameter is omitted (e.g. ham[1 + 1 :]). - # This behaviour may raise E203 whitespace before ':' warnings in style guide enforcement tools like Flake8. - # Since E203 is not PEP 8 compliant, we tell Flake8 to ignore this warning. - # https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#slices - "E203" + # PEP 8 recommends to treat : in slices as a binary operator with the lowest priority, and to leave an equal + # amount of space on either side, except if a parameter is omitted (e.g. ham[1 + 1 :]). + # This behaviour may raise E203 whitespace before ':' warnings in style guide enforcement tools like Flake8. + # Since E203 is not PEP 8 compliant, we tell Flake8 to ignore this warning. + # https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#slices + "E203", ] [tool.pytest.ini_options] -minversion = "6.0" addopts = "-ra -q" +minversion = "6.0" testpaths = [ - "tests", -] \ No newline at end of file + "tests", +] diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index e9bf97c5..116a3d8a 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -9,7 +9,6 @@ import gymnasium as gym import numpy as np from gymnasium.core import ActType, ObsType, RenderFrame -import torch from environment.action_space import ( ActionSpaceVersion, @@ -66,8 +65,11 @@ def __init__( self.track_native_memory = track_native_memory self.ld_preload = ld_preload self.encoding_mode = initial_env.screen_encoding_mode - self.last_rgb_frames: List[Union[np.ndarray, torch.Tensor, None]] = [None, None] - self.last_images: List[Union[np.ndarray, torch.Tensor, None]] = [None, None] + self.last_rgb_frames: List[Union[np.ndarray, "torch.Tensor", None]] = [ + None, + None, + ] + self.last_images: List[Union[np.ndarray, "torch.Tensor", None]] = [None, None] self.last_action = None self.render_action = render_action @@ -145,7 +147,7 @@ def convert_observation_v2(self, res): self.queued_commands = [] res.yaw = ((res.yaw + 180) % 360) - 180 - final_obs: Dict[str, Union[np.ndarray, torch.Tensor, Any]] = { + final_obs: Dict[str, Union[np.ndarray, "torch.Tensor"]] = { "full": res, "pov": rgb_1, } From c7ffda90b2bd8f4646c2375495d37f23bd9ff94b Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 19:01:50 +0900 Subject: [PATCH 29/91] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/environment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 116a3d8a..1767ff47 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -4,7 +4,7 @@ import signal import subprocess from enum import Enum -from typing import Tuple, Optional, Union, List, Any, Dict +from typing import TYPE_CHECKING, Tuple, Optional, Union, List, Any, Dict import gymnasium as gym import numpy as np @@ -21,6 +21,7 @@ from csv_logger import CsvLogger, LogBackend from initial_environment_config import InitialEnvironmentConfig +import torch class ObservationTensorType(Enum): From bebfe820104f5555bbde617cc2c674c697ca8fd3 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 20:52:09 +0900 Subject: [PATCH 30/91] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- src/craftground/environment/environment.py | 46 ++++++++++++++++------ src/craftground/environment/socket_ipc.py | 2 +- tests/python/unit/test_environment.py | 9 +++-- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 87a7e391..7ba07a24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "gymnasium", "Pillow", "numpy", - "protobuf>=5.29.1", + "protobuf>=5.29.3", "typing_extensions", "psutil", "torch", diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 1767ff47..2d70cc5e 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -4,7 +4,7 @@ import signal import subprocess from enum import Enum -from typing import TYPE_CHECKING, Tuple, Optional, Union, List, Any, Dict +from typing import Tuple, Optional, Union, List, Any, Dict import gymnasium as gym import numpy as np @@ -66,11 +66,7 @@ def __init__( self.track_native_memory = track_native_memory self.ld_preload = ld_preload self.encoding_mode = initial_env.screen_encoding_mode - self.last_rgb_frames: List[Union[np.ndarray, "torch.Tensor", None]] = [ - None, - None, - ] - self.last_images: List[Union[np.ndarray, "torch.Tensor", None]] = [None, None] + self.last_action = None self.render_action = render_action @@ -109,7 +105,11 @@ def __init__( if self.use_shared_memory: from .boost_ipc import BoostIPC # type: ignore - self.ipc = BoostIPC(str(port), initial_env) + self.ipc = BoostIPC( + str(port), + find_free_port, + self.initial_env.to_initial_environment_message(), + ) else: self.ipc = SocketIPC( self.logger, @@ -168,18 +168,34 @@ def is_alive(self) -> bool: # (alive and fast_reset) -> send fast reset # (alive and not fast_reset) -> destroy and start server # (not alive) -> start server - def ensure_alive(self, fast_reset, extra_commands, seed): + def ensure_alive(self, fast_reset, extra_commands, seed: int): if self.is_alive: if fast_reset: self.ipc.send_fastreset2(extra_commands) return else: self.terminate() + if self.use_shared_memory: + from .boost_ipc import BoostIPC # type: ignore + + self.ipc = BoostIPC( + str(port), + find_free_port, + self.initial_env.to_initial_environment_message(), + ) + else: + self.ipc = SocketIPC( + self.logger, + self.initial_env.to_initial_environment_message(), + port, + find_free_port, + ) self.start_server( port=self.ipc.port, use_vglrun=self.use_vglrun, track_native_memory=self.track_native_memory, ld_preload=self.ld_preload, + seed=seed, ) def start_server( @@ -224,6 +240,7 @@ def start_server( shell=True, stdout=subprocess.DEVNULL if not self.verbose_gradle else None, env=my_env, + preexec_fn=os.setsid, # To kill the process and its children properly ) self.ipc.start_communication() @@ -320,13 +337,18 @@ def terminate(self): # wait for the pid to exit try: if pid: - os.kill(pid, signal.SIGKILL) + pgrp = os.getpgid(pid) + os.killpg(pgrp, signal.SIGKILL) _, exit_status = os.waitpid(pid, 0) + self.logger.log( + f"Terminated the java process with exit status {exit_status}" + ) else: - print("No pid to wait for") + self.logger.log("No process to terminate") except ChildProcessError: - print("Child process already terminated") - print("Terminated the java process") + self.logger.log("Child process already terminated") + self.process = None + self.logger.log("Terminated the java process") @staticmethod def get_env_base_path() -> str: diff --git a/src/craftground/environment/socket_ipc.py b/src/craftground/environment/socket_ipc.py index b0d7829a..c88dc1e7 100644 --- a/src/craftground/environment/socket_ipc.py +++ b/src/craftground/environment/socket_ipc.py @@ -21,7 +21,7 @@ def __init__( logger: CsvLogger, initial_environment: InitialEnvironmentMessage, port: int, - find_free_port: bool = False, + find_free_port: bool = True, ): self.logger = logger self.find_free_port = find_free_port diff --git a/tests/python/unit/test_environment.py b/tests/python/unit/test_environment.py index 0fefebec..b159629c 100644 --- a/tests/python/unit/test_environment.py +++ b/tests/python/unit/test_environment.py @@ -117,11 +117,14 @@ def test_close_environment(mock_close, environment): mock_close.assert_called() -@patch("os.kill") -def test_terminate_environment(mock_kill, environment): +@patch("os.getpgid") +@patch("os.killpg") +def test_terminate_environment(mock_kill, mock_getpgid, environment): environment.process = MagicMock() environment.process.pid = 1234 + mock_getpgid.return_value = -1234 environment.terminate() - mock_kill.assert_called_with(1234, signal.SIGKILL) + mock_getpgid.assert_called_with(1234) + mock_kill.assert_called_with(-1234, signal.SIGKILL) From f54d19d934a4b5f9539b6c0b55da116261aa37f3 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 21:42:38 +0900 Subject: [PATCH 31/91] =?UTF-8?q?=E2=9C=85=20Pass=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/environment.py | 46 +++--- tests/python/unit/test_environment.py | 4 +- tests/python/unit/test_socket_ipc.py | 174 +++++++++++++++++++++ 3 files changed, 194 insertions(+), 30 deletions(-) create mode 100644 tests/python/unit/test_socket_ipc.py diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 2d70cc5e..22d68a44 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -59,6 +59,8 @@ def __init__( initial_env.imageSizeX, initial_env.imageSizeY ) self.initial_env = initial_env + self.initial_env_message = initial_env.to_initial_environment_message() + self.use_terminate = use_terminate self.cleanup_world = cleanup_world self.use_vglrun = use_vglrun @@ -108,12 +110,12 @@ def __init__( self.ipc = BoostIPC( str(port), find_free_port, - self.initial_env.to_initial_environment_message(), + self.initial_env_message, ) else: self.ipc = SocketIPC( self.logger, - self.initial_env.to_initial_environment_message(), + self.initial_env_message, port, find_free_port, ) @@ -175,43 +177,33 @@ def ensure_alive(self, fast_reset, extra_commands, seed: int): return else: self.terminate() + if self.use_shared_memory: from .boost_ipc import BoostIPC # type: ignore self.ipc = BoostIPC( - str(port), - find_free_port, - self.initial_env.to_initial_environment_message(), + str(self.ipc.port), + self.ipc.find_free_port, + self.initial_env_message, ) else: self.ipc = SocketIPC( self.logger, - self.initial_env.to_initial_environment_message(), - port, - find_free_port, + self.initial_env_message, + self.ipc.port, + self.ipc.find_free_port, ) - self.start_server( - port=self.ipc.port, - use_vglrun=self.use_vglrun, - track_native_memory=self.track_native_memory, - ld_preload=self.ld_preload, - seed=seed, - ) - def start_server( - self, - port: int, - use_vglrun: bool, - track_native_memory: bool, - ld_preload: Optional[str], - ): + self.start_server(seed=seed) + + def start_server(self, seed: int): # Remove orphan java processes self.ipc.remove_orphan_java_processes() # Prepare command TODO my_env = os.environ.copy() - my_env["PORT"] = str(port) + my_env["PORT"] = str(self.ipc.port) my_env["VERBOSE"] = str(int(self.verbose_jvm)) - if track_native_memory: + if self.track_native_memory: my_env["CRAFTGROUND_JVM_NATIVE_TRACKING"] = "detail" if self.native_debug: my_env["CRAFGROUND_NATIVE_DEBUG"] = "True" @@ -227,10 +219,10 @@ def start_server( # self.update_override_resolutions(options_txt_path) cmd = f"./gradlew runClient -w --no-daemon" # --args="--width {self.initial_env.imageSizeX} --height {self.initial_env.imageSizeY}"' - if use_vglrun: + if self.use_vglrun: cmd = f"vglrun {cmd}" - if ld_preload: - my_env["LD_PRELOAD"] = ld_preload + if self.ld_preload: + my_env["LD_PRELOAD"] = self.ld_preload self.logger.log(f"Starting server with command: {cmd}") # Launch the server diff --git a/tests/python/unit/test_environment.py b/tests/python/unit/test_environment.py index b159629c..7d2bb484 100644 --- a/tests/python/unit/test_environment.py +++ b/tests/python/unit/test_environment.py @@ -66,9 +66,7 @@ def test_start_server(mock_popen, mock_socket, environment): mock_sock_instance = MagicMock() mock_socket.return_value = mock_sock_instance - environment.start_server( - port=8000, use_vglrun=False, track_native_memory=False, ld_preload=None - ) + environment.start_server(seed=1234) assert mock_popen.called assert environment.process is not None diff --git a/tests/python/unit/test_socket_ipc.py b/tests/python/unit/test_socket_ipc.py new file mode 100644 index 00000000..eaffdabc --- /dev/null +++ b/tests/python/unit/test_socket_ipc.py @@ -0,0 +1,174 @@ +import signal +import pytest +from unittest.mock import MagicMock, patch +import struct + +from buffered_socket import BufferedSocket +from csv_logger import CsvLogger +from environment.socket_ipc import SocketIPC +from proto.action_space_pb2 import ActionSpaceMessageV2 +from proto.initial_environment_pb2 import InitialEnvironmentMessage +from proto.observation_space_pb2 import ObservationSpaceMessage + + +@pytest.fixture +def mock_logger(): + return MagicMock(spec=CsvLogger) + + +@pytest.fixture +def mock_initial_environment(): + return MagicMock(spec=InitialEnvironmentMessage) + + +@pytest.fixture +def socket_ipc(mock_logger, mock_initial_environment): + return SocketIPC( + logger=mock_logger, initial_environment=mock_initial_environment, port=8000 + ) + + +@patch("os.path.exists") +@patch("os.name", "posix") +def test_check_port_unix(mock_exists, socket_ipc): + mock_exists.side_effect = lambda path: path == "/tmp/minecraftrl_8000.sock" + + socket_ipc.find_free_port = False + with pytest.raises(FileExistsError): + socket_ipc.check_port(8000) + + socket_ipc.find_free_port = True + port = socket_ipc.check_port(8000) + assert port > 8000 + + +@patch("socket.socket") +def test_send_initial_environment(mock_socket, socket_ipc, mock_initial_environment): + mock_sock = MagicMock() + mock_socket.return_value = mock_sock + socket_ipc.sock = mock_sock + + socket_ipc._send_initial_environment(mock_initial_environment) + + mock_sock.send.assert_called() + mock_sock.sendall.assert_called() + + +@patch("socket.socket") +def test_send_action(mock_socket, socket_ipc): + mock_sock = MagicMock() + mock_socket.return_value = mock_sock + socket_ipc.sock = mock_sock + + action = MagicMock(spec=ActionSpaceMessageV2) + action.commands = [] + socket_ipc.send_action(action, ["command1", "command2"]) + + assert "command1" in action.commands + assert "command2" in action.commands + mock_sock.send.assert_called() + mock_sock.sendall.assert_called() + + +@patch("socket.socket") +def test_read_observation(mock_socket, socket_ipc): + mock_sock = MagicMock() + mock_socket.return_value = mock_sock + socket_ipc.buffered_socket = MagicMock(spec=BufferedSocket) + + example_observation = ObservationSpaceMessage().SerializeToString() + socket_ipc.buffered_socket.read.side_effect = [ + struct.pack(" Date: Tue, 21 Jan 2025 21:45:42 +0900 Subject: [PATCH 32/91] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20flake8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/ipc_interface.py | 2 -- tests/python/unit/test_addition.py | 5 ----- tests/python/unit/test_environment.py | 4 ++-- 3 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 tests/python/unit/test_addition.py diff --git a/src/craftground/environment/ipc_interface.py b/src/craftground/environment/ipc_interface.py index 1dfa2861..957c3da2 100644 --- a/src/craftground/environment/ipc_interface.py +++ b/src/craftground/environment/ipc_interface.py @@ -1,10 +1,8 @@ from abc import ABC, abstractmethod import os import subprocess -from time import sleep from proto.action_space_pb2 import ActionSpaceMessageV2 -from proto.initial_environment_pb2 import InitialEnvironmentMessage from proto.observation_space_pb2 import ObservationSpaceMessage diff --git a/tests/python/unit/test_addition.py b/tests/python/unit/test_addition.py deleted file mode 100644 index e58e1e4a..00000000 --- a/tests/python/unit/test_addition.py +++ /dev/null @@ -1,5 +0,0 @@ -import pytest - - -def test_some_function(): - assert 4 == 4 # 예상 결과값 검증 diff --git a/tests/python/unit/test_environment.py b/tests/python/unit/test_environment.py index 7d2bb484..d1afa2ef 100644 --- a/tests/python/unit/test_environment.py +++ b/tests/python/unit/test_environment.py @@ -27,7 +27,7 @@ def environment(mock_initial_env): @patch("environment.environment.SocketIPC") -def test_initialize_environment(mock_ipc_class, mock_initial_env): +def test_initialize_socket_environment(mock_ipc_class, mock_initial_env): mock_ipc_instance = MagicMock(spec=SocketIPC) mock_ipc_class.return_value = mock_ipc_instance @@ -43,7 +43,7 @@ def test_initialize_environment(mock_ipc_class, mock_initial_env): @patch("environment.boost_ipc.BoostIPC") -def test_initialize_environment(mock_ipc_class, mock_initial_env): +def test_initialize_boost_environment(mock_ipc_class, mock_initial_env): mock_ipc_instance = MagicMock(spec=BoostIPC) mock_ipc_class.return_value = mock_ipc_instance From fc4fd0fbd0a3fa64a3c5c18b8fb72009a854ca13 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 22:09:40 +0900 Subject: [PATCH 33/91] =?UTF-8?q?=F0=9F=93=8C=20Pin=20protobuf=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7ba07a24..cac99271 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "gymnasium", "Pillow", "numpy", - "protobuf>=5.29.3", + "protobuf==5.29.2", "typing_extensions", "psutil", "torch", From 299927ccc7976fa99ddaefc54b0e2d13cb6d9d6a Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 22:21:27 +0900 Subject: [PATCH 34/91] =?UTF-8?q?=F0=9F=94=A5=20Remove=20requirements.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 967810ce..00000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -cloudpickle==2.2.1 -Farama-Notifications==0.0.4 -gymnasium==0.29.1 -numpy==1.26.0 -Pillow==10.0.1 -protobuf==4.24.3 -typing_extensions==4.8.0 From 97eeb8b1f750468ea1473a6a48a15bc82528f7d7 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 21 Jan 2025 22:22:50 +0900 Subject: [PATCH 35/91] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Unpin=20protobuf=20v?= =?UTF-8?q?ersion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cac99271..9d463b67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "gymnasium", "Pillow", "numpy", - "protobuf==5.29.2", + "protobuf>=5.0.0", "typing_extensions", "psutil", "torch", From cac0610bcc3bf50b85c9ccea81af67f1567c8c68 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 00:36:52 +0900 Subject: [PATCH 36/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/__init__.py | 4 +++- src/craftground/environment/action_space.py | 2 +- src/craftground/environment/environment.py | 12 ++++++------ src/craftground/environment/ipc_interface.py | 4 ++-- .../environment/observation_converter.py | 10 +++++----- src/craftground/environment/socket_ipc.py | 14 +++++++------- src/craftground/initial_environment_config.py | 4 ++-- 7 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/craftground/__init__.py b/src/craftground/__init__.py index 9c3a9d61..70ffb7a5 100644 --- a/src/craftground/__init__.py +++ b/src/craftground/__init__.py @@ -1,5 +1,7 @@ from typing import Optional -from .environment import ActionSpaceVersion, CraftGroundEnvironment + +from .environment.action_space import ActionSpaceVersion +from .environment.environment import CraftGroundEnvironment from .initial_environment_config import InitialEnvironmentConfig diff --git a/src/craftground/environment/action_space.py b/src/craftground/environment/action_space.py index 97ce8e75..cc0cebdb 100644 --- a/src/craftground/environment/action_space.py +++ b/src/craftground/environment/action_space.py @@ -4,7 +4,7 @@ import gymnasium as gym import numpy as np -from proto.action_space_pb2 import ActionSpaceMessageV2 +from ..proto.action_space_pb2 import ActionSpaceMessageV2 class ActionSpaceVersion(Enum): diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 22d68a44..1b389604 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -10,17 +10,17 @@ import numpy as np from gymnasium.core import ActType, ObsType, RenderFrame -from environment.action_space import ( +from ..environment.action_space import ( ActionSpaceVersion, declare_action_space, translate_action_to_v2, ) -from environment.observation_converter import ObservationConverter -from environment.observation_space import declare_observation_space -from environment.socket_ipc import SocketIPC +from ..environment.observation_converter import ObservationConverter +from ..environment.observation_space import declare_observation_space +from ..environment.socket_ipc import SocketIPC -from csv_logger import CsvLogger, LogBackend -from initial_environment_config import InitialEnvironmentConfig +from ..csv_logger import CsvLogger, LogBackend +from ..initial_environment_config import InitialEnvironmentConfig import torch diff --git a/src/craftground/environment/ipc_interface.py b/src/craftground/environment/ipc_interface.py index 957c3da2..23907006 100644 --- a/src/craftground/environment/ipc_interface.py +++ b/src/craftground/environment/ipc_interface.py @@ -2,8 +2,8 @@ import os import subprocess -from proto.action_space_pb2 import ActionSpaceMessageV2 -from proto.observation_space_pb2 import ObservationSpaceMessage +from ..proto.action_space_pb2 import ActionSpaceMessageV2 +from ..proto.observation_space_pb2 import ObservationSpaceMessage class IPCInterface(ABC): diff --git a/src/craftground/environment/observation_converter.py b/src/craftground/environment/observation_converter.py index 44a809f6..e9c645ee 100644 --- a/src/craftground/environment/observation_converter.py +++ b/src/craftground/environment/observation_converter.py @@ -2,11 +2,11 @@ import io from typing import TYPE_CHECKING, Optional, Tuple, Union -from environment.action_space import ActionSpaceVersion -from font import get_font -from print_with_time import print_with_time -from proto.observation_space_pb2 import ObservationSpaceMessage -from screen_encoding_modes import ScreenEncodingMode +from ..environment.action_space import ActionSpaceVersion +from ..font import get_font +from ..print_with_time import print_with_time +from ..proto.observation_space_pb2 import ObservationSpaceMessage +from ..screen_encoding_modes import ScreenEncodingMode import numpy as np from PIL import Image, ImageDraw diff --git a/src/craftground/environment/socket_ipc.py b/src/craftground/environment/socket_ipc.py index c88dc1e7..fcb3915f 100644 --- a/src/craftground/environment/socket_ipc.py +++ b/src/craftground/environment/socket_ipc.py @@ -5,13 +5,13 @@ from typing import Dict, List, Optional, Union import psutil -from buffered_socket import BufferedSocket -from csv_logger import CsvLogger -from environment.action_space import action_v2_dict_to_message, no_op_v2 -from environment.ipc_interface import IPCInterface -from proto.action_space_pb2 import ActionSpaceMessageV2 -from proto.initial_environment_pb2 import InitialEnvironmentMessage -from proto.observation_space_pb2 import ObservationSpaceMessage +from ..buffered_socket import BufferedSocket +from ..csv_logger import CsvLogger +from ..environment.action_space import action_v2_dict_to_message, no_op_v2 +from ..environment.ipc_interface import IPCInterface +from ..proto.action_space_pb2 import ActionSpaceMessageV2 +from ..proto.initial_environment_pb2 import InitialEnvironmentMessage +from ..proto.observation_space_pb2 import ObservationSpaceMessage import socket diff --git a/src/craftground/initial_environment_config.py b/src/craftground/initial_environment_config.py index 86c2f2ff..9d9667fe 100644 --- a/src/craftground/initial_environment_config.py +++ b/src/craftground/initial_environment_config.py @@ -3,8 +3,8 @@ import os from typing import List, Tuple, Optional -from proto import initial_environment_pb2 -from screen_encoding_modes import ScreenEncodingMode +from .proto import initial_environment_pb2 +from .screen_encoding_modes import ScreenEncodingMode class GameMode(Enum): From fce7d54031594d6eef97204b20d1a76c7d21adfb Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 00:45:12 +0900 Subject: [PATCH 37/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20port=20not=20finding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/ipc_interface.py | 65 -------------------- src/craftground/environment/socket_ipc.py | 4 +- 2 files changed, 3 insertions(+), 66 deletions(-) diff --git a/src/craftground/environment/ipc_interface.py b/src/craftground/environment/ipc_interface.py index 23907006..5ef371f3 100644 --- a/src/craftground/environment/ipc_interface.py +++ b/src/craftground/environment/ipc_interface.py @@ -1,6 +1,4 @@ from abc import ABC, abstractmethod -import os -import subprocess from ..proto.action_space_pb2 import ActionSpaceMessageV2 from ..proto.observation_space_pb2 import ObservationSpaceMessage @@ -24,66 +22,3 @@ def is_alive(self) -> bool: @abstractmethod def destroy(self): pass - - def ensure_alive(self, fast_reset, extra_commands, seed): - if not self.is_alive(): # first time - self.start_server( - port=self.port, - use_vglrun=self.use_vglrun, - track_native_memory=self.track_native_memory, - ld_preload=self.ld_preload, - seed=seed, - ) - elif not fast_reset: - self.destroy() - self.start_server( - port=self.port, - use_vglrun=self.use_vglrun, - track_native_memory=self.track_native_memory, - ld_preload=self.ld_preload, - ) - else: - with self.csv_logger.profile("fast_reset"): - self.send_fastreset2(self.sock, extra_commands) - self.csv_logger.log("Sent fast reset") - - def start_server( - self, - env_path, - track_native_memory: bool, - verbose_jvm: bool, - use_vglrun, - ld_preload, - options_txt_path, - verbose_gradle: bool = False, - ): - my_env = os.environ.copy() - my_env["PORT"] = str(self.port) - my_env["VERBOSE"] = str(int(verbose_jvm)) - if track_native_memory: - my_env["CRAFTGROUND_JVM_NATIVE_TRACKING"] = "detail" - if self.native_debug: - my_env["CRAFGROUND_NATIVE_DEBUG"] = "True" - # configure permission of the gradlew - gradlew_path = os.path.join(env_path, "gradlew") - if not os.access(gradlew_path, os.X_OK): - os.chmod(gradlew_path, 0o755) - # update image settings of options.txt if exists - if options_txt_path is not None: - if os.path.exists(options_txt_path): - pass - # self.update_override_resolutions(options_txt_path) - - cmd = f"./gradlew runClient -w --no-daemon" # --args="--width {self.initial_env.imageSizeX} --height {self.initial_env.imageSizeY}"' - if use_vglrun: - cmd = f"vglrun {cmd}" - if ld_preload: - my_env["LD_PRELOAD"] = ld_preload - print(f"{cmd=}") - self.process = subprocess.Popen( - cmd, - cwd=self.env_path, - shell=True, - stdout=subprocess.DEVNULL if not verbose_gradle else None, - env=my_env, - ) diff --git a/src/craftground/environment/socket_ipc.py b/src/craftground/environment/socket_ipc.py index fcb3915f..94aeadeb 100644 --- a/src/craftground/environment/socket_ipc.py +++ b/src/craftground/environment/socket_ipc.py @@ -1,7 +1,7 @@ import os import signal import struct -from time import time +import time from typing import Dict, List, Optional, Union import psutil @@ -62,6 +62,8 @@ def check_port(self, port: int) -> int: raise FileExistsError( f"Socket file {socket_path} already exists. Please choose another port." ) + else: + return port def _send_initial_environment(self, initial_env: InitialEnvironmentMessage): v = initial_env.SerializeToString() From ff32d8b98c764ffb712125fb0473847c343da368 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 00:52:14 +0900 Subject: [PATCH 38/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20critical=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/csv_logger.py | 10 +++++----- src/craftground/environment/environment.py | 1 + src/craftground/environment/observation_converter.py | 8 ++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/craftground/csv_logger.py b/src/craftground/csv_logger.py index a89294c3..9b46f12c 100644 --- a/src/craftground/csv_logger.py +++ b/src/craftground/csv_logger.py @@ -15,9 +15,9 @@ class CsvLogger: def __init__( self, filename, profile: bool = False, backend: LogBackend = LogBackend.NONE ): - self.profile = profile + self.do_profile = profile self.backend = backend - if profile and backend not in [LogBackend.CSV, LogBackend.BOTH]: + if self.do_profile and backend not in [LogBackend.CSV, LogBackend.BOTH]: raise ValueError("Cannot enable profiling without a csv backend") if backend in [LogBackend.CSV, LogBackend.BOTH]: self.filename = filename @@ -35,7 +35,7 @@ def log(self, message): print(f"{time_str}: {message}") def profile_start(self, tag): - if not self.profile: + if not self.do_profile: return time_str = datetime.now().strftime("%H:%M:%S.%f") if self.backend in [LogBackend.CSV, LogBackend.BOTH]: @@ -45,7 +45,7 @@ def profile_start(self, tag): print(f"{time_str}: start: {tag}") def profile_end(self, tag): - if not self.profile: + if not self.do_profile: return time_str = datetime.now().strftime("%H:%M:%S.%f") if self.backend in [LogBackend.CONSOLE, LogBackend.BOTH]: @@ -61,7 +61,7 @@ def close(self): @contextmanager def profile(self, tag): """Context manager for profiling.""" - if not self.profile: + if not self.do_profile: yield return diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 1b389604..33e0fd99 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -166,6 +166,7 @@ def is_alive(self) -> bool: if exit_code is not None: self.logger.log(f"Java process exited with code {exit_code}") return False + return True # (alive and fast_reset) -> send fast reset # (alive and not fast_reset) -> destroy and start server diff --git a/src/craftground/environment/observation_converter.py b/src/craftground/environment/observation_converter.py index e9c645ee..4213515a 100644 --- a/src/craftground/environment/observation_converter.py +++ b/src/craftground/environment/observation_converter.py @@ -166,7 +166,7 @@ def render(self): if last_image is None: # it is inevitable to convert the tensor to numpy array last_image = Image.fromarray(last_rgb_frame) - with self.csv_logger.profile("render_action"): + with self.logger.profile("render_action"): draw = ImageDraw.Draw(last_image) if self.action_space_version == ActionSpaceVersion.V1_MINEDOJO: text = self.action_to_symbol(self.last_action) @@ -190,14 +190,14 @@ def convert_png_observation( ) -> Tuple[np.ndarray, Image.Image]: # decode png byte array to numpy array # Create a BytesIO object from the byte array - with self.csv_logger.profile("convert_observation/decode_png"): + with self.logger.profile("convert_observation/decode_png"): bytes_io = io.BytesIO(image_bytes) # Use PIL to open the image from the BytesIO object img = Image.open(bytes_io).convert("RGB") # Flip y axis img = img.transpose(Image.FLIP_TOP_BOTTOM) - with self.csv_logger.profile("convert_observation/convert_to_numpy"): + with self.logger.profile("convert_observation/convert_to_numpy"): # Convert the PIL image to a numpy array last_rgb_frame = np.array(img) arr = np.transpose(last_rgb_frame, (2, 1, 0)) @@ -207,7 +207,7 @@ def convert_png_observation( def convert_raw_observation(self, image_bytes: bytes) -> np.ndarray: # decode raw byte array to numpy array - with self.csv_logger.profile("convert_observation/decode_raw"): + with self.logger.profile("convert_observation/decode_raw"): last_rgb_frame = np.frombuffer(image_bytes, dtype=np.uint8).reshape( (self.initial_env.imageSizeY, self.initial_env.imageSizeX, 3) ) From 4920519ff54ffa62fe2309a0b5965b9d57c4e768 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 01:00:07 +0900 Subject: [PATCH 39/91] =?UTF-8?q?=F0=9F=90=9B=20Bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/environment.py | 1 + src/craftground/environment/observation_converter.py | 4 ++++ src/craftground/environment/socket_ipc.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 33e0fd99..cb71d3b3 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -122,6 +122,7 @@ def __init__( self.observation_converter = ObservationConverter( self.initial_env.screen_encoding_mode, + self.logger, self.initial_env.eye_distance > 0, self.render_action, ) diff --git a/src/craftground/environment/observation_converter.py b/src/craftground/environment/observation_converter.py index 4213515a..d5061e3c 100644 --- a/src/craftground/environment/observation_converter.py +++ b/src/craftground/environment/observation_converter.py @@ -2,6 +2,8 @@ import io from typing import TYPE_CHECKING, Optional, Tuple, Union +from ..csv_logger import CsvLogger + from ..environment.action_space import ActionSpaceVersion from ..font import get_font from ..print_with_time import print_with_time @@ -45,11 +47,13 @@ class ObservationConverter: def __init__( self, output_type: ScreenEncodingMode, + logger: CsvLogger = None, is_binocular: bool = False, render_action: bool = False, ) -> None: self.output_type = output_type self.internal_type = ObservationTensorType.NONE + self.logger = logger self.last_observations = [None, None] self.last_images = [None, None] self.is_binocular = is_binocular diff --git a/src/craftground/environment/socket_ipc.py b/src/craftground/environment/socket_ipc.py index 94aeadeb..4a4822d5 100644 --- a/src/craftground/environment/socket_ipc.py +++ b/src/craftground/environment/socket_ipc.py @@ -98,7 +98,7 @@ def is_alive(self) -> bool: def destroy(self): if self.sock: - self.send_exit(self.sock) + self.send_exit() self.sock.close() self.sock = None From cfe6aed0e4b5a676cfda9731d0ed487eb50ff067 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 01:08:42 +0900 Subject: [PATCH 40/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/environment.py | 3 +++ .../environment/observation_converter.py | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index cb71d3b3..2874e8c2 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -122,6 +122,8 @@ def __init__( self.observation_converter = ObservationConverter( self.initial_env.screen_encoding_mode, + self.initial_env.imageSizeX, + self.initial_env.imageSizeY, self.logger, self.initial_env.eye_distance > 0, self.render_action, @@ -333,6 +335,7 @@ def terminate(self): if pid: pgrp = os.getpgid(pid) os.killpg(pgrp, signal.SIGKILL) + self.logger.log(f"Sent SIGKILL to process group {pgrp}(pid {pid})") _, exit_status = os.waitpid(pid, 0) self.logger.log( f"Terminated the java process with exit status {exit_status}" diff --git a/src/craftground/environment/observation_converter.py b/src/craftground/environment/observation_converter.py index d5061e3c..ec39c376 100644 --- a/src/craftground/environment/observation_converter.py +++ b/src/craftground/environment/observation_converter.py @@ -47,12 +47,17 @@ class ObservationConverter: def __init__( self, output_type: ScreenEncodingMode, - logger: CsvLogger = None, + image_width: int, + image_height: int, + logger: CsvLogger, is_binocular: bool = False, render_action: bool = False, ) -> None: self.output_type = output_type self.internal_type = ObservationTensorType.NONE + self.image_width = image_width + self.image_height = image_height + self.logger = logger self.last_observations = [None, None] self.last_images = [None, None] @@ -213,7 +218,7 @@ def convert_raw_observation(self, image_bytes: bytes) -> np.ndarray: # decode raw byte array to numpy array with self.logger.profile("convert_observation/decode_raw"): last_rgb_frame = np.frombuffer(image_bytes, dtype=np.uint8).reshape( - (self.initial_env.imageSizeY, self.initial_env.imageSizeX, 3) + (self.image_height, self.image_width, 3) ) # Flip y axis using np last_rgb_frame = np.flip(last_rgb_frame, axis=0) @@ -232,7 +237,7 @@ def initialize_zerocopy(self, ipc_handle: bytes): mach_port = int.from_bytes(ipc_handle, byteorder="little", signed=False) print_with_time(f"{mach_port=}") apple_dl_tensor = initialize_from_mach_port( - mach_port, self.initial_env.imageSizeX, self.initial_env.imageSizeY + mach_port, self.image_width, self.image_height ) if apple_dl_tensor is None: raise ValueError(f"Failed to initialize from mach port {mach_port}.") @@ -249,8 +254,8 @@ def initialize_zerocopy(self, ipc_handle: bytes): cuda_dl_tensor = mtl_tensor_from_cuda_mem_handle( ipc_handle, - self.initial_env.imageSizeX, - self.initial_env.imageSizeY, + self.image_width, + self.image_height, ) if not cuda_dl_tensor: raise ValueError("Invalid DLPack capsule: None") @@ -274,7 +279,7 @@ def convert_jax_observation(self, ipc_handle: bytes) -> "JaxArrayType": mach_port = int.from_bytes(ipc_handle, byteorder="little", signed=False) print_with_time(f"{mach_port=}") dlpack_capsule = mtl_dlpack_from_mach_port( - mach_port, self.initial_env.imageSizeX, self.initial_env.imageSizeY + mach_port, self.image_width, self.image_height ) if not dlpack_capsule: raise ValueError(f"Failed to initialize from mach port {ipc_handle}.") @@ -292,8 +297,8 @@ def convert_jax_observation(self, ipc_handle: bytes) -> "JaxArrayType": else: cuda_dlpack = mtl_tensor_from_cuda_mem_handle( ipc_handle, - self.initial_env.imageSizeX, - self.initial_env.imageSizeY, + self.image_width, + self.image_height, ) if not cuda_dlpack: raise ValueError("Invalid DLPack capsule: None") From a96f703c97ca1775980b97737f11d2c4e6e782e0 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 01:11:46 +0900 Subject: [PATCH 41/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20bgs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/environment.py | 5 ++++- src/craftground/wrappers/vision.py | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 2874e8c2..effe794c 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -12,6 +12,7 @@ from ..environment.action_space import ( ActionSpaceVersion, + action_v2_dict_to_message, declare_action_space, translate_action_to_v2, ) @@ -282,7 +283,9 @@ def step(self, action: ActType) -> Tuple[ObsType, float, bool, bool, dict]: else: translated_action = action - self.ipc.send_action(translated_action, self.queued_commands) + self.ipc.send_action( + action_v2_dict_to_message(translated_action), self.queued_commands + ) self.queued_commands.clear() # read the response self.logger.log("Sent action and reading response...") diff --git a/src/craftground/wrappers/vision.py b/src/craftground/wrappers/vision.py index 33fb6085..66e553f6 100644 --- a/src/craftground/wrappers/vision.py +++ b/src/craftground/wrappers/vision.py @@ -21,7 +21,7 @@ def step( self, action: WrapperActType ) -> tuple[WrapperObsType, SupportsFloat, bool, bool, dict[str, Any]]: obs, reward, terminated, truncated, info = self.env.step(action) - rgb = info["rgb"] + rgb = info["pov"] return ( rgb, reward, @@ -37,5 +37,5 @@ def reset( options: Optional[dict[str, Any]] = None, ): obs, info = self.env.reset(seed=seed, options=options) - rgb = info["rgb"] + rgb = info["pov"] return rgb, info From d4409daadea8f20a6985456b9e0263e0771377c5 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 01:16:11 +0900 Subject: [PATCH 42/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20shmem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/__init__.py | 2 ++ src/craftground/environment/boost_ipc.py | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/craftground/__init__.py b/src/craftground/__init__.py index 70ffb7a5..1a12cccf 100644 --- a/src/craftground/__init__.py +++ b/src/craftground/__init__.py @@ -10,6 +10,7 @@ def make( verbose=False, env_path=None, port=8000, + use_shared_memory=False, action_space_version=ActionSpaceVersion.V1_MINEDOJO, render_action=False, render_alternating_eyes=False, @@ -42,4 +43,5 @@ def make( verbose_python=verbose_python, verbose_jvm=verbose_jvm, verbose_gradle=verbose_gradle, + use_shared_memory=use_shared_memory, ) diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index 2477544d..93c45957 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -1,6 +1,6 @@ -from environment.ipc_interface import IPCInterface -from proto.action_space_pb2 import ActionSpaceMessageV2 -from proto.initial_environment_pb2 import InitialEnvironmentMessage +from ..environment.ipc_interface import IPCInterface +from ..proto.action_space_pb2 import ActionSpaceMessageV2 +from ..proto.initial_environment_pb2 import InitialEnvironmentMessage # Torch should be imported first before craftground_native to avoid segfaults try: @@ -8,7 +8,7 @@ except ImportError: pass -from craftground_native import ( # noqa +from ..craftground_native import ( # noqa initialize_shared_memory, # noqa write_to_shared_memory, # noqa read_from_shared_memory, # noqa @@ -43,7 +43,7 @@ def __init__( self.observation_shared_memory_name = f"craftground_{port}_observation" self.synchronization_shared_memory_name = f"craftground_{port}_synchronization" - def write_action(self, action: ActionSpaceMessageV2): + def send_action(self, action: ActionSpaceMessageV2): action_bytes: bytes = action.SerializeToString() write_to_shared_memory( self.action_shared_memory_name, action_bytes, len(action_bytes) From 6c37066e69e1bbb9bb4ee63ef1e8975e75b19f27 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 01:19:20 +0900 Subject: [PATCH 43/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20type=20of=20port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/boost_ipc.py | 4 ++-- src/craftground/environment/environment.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index 93c45957..8589b561 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -19,7 +19,7 @@ class BoostIPC(IPCInterface): def __init__( self, - port: str, + port: int, find_free_port: bool, initial_environment: InitialEnvironmentMessage, ): @@ -30,7 +30,7 @@ def __init__( dummy_action: ActionSpaceMessageV2 = ActionSpaceMessageV2() dummy_action_bytes: bytes = dummy_action.SerializeToString() self.port = initialize_shared_memory( - self.port, + int(self.port), initial_environment_bytes, len(initial_environment_bytes), len(dummy_action_bytes), diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index effe794c..e7d6bbda 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -109,7 +109,7 @@ def __init__( from .boost_ipc import BoostIPC # type: ignore self.ipc = BoostIPC( - str(port), + port, find_free_port, self.initial_env_message, ) @@ -187,7 +187,7 @@ def ensure_alive(self, fast_reset, extra_commands, seed: int): from .boost_ipc import BoostIPC # type: ignore self.ipc = BoostIPC( - str(self.ipc.port), + self.ipc.port, self.ipc.find_free_port, self.initial_env_message, ) From 5fd18f6faaa996400965437f79e4508e9f82f747 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 10:25:19 +0900 Subject: [PATCH 44/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20package=20paths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_boost.cpp | 2 +- src/craftground/environment/action_space.py | 2 +- src/craftground/environment/boost_ipc.py | 2 +- src/craftground/environment/environment.py | 8 ++++---- src/craftground/environment/socket_ipc.py | 14 ++++++------- tests/python/unit/test_environment.py | 22 ++++++++++----------- tests/python/unit/test_socket_ipc.py | 14 ++++++------- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 2befc470..60a5566f 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -26,7 +26,7 @@ int create_shared_memory_impl( std::string action_memory_name; bool found_free_port = false; do { - initial_memory_name = "craftground_" + std::to_string(port) + "_action"; + initial_memory_name = "craftground_" + std::to_string(port) + "_initial"; synchronization_memory_name = "craftground_" + std::to_string(port) + "_synchronization"; action_memory_name = "craftground_" + std::to_string(port) + "_action"; diff --git a/src/craftground/environment/action_space.py b/src/craftground/environment/action_space.py index cc0cebdb..dd51b8bc 100644 --- a/src/craftground/environment/action_space.py +++ b/src/craftground/environment/action_space.py @@ -4,7 +4,7 @@ import gymnasium as gym import numpy as np -from ..proto.action_space_pb2 import ActionSpaceMessageV2 +from craftground.proto.action_space_pb2 import ActionSpaceMessageV2 class ActionSpaceVersion(Enum): diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index 8589b561..dffb2e11 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -8,7 +8,7 @@ except ImportError: pass -from ..craftground_native import ( # noqa +from craftground_native import ( # noqa initialize_shared_memory, # noqa write_to_shared_memory, # noqa read_from_shared_memory, # noqa diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index e7d6bbda..2c65d77d 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -10,15 +10,15 @@ import numpy as np from gymnasium.core import ActType, ObsType, RenderFrame -from ..environment.action_space import ( +from .action_space import ( ActionSpaceVersion, action_v2_dict_to_message, declare_action_space, translate_action_to_v2, ) -from ..environment.observation_converter import ObservationConverter -from ..environment.observation_space import declare_observation_space -from ..environment.socket_ipc import SocketIPC +from .observation_converter import ObservationConverter +from .observation_space import declare_observation_space +from .socket_ipc import SocketIPC from ..csv_logger import CsvLogger, LogBackend from ..initial_environment_config import InitialEnvironmentConfig diff --git a/src/craftground/environment/socket_ipc.py b/src/craftground/environment/socket_ipc.py index 4a4822d5..bf291916 100644 --- a/src/craftground/environment/socket_ipc.py +++ b/src/craftground/environment/socket_ipc.py @@ -5,13 +5,13 @@ from typing import Dict, List, Optional, Union import psutil -from ..buffered_socket import BufferedSocket -from ..csv_logger import CsvLogger -from ..environment.action_space import action_v2_dict_to_message, no_op_v2 -from ..environment.ipc_interface import IPCInterface -from ..proto.action_space_pb2 import ActionSpaceMessageV2 -from ..proto.initial_environment_pb2 import InitialEnvironmentMessage -from ..proto.observation_space_pb2 import ObservationSpaceMessage +from craftground.buffered_socket import BufferedSocket +from craftground.csv_logger import CsvLogger +from craftground.environment.action_space import action_v2_dict_to_message, no_op_v2 +from craftground.environment.ipc_interface import IPCInterface +from craftground.proto.action_space_pb2 import ActionSpaceMessageV2 +from craftground.proto.initial_environment_pb2 import InitialEnvironmentMessage +from craftground.proto.observation_space_pb2 import ObservationSpaceMessage import socket diff --git a/tests/python/unit/test_environment.py b/tests/python/unit/test_environment.py index d1afa2ef..95d1ecf7 100644 --- a/tests/python/unit/test_environment.py +++ b/tests/python/unit/test_environment.py @@ -3,12 +3,12 @@ from unittest.mock import MagicMock, patch -from environment.environment import CraftGroundEnvironment -from environment.observation_converter import ObservationConverter -from environment.socket_ipc import SocketIPC -from initial_environment_config import InitialEnvironmentConfig -from screen_encoding_modes import ScreenEncodingMode -from environment.boost_ipc import BoostIPC +from craftground.environment.environment import CraftGroundEnvironment +from craftground.environment.observation_converter import ObservationConverter +from craftground.environment.socket_ipc import SocketIPC +from craftground.initial_environment_config import InitialEnvironmentConfig +from craftground.screen_encoding_modes import ScreenEncodingMode +from craftground.environment.boost_ipc import BoostIPC @pytest.fixture @@ -26,7 +26,7 @@ def environment(mock_initial_env): return CraftGroundEnvironment(initial_env=mock_initial_env) -@patch("environment.environment.SocketIPC") +@patch("craftground.environment.environment.SocketIPC") def test_initialize_socket_environment(mock_ipc_class, mock_initial_env): mock_ipc_instance = MagicMock(spec=SocketIPC) mock_ipc_class.return_value = mock_ipc_instance @@ -42,7 +42,7 @@ def test_initialize_socket_environment(mock_ipc_class, mock_initial_env): assert isinstance(env.ipc, SocketIPC) -@patch("environment.boost_ipc.BoostIPC") +@patch("craftground.environment.boost_ipc.BoostIPC") def test_initialize_boost_environment(mock_ipc_class, mock_initial_env): mock_ipc_instance = MagicMock(spec=BoostIPC) mock_ipc_class.return_value = mock_ipc_instance @@ -86,7 +86,7 @@ def test_update_override_resolutions(mock_open, environment): mock_file.write.assert_called() -@patch("environment.environment.CraftGroundEnvironment.reset") +@patch("craftground.environment.environment.CraftGroundEnvironment.reset") def test_reset_environment(mock_reset, environment): mock_reset.return_value = ({"observation": "data"}, {}) @@ -96,7 +96,7 @@ def test_reset_environment(mock_reset, environment): assert isinstance(info, dict) -@patch("environment.environment.CraftGroundEnvironment.step") +@patch("craftground.environment.environment.CraftGroundEnvironment.step") def test_step_action(mock_step, environment): mock_step.return_value = ("observation", 1.0, False, False, {}) @@ -109,7 +109,7 @@ def test_step_action(mock_step, environment): assert isinstance(info, dict) -@patch("environment.environment.CraftGroundEnvironment.close") +@patch("craftground.environment.environment.CraftGroundEnvironment.close") def test_close_environment(mock_close, environment): environment.close() mock_close.assert_called() diff --git a/tests/python/unit/test_socket_ipc.py b/tests/python/unit/test_socket_ipc.py index eaffdabc..b090720e 100644 --- a/tests/python/unit/test_socket_ipc.py +++ b/tests/python/unit/test_socket_ipc.py @@ -3,12 +3,12 @@ from unittest.mock import MagicMock, patch import struct -from buffered_socket import BufferedSocket -from csv_logger import CsvLogger -from environment.socket_ipc import SocketIPC -from proto.action_space_pb2 import ActionSpaceMessageV2 -from proto.initial_environment_pb2 import InitialEnvironmentMessage -from proto.observation_space_pb2 import ObservationSpaceMessage +from craftground.buffered_socket import BufferedSocket +from craftground.csv_logger import CsvLogger +from craftground.environment.socket_ipc import SocketIPC +from craftground.proto.action_space_pb2 import ActionSpaceMessageV2 +from craftground.proto.initial_environment_pb2 import InitialEnvironmentMessage +from craftground.proto.observation_space_pb2 import ObservationSpaceMessage @pytest.fixture @@ -104,7 +104,7 @@ def test_remove_orphan_java_processes(mock_kill, mock_remove, socket_ipc): mock_kill.assert_called_with(1234, signal.SIGTERM) -@patch("buffered_socket.socket.socket") +@patch("craftground.buffered_socket.socket.socket") def test_start_communication(mock_socket, socket_ipc): mock_sock = MagicMock() mock_socket.return_value = mock_sock From cd4a42375eed1a563fe2da907c8d4091e9de8348 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 10:37:28 +0900 Subject: [PATCH 45/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20native=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/boost_ipc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index dffb2e11..8589b561 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -8,7 +8,7 @@ except ImportError: pass -from craftground_native import ( # noqa +from ..craftground_native import ( # noqa initialize_shared_memory, # noqa write_to_shared_memory, # noqa read_from_shared_memory, # noqa From 1fb7ed543528c2732885b9025a4e5f6f92440b66 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 10:51:44 +0900 Subject: [PATCH 46/91] =?UTF-8?q?=F0=9F=90=9B=20Debugging=20ipc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc.cpp | 14 +++++++++++--- src/cpp/ipc_boost.cpp | 3 ++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/cpp/ipc.cpp b/src/cpp/ipc.cpp index 09309515..c3336c75 100644 --- a/src/cpp/ipc.cpp +++ b/src/cpp/ipc.cpp @@ -1,3 +1,4 @@ +#include #include #include "ipc.h" #define MACRO_STRINGIFY(x) #x @@ -67,9 +68,16 @@ int initialize_shared_memory( size_t action_size, bool find_free_port ) { - return create_shared_memory_impl( - port, initial_data, data_size, action_size, find_free_port - ); + try { + return create_shared_memory_impl( + port, initial_data, data_size, action_size, find_free_port + ); + } catch (const std::exception &e) { + std::cerr << e.what() << std::endl; + std::cerr << "Failed to initialize shared memory: errno=" << errno + << std::endl; + throw std::runtime_error(e.what()); + } } void write_to_shared_memory( diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 60a5566f..20308317 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -26,7 +26,8 @@ int create_shared_memory_impl( std::string action_memory_name; bool found_free_port = false; do { - initial_memory_name = "craftground_" + std::to_string(port) + "_initial"; + initial_memory_name = + "craftground_" + std::to_string(port) + "_initial"; synchronization_memory_name = "craftground_" + std::to_string(port) + "_synchronization"; action_memory_name = "craftground_" + std::to_string(port) + "_action"; From 6c52ea97c9ad51fbb1ae52a25c156b79d4a62d33 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 11:40:56 +0900 Subject: [PATCH 47/91] =?UTF-8?q?=F0=9F=8C=90=20Exception?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- src/cpp/ipc_boost.cpp | 94 +++++++++++++++++++++++++++++++------------ src/cpp/ipc_boost.hpp | 11 +++++ 3 files changed, 80 insertions(+), 27 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9d463b67..93a85224 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ keywords = ["minecraft", "reinforcement learning", "environment"] license = {file = "LICENSE"} name = "craftground" readme = "README.md" -version = "2.6.1" +version = "2.6.2" dependencies = [ "gymnasium", diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 20308317..5b5ff2e8 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -1,6 +1,9 @@ #include "ipc_boost.hpp" +#include "boost/interprocess/interprocess_fwd.hpp" +#include #include #include +#include bool shared_memory_exists(const std::string &name) { try { @@ -25,32 +28,65 @@ int create_shared_memory_impl( std::string synchronization_memory_name; std::string action_memory_name; bool found_free_port = false; - do { - initial_memory_name = - "craftground_" + std::to_string(port) + "_initial"; - synchronization_memory_name = - "craftground_" + std::to_string(port) + "_synchronization"; - action_memory_name = "craftground_" + std::to_string(port) + "_action"; - if (shared_memory_exists(initial_memory_name)) { - if (find_free_port) { - port++; - continue; - } else { - throw std::runtime_error( - "Shared memory " + initial_memory_name + " already exists" - ); + + try { + do { + initial_memory_name = + "/craftground_" + std::to_string(port) + "_initial"; + synchronization_memory_name = + "/craftground_" + std::to_string(port) + "_synchronization"; + action_memory_name = + "/craftground_" + std::to_string(port) + "_action"; + if (shared_memory_exists(initial_memory_name)) { + if (find_free_port) { + port++; + continue; + } else { + throw std::runtime_error( + "Shared memory " + initial_memory_name + + " already exists" + ); + } } - } - found_free_port = true; - } while (!found_free_port); + found_free_port = true; + } while (!found_free_port); + } catch (const interprocess_exception &e) { + std::cerr << e.what() << std::endl; + std::cerr + << "Failed to initialize shared memory during finding port: errno=" + << errno << std::endl; + throw std::runtime_error(e.what()); + } + errno = 0; - shared_memory_object::remove(initial_memory_name.c_str()); - managed_shared_memory sharedMemory( - create_only, - initial_memory_name.c_str(), - sizeof(SharedDataHeader) + data_size - ); - void *addr = sharedMemory.allocate(sizeof(SharedDataHeader) + data_size); + if (!shared_memory_object::remove(initial_memory_name.c_str())) { + std::cerr << "Failed to remove shared memory: errno=" << errno + << std::endl; + } + + managed_shared_memory sharedMemory; + try { + sharedMemory = managed_shared_memory( + create_only, + initial_memory_name.c_str(), + sizeof(SharedDataHeader) + data_size + ); + } catch (const interprocess_exception &e) { + std::cerr << e.what() << std::endl; + std::cerr << "Failed to initialize shared memory creating" + << initial_memory_name << " : errno=" << errno << std::endl; + throw std::runtime_error(e.what()); + } + + void *addr = nullptr; + try { + addr = sharedMemory.allocate(sizeof(SharedDataHeader) + data_size); + } catch (const interprocess_exception &e) { + std::cerr << e.what() << std::endl; + std::cerr << "Failed to initialize shared memory allocating" + << initial_memory_name << " : errno=" << errno << std::endl; + throw std::runtime_error(e.what()); + } auto *header = new (addr) SharedDataHeader(); header->ready = false; @@ -64,7 +100,10 @@ int create_shared_memory_impl( // Java will remove the initial environment shared memory // Create synchronization shared memory (fixed size, the size field ) - shared_memory_object::remove(synchronization_memory_name.c_str()); + if (!shared_memory_object::remove(synchronization_memory_name.c_str())) { + std::cerr << "Failed to remove shared memory: errno=" << errno + << std::endl; + } managed_shared_memory sharedMemorySynchronization( create_only, synchronization_memory_name.c_str(), @@ -77,7 +116,10 @@ int create_shared_memory_impl( headerSynchronization->ready = true; // Allocate shared memory for action - shared_memory_object::remove(action_memory_name.c_str()); + if (!shared_memory_object::remove(action_memory_name.c_str())) { + std::cerr << "Failed to remove shared memory: errno=" << errno + << std::endl; + } managed_shared_memory sharedMemoryAction( create_only, action_memory_name.c_str(), diff --git a/src/cpp/ipc_boost.hpp b/src/cpp/ipc_boost.hpp index 928387b7..8457e2fb 100644 --- a/src/cpp/ipc_boost.hpp +++ b/src/cpp/ipc_boost.hpp @@ -11,12 +11,23 @@ using namespace boost::interprocess; +struct SharedMemoryLayout { + size_t action_offset; // Always 0 + size_t action_size; // to be set on initialization + size_t header_offset; // Always action_size + size_t header_size; // Always sizeof(SharedDataHeader) + size_t initial_environment_offset; // Always action_size + sizeof(SharedDataHeader) + size_t initial_environment_size; // to be set on initialization +}; + + struct SharedDataHeader { interprocess_mutex mutex; interprocess_condition condition; size_t size; bool ready; }; + // Message follows the header int create_shared_memory_impl( From dc8dd352a8a8930394f8d2b4b4e2ba3ba7b4b4d1 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 12:53:45 +0900 Subject: [PATCH 48/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20ipc=20crash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clangd | 8 +- .vscode/settings.json | 1 - src/cpp/ipc.cpp | 6 +- src/cpp/ipc_boost.cpp | 168 +++++++---------- src/cpp/ipc_boost.hpp | 38 ++-- .../MinecraftEnv/src/main/cpp/boost_ipc.cpp | 172 ++++++++---------- 6 files changed, 161 insertions(+), 232 deletions(-) diff --git a/.clangd b/.clangd index a1589371..a2c7a835 100644 --- a/.clangd +++ b/.clangd @@ -1,13 +1,13 @@ If: - PathMatch: /src/cpp/.* + PathMatch: src/cpp/.* CompileFlags: - CompilationDatabase: /build + CompilationDatabase: build --- If: - PathMatch: /src/craftground/MinecraftEnv/src/main/cpp/.* + PathMatch: src/craftground/MinecraftEnv/src/main/cpp/.* CompileFlags: - CompilationDatabase: /src/craftground/MinecraftEnv \ No newline at end of file + CompilationDatabase: src/craftground/MinecraftEnv \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index b0caada2..4c8b0dcf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,6 @@ "C_Cpp.default.compileCommands": "${workspaceFolder}/src/MinecraftEnv/compile_commands.json", "clangd.arguments": [ "--background-index", - "--compile-commands-dir=${workspaceFolder}/src/craftground/MinecraftEnv/" ], "java.compile.nullAnalysis.mode": "automatic", } \ No newline at end of file diff --git a/src/cpp/ipc.cpp b/src/cpp/ipc.cpp index c3336c75..2e576fdd 100644 --- a/src/cpp/ipc.cpp +++ b/src/cpp/ipc.cpp @@ -89,11 +89,7 @@ void write_to_shared_memory( py::bytes read_from_shared_memory( const char *memory_name, const char *management_memory_name ) { - size_t data_size = 0; - const char *data = read_from_shared_memory_impl( - memory_name, management_memory_name, data_size - ); - return py::bytes(data, data_size); + return read_from_shared_memory_impl(memory_name, management_memory_name); } void destroy_shared_memory(const char *memory_name) { diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 5b5ff2e8..a1afa728 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -1,3 +1,4 @@ +#include #include "ipc_boost.hpp" #include "boost/interprocess/interprocess_fwd.hpp" #include @@ -24,27 +25,19 @@ int create_shared_memory_impl( size_t action_size, bool find_free_port ) { - std::string initial_memory_name; - std::string synchronization_memory_name; - std::string action_memory_name; + std::string p2j_memory_name; bool found_free_port = false; try { do { - initial_memory_name = - "/craftground_" + std::to_string(port) + "_initial"; - synchronization_memory_name = - "/craftground_" + std::to_string(port) + "_synchronization"; - action_memory_name = - "/craftground_" + std::to_string(port) + "_action"; - if (shared_memory_exists(initial_memory_name)) { + p2j_memory_name = "/craftground_" + std::to_string(port) + "_p2j"; + if (shared_memory_exists(p2j_memory_name)) { if (find_free_port) { port++; continue; } else { throw std::runtime_error( - "Shared memory " + initial_memory_name + - " already exists" + "Shared memory " + p2j_memory_name + " already exists" ); } } @@ -59,7 +52,7 @@ int create_shared_memory_impl( } errno = 0; - if (!shared_memory_object::remove(initial_memory_name.c_str())) { + if (!shared_memory_object::remove(p2j_memory_name.c_str())) { std::cerr << "Failed to remove shared memory: errno=" << errno << std::endl; } @@ -68,123 +61,88 @@ int create_shared_memory_impl( try { sharedMemory = managed_shared_memory( create_only, - initial_memory_name.c_str(), - sizeof(SharedDataHeader) + data_size + p2j_memory_name.c_str(), + 1024 // Too small size failes to allocate ); } catch (const interprocess_exception &e) { std::cerr << e.what() << std::endl; std::cerr << "Failed to initialize shared memory creating" - << initial_memory_name << " : errno=" << errno << std::endl; + << p2j_memory_name << " : errno=" << errno << std::endl; throw std::runtime_error(e.what()); } - void *addr = nullptr; - try { - addr = sharedMemory.allocate(sizeof(SharedDataHeader) + data_size); - } catch (const interprocess_exception &e) { - std::cerr << e.what() << std::endl; - std::cerr << "Failed to initialize shared memory allocating" - << initial_memory_name << " : errno=" << errno << std::endl; - throw std::runtime_error(e.what()); + SharedMemoryLayout *layout = + static_cast(sharedMemory.allocate( + sizeof(SharedMemoryLayout) + action_size + data_size + )); + layout->layout_size = sizeof(SharedMemoryLayout); + layout->action_offset = sizeof(SharedMemoryLayout); + layout->action_size = action_size; + layout->initial_environment_offset = + sizeof(SharedMemoryLayout) + action_size; + layout->initial_environment_size = data_size; + void *action_start = + reinterpret_cast(layout) + layout->action_offset; + void *data_start = + reinterpret_cast(layout) + layout->initial_environment_offset; + + if (data_size > layout->initial_environment_size) { + throw std::runtime_error( + "Data size exceeds allocated shared memory size" + ); } - - auto *header = new (addr) SharedDataHeader(); - header->ready = false; - - char *data_start = - reinterpret_cast(header) + sizeof(SharedDataHeader); std::memcpy(data_start, initial_data, data_size); - header->size = data_size; - - header->ready = true; - // Java will remove the initial environment shared memory - - // Create synchronization shared memory (fixed size, the size field ) - if (!shared_memory_object::remove(synchronization_memory_name.c_str())) { - std::cerr << "Failed to remove shared memory: errno=" << errno - << std::endl; - } - managed_shared_memory sharedMemorySynchronization( - create_only, - synchronization_memory_name.c_str(), - sizeof(SharedDataHeader) - ); - void *addrSyncrhonization = - sharedMemorySynchronization.allocate(sizeof(SharedDataHeader)); - auto *headerSynchronization = new (addrSyncrhonization) SharedDataHeader(); - headerSynchronization->size = 0; - headerSynchronization->ready = true; - - // Allocate shared memory for action - if (!shared_memory_object::remove(action_memory_name.c_str())) { - std::cerr << "Failed to remove shared memory: errno=" << errno - << std::endl; - } - managed_shared_memory sharedMemoryAction( - create_only, - action_memory_name.c_str(), - sizeof(SharedDataHeader) + action_size - ); - void *addrAction = - sharedMemoryAction.allocate(sizeof(SharedDataHeader) + action_size); - auto *headerAction = new (addrAction) SharedDataHeader(); - headerAction->size = action_size; - headerAction->ready = true; - + layout->p2j_ready = true; + layout->j2p_ready = false; return port; } // Write action to shared memory void write_to_shared_memory_impl( - const std::string &action_memory_name, - const char *data, - const size_t action_size + const std::string &p2j_memory_name, const char *data ) { - managed_shared_memory actionMemory(open_only, action_memory_name.c_str()); - void *addr = actionMemory.get_address(); - auto *actionHeader = reinterpret_cast(addr); - - std::unique_lock actionLock(actionHeader->mutex); - char *data_start = - reinterpret_cast(actionHeader) + sizeof(SharedDataHeader); - std::memcpy(data_start, data, action_size); - actionHeader->ready = true; - actionHeader->condition.notify_one(); + managed_shared_memory p2jMemory(open_only, p2j_memory_name.c_str()); + SharedMemoryLayout *layout = + static_cast(p2jMemory.get_address()); + char *action_addr = + static_cast(p2jMemory.get_address()) + layout->action_offset; + + std::unique_lock actionLock(layout->mutex); + std::memcpy(action_addr, data, layout->action_size); + layout->p2j_ready = true; + layout->j2p_ready = false; + layout->condition.notify_one(); actionLock.unlock(); } // Read observation from shared memory -const char *read_from_shared_memory_impl( - const std::string &memory_name, - const std::string &synchronization_memory_name, - size_t &data_size +py::bytes read_from_shared_memory_impl( + const std::string &p2j_memory_name, const std::string &j2p_memory_name ) { - // Synchronize with Java using synchronization shared memory - managed_shared_memory synchronizationSharedMemory( - open_only, synchronization_memory_name.c_str() - ); - void *addrSynchronization = synchronizationSharedMemory.get_address(); - auto *headerSynchronization = - reinterpret_cast(addrSynchronization); - std::unique_lock lockSynchronization( - headerSynchronization->mutex - ); - headerSynchronization->condition.wait(lockSynchronization, [&] { - return headerSynchronization->ready; + managed_shared_memory p2jMemory(open_only, p2j_memory_name.c_str()); + SharedMemoryLayout *p2jLayout = + static_cast(p2jMemory.get_address()); + + std::unique_lock lockSynchronization(p2jLayout->mutex); + // wait for java to write the observation + p2jLayout->condition.wait(lockSynchronization, [&] { + return p2jLayout->j2p_ready; }); // Read the observation from shared memory - managed_shared_memory sharedMemory(open_only, memory_name.c_str()); - void *addr = sharedMemory.get_address(); - auto *header = reinterpret_cast(addr); - // Read the message from Java - char *data_start = - reinterpret_cast(header) + sizeof(SharedDataHeader); - header->ready = false; + managed_shared_memory j2pMemory(open_only, j2p_memory_name.c_str()); + J2PSharedMemoryLayout *j2pLayout = + static_cast(j2pMemory.get_address()); + + const char *data_start = + reinterpret_cast(j2pLayout) + j2pLayout->data_offset; + py::bytes data(data_start, j2pLayout->data_size); + + p2jLayout->j2p_ready = false; + p2jLayout->p2j_ready = false; lockSynchronization.unlock(); - data_size = header->size; - return data_start; + return data; } // Destroy shared memory diff --git a/src/cpp/ipc_boost.hpp b/src/cpp/ipc_boost.hpp index 8457e2fb..8d201c2c 100644 --- a/src/cpp/ipc_boost.hpp +++ b/src/cpp/ipc_boost.hpp @@ -8,27 +8,29 @@ #include #include #include +#include using namespace boost::interprocess; +namespace py = pybind11; struct SharedMemoryLayout { - size_t action_offset; // Always 0 - size_t action_size; // to be set on initialization - size_t header_offset; // Always action_size - size_t header_size; // Always sizeof(SharedDataHeader) - size_t initial_environment_offset; // Always action_size + sizeof(SharedDataHeader) - size_t initial_environment_size; // to be set on initialization -}; - - -struct SharedDataHeader { + size_t layout_size; // to be set on initialization + size_t action_offset; // Always sizeof(SharedMemoryLayout) + size_t action_size; // to be set on initialization + size_t initial_environment_offset; // Always action_size + + // sizeof(SharedDataHeader) + size_t initial_environment_size; // to be set on initialization interprocess_mutex mutex; interprocess_condition condition; - size_t size; - bool ready; + bool p2j_ready; + bool j2p_ready; }; -// Message follows the header +struct J2PSharedMemoryLayout { + size_t layout_size; // to be set on initialization + size_t data_offset; // Always sizeof(J2PSharedMemoryLayout) + size_t data_size; // to be set on initialization +}; int create_shared_memory_impl( int port, @@ -39,15 +41,11 @@ int create_shared_memory_impl( ); void write_to_shared_memory_impl( - const std::string &memory_name, - const char *data, - const size_t data_size + const std::string &p2j_memory_name, const char *data, const size_t data_size ); -const char *read_from_shared_memory_impl( - const std::string &memory_name, - const std::string &management_memory_name, - size_t &data_size +py::bytes read_from_shared_memory_impl( + const std::string &p2j_memory_name, const std::string &j2p_memory_name ); // remove shared memory diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index d5d3df6d..fdad6706 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -9,32 +9,36 @@ using namespace boost::interprocess; -// explicit structure of the object is handled using protobuf -struct SharedDataHeader { +struct SharedMemoryLayout { + size_t layout_size; // to be set on initialization + size_t action_offset; // Always sizeof(SharedMemoryLayout) + size_t action_size; // to be set on initialization + size_t initial_environment_offset; // Always action_size + + // sizeof(SharedDataHeader) + size_t initial_environment_size; // to be set on initialization interprocess_mutex mutex; interprocess_condition condition; - size_t size; - bool ready; + bool p2j_ready; + bool j2p_ready; +}; + +struct J2PSharedMemoryLayout { + size_t layout_size; // to be set on initialization + size_t data_offset; // Always sizeof(J2PSharedMemoryLayout) + size_t data_size; // to be set on initialization }; -// Message follows the header // Returns ByteArray object containing the initial environment message jobject read_initial_environment( - JNIEnv *env, - jclass clazz, - const std::string &initial_environment_memory_name + JNIEnv *env, jclass clazz, const std::string &p2j_memory_name ) { - managed_shared_memory sharedMemoryInitialEnvironment( - open_only, initial_environment_memory_name.c_str() - ); - void *addrInitialEnvironment = sharedMemoryInitialEnvironment.get_address(); - auto *headerInitialEnvironment = - reinterpret_cast(addrInitialEnvironment); - // Read the initial environment message - char *data_startInitialEnvironment = - reinterpret_cast(headerInitialEnvironment) + - sizeof(SharedDataHeader); - size_t data_size = headerInitialEnvironment->size; + managed_shared_memory p2jMemory(open_only, p2j_memory_name.c_str()); + SharedMemoryLayout *p2jLayout = + static_cast(p2jMemory.get_address()); + + char *data_startInitialEnvironment = reinterpret_cast(p2jLayout) + + p2jLayout->initial_environment_offset; + size_t data_size = p2jLayout->initial_environment_size; jbyteArray byteArray = env->NewByteArray(data_size); if (byteArray == nullptr || env->ExceptionCheck()) { @@ -46,104 +50,85 @@ jobject read_initial_environment( data_size, reinterpret_cast(data_startInitialEnvironment) ); - // Delete the shared memory - shared_memory_object::remove(initial_environment_memory_name.c_str()); return byteArray; } jbyteArray read_action( JNIEnv *env, jclass clazz, - const std::string &action_memory_name, + const std::string &p2j_memory_name, jbyteArray data ) { - managed_shared_memory actionSharedMemory( - open_only, action_memory_name.c_str() - ); - void *addr = actionSharedMemory.get_address(); - auto *actionHeader = reinterpret_cast(addr); + managed_shared_memory p2jMemory(open_only, p2j_memory_name.c_str()); + SharedMemoryLayout *p2jHeader = + static_cast(p2jMemory.get_address()); + char *data_start = + reinterpret_cast(p2jHeader) + p2jHeader->action_offset; - std::unique_lock actionLock(actionHeader->mutex); - actionHeader->condition.wait(actionLock, [&] { - return actionHeader->ready; - }); + std::unique_lock actionLock(p2jHeader->mutex); + p2jHeader->condition.wait(actionLock, [&] { return p2jHeader->p2j_ready; }); if (data == nullptr) { - data = env->NewByteArray(actionHeader->size); + data = env->NewByteArray(p2jHeader->action_size); } if (data == nullptr || env->ExceptionCheck()) { return nullptr; } // Read the action message - char *data_start = - reinterpret_cast(actionHeader) + sizeof(SharedDataHeader); env->SetByteArrayRegion( - data, 0, actionHeader->size, reinterpret_cast(data_start) + data, 0, p2jHeader->action_size, reinterpret_cast(data_start) ); - actionHeader->ready = false; + p2jHeader->p2j_ready = false; + p2jHeader->j2p_ready = false; actionLock.unlock(); return data; } void write_observation( - const std::string &observation_memory_name, - const std::string &synchronization_memory_name, + const std::string &p2j_memory_name, + const std::string &j2p_memory_name, const char *data, const size_t observation_size ) { - managed_shared_memory synchronizationSharedMemory( - open_only, synchronization_memory_name.c_str() - ); - void *addrSynchronization = synchronizationSharedMemory.get_address(); - auto *headerSynchronization = - reinterpret_cast(addrSynchronization); - std::unique_lock lockSynchronization( - headerSynchronization->mutex - ); - headerSynchronization->ready = false; + managed_shared_memory p2jMemory(open_only, p2j_memory_name.c_str()); + SharedMemoryLayout *p2jLayout = + static_cast(p2jMemory.get_address()); - managed_shared_memory observationSharedMemory( - open_only, observation_memory_name.c_str() - ); + std::unique_lock lockSynchronization(p2jLayout->mutex); + p2jLayout->j2p_ready = false; + + managed_shared_memory j2pMemory(open_only, j2p_memory_name.c_str()); // Resize the shared memory if needed - const size_t currentSize = observationSharedMemory.get_size(); - const size_t requiredSize = observation_size + sizeof(SharedDataHeader); + const size_t currentSize = j2pMemory.get_size(); + const size_t requiredSize = + observation_size + sizeof(J2PSharedMemoryLayout); if (currentSize < requiredSize) { - observationSharedMemory.grow( - observation_memory_name.c_str(), (requiredSize - currentSize) - ); + j2pMemory.grow(j2p_memory_name.c_str(), (requiredSize - currentSize)); } // Write the observation to shared memory - void *observationAddr = observationSharedMemory.get_address(); - auto *observationHeader = - reinterpret_cast(observationAddr); + J2PSharedMemoryLayout *j2pHeader = + static_cast(j2pMemory.get_address()); char *data_start = - reinterpret_cast(observationHeader) + sizeof(SharedDataHeader); + reinterpret_cast(j2pHeader) + j2pHeader->data_offset; std::memcpy(data_start, data, observation_size); - observationHeader->size = observation_size; - observationHeader->ready = true; - // mutex, condition of observationHeader SHOULD NOT BE USED. Use - // synchronizationSharedMemory instead + j2pHeader->data_size = observation_size; // Notify Python that the observation is ready - headerSynchronization->ready = true; - headerSynchronization->condition.notify_one(); + p2jLayout->j2p_ready = true; + p2jLayout->p2j_ready = false; + p2jLayout->condition.notify_one(); } extern "C" JNIEXPORT jobject JNICALL Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readInitialEnvironmentImpl( - JNIEnv *env, jclass clazz, jstring initial_environment_memory_name + JNIEnv *env, jclass clazz, jstring p2j_memory_name ) { - const char *initial_environment_memory_name_cstr = - env->GetStringUTFChars(initial_environment_memory_name, nullptr); - jobject result = read_initial_environment( - env, clazz, initial_environment_memory_name_cstr - ); - env->ReleaseStringUTFChars( - initial_environment_memory_name, initial_environment_memory_name_cstr - ); + const char *p2j_memory_name_cstr = + env->GetStringUTFChars(p2j_memory_name, nullptr); + jobject result = read_initial_environment(env, clazz, p2j_memory_name_cstr); + env->ReleaseStringUTFChars(p2j_memory_name, p2j_memory_name_cstr); return result; } @@ -151,18 +136,15 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readInitialEnvironmentImp // ByteArray extern "C" JNIEXPORT jbyteArray JNICALL Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readActionImpl( - JNIEnv *env, - jclass clazz, - jstring action_memory_name, - jbyteArray action_data + JNIEnv *env, jclass clazz, jstring p2j_memory_name, jbyteArray action_data ) { - const char *action_memory_name_cstr = - env->GetStringUTFChars(action_memory_name, nullptr); + const char *j2p_memory_name_cstr = + env->GetStringUTFChars(p2j_memory_name, nullptr); size_t data_size = 0; jbyteArray data = - read_action(env, clazz, action_memory_name_cstr, action_data); - env->ReleaseStringUTFChars(action_memory_name, action_memory_name_cstr); + read_action(env, clazz, j2p_memory_name_cstr, action_data); + env->ReleaseStringUTFChars(p2j_memory_name, j2p_memory_name_cstr); return data; } @@ -175,31 +157,27 @@ extern "C" JNIEXPORT void JNICALL Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_writeObservationImpl( JNIEnv *env, jclass clazz, - jstring observation_memory_name, - jstring synchronization_memory_name, + jstring p2j_memory_name, + jstring j2p_memory_name, jbyteArray observation_data ) { - const char *observation_memory_name_cstr = - env->GetStringUTFChars(observation_memory_name, nullptr); - const char *synchronization_memory_name_cstr = - env->GetStringUTFChars(synchronization_memory_name, nullptr); + const char *j2p_memory_name_cstr = + env->GetStringUTFChars(j2p_memory_name, nullptr); + const char *p2j_memory_name_cstr = + env->GetStringUTFChars(p2j_memory_name, nullptr); jbyte *observation_data_ptr = env->GetByteArrayElements(observation_data, nullptr); jsize observation_data_size = env->GetArrayLength(observation_data); write_observation( - observation_memory_name_cstr, - synchronization_memory_name_cstr, + p2j_memory_name_cstr, + j2p_memory_name_cstr, reinterpret_cast(observation_data_ptr), static_cast(observation_data_size) ); - env->ReleaseStringUTFChars( - observation_memory_name, observation_memory_name_cstr - ); - env->ReleaseStringUTFChars( - synchronization_memory_name, synchronization_memory_name_cstr - ); + env->ReleaseStringUTFChars(j2p_memory_name, j2p_memory_name_cstr); + env->ReleaseStringUTFChars(p2j_memory_name, p2j_memory_name_cstr); env->ReleaseByteArrayElements( observation_data, observation_data_ptr, JNI_ABORT ); From be5d32e581e0067d914b3465614592fae67101cb Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 12:57:07 +0900 Subject: [PATCH 49/91] =?UTF-8?q?=F0=9F=A5=85=20Exception=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MinecraftEnv/src/main/cpp/boost_ipc.cpp | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index fdad6706..c2a729bf 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -127,7 +127,13 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readInitialEnvironmentImp ) { const char *p2j_memory_name_cstr = env->GetStringUTFChars(p2j_memory_name, nullptr); - jobject result = read_initial_environment(env, clazz, p2j_memory_name_cstr); + jobject result = nullptr; + try { + result = read_initial_environment(env, clazz, p2j_memory_name_cstr); + } catch (const std::exception &e) { + env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what()); + return nullptr; + } env->ReleaseStringUTFChars(p2j_memory_name, p2j_memory_name_cstr); return result; } @@ -142,8 +148,13 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readActionImpl( env->GetStringUTFChars(p2j_memory_name, nullptr); size_t data_size = 0; - jbyteArray data = - read_action(env, clazz, j2p_memory_name_cstr, action_data); + jbyteArray data; + try { + data = read_action(env, clazz, j2p_memory_name_cstr, action_data); + } catch (const std::exception &e) { + env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what()); + return nullptr; + } env->ReleaseStringUTFChars(p2j_memory_name, j2p_memory_name_cstr); return data; } @@ -169,12 +180,16 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_writeObservationImpl( env->GetByteArrayElements(observation_data, nullptr); jsize observation_data_size = env->GetArrayLength(observation_data); - write_observation( - p2j_memory_name_cstr, - j2p_memory_name_cstr, - reinterpret_cast(observation_data_ptr), - static_cast(observation_data_size) - ); + try { + write_observation( + p2j_memory_name_cstr, + j2p_memory_name_cstr, + reinterpret_cast(observation_data_ptr), + static_cast(observation_data_size) + ); + } catch (const std::exception &e) { + env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what()); + } env->ReleaseStringUTFChars(j2p_memory_name, j2p_memory_name_cstr); env->ReleaseStringUTFChars(p2j_memory_name, p2j_memory_name_cstr); From 890666b7704d61f3d7bd1489d8ac8d55943be150 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 12:59:55 +0900 Subject: [PATCH 50/91] =?UTF-8?q?=E2=9C=A8=20Interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc.cpp | 10 ++++------ src/cpp/ipc.h | 6 ++---- src/cpp/ipc_boost.hpp | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/cpp/ipc.cpp b/src/cpp/ipc.cpp index 2e576fdd..4de98ea0 100644 --- a/src/cpp/ipc.cpp +++ b/src/cpp/ipc.cpp @@ -80,16 +80,14 @@ int initialize_shared_memory( } } -void write_to_shared_memory( - const char *memory_name, const char *data, const size_t data_size -) { - write_to_shared_memory_impl(memory_name, data, data_size); +void write_to_shared_memory(const char *memory_name, const char *data) { + write_to_shared_memory_impl(memory_name, data); } py::bytes read_from_shared_memory( - const char *memory_name, const char *management_memory_name + const char *p2j_memory_name, const char *j2p_memory_name ) { - return read_from_shared_memory_impl(memory_name, management_memory_name); + return read_from_shared_memory_impl(p2j_memory_name, j2p_memory_name); } void destroy_shared_memory(const char *memory_name) { diff --git a/src/cpp/ipc.h b/src/cpp/ipc.h index 2ce2ff13..e8d9c485 100644 --- a/src/cpp/ipc.h +++ b/src/cpp/ipc.h @@ -17,12 +17,10 @@ int initialize_shared_memory( bool find_free_port ); -void write_to_shared_memory( - const char *memory_name, const char *data, const size_t data_size -); +void write_to_shared_memory(const char *memory_name, const char *data); py::bytes read_from_shared_memory( - const char *memory_name, const char *management_memory_name + const char *p2j_memory_name, const char *j2p_memory_name ); void destroy_shared_memory(const char *memory_name); diff --git a/src/cpp/ipc_boost.hpp b/src/cpp/ipc_boost.hpp index 8d201c2c..f2b588f6 100644 --- a/src/cpp/ipc_boost.hpp +++ b/src/cpp/ipc_boost.hpp @@ -41,7 +41,7 @@ int create_shared_memory_impl( ); void write_to_shared_memory_impl( - const std::string &p2j_memory_name, const char *data, const size_t data_size + const std::string &p2j_memory_name, const char *data ); py::bytes read_from_shared_memory_impl( From bae620faa46e447098cf789c77aac88d7b8ea16a Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 13:02:34 +0900 Subject: [PATCH 51/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20ipc=20naem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_boost.cpp | 2 +- src/craftground/environment/boost_ipc.py | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index a1afa728..5c3a13d8 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -30,7 +30,7 @@ int create_shared_memory_impl( try { do { - p2j_memory_name = "/craftground_" + std::to_string(port) + "_p2j"; + p2j_memory_name = "craftground_" + std::to_string(port) + "_p2j"; if (shared_memory_exists(p2j_memory_name)) { if (find_free_port) { port++; diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index 8589b561..eb73dc63 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -36,12 +36,8 @@ def __init__( len(dummy_action_bytes), find_free_port, ) - self.initial_environment_shared_memory_name = ( - f"craftground_{port}_initial_environment" - ) - self.action_shared_memory_name = f"craftground_{port}_action" - self.observation_shared_memory_name = f"craftground_{port}_observation" - self.synchronization_shared_memory_name = f"craftground_{port}_synchronization" + self.p2j_shared_memory_name = f"craftground_{port}_p2j" + self.j2p_shared_memory_name = f"craftground_{port}_j2p" def send_action(self, action: ActionSpaceMessageV2): action_bytes: bytes = action.SerializeToString() From 9a0133354ef939a004d7167ed191d115fec6ddaa Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 13:56:14 +0900 Subject: [PATCH 52/91] =?UTF-8?q?=F0=9F=90=9B=20Fx=20ipc=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/develop.md | 6 ++- pyproject.toml | 2 +- src/cpp/ipc.cpp | 5 +- src/cpp/ipc.h | 6 ++- src/cpp/ipc_boost.cpp | 46 +++++++++++++--- .../minecraftenv/FramebufferCapturer.kt | 22 ++++---- .../kyhsgeekcode/minecraftenv/MessageIO.kt | 21 ++++++-- .../kyhsgeekcode/minecraftenv/MinecraftEnv.kt | 54 +++++++++++-------- src/craftground/environment/boost_ipc.py | 21 +++++--- src/craftground/environment/environment.py | 3 +- 10 files changed, 130 insertions(+), 56 deletions(-) diff --git a/docs/develop.md b/docs/develop.md index 034ed68d..48571413 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -73,6 +73,10 @@ python -m pip install coverage pytest cd build cmake .. cmake --build . -PYTHONPATH=./build:src/craftground coverage run --source=src/craftground -m pytest tests/python/unit/ +cd .. +ln -s build/*.dylib craftground/src/ +ln -s build/*.so craftground/src/ +ln -s build/*.pyd craftground/src/ +PYTHONPATH=./build:src coverage run --source=src/craftground -m pytest tests/python/unit/ coverage report ``` \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 93a85224..2301f02b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ keywords = ["minecraft", "reinforcement learning", "environment"] license = {file = "LICENSE"} name = "craftground" readme = "README.md" -version = "2.6.2" +version = "2.7.0" dependencies = [ "gymnasium", diff --git a/src/cpp/ipc.cpp b/src/cpp/ipc.cpp index 4de98ea0..31d55ad0 100644 --- a/src/cpp/ipc.cpp +++ b/src/cpp/ipc.cpp @@ -80,8 +80,8 @@ int initialize_shared_memory( } } -void write_to_shared_memory(const char *memory_name, const char *data) { - write_to_shared_memory_impl(memory_name, data); +void write_to_shared_memory(const char *p2j_memory_name, const char *data) { + write_to_shared_memory_impl(p2j_memory_name, data); } py::bytes read_from_shared_memory( @@ -102,6 +102,7 @@ PYBIND11_MODULE(craftground_native, m) { m.def("write_to_shared_memory", &write_to_shared_memory); m.def("read_from_shared_memory", &read_from_shared_memory); m.def("destroy_shared_memory", &destroy_shared_memory); + m.def("shared_memory_exists", &shared_memory_exists); #ifdef VERSION_INFO m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); diff --git a/src/cpp/ipc.h b/src/cpp/ipc.h index e8d9c485..7ec3bb26 100644 --- a/src/cpp/ipc.h +++ b/src/cpp/ipc.h @@ -2,6 +2,8 @@ #define __IPC_H__ #include +#include + namespace py = pybind11; py::object initialize_from_mach_port(unsigned int machPort, int width, int height); @@ -17,7 +19,7 @@ int initialize_shared_memory( bool find_free_port ); -void write_to_shared_memory(const char *memory_name, const char *data); +void write_to_shared_memory(const char *p2j_memory_name, const char *data); py::bytes read_from_shared_memory( const char *p2j_memory_name, const char *j2p_memory_name @@ -25,4 +27,6 @@ py::bytes read_from_shared_memory( void destroy_shared_memory(const char *memory_name); +bool shared_memory_exists(const std::string &name); + #endif \ No newline at end of file diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 5c3a13d8..5cceab19 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -25,19 +25,22 @@ int create_shared_memory_impl( size_t action_size, bool find_free_port ) { - std::string p2j_memory_name; + std::string p2j_memory_name, j2p_memory_name; bool found_free_port = false; try { do { p2j_memory_name = "craftground_" + std::to_string(port) + "_p2j"; - if (shared_memory_exists(p2j_memory_name)) { + j2p_memory_name = "craftground_" + std::to_string(port) + "_j2p"; + if (shared_memory_exists(p2j_memory_name) || + shared_memory_exists(j2p_memory_name)) { if (find_free_port) { port++; continue; } else { throw std::runtime_error( - "Shared memory " + p2j_memory_name + " already exists" + "Shared memory " + p2j_memory_name + " or " + + j2p_memory_name + " already exists" ); } } @@ -57,12 +60,12 @@ int create_shared_memory_impl( << std::endl; } - managed_shared_memory sharedMemory; + managed_shared_memory p2jSharedMemory, j2pSharedMemory; try { - sharedMemory = managed_shared_memory( + p2jSharedMemory = managed_shared_memory( create_only, p2j_memory_name.c_str(), - 1024 // Too small size failes to allocate + 1024 // Too small size fails to allocate ); } catch (const interprocess_exception &e) { std::cerr << e.what() << std::endl; @@ -71,8 +74,27 @@ int create_shared_memory_impl( throw std::runtime_error(e.what()); } + try { + j2pSharedMemory = managed_shared_memory( + create_only, + j2p_memory_name.c_str(), + 1024 // Too small size fails to allocate + ); + } catch (const interprocess_exception &e) { + std::cerr << e.what() << std::endl; + std::cerr << "Failed to initialize shared memory creating" + << j2p_memory_name << " : errno=" << errno << std::endl; + throw std::runtime_error(e.what()); + } + J2PSharedMemoryLayout *j2pLayout = static_cast( + j2pSharedMemory.allocate(sizeof(J2PSharedMemoryLayout)) + ); + j2pLayout->layout_size = sizeof(J2PSharedMemoryLayout); + j2pLayout->data_offset = sizeof(J2PSharedMemoryLayout); + j2pLayout->data_size = 0; + SharedMemoryLayout *layout = - static_cast(sharedMemory.allocate( + static_cast(p2jSharedMemory.allocate( sizeof(SharedMemoryLayout) + action_size + data_size )); layout->layout_size = sizeof(SharedMemoryLayout); @@ -119,7 +141,15 @@ void write_to_shared_memory_impl( py::bytes read_from_shared_memory_impl( const std::string &p2j_memory_name, const std::string &j2p_memory_name ) { - managed_shared_memory p2jMemory(open_only, p2j_memory_name.c_str()); + managed_shared_memory p2jMemory; + try { + p2jMemory = managed_shared_memory(open_only, p2j_memory_name.c_str()); + } catch (const interprocess_exception &e) { + std::cerr << e.what() << std::endl; + std::cerr << "Failed to open shared memory to read observation: " << p2j_memory_name << " errno=" << errno + << std::endl; + throw std::runtime_error(e.what()); + } SharedMemoryLayout *p2jLayout = static_cast(p2jMemory.get_address()); diff --git a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt index 55ed1a49..aa9fde30 100644 --- a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt +++ b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt @@ -149,32 +149,32 @@ object FramebufferCapturer { private var actionBuffer: ByteArray? = null - external fun readInitialEnvironmentImpl(initialEnvironmentMemoryName: String): ByteArray + external fun readInitialEnvironmentImpl(p2jMemoryName: String): ByteArray external fun readActionImpl( - actionMemoryName: String, + p2jMemoryName: String, actionData: ByteArray?, ): ByteArray external fun writeObservationImpl( - observationMemoryName: String, - synchronizationMemoryName: String, + p2jMemoryName: String, + j2pMemoryName: String, observationData: ByteArray, ) - fun readInitialEnvironment(initialEnvironmentMemoryName: String): InitialEnvironment.InitialEnvironmentMessage = - InitialEnvironment.InitialEnvironmentMessage.parseFrom(readInitialEnvironmentImpl(initialEnvironmentMemoryName)) + fun readInitialEnvironment(p2jMemoryName: String): InitialEnvironment.InitialEnvironmentMessage = + InitialEnvironment.InitialEnvironmentMessage.parseFrom(readInitialEnvironmentImpl(p2jMemoryName)) - fun readAction(actionMemoryName: String): ActionSpace.ActionSpaceMessageV2 { - actionBuffer = readActionImpl(actionMemoryName, actionBuffer) + fun readAction(p2jMemoryName: String): ActionSpace.ActionSpaceMessageV2 { + actionBuffer = readActionImpl(p2jMemoryName, actionBuffer) return ActionSpace.ActionSpaceMessageV2.parseFrom(actionBuffer) } fun writeObservation( - observationMemoryName: String, - synchronizationMemoryName: String, + p2jMemoryName: String, + j2pMemoryName: String, observationData: ObservationSpace.ObservationSpaceMessage, ) { - writeObservationImpl(observationMemoryName, synchronizationMemoryName, observationData.toByteArray()) + writeObservationImpl(p2jMemoryName, j2pMemoryName, observationData.toByteArray()) } } diff --git a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt index 1cb0e953..7b9fd9b0 100644 --- a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt +++ b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt @@ -179,12 +179,27 @@ class NamedPipeMessageIO( } } - override fun writeObservation(observationSpace: ObservationSpace.ObservationSpaceMessage) { + override fun writeObservation(observation: ObservationSpace.ObservationSpaceMessage) { FileOutputStream(writePipePath).use { fos -> LittleEndianDataOutputStream(fos).use { dos -> - dos.writeInt(observationSpace.serializedSize) - observationSpace.writeTo(dos) + dos.writeInt(observation.serializedSize) + observation.writeTo(dos) } } } } + +class SharedMemoryMessageIO( + val port: Int, +) : MessageIO { + private val p2jMemoryName = "craftground_${port}_p2j" + private val j2pMemoryName = "craftground_${port}_j2p" + + override fun readAction(): ActionSpace.ActionSpaceMessageV2 = FramebufferCapturer.readAction(p2jMemoryName) + + override fun readInitialEnvironment(): InitialEnvironment.InitialEnvironmentMessage = + FramebufferCapturer.readInitialEnvironment(p2jMemoryName) + + override fun writeObservation(observation: ObservationSpace.ObservationSpaceMessage) = + FramebufferCapturer.writeObservation(j2pMemoryName, p2jMemoryName, observation) +} diff --git a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MinecraftEnv.kt b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MinecraftEnv.kt index 78d75a83..e0b532ac 100644 --- a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MinecraftEnv.kt +++ b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MinecraftEnv.kt @@ -91,6 +91,7 @@ class MinecraftEnv : private val variableCommandsAfterReset = mutableListOf() private var skipSync = false private var ioPhase = IOPhase.BEGINNING + private var useSharedMemory = false override fun onInitialize() { val isLdPreloadSet = System.getenv("LD_PRELOAD") @@ -112,28 +113,39 @@ class MinecraftEnv : } doPrintWithTime = verbose - val isWindows = System.getProperty("os.name").lowercase().contains("win") - if (isWindows) { - val serverSocket = ServerSocketChannel.open() - serverSocket.bind(InetSocketAddress("127.0.0.1", port)) - csvLogger.profileStartPrint("Minecraft_env/onInitialize/Accept") - socket = serverSocket.accept() - csvLogger.profileEndPrint("Minecraft_env/onInitialize/Accept") - messageIO = DomainSocketMessageIO(socket) + useSharedMemory = + when (val useSharedMemoryStr = System.getenv("USE_SHARED_MEMORY")) { + "1" -> true + "0" -> false + else -> useSharedMemoryStr?.toBoolean() ?: false + } + + if (useSharedMemory) { + messageIO = SharedMemoryMessageIO(port) } else { - val socketFilePath = Path.of("/tmp/minecraftrl_$port.sock") - socketFilePath.toFile().deleteOnExit() - csvLogger.log("Connecting to $port") - printWithTime("Connecting to $port") - Files.deleteIfExists(socketFilePath) - val serverSocket = - ServerSocketChannel - .open(StandardProtocolFamily.UNIX) - .bind(UnixDomainSocketAddress.of(socketFilePath)) - csvLogger.profileStartPrint("Minecraft_env/onInitialize/Accept") - socket = serverSocket.accept() - csvLogger.profileEndPrint("Minecraft_env/onInitialize/Accept") - messageIO = DomainSocketMessageIO(socket) + val isWindows = System.getProperty("os.name").lowercase().contains("win") + if (isWindows) { + val serverSocket = ServerSocketChannel.open() + serverSocket.bind(InetSocketAddress("127.0.0.1", port)) + csvLogger.profileStartPrint("Minecraft_env/onInitialize/Accept") + socket = serverSocket.accept() + csvLogger.profileEndPrint("Minecraft_env/onInitialize/Accept") + messageIO = DomainSocketMessageIO(socket) + } else { + val socketFilePath = Path.of("/tmp/minecraftrl_$port.sock") + socketFilePath.toFile().deleteOnExit() + csvLogger.log("Connecting to $port") + printWithTime("Connecting to $port") + Files.deleteIfExists(socketFilePath) + val serverSocket = + ServerSocketChannel + .open(StandardProtocolFamily.UNIX) + .bind(UnixDomainSocketAddress.of(socketFilePath)) + csvLogger.profileStartPrint("Minecraft_env/onInitialize/Accept") + socket = serverSocket.accept() + csvLogger.profileEndPrint("Minecraft_env/onInitialize/Accept") + messageIO = DomainSocketMessageIO(socket) + } } } catch (e: IOException) { throw RuntimeException(e) diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index eb73dc63..5dfc165d 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -29,6 +29,7 @@ def __init__( # Get the length of the action space message dummy_action: ActionSpaceMessageV2 = ActionSpaceMessageV2() dummy_action_bytes: bytes = dummy_action.SerializeToString() + self.find_free_port = find_free_port self.port = initialize_shared_memory( int(self.port), initial_environment_bytes, @@ -41,24 +42,30 @@ def __init__( def send_action(self, action: ActionSpaceMessageV2): action_bytes: bytes = action.SerializeToString() - write_to_shared_memory( - self.action_shared_memory_name, action_bytes, len(action_bytes) - ) + write_to_shared_memory(self.p2j_shared_memory_name, action_bytes) def read_observation(self) -> bytes: return read_from_shared_memory( - self.observation_shared_memory_name, self.synchronization_shared_memory_name + self.p2j_shared_memory_name, self.j2p_shared_memory_name ) def destroy(self): - destroy_shared_memory(self.action_shared_memory_name) - destroy_shared_memory(self.observation_shared_memory_name) - destroy_shared_memory(self.synchronization_shared_memory_name) + destroy_shared_memory(self.p2j_shared_memory_name) + destroy_shared_memory(self.j2p_shared_memory_name) # Java destroys the initial environment shared memory # destroy_shared_memory(self.initial_environment_shared_memory_name) def is_alive(self) -> bool: return True + def remove_orphan_java_processes(self): + pass + + def start_communication(self): + # wait until the j2p shared memory is created + while True: + if self.is_alive(): + break + def __del__(self): self.destroy() diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 2c65d77d..2ef9319a 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -204,9 +204,10 @@ def ensure_alive(self, fast_reset, extra_commands, seed: int): def start_server(self, seed: int): # Remove orphan java processes self.ipc.remove_orphan_java_processes() - # Prepare command TODO + # Prepare command my_env = os.environ.copy() my_env["PORT"] = str(self.ipc.port) + my_env["USE_SHARED_MEMORY"] = str(int(self.use_shared_memory)) my_env["VERBOSE"] = str(int(self.verbose_jvm)) if self.track_native_memory: my_env["CRAFTGROUND_JVM_NATIVE_TRACKING"] = "detail" From f4db0b6b564e1bb4f242f4ac2882111847f03ed0 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 22 Jan 2025 13:58:52 +0900 Subject: [PATCH 53/91] =?UTF-8?q?=E2=9C=A8=20Wait=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/boost_ipc.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index 5dfc165d..1be93a50 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -1,3 +1,4 @@ +import time from ..environment.ipc_interface import IPCInterface from ..proto.action_space_pb2 import ActionSpaceMessageV2 from ..proto.initial_environment_pb2 import InitialEnvironmentMessage @@ -13,6 +14,7 @@ write_to_shared_memory, # noqa read_from_shared_memory, # noqa destroy_shared_memory, # noqa + shared_memory_exists, # noqa ) @@ -63,9 +65,23 @@ def remove_orphan_java_processes(self): def start_communication(self): # wait until the j2p shared memory is created + wait_time = 1 + next_output = 1 # 3 7 15 31 63 127 255 seconds passed while True: - if self.is_alive(): + if shared_memory_exists(self.j2p_shared_memory_name): break + self.logger.log( + f"Waiting for Java process to create shared memory {self.j2p_shared_memory_name}" + ) + time.sleep(wait_time) + wait_time *= 2 + if wait_time > 1024: + raise Exception( + f"Java process failed to create shared memory {self.j2p_shared_memory_name}" + ) + self.logger.log( + f"Java process created shared memory {self.j2p_shared_memory_name}" + ) def __del__(self): self.destroy() From eef37329a2a3285bb1f1dd2a69163ad40ad68b5a Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Fri, 24 Jan 2025 16:02:46 +0900 Subject: [PATCH 54/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20gradle=20cmd=20path?= =?UTF-8?q?=20thanks=20to=20https://github.com/yhs0602/CraftGround/issues/?= =?UTF-8?q?37?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/communication.py | 0 src/craftground/environment/environment.py | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) delete mode 100644 src/craftground/environment/communication.py diff --git a/src/craftground/environment/communication.py b/src/craftground/environment/communication.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 2ef9319a..8fd41d07 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -224,7 +224,10 @@ def start_server(self, seed: int): pass # self.update_override_resolutions(options_txt_path) - cmd = f"./gradlew runClient -w --no-daemon" # --args="--width {self.initial_env.imageSizeX} --height {self.initial_env.imageSizeY}"' + if os.name == "nt": + cmd = f".\\gradlew runClient -w --no-daemon" + else: + cmd = f"./gradlew runClient -w --no-daemon" # --args="--width {self.initial_env.imageSizeX} --height {self.initial_env.imageSizeY}"' if self.use_vglrun: cmd = f"vglrun {cmd}" if self.ld_preload: From b41e13315e5a59e52243f8068e3b25a9d3d056b7 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 28 Jan 2025 18:04:51 +0900 Subject: [PATCH 55/91] =?UTF-8?q?=F0=9F=8E=A8=20Format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_boost.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 5cceab19..0759fc56 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -146,8 +146,8 @@ py::bytes read_from_shared_memory_impl( p2jMemory = managed_shared_memory(open_only, p2j_memory_name.c_str()); } catch (const interprocess_exception &e) { std::cerr << e.what() << std::endl; - std::cerr << "Failed to open shared memory to read observation: " << p2j_memory_name << " errno=" << errno - << std::endl; + std::cerr << "Failed to open shared memory to read observation: " + << p2j_memory_name << " errno=" << errno << std::endl; throw std::runtime_error(e.what()); } SharedMemoryLayout *p2jLayout = From 022157870e6c66c3cb97ee4d100b617a156e3433 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 28 Jan 2025 18:17:09 +0900 Subject: [PATCH 56/91] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs,=20update=20?= =?UTF-8?q?parallel=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 26 +++++++++++++++++++---- src/craftground/MinecraftEnv/build.gradle | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 13128ade..2655c086 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,7 @@ ```bash conda create -n my_experiment_env python=3.11 conda activate my_experiment_env -conda install conda-forge::openjdk=21 cmake -sudo apt install libglew-dev +conda install conda-forge::openjdk=21 conda-forge::cmake conda-forge::glew-dev conda-forge::libpng conda-forge::libzlib conda-forge::libopengl conda-forge::libflite pip install craftground ``` @@ -56,7 +55,27 @@ pip3 install cmake # You need latest cmake, not the one provided by apt-get pip3 install craftground ``` -### Setup Headless Environment +### Windows Setup +#### Resolve Windows Path Length Limitation +Note: you may need to enable long file path due to windows limitation. You can enable it by editing registry as mentioned [here](https://docs.python.org/3/using/windows.html#removing-the-max-path-limitation) + +> In the latest versions of Windows, this limitation can be expanded to approximately 32,000 characters. Your administrator will need to activate the “Enable Win32 long paths” group policy, or set LongPathsEnabled to 1 in the registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem. + +The following command does the same as the above instruction: +```cmd +reg add "HKLM\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f +``` + +#### Resolve Conda Installation Issues +Using conda is recommended for Windows users. You can install conda from [here](https://docs.anaconda.com/miniconda/). Make sure to execute +```powershell +powershell -ExecutionPolicy Bypass +conda init powershell +``` +if you are using PowerShell, in the anaconda powershell prompt. + + +### Setup Headless Environment (Linux) Refer to [Headless Environment Setup](docs/headless.md) for setting up a headless environment. ### Install development version @@ -66,7 +85,6 @@ pip install git+https://github.com/yhs0602/CraftGround.git@dev ## Run your first experiment ### Example repositories -- Check [the demo repository](https://github.com/yhs0602/CraftGround-Baselines3) for detailed examples. - Check [the example repository](https://github.com/yhs0602/minecraft-simulator-benchmark) for benchmarking experiments. ### Example code diff --git a/src/craftground/MinecraftEnv/build.gradle b/src/craftground/MinecraftEnv/build.gradle index eeebb128..b4e33d6d 100644 --- a/src/craftground/MinecraftEnv/build.gradle +++ b/src/craftground/MinecraftEnv/build.gradle @@ -73,7 +73,7 @@ tasks.register('configureCppProject', Exec) { } tasks.register('compileCpp', Exec) { - commandLine 'cmake', '--build', '.', '--config', 'Release' + commandLine 'cmake', '--build', '.', '--config', 'Release', '--parallel', '8' inputs.dir("src/main/cpp") def os = org.gradle.internal.os.OperatingSystem.current() if (os.isLinux()) { From 57bbdc0c4bff5cd60c807813b90eec479f1670f6 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 28 Jan 2025 21:24:54 +0900 Subject: [PATCH 57/91] =?UTF-8?q?=F0=9F=92=9A=20Cache=20boost=20in=20ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cmake-build.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/cmake-build.yml b/.github/workflows/cmake-build.yml index 5a57751d..b9fb53da 100644 --- a/.github/workflows/cmake-build.yml +++ b/.github/workflows/cmake-build.yml @@ -78,6 +78,16 @@ jobs: run: | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + - name: Set up cache for CMake FetchContent + uses: actions/cache@v3 + with: + path: | + ${GITHUB_WORKSPACE}/.build/_deps + ${HOME}/.cmake/packages + key: ${{ runner.os }}-fetchcontent-${{ hashFiles('CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-fetchcontent- + - name: Configure CMake for python part # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 66f540eec029eefe2d4d42f746194d7ddfb359ea Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 28 Jan 2025 21:32:44 +0900 Subject: [PATCH 58/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20cmake=20cache=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cmake-build-cuda.yml | 10 ++++++++++ .github/workflows/cmake-build.yml | 2 +- .github/workflows/gradle.yml | 10 ++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmake-build-cuda.yml b/.github/workflows/cmake-build-cuda.yml index d836a204..c0f79652 100644 --- a/.github/workflows/cmake-build-cuda.yml +++ b/.github/workflows/cmake-build-cuda.yml @@ -39,6 +39,16 @@ jobs: run: | nvcc -V + - name: Set up cache for CMake FetchContent + uses: actions/cache@v3 + with: + path: | + ${GITHUB_WORKSPACE}/build/_deps + ${HOME}/.cmake/packages + key: ${{ runner.os }}-fetchcontent-${{ hashFiles('CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-fetchcontent- + - name: Configure CMake for python part # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type diff --git a/.github/workflows/cmake-build.yml b/.github/workflows/cmake-build.yml index b9fb53da..a4df43a4 100644 --- a/.github/workflows/cmake-build.yml +++ b/.github/workflows/cmake-build.yml @@ -82,7 +82,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/.build/_deps + ${GITHUB_WORKSPACE}/build/_deps ${HOME}/.cmake/packages key: ${{ runner.os }}-fetchcontent-${{ hashFiles('CMakeLists.txt') }} restore-keys: | diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index d5a72247..139b068e 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -94,6 +94,16 @@ jobs: uses: gradle/actions/setup-gradle@v4 # with: # gradle-version: "8.8" # Quotes required to prevent YAML converting to number + + - name: Set up cache for CMake FetchContent + uses: actions/cache@v3 + with: + path: | + ${GITHUB_WORKSPACE}/src/craftground/MinecraftEnv/_deps + ${HOME}/.cmake/packages + key: ${{ runner.os }}-fetchcontent-${{ hashFiles('src/craftground/MinecraftEnv/CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-fetchcontent- - name: Build with Gradle Wrapper run: ./gradlew build From 6d505771bd070e4631117b98c03d12c0ed36101c Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 28 Jan 2025 21:46:44 +0900 Subject: [PATCH 59/91] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20Ci=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cmake-build-cuda.yml | 2 +- .github/workflows/cmake-build.yml | 2 +- .github/workflows/gradle.yml | 6 +++--- .../workflows/publish-package-cuda-linux.yml | 18 ++++++++++++++++++ .../workflows/publish-package-cuda-windows.yml | 18 ++++++++++++++++++ .github/workflows/publish-package-nocuda.yml | 18 ++++++++++++++++++ .github/workflows/python-ci.yml | 10 ++++++++++ 7 files changed, 69 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cmake-build-cuda.yml b/.github/workflows/cmake-build-cuda.yml index c0f79652..2b0d011d 100644 --- a/.github/workflows/cmake-build-cuda.yml +++ b/.github/workflows/cmake-build-cuda.yml @@ -43,7 +43,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/build/_deps + ${GITHUB_WORKSPACE}/CraftGround/build/_deps ${HOME}/.cmake/packages key: ${{ runner.os }}-fetchcontent-${{ hashFiles('CMakeLists.txt') }} restore-keys: | diff --git a/.github/workflows/cmake-build.yml b/.github/workflows/cmake-build.yml index a4df43a4..91e98e16 100644 --- a/.github/workflows/cmake-build.yml +++ b/.github/workflows/cmake-build.yml @@ -82,7 +82,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/build/_deps + ${GITHUB_WORKSPACE}/CraftGround/build/_deps ${HOME}/.cmake/packages key: ${{ runner.os }}-fetchcontent-${{ hashFiles('CMakeLists.txt') }} restore-keys: | diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 139b068e..c57d7aef 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -14,7 +14,7 @@ on: branches: [ "main" ] jobs: - build: + gradle_build: runs-on: ${{ matrix.os }} strategy: # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. @@ -99,9 +99,9 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/src/craftground/MinecraftEnv/_deps + ${GITHUB_WORKSPACE}/CraftGround/src/craftground/MinecraftEnv/_deps ${HOME}/.cmake/packages - key: ${{ runner.os }}-fetchcontent-${{ hashFiles('src/craftground/MinecraftEnv/CMakeLists.txt') }} + key: ${{ runner.os }}-fetchcontent-${{ hashFiles('src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent- diff --git a/.github/workflows/publish-package-cuda-linux.yml b/.github/workflows/publish-package-cuda-linux.yml index 3261ca19..7f673b8c 100644 --- a/.github/workflows/publish-package-cuda-linux.yml +++ b/.github/workflows/publish-package-cuda-linux.yml @@ -32,6 +32,24 @@ jobs: - uses: astral-sh/setup-uv@v4 + - name: Set up cache for CMake FetchContent python cache + uses: actions/cache@v3 + with: + path: | + ${GITHUB_WORKSPACE}/CraftGround/build/_deps + key: ${{ runner.os }}-fetchcontent-python-${{ hashFiles('CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-fetchcontent-python- + + - name: Set up cache for CMake FetchContent gradle cache + uses: actions/cache@v3 + with: + path: | + ${GITHUB_WORKSPACE}/CraftGround/src/craftground/MinecraftEnv/_deps + key: ${{ runner.os }}-fetchcontent-gradle-${{ hashFiles('src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-fetchcontent-gradle- + - name: Build wheels uses: pypa/cibuildwheel@v2.22.0 env: diff --git a/.github/workflows/publish-package-cuda-windows.yml b/.github/workflows/publish-package-cuda-windows.yml index ccb5f33e..a30c8d25 100644 --- a/.github/workflows/publish-package-cuda-windows.yml +++ b/.github/workflows/publish-package-cuda-windows.yml @@ -46,6 +46,24 @@ jobs: - uses: actions/checkout@v4 with: submodules: true + + - name: Set up cache for CMake FetchContent python cache + uses: actions/cache@v3 + with: + path: | + ${GITHUB_WORKSPACE}/CraftGround/build/_deps + key: ${{ runner.os }}-fetchcontent-python-${{ hashFiles('CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-fetchcontent-python- + + - name: Set up cache for CMake FetchContent gradle cache + uses: actions/cache@v3 + with: + path: | + ${GITHUB_WORKSPACE}/CraftGround/src/craftground/MinecraftEnv/_deps + key: ${{ runner.os }}-fetchcontent-gradle-${{ hashFiles('src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-fetchcontent-gradle- - name: Set up JDK 21 uses: actions/setup-java@v4 diff --git a/.github/workflows/publish-package-nocuda.yml b/.github/workflows/publish-package-nocuda.yml index 12ec8abb..06a27e77 100644 --- a/.github/workflows/publish-package-nocuda.yml +++ b/.github/workflows/publish-package-nocuda.yml @@ -38,6 +38,24 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.12' + + - name: Set up cache for CMake FetchContent python cache + uses: actions/cache@v3 + with: + path: | + ${GITHUB_WORKSPACE}/CraftGround/build/_deps + key: ${{ runner.os }}-fetchcontent-python-${{ hashFiles('CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-fetchcontent-python- + + - name: Set up cache for CMake FetchContent gradle cache + uses: actions/cache@v3 + with: + path: | + ${GITHUB_WORKSPACE}/CraftGround/src/craftground/MinecraftEnv/_deps + key: ${{ runner.os }}-fetchcontent-gradle-${{ hashFiles('src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-fetchcontent-gradle- - name: Build wheels uses: pypa/cibuildwheel@v2.22.0 diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index dcc65f67..91487c0c 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -36,6 +36,16 @@ jobs: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + + - name: Set up cache for CMake FetchContent python cache + uses: actions/cache@v3 + with: + path: | + ${GITHUB_WORKSPACE}/CraftGround/build/_deps + key: ${{ runner.os }}-fetchcontent-python-${{ hashFiles('CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-fetchcontent-python- + - name: Test with pytest run: | # build cpp extension first From 6f7fe07215fbee5fff65109619826f0f5499ddf2 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 28 Jan 2025 21:57:33 +0900 Subject: [PATCH 60/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20path=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cmake-build-cuda.yml | 3 +-- .github/workflows/cmake-build.yml | 5 ++--- .github/workflows/gradle.yml | 3 +-- .github/workflows/publish-package-cuda-linux.yml | 4 ++-- .github/workflows/publish-package-cuda-windows.yml | 4 ++-- .github/workflows/publish-package-nocuda.yml | 4 ++-- .github/workflows/python-ci.yml | 2 +- 7 files changed, 11 insertions(+), 14 deletions(-) diff --git a/.github/workflows/cmake-build-cuda.yml b/.github/workflows/cmake-build-cuda.yml index 2b0d011d..e7785908 100644 --- a/.github/workflows/cmake-build-cuda.yml +++ b/.github/workflows/cmake-build-cuda.yml @@ -43,8 +43,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/CraftGround/build/_deps - ${HOME}/.cmake/packages + ${GITHUB_WORKSPACE}/build/_deps key: ${{ runner.os }}-fetchcontent-${{ hashFiles('CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent- diff --git a/.github/workflows/cmake-build.yml b/.github/workflows/cmake-build.yml index 91e98e16..7bc7dcb6 100644 --- a/.github/workflows/cmake-build.yml +++ b/.github/workflows/cmake-build.yml @@ -9,7 +9,7 @@ on: branches: [ "main" ] jobs: - build: + cmake-build: runs-on: ${{ matrix.os }} strategy: @@ -82,8 +82,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/CraftGround/build/_deps - ${HOME}/.cmake/packages + ${GITHUB_WORKSPACE}/build/_deps key: ${{ runner.os }}-fetchcontent-${{ hashFiles('CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent- diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index c57d7aef..86590d99 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -99,8 +99,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/CraftGround/src/craftground/MinecraftEnv/_deps - ${HOME}/.cmake/packages + ${GITHUB_WORKSPACE}/src/craftground/MinecraftEnv/_deps key: ${{ runner.os }}-fetchcontent-${{ hashFiles('src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent- diff --git a/.github/workflows/publish-package-cuda-linux.yml b/.github/workflows/publish-package-cuda-linux.yml index 7f673b8c..05eca44f 100644 --- a/.github/workflows/publish-package-cuda-linux.yml +++ b/.github/workflows/publish-package-cuda-linux.yml @@ -36,7 +36,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/CraftGround/build/_deps + ${GITHUB_WORKSPACE}/build/_deps key: ${{ runner.os }}-fetchcontent-python-${{ hashFiles('CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-python- @@ -45,7 +45,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/CraftGround/src/craftground/MinecraftEnv/_deps + ${GITHUB_WORKSPACE}/src/craftground/MinecraftEnv/_deps key: ${{ runner.os }}-fetchcontent-gradle-${{ hashFiles('src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-gradle- diff --git a/.github/workflows/publish-package-cuda-windows.yml b/.github/workflows/publish-package-cuda-windows.yml index a30c8d25..f2d94426 100644 --- a/.github/workflows/publish-package-cuda-windows.yml +++ b/.github/workflows/publish-package-cuda-windows.yml @@ -51,7 +51,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/CraftGround/build/_deps + ${GITHUB_WORKSPACE}/build/_deps key: ${{ runner.os }}-fetchcontent-python-${{ hashFiles('CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-python- @@ -60,7 +60,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/CraftGround/src/craftground/MinecraftEnv/_deps + ${GITHUB_WORKSPACE}/src/craftground/MinecraftEnv/_deps key: ${{ runner.os }}-fetchcontent-gradle-${{ hashFiles('src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-gradle- diff --git a/.github/workflows/publish-package-nocuda.yml b/.github/workflows/publish-package-nocuda.yml index 06a27e77..3d2096cf 100644 --- a/.github/workflows/publish-package-nocuda.yml +++ b/.github/workflows/publish-package-nocuda.yml @@ -43,7 +43,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/CraftGround/build/_deps + ${GITHUB_WORKSPACE}/build/_deps key: ${{ runner.os }}-fetchcontent-python-${{ hashFiles('CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-python- @@ -52,7 +52,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/CraftGround/src/craftground/MinecraftEnv/_deps + ${GITHUB_WORKSPACE}/src/craftground/MinecraftEnv/_deps key: ${{ runner.os }}-fetchcontent-gradle-${{ hashFiles('src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-gradle- diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 91487c0c..3dbf6177 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -41,7 +41,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/CraftGround/build/_deps + ${GITHUB_WORKSPACE}/build/_deps key: ${{ runner.os }}-fetchcontent-python-${{ hashFiles('CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-python- From 390b823af1aad9fcd73442a6fabb6a4a6ee0bc47 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 28 Jan 2025 22:05:27 +0900 Subject: [PATCH 61/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20cache=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cmake-build-cuda.yml | 2 +- .github/workflows/cmake-build.yml | 2 +- .github/workflows/gradle.yml | 2 +- .github/workflows/publish-package-cuda-linux.yml | 4 ++-- .github/workflows/publish-package-cuda-windows.yml | 4 ++-- .github/workflows/publish-package-nocuda.yml | 4 ++-- .github/workflows/python-ci.yml | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/cmake-build-cuda.yml b/.github/workflows/cmake-build-cuda.yml index e7785908..203ffa5a 100644 --- a/.github/workflows/cmake-build-cuda.yml +++ b/.github/workflows/cmake-build-cuda.yml @@ -43,7 +43,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/build/_deps + build/_deps key: ${{ runner.os }}-fetchcontent-${{ hashFiles('CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent- diff --git a/.github/workflows/cmake-build.yml b/.github/workflows/cmake-build.yml index 7bc7dcb6..d541cc1e 100644 --- a/.github/workflows/cmake-build.yml +++ b/.github/workflows/cmake-build.yml @@ -82,7 +82,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/build/_deps + build/_deps key: ${{ runner.os }}-fetchcontent-${{ hashFiles('CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent- diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 86590d99..14966978 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -99,7 +99,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/src/craftground/MinecraftEnv/_deps + src/craftground/MinecraftEnv/_deps key: ${{ runner.os }}-fetchcontent-${{ hashFiles('src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent- diff --git a/.github/workflows/publish-package-cuda-linux.yml b/.github/workflows/publish-package-cuda-linux.yml index 05eca44f..3756b3cb 100644 --- a/.github/workflows/publish-package-cuda-linux.yml +++ b/.github/workflows/publish-package-cuda-linux.yml @@ -36,7 +36,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/build/_deps + build/_deps key: ${{ runner.os }}-fetchcontent-python-${{ hashFiles('CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-python- @@ -45,7 +45,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/src/craftground/MinecraftEnv/_deps + src/craftground/MinecraftEnv/_deps key: ${{ runner.os }}-fetchcontent-gradle-${{ hashFiles('src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-gradle- diff --git a/.github/workflows/publish-package-cuda-windows.yml b/.github/workflows/publish-package-cuda-windows.yml index f2d94426..05ebc410 100644 --- a/.github/workflows/publish-package-cuda-windows.yml +++ b/.github/workflows/publish-package-cuda-windows.yml @@ -51,7 +51,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/build/_deps + build/_deps key: ${{ runner.os }}-fetchcontent-python-${{ hashFiles('CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-python- @@ -60,7 +60,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/src/craftground/MinecraftEnv/_deps + src/craftground/MinecraftEnv/_deps key: ${{ runner.os }}-fetchcontent-gradle-${{ hashFiles('src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-gradle- diff --git a/.github/workflows/publish-package-nocuda.yml b/.github/workflows/publish-package-nocuda.yml index 3d2096cf..e7091642 100644 --- a/.github/workflows/publish-package-nocuda.yml +++ b/.github/workflows/publish-package-nocuda.yml @@ -43,7 +43,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/build/_deps + build/_deps key: ${{ runner.os }}-fetchcontent-python-${{ hashFiles('CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-python- @@ -52,7 +52,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/src/craftground/MinecraftEnv/_deps + src/craftground/MinecraftEnv/_deps key: ${{ runner.os }}-fetchcontent-gradle-${{ hashFiles('src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-gradle- diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 3dbf6177..2854b9fe 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -41,7 +41,7 @@ jobs: uses: actions/cache@v3 with: path: | - ${GITHUB_WORKSPACE}/build/_deps + build/_deps key: ${{ runner.os }}-fetchcontent-python-${{ hashFiles('CMakeLists.txt') }} restore-keys: | ${{ runner.os }}-fetchcontent-python- From 05455220c4e13ce0f166af23995331ce82e84cbd Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 28 Jan 2025 23:22:39 +0900 Subject: [PATCH 62/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20ipc=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment.py | 995 --------------------- src/craftground/environment/boost_ipc.py | 4 + src/craftground/environment/environment.py | 2 + 3 files changed, 6 insertions(+), 995 deletions(-) delete mode 100644 src/craftground/environment.py diff --git a/src/craftground/environment.py b/src/craftground/environment.py deleted file mode 100644 index 3a869b6b..00000000 --- a/src/craftground/environment.py +++ /dev/null @@ -1,995 +0,0 @@ -import io -import os -import re -import signal -import socket -import struct -import subprocess -from enum import Enum -from time import sleep -from typing import Tuple, Optional, Union, List, Any, Dict - -import gymnasium as gym -import numpy as np -import psutil -from PIL import Image, ImageDraw -from gymnasium import spaces -from gymnasium.core import ActType, ObsType, RenderFrame -import torch - -from .action_space import ActionSpace -from .buffered_socket import BufferedSocket -from .csv_logger import CsvLogger, LogBackend -from .font import get_font -from .initial_environment_config import InitialEnvironmentConfig -from .minecraft import ( - wait_for_server, - send_fastreset2, - send_action_and_commands, - send_exit, -) -from .print_with_time import print_with_time -from .proto import observation_space_pb2 -from .screen_encoding_modes import ScreenEncodingMode - - -class ActionSpaceVersion(Enum): - V1_MINEDOJO = 1 - V2_MINERL_HUMAN = 2 - - -class ObservationTensorType(Enum): - NONE = 0 - CUDA_DLPACK = 1 - APPLE_TENSOR = 2 - - -class CraftGroundEnvironment(gym.Env): - def __init__( - self, - initial_env: InitialEnvironmentConfig, - action_space_version: ActionSpaceVersion = ActionSpaceVersion.V1_MINEDOJO, - verbose=False, - env_path=None, - port=8000, - find_free_port: bool = True, - render_action: bool = False, - render_alternating_eyes: bool = False, - use_terminate: bool = False, - cleanup_world: bool = True, # removes the world when the environment is closed - use_vglrun: bool = False, # use vglrun to run the server (headless 3d acceleration) - track_native_memory: bool = False, - ld_preload: Optional[str] = None, - native_debug: bool = False, - verbose_python: bool = False, - verbose_gradle: bool = False, - verbose_jvm: bool = False, - profile: bool = False, - ): - self.action_space_version = action_space_version - if action_space_version == ActionSpaceVersion.V1_MINEDOJO: - self.action_space = ActionSpace(6) - elif action_space_version == ActionSpaceVersion.V2_MINERL_HUMAN: - self.action_space = gym.spaces.Dict( - { - "attack": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "back": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "forward": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "jump": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "left": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "right": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "sneak": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "sprint": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "use": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "drop": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "inventory": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.1": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.2": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.3": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.4": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.5": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.6": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.7": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.8": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "hotbar.9": gym.spaces.Discrete(2), # 0 or 1 (boolean) - "camera": gym.spaces.Box( - low=np.array([-180, -180]), - high=np.array([180, 180]), - dtype=np.float32, - ), - # Camera pitch/yaw between -180 and 180 degrees - } - ) - else: - raise ValueError(f"Unknown action space version: {action_space_version}") - entity_info_space = gym.spaces.Dict( - { - "unique_name": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "translation_key": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "x": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.float64, - ), - "y": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.float64, - ), - "z": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.float64, - ), - "yaw": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.float64, - ), - "pitch": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.float64, - ), - "health": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.float64, - ), - } - ) - sound_entry_space = gym.spaces.Dict( - { - "translate_key": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32 - ), - "x": spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64), - "y": spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64), - "z": spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64), - "age": spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32), - } - ) - entities_within_distance_space = gym.spaces.Sequence(entity_info_space) - status_effect_space = gym.spaces.Dict( - { - "translation_key": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32 - ), - "amplifier": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32 - ), - "duration": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.int32 - ), - } - ) - self.observation_space = gym.spaces.Dict( - { - "obs": spaces.Dict( - { - "image": spaces.Box( - low=0, - high=255, - shape=(initial_env.imageSizeY, initial_env.imageSizeX, 3), - dtype=np.uint8, - ), - "position": spaces.Box( - low=-np.inf, high=np.inf, shape=(3,), dtype=np.float64 - ), - "yaw": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64 - ), - "pitch": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.float64 - ), - "health": spaces.Box( - low=0, high=np.inf, shape=(1,), dtype=np.float64 - ), - "food_level": spaces.Box( - low=0, high=np.inf, shape=(1,), dtype=np.float64 - ), - "saturation_level": spaces.Box( - low=0, high=np.inf, shape=(1,), dtype=np.float64 - ), - "is_dead": spaces.Discrete(2), - "inventory": spaces.Sequence( - spaces.Dict( - { - "raw_id": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "translation_key": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "count": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "durability": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "max_durability": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - } - ), - ), - "raycast_result": spaces.Dict( - { - "type": spaces.Discrete(3), - "target_block": spaces.Dict( - { - "x": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "y": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "z": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - "translation_key": spaces.Box( - low=-np.inf, - high=np.inf, - shape=(1,), - dtype=np.int32, - ), - } - ), - "target_entity": entity_info_space, - } - ), - "sound_subtitles": spaces.Sequence(sound_entry_space), - "status_effects": spaces.Sequence(status_effect_space), - "killed_statistics": spaces.Dict(), - "mined_statistics": spaces.Dict(), - "misc_statistics": spaces.Dict(), - "visible_entities": spaces.Sequence(entity_info_space), - "surrounding_entities": entities_within_distance_space, # This is actually - "bobber_thrown": spaces.Discrete(2), - "experience": spaces.Box( - low=0, high=np.inf, shape=(1,), dtype=np.int32 - ), - "world_time": spaces.Box( - low=-np.inf, high=np.inf, shape=(1,), dtype=np.int64 - ), - "last_death_message": spaces.Text( - min_length=0, max_length=1000 - ), - "image_2": spaces.Box( - low=0, - high=255, - shape=(initial_env.imageSizeY, initial_env.imageSizeX, 3), - dtype=np.uint8, - ), - } - ), - } - ) - self.initial_env = initial_env - self.use_terminate = use_terminate - self.cleanup_world = cleanup_world - self.use_vglrun = use_vglrun - self.native_debug = native_debug - self.track_native_memory = track_native_memory - self.ld_preload = ld_preload - self.encoding_mode = initial_env.screen_encoding_mode - self.sock = None - self.buffered_socket = None - self.last_rgb_frames: List[Union[np.ndarray, torch.Tensor, None]] = [None, None] - self.last_images: List[Union[np.ndarray, torch.Tensor, None]] = [None, None] - self.last_action = None - self.render_action = render_action - self.verbose = verbose - self.verbose_python = verbose_python - self.verbose_gradle = verbose_gradle - self.verbose_jvm = verbose_jvm - self.profile = profile - - self.render_alternating_eyes = render_alternating_eyes - self.render_alternating_eyes_counter = 0 - self.port = port - self.find_free_port = find_free_port - self.queued_commands = [] - self.process = None - if env_path is None: - self.env_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - "MinecraftEnv", - ) - else: - self.env_path = env_path - self.csv_logger = CsvLogger( - "py_log.csv", - profile=profile, - backend=LogBackend.BOTH if verbose_python else LogBackend.NONE, - ) - - # in case when using zerocopy - self.observation_tensors = [None, None] - self.observation_tensor_type = ObservationTensorType.NONE - - if initial_env.screen_encoding_mode == ScreenEncodingMode.ZEROCOPY: - try: - from .craftground_native import initialize_from_mach_port # type: ignore - from .craftground_native import mtl_tensor_from_cuda_mem_handle # type: ignore - except ImportError: - raise ImportError( - "To use zerocopy encoding mode, please install the craftground[cuda] package on linux or windows." - " If this error happens in macOS, please report it to the developers." - ) - - def reset( - self, - *, - seed: Optional[int] = None, - options: Optional[dict] = None, - ) -> Tuple[ObsType, Dict[str, Any]]: - if options is None: - options = {} - fast_reset = options.get("fast_reset", True) - extra_commands = options.get("extra_commands", []) - if not self.sock: # first time - self.start_server( - port=self.port, - use_vglrun=self.use_vglrun, - track_native_memory=self.track_native_memory, - ld_preload=self.ld_preload, - ) - else: - if not fast_reset: - self.sock.close() - self.terminate() - # wait for server death and restart server - sleep(5) - self.start_server( - port=self.port, - use_vglrun=self.use_vglrun, - track_native_memory=self.track_native_memory, - ld_preload=self.ld_preload, - ) - else: - self.csv_logger.profile_start("fast_reset") - send_fastreset2(self.sock, extra_commands) - self.csv_logger.profile_end("fast_reset") - self.csv_logger.log("Sent fast reset") - if self.verbose_python: - print_with_time("Sent fast reset") - self.csv_logger.log("Reading response...") - self.csv_logger.profile_start("read_response") - siz, res = self.read_one_observation() - self.csv_logger.profile_end("read_response") - - self.csv_logger.log(f"Got response with size {siz}") - self.csv_logger.profile_start("convert_observation") - rgb_1, img_1 = self.convert_observation(res.image, res) - self.csv_logger.profile_end("convert_observation") - rgb_2 = None - img_2 = None - if res.image_2 is not None and res.image_2 != b"": - rgb_2, img_2 = self.convert_observation(res.image_2, res) - self.queued_commands = [] - res.yaw = ((res.yaw + 180) % 360) - 180 - final_obs: Dict[str, Union[np.ndarray, torch.Tensor, Any]] = { - "obs": res, - "rgb": rgb_1, - } - self.last_images = [img_1, img_2] - self.last_rgb_frames = [rgb_1, rgb_2] - if rgb_2 is not None: - final_obs["rgb_2"] = rgb_2 - return final_obs, final_obs - - """ - returns: - - numpy array or torch Tensor of the image - - PIL image (optional) - - numpy array of the last_rgb_frame - """ - - def convert_observation( - self, image_bytes: bytes, res: ObsType - ) -> Tuple[Union[np.ndarray, torch.Tensor], Optional[Image.Image]]: - if self.encoding_mode == ScreenEncodingMode.PNG: - # decode png byte array to numpy array - # Create a BytesIO object from the byte array - self.csv_logger.profile_start("convert_observation/decode_png") - bytes_io = io.BytesIO(image_bytes) - # Use PIL to open the image from the BytesIO object - img = Image.open(bytes_io).convert("RGB") - # Flip y axis - img = img.transpose(Image.FLIP_TOP_BOTTOM) - self.csv_logger.profile_end("convert_observation/decode_png") - self.csv_logger.profile_start("convert_observation/convert_to_numpy") - # Convert the PIL image to a numpy array - last_rgb_frame = np.array(img) - arr = np.transpose(last_rgb_frame, (2, 1, 0)) - rgb_array_or_tensor = arr.astype(np.uint8) - self.csv_logger.profile_end("convert_observation/convert_to_numpy") - elif self.encoding_mode == ScreenEncodingMode.RAW: - # decode raw byte array to numpy array - self.csv_logger.profile_start("convert_observation/decode_raw") - last_rgb_frame = np.frombuffer(image_bytes, dtype=np.uint8).reshape( - (self.initial_env.imageSizeY, self.initial_env.imageSizeX, 3) - ) - # Flip y axis using np - # last_rgb_frame = np.transpose(last_rgb_frame, (1, 0, 2)) - last_rgb_frame = np.flip(last_rgb_frame, axis=0) - rgb_array_or_tensor = last_rgb_frame - # arr = np.transpose(last_rgb_frame, (2, 1, 0)) # channels, width, height - img = None - self.csv_logger.profile_end("convert_observation/decode_raw") - elif self.encoding_mode == ScreenEncodingMode.ZEROCOPY: - import torch - - if self.initial_env.eye_distance > 0: # binocular vision - # TODO: Handle binocular vision - if ( - self.observation_tensors[0] is not None - and self.observation_tensors[1] is not None - ): - # already intialized - # drop alpha, flip y axis, and clone - if ( - self.observation_tensor_type - == ObservationTensorType.APPLE_TENSOR - ): - return ( - self.observation_tensors[0] - .clone()[:, :, [2, 1, 0]] - .flip(0), - None, - ) - elif ( - self.observation_tensor_type - == ObservationTensorType.CUDA_DLPACK - ): - return ( - self.observation_tensors[0].clone()[:, :, :3].flip(0), - None, - ) - else: - if self.observation_tensors[0] is not None: - # already intialized - # drop alpha, flip y axis, and clone - if ( - self.observation_tensor_type - == ObservationTensorType.APPLE_TENSOR - ): - return ( - self.observation_tensors[0] - .clone()[:, :, [2, 1, 0]] - .flip(0), - None, - ) - elif ( - self.observation_tensor_type - == ObservationTensorType.CUDA_DLPACK - ): - return ( - self.observation_tensors[0].clone()[:, :, :3].flip(0), - None, - ) - - from .craftground_native import initialize_from_mach_port # type: ignore - from .craftground_native import mtl_tensor_from_cuda_mem_handle # type: ignore - - if len(res.ipc_handle) == 0: - raise ValueError("No ipc handle found.") - if len(res.ipc_handle) == 4: - mach_port = int.from_bytes( - res.ipc_handle, byteorder="little", signed=False - ) - print(f"{mach_port=}") - apple_dl_tensor = initialize_from_mach_port( - mach_port, self.initial_env.imageSizeX, self.initial_env.imageSizeY - ) - if apple_dl_tensor is not None: - # image_tensor = torch.utils.dlpack.from_dlpack(apple_dl_tensor) - rgb_array_or_tensor = apple_dl_tensor - print(rgb_array_or_tensor.shape) - print(rgb_array_or_tensor.dtype) - print(rgb_array_or_tensor.device) - self.observation_tensors[0] = rgb_array_or_tensor - # drop alpha, flip y axis, and clone - rgb_array_or_tensor = rgb_array_or_tensor.clone()[ - :, :, [2, 1, 0] - ].flip(0) - self.observation_tensor_type = ObservationTensorType.APPLE_TENSOR - else: - raise ValueError( - f"Failed to initialize from mach port {res.ipc_handle}." - ) - else: - import torch.utils.dlpack - - cuda_dl_tensor = mtl_tensor_from_cuda_mem_handle( - res.ipc_handle, - self.initial_env.imageSizeX, - self.initial_env.imageSizeY, - ) - if not cuda_dl_tensor: - raise ValueError("Invalid DLPack capsule: None") - rgb_array_or_tensor = torch.utils.dlpack.from_dlpack(cuda_dl_tensor) - print(rgb_array_or_tensor.shape) - print(rgb_array_or_tensor.dtype) - print(rgb_array_or_tensor.device) - print(f"{rgb_array_or_tensor.data_ptr()=}\n\n") - self.observation_tensors[0] = rgb_array_or_tensor - # drop alpha, flip y axis, and clone - rgb_array_or_tensor = ( - self.observation_tensors[0].clone()[:, :, :3].flip(0) - ) - self.observation_tensor_type = ObservationTensorType.CUDA_DLPACK - img = None - - else: - raise ValueError(f"Unknown encoding mode: {self.encoding_mode}") - return rgb_array_or_tensor, img - - def start_server( - self, - port: int, - use_vglrun: bool, - track_native_memory: bool, - ld_preload: Optional[str], - ): - self.remove_orphan_java_processes() - # Check if a file exists - if os.name == "nt": - while True: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - if s.connect_ex(("127.0.0.1", port)) == 0: # The port is in use - if self.find_free_port: - print( - f"[Warning]: Port {port} is already in use. Trying another port." - ) - port += 1 - else: - raise ConnectionError( - f"Port {port} is already in use. Please choose another port." - ) - else: - break - else: - socket_path = f"/tmp/minecraftrl_{port}.sock" - if os.path.exists(socket_path): - if self.find_free_port: - print( - f"[Warning]: Socket file {socket_path} already exists. Trying another port." - ) - while os.path.exists(socket_path): - port += 1 - socket_path = f"/tmp/minecraftrl_{port}.sock" - print(f"Using port {socket_path}") - else: - raise FileExistsError( - f"Socket file {socket_path} already exists. Please choose another port." - ) - my_env = os.environ.copy() - my_env["PORT"] = str(port) - my_env["VERBOSE"] = str(int(self.verbose_jvm)) - if track_native_memory: - my_env["CRAFTGROUND_JVM_NATIVE_TRACKING"] = "detail" - if self.native_debug: - my_env["CRAFGROUND_NATIVE_DEBUG"] = "True" - # configure permission of the gradlew - gradlew_path = os.path.join(self.env_path, "gradlew") - if not os.access(gradlew_path, os.X_OK): - os.chmod(gradlew_path, 0o755) - # update image settings of options.txt if exists - options_txt_path = self.get_env_option_path() - if options_txt_path is not None: - if os.path.exists(options_txt_path): - pass - # self.update_override_resolutions(options_txt_path) - - if os.name == "nt": - cmd = f".\\gradlew runClient -w --no-daemon" - else: - cmd = f"./gradlew runClient -w --no-daemon" # --args="--width {self.initial_env.imageSizeX} --height {self.initial_env.imageSizeY}"' - if use_vglrun: - cmd = f"vglrun {cmd}" - if ld_preload: - my_env["LD_PRELOAD"] = ld_preload - print(f"{cmd=}") - self.process = subprocess.Popen( - cmd, - cwd=self.env_path, - shell=True, - stdout=subprocess.DEVNULL if not self.verbose_gradle else None, - env=my_env, - ) - sock: socket.socket = wait_for_server(port) - self.sock = sock - self.send_initial_env() - self.buffered_socket = BufferedSocket(self.sock) - # self.json_socket.send_json_as_base64(self.initial_env.to_dict()) - if self.verbose_python: - print_with_time("Sent initial environment") - self.csv_logger.log("Sent initial environment") - - def update_override_resolutions(self, options_txt_path): - with open(options_txt_path, "r") as file: - text = file.read() - - # Define the patterns for overrideWidth and overrideHeight - width_pattern = r"overrideWidth:\d+" - height_pattern = r"overrideHeight:\d+" - - # Update or add overrideWidth - if re.search(width_pattern, text): - text = re.sub( - width_pattern, f"overrideWidth:{self.initial_env.imageSizeX}", text - ) - else: - text += f"\noverrideWidth:{self.initial_env.imageSizeX}" - - # Update or add overrideHeight - if re.search(height_pattern, text): - text = re.sub( - height_pattern, f"overrideHeight:{self.initial_env.imageSizeY}", text - ) - else: - text += f"\noverrideHeight:{self.initial_env.imageSizeY}" - - # Write the updated text back to the file - with open(options_txt_path, "w") as file: - file.write(text) - print( - f"Updated {options_txt_path} to {self.initial_env.imageSizeX}x{self.initial_env.imageSizeY}" - ) - - def read_one_observation(self) -> Tuple[int, ObsType]: - # print("Reading observation size...") - data_len_bytes = self.buffered_socket.read(4, True) - # print("Reading observation...") - data_len = struct.unpack(" Dict[str, Union[bool, float]]: - translated_action = { - "attack": action[5] == 3, - "back": action[0] == 2, - "forward": action[0] == 1, - "jump": action[2] == 1, - "left": action[1] == 1, - "right": action[1] == 2, - "sneak": action[2] == 2, - "sprint": action[2] == 3, - "use": action[5] == 1, - "drop": action[5] == 2, - "inventory": False, - } - for i in range(1, 10): - translated_action[f"hotbar.{i}"] = False - - translated_action["camera_pitch"] = action[3] * 15 - 180.0 - translated_action["camera_yaw"] = action[4] * 15 - 180.0 - - return translated_action - - def step(self, action: ActType) -> Tuple[ObsType, float, bool, bool, dict]: - # send the action - self.last_action = action - self.csv_logger.profile_start("send_action_and_commands") - # Translate the action v1 to v2 - if self.action_space_version == ActionSpaceVersion.V1_MINEDOJO: - translated_action = self.translate_action_to_v2(action) - else: - translated_action = action - send_action_and_commands( - self.sock, - translated_action, - commands=self.queued_commands, - verbose=self.verbose_python, - ) - self.csv_logger.profile_end("send_action_and_commands") - self.queued_commands.clear() - # read the response - if self.verbose_python: - print_with_time("Sent action and reading response...") - self.csv_logger.log("Sent action and reading response...") - self.csv_logger.profile_start("read_response") - siz, res = self.read_one_observation() - self.csv_logger.profile_end("read_response") - if self.verbose_python: - print_with_time("Read observation...") - self.csv_logger.log("Read observation...") - self.csv_logger.profile_start("convert_observation") - rgb_1, img_1 = self.convert_observation(res.image, res) - self.csv_logger.profile_end("convert_observation") - rgb_2 = None - img_2 = None - frame_2 = None - if res.image_2 is not None and res.image_2 != b"": - rgb_2, img_2 = self.convert_observation(res.image_2, res) - final_obs = { - "obs": res, - "rgb": rgb_1, - } - res.yaw = ((res.yaw + 180) % 360) - 180 - self.last_images = [img_1, img_2] - self.last_rgb_frames = [rgb_1, rgb_2] - if rgb_2 is not None: - final_obs["rgb_2"] = rgb_2 - - reward = 0 # Initialize reward to zero - done = False # Initialize done flag to False - truncated = False # Initialize truncated flag to False - return ( - final_obs, - reward, - done, - truncated, - final_obs, - ) - - # when self.render_mode is None: no render is computed - # when self.render_mode is "human": render returns None, envrionment is already being rendered on the screen - # when self.render_mode is "rgb_array": render returns the image to be rendered - # when self.render_mode is "ansi": render returns the text to be rendered - # when self.render_mode is "rgb_array_list": render returns a list of images to be rendered - # when self.render_mode is "rgb_tensor": render returns a torch tensor to be rendered - def render(self) -> Union[RenderFrame, List[RenderFrame], None]: - # print("Rendering...") - # select last_image and last_frame - if self.render_mode is None: - return None - if self.render_mode == "human": - # do not render anything - return None - if self.render_mode == "ansi": - raise ValueError("Rendering mode ansi not supported") - if self.render_mode == "rgb_array_list": - raise ValueError("Rendering mode rgb_array_list not supported") - - if self.render_alternating_eyes: - last_image = self.last_images[self.render_alternating_eyes_counter] - last_rgb_frame = self.last_rgb_frames[self.render_alternating_eyes_counter] - self.render_alternating_eyes_counter = ( - 1 - self.render_alternating_eyes_counter - ) - else: - last_image = self.last_images[0] - last_rgb_frame = self.last_rgb_frames[0] - if last_image is None and last_rgb_frame is None: - return None - - if isinstance(last_rgb_frame, torch.Tensor) and ( - self.render_mode != "rgb_array_tensor" or self.render_action - ): - # drop the alpha channel and convert to numpy array - last_rgb_frame = last_rgb_frame.cpu().numpy() - # last_rgb_frame: np.ndarray or torch.Tensor - # last_image: PIL.Image.Image or None - if self.render_action and self.last_action: - if last_image is None: - # it is inevitable to convert the tensor to numpy array - last_image = Image.fromarray(last_rgb_frame) - self.csv_logger.profile_start("render_action") - draw = ImageDraw.Draw(last_image) - if self.action_space_version == ActionSpaceVersion.V1_MINEDOJO: - text = self.action_to_symbol(self.last_action) - elif self.action_space_version == ActionSpaceVersion.V2_MINERL_HUMAN: - text = self.action_v2_to_symbol(self.last_action) - else: - raise ValueError( - f"Unknown action space version {self.action_space_version}" - ) - position = (0, 0) - font = get_font() - font_size = 8 - color = (255, 0, 0) - draw.text(position, text, font=font, font_size=font_size, fill=color) - self.csv_logger.profile_end("render_action") - return np.array(last_image) - else: - return last_rgb_frame - - def action_to_symbol(self, action) -> str: # noqa: C901 - res = "" - if action[0] == 1: - res += "↑" - elif action[0] == 2: - res += "↓" - if action[1] == 1: - res += "←" - elif action[1] == 2: - res += "→" - if action[2] == 1: - res += "jump" # "⤴" - elif action[2] == 2: - res += "sneak" # "⤵" - elif action[2] == 3: - res += "sprint" # "⚡" - if action[3] > 12: # pitch up - res += "⤒" - elif action[3] < 12: # pitch down - res += "⤓" - if action[4] > 12: # yaw right - res += "⏭" - elif action[4] < 12: # yaw left - res += "⏮" - if action[5] == 1: # use - res += "use" # "⚒" - elif action[5] == 2: # drop - res += "drop" # "🤮" - elif action[5] == 3: # attack - res += "attack" # "⚔" - return res - - def action_v2_to_symbol( # noqa: C901 - self, action_v2: Dict[str, Union[int, float]] - ) -> str: - res = "" - - if action_v2.get("forward") == 1: - res += "↑" - if action_v2.get("backward") == 1: - res += "↓" - if action_v2.get("left") == 1: - res += "←" - if action_v2.get("right") == 1: - res += "→" - if action_v2.get("jump") == 1: - res += "JMP" - if action_v2.get("sneak") == 1: - res += "SNK" - if action_v2.get("sprint") == 1: - res += "SPRT" - if action_v2.get("attack") == 1: - res += "ATK" - if action_v2.get("use") == 1: - res += "USE" - if action_v2.get("drop") == 1: - res += "Q" - if action_v2.get("inventory") == 1: - res += "I" - - for i in range(1, 10): - if action_v2.get(f"hotbar.{i}") == 1: - res += f"hotbar.{i}" - - return res - - @property - def render_mode(self) -> Optional[str]: - return "rgb_array" - - def close(self): - if not self.use_terminate: - self.terminate() - else: - print("Not terminating the java process") - - def add_command(self, command: str): - self.queued_commands.append(command) - - def add_commands(self, commands: List[str]): - self.queued_commands.extend(commands) - - def terminate(self): - if self.sock is not None: - send_exit(self.sock) - self.sock.close() - self.sock = None - print("Terminated the java process") - pid = self.process.pid if self.process else None - # wait for the pid to exit - try: - if pid: - os.kill(pid, signal.SIGKILL) - _, exit_status = os.waitpid(pid, 0) - else: - print("No pid to wait for") - except ChildProcessError: - print("Child process already terminated") - print("Terminated the java process") - - def remove_orphan_java_processes(self): # noqa: C901 - print("Removing orphan Java processes...") - target_directory = "/tmp" - file_pattern = "minecraftrl_" - file_usage = {} - no_such_processes = 0 - access_denied_processes = 0 - for proc in psutil.process_iter(["pid", "name"]): - try: - for file in proc.open_files(): - if ( - file.path.startswith(target_directory) - and file_pattern in file.path - ): - if file.path not in file_usage: - file_usage[file.path] = [] - file_usage[file.path].append(proc.info) - except psutil.NoSuchProcess: - no_such_processes += 1 - continue - except psutil.AccessDenied: - access_denied_processes += 1 - continue - except Exception as e: - print(f"Error: {e}") - continue - - for file_path, processes in file_usage.items(): - if all(proc["name"].lower() == "java" for proc in processes): - for proc in processes: - os.kill(proc["pid"], signal.SIGTERM) - print(f"Killed Java process {proc['pid']} using file {file_path}") - os.remove(file_path) - print(f"Removed {file_path}") - print( - f"Removed orphan Java processes: {access_denied_processes} access denied, {no_such_processes} no such process" - ) - - # Copy or symlink the save file to the returned folder - @staticmethod - def get_env_save_path() -> str: - current_file = os.path.abspath(__file__) - current_dir = os.path.dirname(current_file) - env_dir = os.path.join(current_dir, "MinecraftEnv", "run", "saves") - return env_dir - - @staticmethod - def get_env_option_path() -> str: - current_file = os.path.abspath(__file__) - current_dir = os.path.dirname(current_file) - options_txt = os.path.join(current_dir, "MinecraftEnv", "run", "options.txt") - return options_txt diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index 1be93a50..f406e7c5 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -1,4 +1,6 @@ import time + +from ..csv_logger import CsvLogger from ..environment.ipc_interface import IPCInterface from ..proto.action_space_pb2 import ActionSpaceMessageV2 from ..proto.initial_environment_pb2 import InitialEnvironmentMessage @@ -24,8 +26,10 @@ def __init__( port: int, find_free_port: bool, initial_environment: InitialEnvironmentMessage, + logger: CsvLogger, ): self.port = port + self.logger = logger initial_environment_bytes: bytes = initial_environment.SerializeToString() # Get the length of the action space message diff --git a/src/craftground/environment/environment.py b/src/craftground/environment/environment.py index 8fd41d07..083cf1e8 100644 --- a/src/craftground/environment/environment.py +++ b/src/craftground/environment/environment.py @@ -112,6 +112,7 @@ def __init__( port, find_free_port, self.initial_env_message, + self.logger, ) else: self.ipc = SocketIPC( @@ -190,6 +191,7 @@ def ensure_alive(self, fast_reset, extra_commands, seed: int): self.ipc.port, self.ipc.find_free_port, self.initial_env_message, + self.logger, ) else: self.ipc = SocketIPC( From ee6f6eef152dd98d0b00d417961d1bbf634fcf2e Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 28 Jan 2025 23:37:58 +0900 Subject: [PATCH 63/91] =?UTF-8?q?=F0=9F=94=8A=20Add=20logs,=20reduce=20boo?= =?UTF-8?q?st=20time?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 + src/cpp/ipc_boost.cpp | 21 +++++++++++++++++++ .../MinecraftEnv/src/main/cpp/CMakeLists.txt | 1 + .../MinecraftEnv/src/main/cpp/boost_ipc.cpp | 20 ++++++++++++++++++ 4 files changed, 43 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 29b2f094..ed2c134c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,7 @@ FetchContent_Declare( GIT_TAG boost-1.87.0 GIT_SHALLOW TRUE GIT_PROGRESS TRUE + EXCLUDE_FROM_ALL ) FetchContent_MakeAvailable(Boost) diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 0759fc56..90b01256 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -1,3 +1,4 @@ +#include #include #include "ipc_boost.hpp" #include "boost/interprocess/interprocess_fwd.hpp" @@ -6,6 +7,21 @@ #include #include +void printHex(const char* data, size_t data_size) { + for (size_t i = 0; i < data_size; ++i) { + // Print the hexadecimal representation of the byte + std::cout << std::hex << std::setw(2) << std::setfill('0') + << (static_cast(data[i]) & 0xFF) << " "; + + // Print a newline every 16 bytes + if ((i + 1) % 16 == 0) { + std::cout << std::endl; + } + } + std::cout << std::dec << std::endl; // Reset the output format +} + + bool shared_memory_exists(const std::string &name) { try { // Try to open the shared memory object @@ -114,6 +130,11 @@ int create_shared_memory_impl( ); } std::memcpy(data_start, initial_data, data_size); + + std::cout<<"Wrote initial data to shared memory:"<p2j_ready = true; layout->j2p_ready = false; return port; diff --git a/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt b/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt index e9f89739..dac93be5 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt +++ b/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt @@ -79,6 +79,7 @@ FetchContent_Declare( GIT_TAG boost-1.87.0 GIT_SHALLOW TRUE GIT_PROGRESS TRUE + EXCLUDE_FROM_ALL ) FetchContent_MakeAvailable(Boost) diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index c2a729bf..1c646f21 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include using namespace boost::interprocess; @@ -28,6 +30,21 @@ struct J2PSharedMemoryLayout { size_t data_size; // to be set on initialization }; +void printHex(const char* data, size_t data_size) { + for (size_t i = 0; i < data_size; ++i) { + // Print the hexadecimal representation of the byte + std::cout << std::hex << std::setw(2) << std::setfill('0') + << (static_cast(data[i]) & 0xFF) << " "; + + // Print a newline every 16 bytes + if ((i + 1) % 16 == 0) { + std::cout << std::endl; + } + } + std::cout << std::dec << std::endl; // Reset the output format +} + + // Returns ByteArray object containing the initial environment message jobject read_initial_environment( JNIEnv *env, jclass clazz, const std::string &p2j_memory_name @@ -39,11 +56,14 @@ jobject read_initial_environment( char *data_startInitialEnvironment = reinterpret_cast(p2jLayout) + p2jLayout->initial_environment_offset; size_t data_size = p2jLayout->initial_environment_size; + std::cout << "Java read data_size: " << data_size << std::endl; jbyteArray byteArray = env->NewByteArray(data_size); if (byteArray == nullptr || env->ExceptionCheck()) { return nullptr; } + std::cout << "Java read array: "; + printHex(data_startInitialEnvironment, data_size); env->SetByteArrayRegion( byteArray, 0, From cd9e6f2e25738de1aa4c060bdccbf6a2aa94f84b Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 29 Jan 2025 00:40:03 +0900 Subject: [PATCH 64/91] =?UTF-8?q?=F0=9F=90=9B=20Fixing=20ipc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- src/cpp/ipc_boost.cpp | 29 ++++++++++++++----- .../MinecraftEnv/src/main/cpp/CMakeLists.txt | 2 +- .../MinecraftEnv/src/main/cpp/boost_ipc.cpp | 18 ++++++++---- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ed2c134c..9da5b9c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15...3.29) +cmake_minimum_required(VERSION 3.28) project(craftground LANGUAGES CXX) include(GNUInstallDirs) include(FetchContent) diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 90b01256..a322fd77 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -7,12 +7,12 @@ #include #include -void printHex(const char* data, size_t data_size) { +void printHex(const char *data, size_t data_size) { for (size_t i = 0; i < data_size; ++i) { // Print the hexadecimal representation of the byte std::cout << std::hex << std::setw(2) << std::setfill('0') << (static_cast(data[i]) & 0xFF) << " "; - + // Print a newline every 16 bytes if ((i + 1) % 16 == 0) { std::cout << std::endl; @@ -21,7 +21,6 @@ void printHex(const char* data, size_t data_size) { std::cout << std::dec << std::endl; // Reset the output format } - bool shared_memory_exists(const std::string &name) { try { // Try to open the shared memory object @@ -109,10 +108,15 @@ int create_shared_memory_impl( j2pLayout->data_offset = sizeof(J2PSharedMemoryLayout); j2pLayout->data_size = 0; + // SharedMemoryLayout *layout = + // static_cast(p2jSharedMemory.allocate( + // sizeof(SharedMemoryLayout) + action_size + data_size + // )); + SharedMemoryLayout *layout = - static_cast(p2jSharedMemory.allocate( - sizeof(SharedMemoryLayout) + action_size + data_size - )); + reinterpret_cast(p2jSharedMemory.construct( + "0" + )[sizeof(SharedMemoryLayout) + action_size + data_size]('\0')); layout->layout_size = sizeof(SharedMemoryLayout); layout->action_offset = sizeof(SharedMemoryLayout); layout->action_size = action_size; @@ -131,9 +135,18 @@ int create_shared_memory_impl( } std::memcpy(data_start, initial_data, data_size); - std::cout<<"Wrote initial data to shared memory:"<initial_environment_offset << std::endl; + std::cout << "Initial environment size: " + << layout->initial_environment_size << std::endl; + std::cout << "Action offset: " << layout->action_offset << std::endl; + std::cout << "Action size: " << layout->action_size << std::endl; + std::cout << "p2j ready: " << layout->p2j_ready << std::endl; + std::cout << "j2p ready: " << layout->j2p_ready << std::endl; layout->p2j_ready = true; layout->j2p_ready = false; diff --git a/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt b/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt index dac93be5..b95992ca 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt +++ b/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.28) project(framebuffer_capturer) # Set the C++ standard diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index 1c646f21..ea878ecb 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -30,12 +30,12 @@ struct J2PSharedMemoryLayout { size_t data_size; // to be set on initialization }; -void printHex(const char* data, size_t data_size) { +void printHex(const char *data, size_t data_size) { for (size_t i = 0; i < data_size; ++i) { // Print the hexadecimal representation of the byte std::cout << std::hex << std::setw(2) << std::setfill('0') << (static_cast(data[i]) & 0xFF) << " "; - + // Print a newline every 16 bytes if ((i + 1) % 16 == 0) { std::cout << std::endl; @@ -44,18 +44,26 @@ void printHex(const char* data, size_t data_size) { std::cout << std::dec << std::endl; // Reset the output format } - // Returns ByteArray object containing the initial environment message jobject read_initial_environment( JNIEnv *env, jclass clazz, const std::string &p2j_memory_name ) { managed_shared_memory p2jMemory(open_only, p2j_memory_name.c_str()); - SharedMemoryLayout *p2jLayout = - static_cast(p2jMemory.get_address()); + SharedMemoryLayout *p2jLayout = reinterpret_cast(p2jMemory.find("0").first); char *data_startInitialEnvironment = reinterpret_cast(p2jLayout) + p2jLayout->initial_environment_offset; size_t data_size = p2jLayout->initial_environment_size; + + std::cout << "Java read data_size: " << p2jLayout->initial_environment_size + << std::endl; + std::cout << "Java initial environment offset:" + << p2jLayout->initial_environment_offset << std::endl; + std::cout << "Java layout size:" << p2jLayout->layout_size << std::endl; + std::cout << "Java action offset:" << p2jLayout->action_offset << std::endl; + std::cout << "Java action size:" << p2jLayout->action_size << std::endl; + std::cout << "Java p2j ready:" << p2jLayout->j2p_ready << std::endl; + std::cout << "Java j2p ready:" << p2jLayout->p2j_ready << std::endl; std::cout << "Java read data_size: " << data_size << std::endl; jbyteArray byteArray = env->NewByteArray(data_size); From c66da9b3a79d4e490683cc10dd81f083329cea26 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 29 Jan 2025 01:11:10 +0900 Subject: [PATCH 65/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20ipc=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/environment/boost_ipc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index f406e7c5..cd2cd6df 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -43,8 +43,8 @@ def __init__( len(dummy_action_bytes), find_free_port, ) - self.p2j_shared_memory_name = f"craftground_{port}_p2j" - self.j2p_shared_memory_name = f"craftground_{port}_j2p" + self.p2j_shared_memory_name = f"craftground_{self.port}_p2j" + self.j2p_shared_memory_name = f"craftground_{self.port}_j2p" def send_action(self, action: ActionSpaceMessageV2): action_bytes: bytes = action.SerializeToString() From ea62d6afbac5fa0303cf23e88b532fc7d373e4a9 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 29 Jan 2025 23:28:33 +0900 Subject: [PATCH 66/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20ipc=20shmems?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_boost.cpp | 96 ++++++++++--------- .../MinecraftEnv/src/main/cpp/boost_ipc.cpp | 73 +++++++++++--- 2 files changed, 108 insertions(+), 61 deletions(-) diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index a322fd77..561e7c25 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -1,7 +1,10 @@ #include #include #include "ipc_boost.hpp" +#include "boost/interprocess/detail/os_file_functions.hpp" #include "boost/interprocess/interprocess_fwd.hpp" +#include "boost/interprocess/mapped_region.hpp" +#include "boost/interprocess/shared_memory_object.hpp" #include #include #include @@ -75,13 +78,12 @@ int create_shared_memory_impl( << std::endl; } - managed_shared_memory p2jSharedMemory, j2pSharedMemory; + shared_memory_object p2jSharedMemory, j2pSharedMemory; try { - p2jSharedMemory = managed_shared_memory( - create_only, - p2j_memory_name.c_str(), - 1024 // Too small size fails to allocate + p2jSharedMemory = shared_memory_object( + create_only, p2j_memory_name.c_str(), read_write ); + p2jSharedMemory.truncate(1024); // Too small size fails to allocate } catch (const interprocess_exception &e) { std::cerr << e.what() << std::endl; std::cerr << "Failed to initialize shared memory creating" @@ -90,45 +92,38 @@ int create_shared_memory_impl( } try { - j2pSharedMemory = managed_shared_memory( - create_only, - j2p_memory_name.c_str(), - 1024 // Too small size fails to allocate + j2pSharedMemory = shared_memory_object( + create_only, j2p_memory_name.c_str(), read_write ); + j2pSharedMemory.truncate(1024); // Too small size fails to allocate } catch (const interprocess_exception &e) { std::cerr << e.what() << std::endl; std::cerr << "Failed to initialize shared memory creating" << j2p_memory_name << " : errno=" << errno << std::endl; throw std::runtime_error(e.what()); } - J2PSharedMemoryLayout *j2pLayout = static_cast( - j2pSharedMemory.allocate(sizeof(J2PSharedMemoryLayout)) - ); + mapped_region j2pRegion(j2pSharedMemory, read_write); + mapped_region p2jRegion(p2jSharedMemory, read_write); + J2PSharedMemoryLayout *j2pLayout = + static_cast(j2pRegion.get_address()); j2pLayout->layout_size = sizeof(J2PSharedMemoryLayout); j2pLayout->data_offset = sizeof(J2PSharedMemoryLayout); j2pLayout->data_size = 0; - // SharedMemoryLayout *layout = - // static_cast(p2jSharedMemory.allocate( - // sizeof(SharedMemoryLayout) + action_size + data_size - // )); - - SharedMemoryLayout *layout = - reinterpret_cast(p2jSharedMemory.construct( - "0" - )[sizeof(SharedMemoryLayout) + action_size + data_size]('\0')); - layout->layout_size = sizeof(SharedMemoryLayout); - layout->action_offset = sizeof(SharedMemoryLayout); - layout->action_size = action_size; - layout->initial_environment_offset = + SharedMemoryLayout *p2jLayout = + static_cast(p2jRegion.get_address()); + p2jLayout->layout_size = sizeof(SharedMemoryLayout); + p2jLayout->action_offset = sizeof(SharedMemoryLayout); + p2jLayout->action_size = action_size; + p2jLayout->initial_environment_offset = sizeof(SharedMemoryLayout) + action_size; - layout->initial_environment_size = data_size; + p2jLayout->initial_environment_size = data_size; void *action_start = - reinterpret_cast(layout) + layout->action_offset; - void *data_start = - reinterpret_cast(layout) + layout->initial_environment_offset; + reinterpret_cast(p2jLayout) + p2jLayout->action_offset; + void *data_start = reinterpret_cast(p2jLayout) + + p2jLayout->initial_environment_offset; - if (data_size > layout->initial_environment_size) { + if (data_size > p2jLayout->initial_environment_size) { throw std::runtime_error( "Data size exceeds allocated shared memory size" ); @@ -140,16 +135,16 @@ int create_shared_memory_impl( std::cout << "Data size: " << data_size << std::endl; std::cout << "Action size: " << action_size << std::endl; std::cout << "Initial environment offset: " - << layout->initial_environment_offset << std::endl; + << p2jLayout->initial_environment_offset << std::endl; std::cout << "Initial environment size: " - << layout->initial_environment_size << std::endl; - std::cout << "Action offset: " << layout->action_offset << std::endl; - std::cout << "Action size: " << layout->action_size << std::endl; - std::cout << "p2j ready: " << layout->p2j_ready << std::endl; - std::cout << "j2p ready: " << layout->j2p_ready << std::endl; + << p2jLayout->initial_environment_size << std::endl; + std::cout << "Action offset: " << p2jLayout->action_offset << std::endl; + std::cout << "Action size: " << p2jLayout->action_size << std::endl; + std::cout << "p2j ready: " << p2jLayout->p2j_ready << std::endl; + std::cout << "j2p ready: " << p2jLayout->j2p_ready << std::endl; - layout->p2j_ready = true; - layout->j2p_ready = false; + p2jLayout->p2j_ready = true; + p2jLayout->j2p_ready = false; return port; } @@ -157,11 +152,14 @@ int create_shared_memory_impl( void write_to_shared_memory_impl( const std::string &p2j_memory_name, const char *data ) { - managed_shared_memory p2jMemory(open_only, p2j_memory_name.c_str()); + shared_memory_object p2jMemory( + open_only, p2j_memory_name.c_str(), read_write + ); + mapped_region p2jRegion(p2jMemory, read_write); SharedMemoryLayout *layout = - static_cast(p2jMemory.get_address()); + static_cast(p2jRegion.get_address()); char *action_addr = - static_cast(p2jMemory.get_address()) + layout->action_offset; + reinterpret_cast(layout) + layout->action_offset; std::unique_lock actionLock(layout->mutex); std::memcpy(action_addr, data, layout->action_size); @@ -175,17 +173,20 @@ void write_to_shared_memory_impl( py::bytes read_from_shared_memory_impl( const std::string &p2j_memory_name, const std::string &j2p_memory_name ) { - managed_shared_memory p2jMemory; + shared_memory_object p2jMemory; try { - p2jMemory = managed_shared_memory(open_only, p2j_memory_name.c_str()); + p2jMemory = shared_memory_object( + open_only, p2j_memory_name.c_str(), read_write + ); } catch (const interprocess_exception &e) { std::cerr << e.what() << std::endl; std::cerr << "Failed to open shared memory to read observation: " << p2j_memory_name << " errno=" << errno << std::endl; throw std::runtime_error(e.what()); } + mapped_region p2jRegion(p2jMemory, read_write); SharedMemoryLayout *p2jLayout = - static_cast(p2jMemory.get_address()); + static_cast(p2jRegion.get_address()); std::unique_lock lockSynchronization(p2jLayout->mutex); // wait for java to write the observation @@ -194,9 +195,12 @@ py::bytes read_from_shared_memory_impl( }); // Read the observation from shared memory - managed_shared_memory j2pMemory(open_only, j2p_memory_name.c_str()); + shared_memory_object j2pMemory( + open_only, j2p_memory_name.c_str(), read_write + ); + mapped_region j2pMemoryRegion(j2pMemory, read_write); J2PSharedMemoryLayout *j2pLayout = - static_cast(j2pMemory.get_address()); + static_cast(j2pMemoryRegion.get_address()); const char *data_start = reinterpret_cast(j2pLayout) + j2pLayout->data_offset; diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index ea878ecb..622dd585 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -1,5 +1,7 @@ +#include "boost/interprocess/detail/os_file_functions.hpp" +#include "boost/interprocess/mapped_region.hpp" +#include "boost/interprocess/shared_memory_object.hpp" #include -#include #include #include #include @@ -48,9 +50,12 @@ void printHex(const char *data, size_t data_size) { jobject read_initial_environment( JNIEnv *env, jclass clazz, const std::string &p2j_memory_name ) { - managed_shared_memory p2jMemory(open_only, p2j_memory_name.c_str()); - SharedMemoryLayout *p2jLayout = reinterpret_cast(p2jMemory.find("0").first); - + shared_memory_object p2jMemory( + open_only, p2j_memory_name.c_str(), read_only + ); + mapped_region p2jRegion(p2jMemory, read_only); + SharedMemoryLayout *p2jLayout = + reinterpret_cast(p2jRegion.get_address()); char *data_startInitialEnvironment = reinterpret_cast(p2jLayout) + p2jLayout->initial_environment_offset; size_t data_size = p2jLayout->initial_environment_size; @@ -87,9 +92,12 @@ jbyteArray read_action( const std::string &p2j_memory_name, jbyteArray data ) { - managed_shared_memory p2jMemory(open_only, p2j_memory_name.c_str()); + shared_memory_object p2jMemory( + open_only, p2j_memory_name.c_str(), read_write + ); + mapped_region p2jRegion(p2jMemory, read_write); SharedMemoryLayout *p2jHeader = - static_cast(p2jMemory.get_address()); + static_cast(p2jRegion.get_address()); char *data_start = reinterpret_cast(p2jHeader) + p2jHeader->action_offset; @@ -118,35 +126,70 @@ void write_observation( const char *data, const size_t observation_size ) { - managed_shared_memory p2jMemory(open_only, p2j_memory_name.c_str()); + shared_memory_object p2jMemory( + open_only, p2j_memory_name.c_str(), read_write + ); + mapped_region p2jRegion(p2jMemory, read_write); SharedMemoryLayout *p2jLayout = - static_cast(p2jMemory.get_address()); + static_cast(p2jRegion.get_address()); std::unique_lock lockSynchronization(p2jLayout->mutex); p2jLayout->j2p_ready = false; + lockSynchronization.unlock(); - managed_shared_memory j2pMemory(open_only, j2p_memory_name.c_str()); - + shared_memory_object j2pMemory( + open_only, j2p_memory_name.c_str(), read_write + ); + mapped_region j2pMemoryRegion(j2pMemory, read_write); // Resize the shared memory if needed - const size_t currentSize = j2pMemory.get_size(); - const size_t requiredSize = - observation_size + sizeof(J2PSharedMemoryLayout); + offset_t size = 0; + const size_t currentSize = j2pMemory.get_size(size); + size_t requiredSize = observation_size + sizeof(J2PSharedMemoryLayout); if (currentSize < requiredSize) { - j2pMemory.grow(j2p_memory_name.c_str(), (requiredSize - currentSize)); + try { + shared_memory_object::remove(j2p_memory_name.c_str()); + } catch (const interprocess_exception &e) { + std::cerr << e.what() << std::endl; + std::cerr << "Failed to remove shared memory to write observation: " + << j2p_memory_name << " errno=" << errno << std::endl; + throw std::runtime_error(e.what()); + } + try { + j2pMemory = shared_memory_object( + create_only, j2p_memory_name.c_str(), read_write + ); + } catch (const interprocess_exception &e) { + std::cerr << e.what() << std::endl; + std::cerr << "Failed to create shared memory to write observation: " + << j2p_memory_name << " errno=" << errno << std::endl; + throw std::runtime_error(e.what()); + } + requiredSize = requiredSize > 1024 ? requiredSize : 1024; + j2pMemory.truncate(requiredSize); + j2pMemoryRegion = mapped_region(j2pMemory, read_write); + J2PSharedMemoryLayout *j2pHeader = + static_cast(j2pMemoryRegion.get_address()); + j2pHeader->layout_size = sizeof(J2PSharedMemoryLayout); + j2pHeader->data_offset = sizeof(J2PSharedMemoryLayout); + j2pHeader->data_size = observation_size; + // j2pMemory.grow(j2p_memory_name.c_str(), (requiredSize - + // currentSize)); } // Write the observation to shared memory J2PSharedMemoryLayout *j2pHeader = - static_cast(j2pMemory.get_address()); + static_cast(j2pMemoryRegion.get_address()); char *data_start = reinterpret_cast(j2pHeader) + j2pHeader->data_offset; std::memcpy(data_start, data, observation_size); j2pHeader->data_size = observation_size; // Notify Python that the observation is ready + std::unique_lock lockSynchronization2(p2jLayout->mutex); p2jLayout->j2p_ready = true; p2jLayout->p2j_ready = false; p2jLayout->condition.notify_one(); + lockSynchronization2.unlock(); } extern "C" JNIEXPORT jobject JNICALL From a651dcadb76935c7817a29ff28c8431341f60915 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Thu, 30 Jan 2025 01:03:16 +0900 Subject: [PATCH 67/91] =?UTF-8?q?=F0=9F=94=8A=20Adding=20log=20for=20ipc?= =?UTF-8?q?=20deadlock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_boost.cpp | 9 ++++++++- .../MinecraftEnv/src/main/cpp/boost_ipc.cpp | 11 +++++++++-- src/craftground/environment/boost_ipc.py | 2 ++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 561e7c25..37578b27 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -118,6 +118,9 @@ int create_shared_memory_impl( p2jLayout->initial_environment_offset = sizeof(SharedMemoryLayout) + action_size; p2jLayout->initial_environment_size = data_size; + new (&p2jLayout->mutex) interprocess_mutex(); + new (&p2jLayout->condition) interprocess_condition(); + void *action_start = reinterpret_cast(p2jLayout) + p2jLayout->action_offset; void *data_start = reinterpret_cast(p2jLayout) + @@ -143,7 +146,7 @@ int create_shared_memory_impl( std::cout << "p2j ready: " << p2jLayout->p2j_ready << std::endl; std::cout << "j2p ready: " << p2jLayout->j2p_ready << std::endl; - p2jLayout->p2j_ready = true; + p2jLayout->p2j_ready = false; p2jLayout->j2p_ready = false; return port; } @@ -161,12 +164,14 @@ void write_to_shared_memory_impl( char *action_addr = reinterpret_cast(layout) + layout->action_offset; + std::cout << "Writing action to shared memory" << std::endl; std::unique_lock actionLock(layout->mutex); std::memcpy(action_addr, data, layout->action_size); layout->p2j_ready = true; layout->j2p_ready = false; layout->condition.notify_one(); actionLock.unlock(); + std::cout << "Wrote action to shared memory" << std::endl; } // Read observation from shared memory @@ -190,6 +195,7 @@ py::bytes read_from_shared_memory_impl( std::unique_lock lockSynchronization(p2jLayout->mutex); // wait for java to write the observation + std::cout << "Waiting for Java to write observation" << std::endl; p2jLayout->condition.wait(lockSynchronization, [&] { return p2jLayout->j2p_ready; }); @@ -209,6 +215,7 @@ py::bytes read_from_shared_memory_impl( p2jLayout->j2p_ready = false; p2jLayout->p2j_ready = false; lockSynchronization.unlock(); + std::cout << "Read observation from shared memory" << std::endl; return data; } diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index 622dd585..7e18667d 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -92,6 +92,7 @@ jbyteArray read_action( const std::string &p2j_memory_name, jbyteArray data ) { + std::cout << "Reading action from shared memory 1" << std::endl; shared_memory_object p2jMemory( open_only, p2j_memory_name.c_str(), read_write ); @@ -102,7 +103,9 @@ jbyteArray read_action( reinterpret_cast(p2jHeader) + p2jHeader->action_offset; std::unique_lock actionLock(p2jHeader->mutex); + std::cout << "Reading action from shared memory: Acquired Lock" << std::endl; p2jHeader->condition.wait(actionLock, [&] { return p2jHeader->p2j_ready; }); + p2jHeader->p2j_ready = false; if (data == nullptr) { data = env->NewByteArray(p2jHeader->action_size); @@ -117,6 +120,7 @@ jbyteArray read_action( p2jHeader->p2j_ready = false; p2jHeader->j2p_ready = false; actionLock.unlock(); + std::cout << "Read action from shared memory 2" << std::endl; return data; } @@ -126,6 +130,7 @@ void write_observation( const char *data, const size_t observation_size ) { + std::cout << "Writing observation to shared memory 1" << std::endl; shared_memory_object p2jMemory( open_only, p2j_memory_name.c_str(), read_write ); @@ -175,7 +180,7 @@ void write_observation( // j2pMemory.grow(j2p_memory_name.c_str(), (requiredSize - // currentSize)); } - + std::cout << "Writing observation to shared memory 2" << std::endl; // Write the observation to shared memory J2PSharedMemoryLayout *j2pHeader = static_cast(j2pMemoryRegion.get_address()); @@ -185,11 +190,13 @@ void write_observation( j2pHeader->data_size = observation_size; // Notify Python that the observation is ready + std::cout << "Writing observation to shared memory 3" << std::endl; std::unique_lock lockSynchronization2(p2jLayout->mutex); p2jLayout->j2p_ready = true; p2jLayout->p2j_ready = false; - p2jLayout->condition.notify_one(); + p2jLayout->condition.notify_all(); lockSynchronization2.unlock(); + std::cout << "Wrote observation to shared memory 4" << std::endl; } extern "C" JNIEXPORT jobject JNICALL diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index cd2cd6df..ed7318a6 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -48,9 +48,11 @@ def __init__( def send_action(self, action: ActionSpaceMessageV2): action_bytes: bytes = action.SerializeToString() + self.logger.log("Sending action to shared memory") write_to_shared_memory(self.p2j_shared_memory_name, action_bytes) def read_observation(self) -> bytes: + self.logger.log("Reading observation from shared memory") return read_from_shared_memory( self.p2j_shared_memory_name, self.j2p_shared_memory_name ) From 15460528dcfa86356236ad96d82fcd1001db1887 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Thu, 30 Jan 2025 01:03:42 +0900 Subject: [PATCH 68/91] =?UTF-8?q?=F0=9F=8E=A8=20Format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index 7e18667d..54708783 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -103,7 +103,8 @@ jbyteArray read_action( reinterpret_cast(p2jHeader) + p2jHeader->action_offset; std::unique_lock actionLock(p2jHeader->mutex); - std::cout << "Reading action from shared memory: Acquired Lock" << std::endl; + std::cout << "Reading action from shared memory: Acquired Lock" + << std::endl; p2jHeader->condition.wait(actionLock, [&] { return p2jHeader->p2j_ready; }); p2jHeader->p2j_ready = false; From 002c8c799fe01b6c8d8df4dc38959db7f1bae5bc Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Thu, 30 Jan 2025 16:26:21 +0900 Subject: [PATCH 69/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20ipc=20without=20boos?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 8 +- src/cpp/cross_semaphore.h | 71 +++ src/cpp/ipc.cpp | 13 +- src/cpp/ipc.h | 6 +- src/cpp/ipc_boost.cpp | 31 +- src/cpp/ipc_boost.hpp | 4 +- src/cpp/ipc_noboost.cpp | 302 +++++++++++++ src/cpp/ipc_noboost.hpp | 56 +++ src/cpp/print_hex.cpp | 16 + src/cpp/print_hex.h | 3 + .../MinecraftEnv/src/main/cpp/CMakeLists.txt | 9 +- .../MinecraftEnv/src/main/cpp/boost_ipc.cpp | 20 +- .../src/main/cpp/cross_semaphore.h | 70 +++ .../MinecraftEnv/src/main/cpp/noboost_ipc.cpp | 411 ++++++++++++++++++ .../kyhsgeekcode/minecraftenv/MessageIO.kt | 10 +- src/craftground/environment/boost_ipc.py | 30 +- 16 files changed, 1013 insertions(+), 47 deletions(-) create mode 100644 src/cpp/cross_semaphore.h create mode 100644 src/cpp/ipc_noboost.cpp create mode 100644 src/cpp/ipc_noboost.hpp create mode 100644 src/cpp/print_hex.cpp create mode 100644 src/cpp/print_hex.h create mode 100644 src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h create mode 100644 src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9da5b9c9..fa22d05a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,7 +68,7 @@ else() endif() # Collect source files for the module -set(CRAFTGROUND_PY_SOURCES src/cpp/ipc.cpp src/cpp/ipc_boost.cpp) +set(CRAFTGROUND_PY_SOURCES src/cpp/ipc.cpp src/cpp/ipc_noboost.cpp src/cpp/print_hex.cpp) if(APPLE) # Add Apple-specific source files @@ -101,13 +101,13 @@ FetchContent_Declare( GIT_PROGRESS TRUE EXCLUDE_FROM_ALL ) -FetchContent_MakeAvailable(Boost) +# FetchContent_MakeAvailable(Boost) message(STATUS "Boost is now available") # Add the module pybind11_add_module(craftground_native ${CRAFTGROUND_PY_SOURCES}) -target_link_libraries(craftground_native PRIVATE Boost::system Boost::thread Boost::interprocess) -target_include_directories(craftground_native PRIVATE ${Boost_INCLUDE_DIRS}) +# target_link_libraries(craftground_native PRIVATE Boost::system Boost::thread Boost::interprocess) +# target_include_directories(craftground_native PRIVATE ${Boost_INCLUDE_DIRS}) if(APPLE) if(Torch_FOUND) target_include_directories(craftground_native PRIVATE "${TORCH_INCLUDE_DIRS}") diff --git a/src/cpp/cross_semaphore.h b/src/cpp/cross_semaphore.h new file mode 100644 index 00000000..1c707329 --- /dev/null +++ b/src/cpp/cross_semaphore.h @@ -0,0 +1,71 @@ +#pragma once + +// https://stackoverflow.com/a/27847103/8614565 +#include +#include +#include +#include +#if IS_WINDOWS +#include +#else +#include +#endif + +struct rk_sema { +#if IS_WINDOWS + HANDLE sem; +#else + sem_t *sem; + char name[30]; // Save the name of the semaphore +#endif +}; + +static inline void rk_sema_init( + struct rk_sema *s, const char *name, uint32_t value, uint32_t max +) { +#if IS_WINDOWS + s->sem = CreateSemaphore(NULL, value, max, name); +#else + snprintf(s->name, sizeof(s->name), "/%s", name); + sem_unlink(s->name); // Remove any existing semaphore with the same name + s->sem = sem_open(s->name, O_CREAT, 0644, value); // Binary semaphore + if (s->sem == SEM_FAILED) { + perror("sem_open failed"); + return; + } +#endif +} + +static inline void rk_sema_wait(struct rk_sema *s) { + +#if IS_WINDOWS + DWORD r; + do { + r = WaitForSingleObject(s->sem, INFINITE); + } while (r == WAIT_FAILED && GetLastError() == ERROR_INTERRUPT); +#else + int r; + + do { + r = sem_wait(s->sem); + } while (r == -1 && errno == EINTR); +#endif +} + +static inline void rk_sema_post(struct rk_sema *s) { + +#if IS_WINDOWS + ReleaseSemaphore(s->sem, 1, NULL); +#else + sem_post(s->sem); +#endif +} + +static inline void rk_sema_destroy(struct rk_sema *s) { +#if IS_WINDOWS + CloseHandle(s->sem); +#else + sem_close(s->sem); + sem_unlink(s->name); // Named semaphores are removed after unlinking +#endif +} \ No newline at end of file diff --git a/src/cpp/ipc.cpp b/src/cpp/ipc.cpp index 31d55ad0..1ca9ce76 100644 --- a/src/cpp/ipc.cpp +++ b/src/cpp/ipc.cpp @@ -59,7 +59,8 @@ py::capsule mtl_tensor_from_cuda_mem_handle( } #endif -#include "ipc_boost.hpp" +// #include "ipc_boost.hpp" +#include "ipc_noboost.hpp" int initialize_shared_memory( int port, @@ -80,8 +81,10 @@ int initialize_shared_memory( } } -void write_to_shared_memory(const char *p2j_memory_name, const char *data) { - write_to_shared_memory_impl(p2j_memory_name, data); +void write_to_shared_memory( + const char *p2j_memory_name, const char *data, size_t action_size +) { + write_to_shared_memory_impl(p2j_memory_name, data, action_size); } py::bytes read_from_shared_memory( @@ -90,8 +93,8 @@ py::bytes read_from_shared_memory( return read_from_shared_memory_impl(p2j_memory_name, j2p_memory_name); } -void destroy_shared_memory(const char *memory_name) { - destroy_shared_memory_impl(memory_name); +void destroy_shared_memory(const char *memory_name, bool release_semaphores) { + destroy_shared_memory_impl(memory_name, release_semaphores); } PYBIND11_MODULE(craftground_native, m) { diff --git a/src/cpp/ipc.h b/src/cpp/ipc.h index 7ec3bb26..115886f3 100644 --- a/src/cpp/ipc.h +++ b/src/cpp/ipc.h @@ -19,13 +19,15 @@ int initialize_shared_memory( bool find_free_port ); -void write_to_shared_memory(const char *p2j_memory_name, const char *data); +void write_to_shared_memory( + const char *p2j_memory_name, const char *data, size_t action_size +); py::bytes read_from_shared_memory( const char *p2j_memory_name, const char *j2p_memory_name ); -void destroy_shared_memory(const char *memory_name); +void destroy_shared_memory(const char *memory_name, bool release_semaphores); bool shared_memory_exists(const std::string &name); diff --git a/src/cpp/ipc_boost.cpp b/src/cpp/ipc_boost.cpp index 37578b27..26942de7 100644 --- a/src/cpp/ipc_boost.cpp +++ b/src/cpp/ipc_boost.cpp @@ -1,4 +1,3 @@ -#include #include #include "ipc_boost.hpp" #include "boost/interprocess/detail/os_file_functions.hpp" @@ -9,20 +8,7 @@ #include #include #include - -void printHex(const char *data, size_t data_size) { - for (size_t i = 0; i < data_size; ++i) { - // Print the hexadecimal representation of the byte - std::cout << std::hex << std::setw(2) << std::setfill('0') - << (static_cast(data[i]) & 0xFF) << " "; - - // Print a newline every 16 bytes - if ((i + 1) % 16 == 0) { - std::cout << std::endl; - } - } - std::cout << std::dec << std::endl; // Reset the output format -} +#include "print_hex.h" bool shared_memory_exists(const std::string &name) { try { @@ -148,6 +134,8 @@ int create_shared_memory_impl( p2jLayout->p2j_ready = false; p2jLayout->j2p_ready = false; + p2jLayout->p2j_recv_ready = false; + p2jLayout->j2p_recv_ready = false; return port; } @@ -166,9 +154,12 @@ void write_to_shared_memory_impl( std::cout << "Writing action to shared memory" << std::endl; std::unique_lock actionLock(layout->mutex); + layout->condition.wait(actionLock, [&] { return !layout->p2j_recv_ready; }); std::memcpy(action_addr, data, layout->action_size); layout->p2j_ready = true; layout->j2p_ready = false; + layout->p2j_recv_ready = false; + layout->j2p_recv_ready = false; layout->condition.notify_one(); actionLock.unlock(); std::cout << "Wrote action to shared memory" << std::endl; @@ -194,6 +185,10 @@ py::bytes read_from_shared_memory_impl( static_cast(p2jRegion.get_address()); std::unique_lock lockSynchronization(p2jLayout->mutex); + p2jLayout->j2p_recv_ready = true; + p2jLayout->condition.notify_all(); + lockSynchronization.unlock(); + lockSynchronization.lock(); // wait for java to write the observation std::cout << "Waiting for Java to write observation" << std::endl; p2jLayout->condition.wait(lockSynchronization, [&] { @@ -214,6 +209,8 @@ py::bytes read_from_shared_memory_impl( p2jLayout->j2p_ready = false; p2jLayout->p2j_ready = false; + p2jLayout->j2p_recv_ready = false; + p2jLayout->p2j_recv_ready = false; lockSynchronization.unlock(); std::cout << "Read observation from shared memory" << std::endl; @@ -221,6 +218,8 @@ py::bytes read_from_shared_memory_impl( } // Destroy shared memory -void destroy_shared_memory_impl(const std::string &memory_name) { +void destroy_shared_memory_impl( + const std::string &memory_name, bool destroy_semaphores +) { shared_memory_object::remove(memory_name.c_str()); } diff --git a/src/cpp/ipc_boost.hpp b/src/cpp/ipc_boost.hpp index f2b588f6..0c2020c0 100644 --- a/src/cpp/ipc_boost.hpp +++ b/src/cpp/ipc_boost.hpp @@ -24,6 +24,8 @@ struct SharedMemoryLayout { interprocess_condition condition; bool p2j_ready; bool j2p_ready; + bool p2j_recv_ready; + bool j2p_recv_ready; }; struct J2PSharedMemoryLayout { @@ -49,6 +51,6 @@ py::bytes read_from_shared_memory_impl( ); // remove shared memory -void destroy_shared_memory_impl(const std::string &memory_name); +void destroy_shared_memory_impl(const std::string &memory_name, bool release_semaphores); #endif // SHARED_MEMORY_UTILS_HPP diff --git a/src/cpp/ipc_noboost.cpp b/src/cpp/ipc_noboost.cpp new file mode 100644 index 00000000..8be6ac7e --- /dev/null +++ b/src/cpp/ipc_noboost.cpp @@ -0,0 +1,302 @@ +#include +#include +#include +#include +#include +#include +#include +#include "ipc_noboost.hpp" +#include "cross_semaphore.h" +#include "print_hex.h" + +bool shared_memory_exists(const std::string &name) { + int fd = shm_open(name.c_str(), O_RDONLY, 0666); + if (fd == -1) { + return false; // Shared memory does not exist + } + close(fd); // Close the file descriptor + return true; // Shared memory exists +} + +std::string make_shared_memory_name(int port, const std::string &suffix) { + return SHMEM_PREFIX "craftground_" + std::to_string(port) + "_" + suffix; +} + +int create_shared_memory_impl( + int port, + const char *initial_data, + size_t data_size, + size_t action_size, + bool find_free_port +) { + std::string p2j_memory_name, j2p_memory_name; + bool found_free_port = false; + const int shared_memory_size = + sizeof(SharedMemoryLayout) + action_size + data_size; + + do { + p2j_memory_name = make_shared_memory_name(port, "p2j"); + j2p_memory_name = make_shared_memory_name(port, "j2p"); + if (shared_memory_exists(p2j_memory_name) || + shared_memory_exists(j2p_memory_name)) { + if (find_free_port) { + port++; + continue; + } else { + throw std::runtime_error( + "Shared memory " + p2j_memory_name + " or " + + j2p_memory_name + " already exists" + ); + } + } + found_free_port = true; + } while (!found_free_port); + + int p2jFd = shm_open(p2j_memory_name.c_str(), O_CREAT | O_RDWR, 0666); + if (p2jFd == -1) { + perror("shm_open failed while creating shared memory p2j"); + return -1; + } + + if (ftruncate(p2jFd, shared_memory_size) == -1) { + perror("ftruncate failed for p2jFd"); + close(p2jFd); + shm_unlink(p2j_memory_name.c_str()); + return -1; + } + + int j2pFd = shm_open(j2p_memory_name.c_str(), O_CREAT | O_RDWR, 0666); + if (j2pFd == -1) { + close(p2jFd); + shm_unlink(p2j_memory_name.c_str()); + perror("shm_open failed while creating shared memory j2p"); + return -1; + } + + if (ftruncate(j2pFd, sizeof(J2PSharedMemoryLayout) + data_size) == -1) { + perror("ftruncate failed for j2pFd"); + close(j2pFd); + close(p2jFd); + shm_unlink(j2p_memory_name.c_str()); + shm_unlink(p2j_memory_name.c_str()); + return -1; + } + + void *ptr = mmap( + 0, shared_memory_size, PROT_READ | PROT_WRITE, MAP_SHARED, p2jFd, 0 + ); + if (ptr == MAP_FAILED) { + perror("mmap failed while creating shared memory"); + close(j2pFd); + close(p2jFd); + shm_unlink(j2p_memory_name.c_str()); + shm_unlink(j2p_memory_name.c_str()); + return -1; + } + + SharedMemoryLayout *p2jLayout = static_cast(ptr); + new (p2jLayout) SharedMemoryLayout(); + + std::string sema_obs_ready_name = make_shared_memory_name( + port, "cg_sem_obs" + std::to_string(port) + ); + std::string sema_action_ready_name = make_shared_memory_name( + port, "cg_sem_act" + std::to_string(port) + ); + + rk_sema_init(&p2jLayout->sem_obs_ready, sema_obs_ready_name.c_str(), 0, 1); + rk_sema_init( + &p2jLayout->sem_action_ready, sema_action_ready_name.c_str(), 0, 1 + ); + + p2jLayout->action_offset = sizeof(SharedMemoryLayout); + p2jLayout->action_size = action_size; + p2jLayout->initial_environment_offset = + sizeof(SharedMemoryLayout) + action_size; + p2jLayout->initial_environment_size = data_size; + + void *action_start = (char *)ptr + p2jLayout->action_offset; + void *data_start = (char *)ptr + p2jLayout->initial_environment_offset; + std::memcpy(data_start, initial_data, data_size); + std::cout << "Wrote initial data to shared memory:" << std::endl; + + printHex(initial_data, data_size); + std::cout << "Data size: " << data_size << std::endl; + std::cout << "Action size: " << action_size << std::endl; + std::cout << "Initial environment offset: " + << p2jLayout->initial_environment_offset << std::endl; + std::cout << "Initial environment size: " + << p2jLayout->initial_environment_size << std::endl; + std::cout << "Action offset: " << p2jLayout->action_offset << std::endl; + std::cout << "Action size: " << p2jLayout->action_size << std::endl; + + munmap(ptr, shared_memory_size); + close(p2jFd); + close(j2pFd); + return port; +} + +void write_to_shared_memory_impl( + const std::string &p2j_memory_name, const char *data, size_t action_size +) { + int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); + void *ptr = mmap( + 0, + sizeof(SharedMemoryLayout), + PROT_READ | PROT_WRITE, + MAP_SHARED, + p2jFd, + 0 + ); + if (ptr == MAP_FAILED) { + perror("mmap failed while writing to shared memory"); + close(p2jFd); + return; + } + + SharedMemoryLayout *layout = static_cast(ptr); + layout->action_size = action_size; + const size_t action_offset = layout->action_offset; + munmap(ptr, sizeof(SharedMemoryLayout)); + ptr = mmap( + 0, + sizeof(SharedMemoryLayout) + action_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, + p2jFd, + 0 + ); + layout = static_cast(ptr); + std::cout << "Writing action to shared memory" << std::endl; + rk_sema_wait(&layout->sem_action_ready); + std::memcpy((char *)ptr + layout->action_offset, data, layout->action_size); + rk_sema_post(&layout->sem_action_ready); + std::cout << "Wrote action to shared memory" << std::endl; + munmap(ptr, sizeof(SharedMemoryLayout) + action_size); + close(p2jFd); +} + +py::bytes read_from_shared_memory_impl( + const std::string &p2j_memory_name, const std::string &j2p_memory_name +) { + std::cout << "Reading observation from shared memory 1" << std::endl; + int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); + if (p2jFd == -1) { + perror("shm_open p2j failed while reading from shared memory"); + return py::bytes(); // return empty bytes + } + + void *p2jPtr = mmap( + 0, + sizeof(SharedMemoryLayout), + PROT_READ | PROT_WRITE, + MAP_SHARED, + p2jFd, + 0 + ); + if (p2jPtr == MAP_FAILED) { + perror("mmap p2j failed while reading from shared memory"); + close(p2jFd); + return py::bytes(); + } + SharedMemoryLayout *layout = static_cast(p2jPtr); + size_t action_size = layout->action_size; + size_t data_size = layout->initial_environment_size; + + std::cout << "Reading observation from shared memory 2" << std::endl; + + // Wait for the observation to be ready + rk_sema_wait(&layout->sem_obs_ready); + + int j2pFd = shm_open(j2p_memory_name.c_str(), O_RDWR, 0666); + if (j2pFd == -1) { + perror("shm_open j2p failed while reading from shared memory"); + munmap(p2jPtr, sizeof(SharedMemoryLayout)); + return py::bytes(); // return empty bytes + } + + void *j2pPtr = mmap( + 0, + sizeof(J2PSharedMemoryLayout), + PROT_READ | PROT_WRITE, + MAP_SHARED, + j2pFd, + 0 + ); + + if (j2pPtr == MAP_FAILED) { + perror("mmap j2p failed while reading from shared memory"); + munmap(p2jPtr, sizeof(SharedMemoryLayout)); + close(p2jFd); + close(j2pFd); + return py::bytes(); + } + + J2PSharedMemoryLayout *j2pLayout = + static_cast(j2pPtr); + + const size_t obs_length = j2pLayout->data_size; + munmap(j2pPtr, sizeof(J2PSharedMemoryLayout)); + + j2pPtr = mmap( + 0, + sizeof(J2PSharedMemoryLayout) + obs_length, + PROT_READ | PROT_WRITE, + MAP_SHARED, + j2pFd, + 0 + ); + + if (j2pPtr == MAP_FAILED) { + perror("mmap j2p failed while reading from shared memory"); + munmap(p2jPtr, sizeof(SharedMemoryLayout)); + close(p2jFd); + close(j2pFd); + return py::bytes(); + } + + j2pLayout = static_cast(j2pPtr); + const char *data_start = (char *)j2pPtr + j2pLayout->data_offset; + + py::bytes data(data_start, j2pLayout->data_size); + rk_sema_post(&layout->sem_obs_ready); // Notify that the observation is read + + std::cout << "Read observation from shared memory" << std::endl; + munmap(j2pPtr, sizeof(J2PSharedMemoryLayout) + obs_length); + munmap(p2jPtr, sizeof(SharedMemoryLayout)); + close(p2jFd); + close(j2pFd); + return data; +} + +// remove shared memory +void destroy_shared_memory_impl( + const std::string &memory_name, bool release_semaphores +) { + if (release_semaphores) { + int p2jFd = shm_open(memory_name.c_str(), O_RDWR, 0666); + if (p2jFd == -1) { + perror("shm_open failed while destroying shared memory"); + } else { + void *ptr = mmap( + 0, + sizeof(SharedMemoryLayout), + PROT_READ | PROT_WRITE, + MAP_SHARED, + p2jFd, + 0 + ); + if (ptr == MAP_FAILED) { + perror("mmap failed while destroying shared memory"); + } else { + SharedMemoryLayout *layout = + static_cast(ptr); + rk_sema_destroy(&layout->sem_obs_ready); + rk_sema_destroy(&layout->sem_action_ready); + munmap(ptr, sizeof(SharedMemoryLayout)); + } + close(p2jFd); + } + } + shm_unlink(memory_name.c_str()); +} diff --git a/src/cpp/ipc_noboost.hpp b/src/cpp/ipc_noboost.hpp new file mode 100644 index 00000000..cbd0cd03 --- /dev/null +++ b/src/cpp/ipc_noboost.hpp @@ -0,0 +1,56 @@ +#ifndef IPC_NOBOOST_HPP +#define IPC_NOBOOST_HPP + +#include +#include +#include +#include + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) +#define IS_WINDOWS 1 +#define SHMEM_PREFIX "Global\\" +#else +#define SHMEM_PREFIX "/" +#define IS_WINDOWS 0 +#endif +#include "cross_semaphore.h" + +namespace py = pybind11; + +struct SharedMemoryLayout { + size_t layout_size; // to be set on initialization + size_t action_offset; // Always sizeof(SharedMemoryLayout) + size_t action_size; // to be set on initialization + size_t initial_environment_offset; // Always action_size + + // sizeof(SharedDataHeader) + size_t initial_environment_size; // to be set on initialization + rk_sema sem_obs_ready; + rk_sema sem_action_ready; +}; + +struct J2PSharedMemoryLayout { + size_t layout_size; // to be set on initialization + size_t data_offset; // Always sizeof(J2PSharedMemoryLayout) + size_t data_size; // to be set on initialization +}; + +int create_shared_memory_impl( + int port, + const char *initial_data, + size_t data_size, + size_t action_size, + bool find_free_port +); + +void write_to_shared_memory_impl( + const std::string &p2j_memory_name, const char *data, size_t action_size +); + +py::bytes read_from_shared_memory_impl( + const std::string &p2j_memory_name, const std::string &j2p_memory_name +); + +// remove shared memory노 +void destroy_shared_memory_impl(const std::string &memory_name, bool release_semaphores); + +#endif // SHARED_MEMORY_UTILS_HPP diff --git a/src/cpp/print_hex.cpp b/src/cpp/print_hex.cpp new file mode 100644 index 00000000..833728f6 --- /dev/null +++ b/src/cpp/print_hex.cpp @@ -0,0 +1,16 @@ +#include +#include + +void printHex(const char *data, size_t data_size) { + for (size_t i = 0; i < data_size; ++i) { + // Print the hexadecimal representation of the byte + std::cout << std::hex << std::setw(2) << std::setfill('0') + << (static_cast(data[i]) & 0xFF) << " "; + + // Print a newline every 16 bytes + if ((i + 1) % 16 == 0) { + std::cout << std::endl; + } + } + std::cout << std::dec << std::endl; // Reset the output format +} \ No newline at end of file diff --git a/src/cpp/print_hex.h b/src/cpp/print_hex.h new file mode 100644 index 00000000..92aa571a --- /dev/null +++ b/src/cpp/print_hex.h @@ -0,0 +1,3 @@ +#pragma once +#include +void printHex(const char *data, size_t data_size); \ No newline at end of file diff --git a/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt b/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt index b95992ca..4f0b42e3 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt +++ b/src/craftground/MinecraftEnv/src/main/cpp/CMakeLists.txt @@ -81,7 +81,7 @@ FetchContent_Declare( GIT_PROGRESS TRUE EXCLUDE_FROM_ALL ) -FetchContent_MakeAvailable(Boost) +# FetchContent_MakeAvailable(Boost) message(STATUS "Boost is now available") @@ -89,7 +89,7 @@ message(STATUS "Boost is now available") set( SOURCE_FILES framebuffer_capturer.cpp - boost_ipc.cpp + noboost_ipc.cpp ) message(STATUS "SOURCE_FILES=${SOURCE_FILES}") @@ -111,8 +111,9 @@ if(CRAFGROUND_NATIVE_DEBUG) endif() # Link with JNI and OpenGL libraries -target_link_libraries(native-lib ${JNI_LIBRARIES} ${OPENGL_LIBRARIES} glm::glm Boost::system Boost::thread Boost::interprocess) -target_include_directories(native-lib PRIVATE ${Boost_INCLUDE_DIRS}) +target_link_libraries(native-lib ${JNI_LIBRARIES} ${OPENGL_LIBRARIES} glm::glm) +# target_link_libraries(native-lib Boost::system Boost::thread Boost::interprocess) +# target_include_directories(native-lib PRIVATE ${Boost_INCLUDE_DIRS}) if(PNG_FOUND) target_link_libraries(native-lib ${PNG_LIBRARIES} ${ZLIB_LIBRARIES}) diff --git a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp index 54708783..ab4a2fec 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/boost_ipc.cpp @@ -24,6 +24,8 @@ struct SharedMemoryLayout { interprocess_condition condition; bool p2j_ready; bool j2p_ready; + bool p2j_recv_ready; + bool j2p_recv_ready; }; struct J2PSharedMemoryLayout { @@ -103,6 +105,10 @@ jbyteArray read_action( reinterpret_cast(p2jHeader) + p2jHeader->action_offset; std::unique_lock actionLock(p2jHeader->mutex); + p2jHeader->p2j_recv_ready = true; + p2jHeader->condition.notify_all(); + actionLock.unlock(); + actionLock.lock(); std::cout << "Reading action from shared memory: Acquired Lock" << std::endl; p2jHeader->condition.wait(actionLock, [&] { return p2jHeader->p2j_ready; }); @@ -120,6 +126,8 @@ jbyteArray read_action( ); p2jHeader->p2j_ready = false; p2jHeader->j2p_ready = false; + p2jHeader->p2j_recv_ready = false; + p2jHeader->j2p_recv_ready = false; actionLock.unlock(); std::cout << "Read action from shared memory 2" << std::endl; return data; @@ -140,8 +148,10 @@ void write_observation( static_cast(p2jRegion.get_address()); std::unique_lock lockSynchronization(p2jLayout->mutex); - p2jLayout->j2p_ready = false; - lockSynchronization.unlock(); + p2jLayout->condition.wait(lockSynchronization, [&] { + return p2jLayout->j2p_recv_ready; + }); + p2jLayout->j2p_recv_ready = false; shared_memory_object j2pMemory( open_only, j2p_memory_name.c_str(), read_write @@ -192,11 +202,13 @@ void write_observation( // Notify Python that the observation is ready std::cout << "Writing observation to shared memory 3" << std::endl; - std::unique_lock lockSynchronization2(p2jLayout->mutex); + lockSynchronization.lock(); p2jLayout->j2p_ready = true; p2jLayout->p2j_ready = false; + p2jLayout->j2p_recv_ready = false; + p2jLayout->p2j_recv_ready = false; p2jLayout->condition.notify_all(); - lockSynchronization2.unlock(); + lockSynchronization.unlock(); std::cout << "Wrote observation to shared memory 4" << std::endl; } diff --git a/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h b/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h new file mode 100644 index 00000000..77ed1925 --- /dev/null +++ b/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h @@ -0,0 +1,70 @@ +#pragma once + +// https://stackoverflow.com/a/27847103/8614565 +#include +#include +#include +#if IS_WINDOWS +#include +#else +#include +#endif + +struct rk_sema { +#if IS_WINDOWS + HANDLE sem; +#else + sem_t *sem; + char name[30]; // Save the name of the semaphore +#endif +}; + +static inline void rk_sema_init( + struct rk_sema *s, const char *name, uint32_t value, uint32_t max +) { +#if IS_WINDOWS + s->sem = CreateSemaphore(NULL, value, max, name); +#else + snprintf(s->name, sizeof(s->name), "/%s", name); + sem_unlink(s->name); // Remove any existing semaphore with the same name + s->sem = sem_open(s->name, O_CREAT, 0644, value); // Binary semaphore + if (s->sem == SEM_FAILED) { + perror("sem_open failed"); + return; + } +#endif +} + +static inline void rk_sema_wait(struct rk_sema *s) { + +#if IS_WINDOWS + DWORD r; + do { + r = WaitForSingleObject(s->sem, INFINITE); + } while (r == WAIT_FAILED && GetLastError() == ERROR_INTERRUPT); +#else + int r; + + do { + r = sem_wait(s->sem); + } while (r == -1 && errno == EINTR); +#endif +} + +static inline void rk_sema_post(struct rk_sema *s) { + +#if IS_WINDOWS + ReleaseSemaphore(s->sem, 1, NULL); +#else + sem_post(s->sem); +#endif +} + +static inline void rk_sema_destroy(struct rk_sema *s) { +#if IS_WINDOWS + CloseHandle(s->sem); +#else + sem_close(s->sem); + sem_unlink(s->name); // Named semaphores are removed after unlinking +#endif +} \ No newline at end of file diff --git a/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp new file mode 100644 index 00000000..f65cdaae --- /dev/null +++ b/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp @@ -0,0 +1,411 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(WIN32) || defined(_WIN32) || \ + defined(__WIN32) && !defined(__CYGWIN__) +#define IS_WINDOWS 1 +#define SHMEM_PREFIX "Global\\" +#else +#define SHMEM_PREFIX "/" +#define IS_WINDOWS 0 +#endif +#include "cross_semaphore.h" + +struct SharedMemoryLayout { + size_t layout_size; // to be set on initialization + size_t action_offset; // Always sizeof(SharedMemoryLayout) + size_t action_size; // to be set on initialization + size_t initial_environment_offset; // Always action_size + + // sizeof(SharedDataHeader) + size_t initial_environment_size; // to be set on initialization + rk_sema sem_obs_ready; + rk_sema sem_action_ready; +}; + +struct J2PSharedMemoryLayout { + size_t layout_size; // to be set on initialization + size_t data_offset; // Always sizeof(J2PSharedMemoryLayout) + size_t data_size; // to be set on initialization +}; + +void printHex(const char *data, size_t data_size) { + for (size_t i = 0; i < data_size; ++i) { + // Print the hexadecimal representation of the byte + std::cout << std::hex << std::setw(2) << std::setfill('0') + << (static_cast(data[i]) & 0xFF) << " "; + + // Print a newline every 16 bytes + if ((i + 1) % 16 == 0) { + std::cout << std::endl; + } + } + std::cout << std::dec << std::endl; // Reset the output format +} + +// Returns ByteArray object containing the initial environment message +jobject read_initial_environment( + JNIEnv *env, jclass clazz, const std::string &p2j_memory_name +) { + std::cout << "Reading initial environment from shared memory 1" + << std::endl; + int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); + if (p2jFd == -1) { + perror("shm_open p2j failed while reading from shared memory"); + return nullptr; + } + void *p2jPtr = mmap( + 0, + sizeof(SharedMemoryLayout), + PROT_READ | PROT_WRITE, + MAP_SHARED, + p2jFd, + 0 + ); + if (p2jPtr == MAP_FAILED) { + perror("mmap p2j failed while reading from shared memory"); + close(p2jFd); + return nullptr; + } + SharedMemoryLayout *p2jLayout = static_cast(p2jPtr); + + const size_t initial_environment_size = p2jLayout->initial_environment_size; + const size_t action_size = p2jLayout->action_size; + std::cout << "Reading initial environment from shared memory 2" + << std::endl; + + munmap(p2jPtr, sizeof(SharedMemoryLayout)); + // Note: action_size is 0 when dummy is provided; actually python overwrites + // on initial environment section. + p2jPtr = mmap( + 0, + sizeof(SharedMemoryLayout) + action_size + initial_environment_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, + p2jFd, + 0 + ); + + if (p2jPtr == MAP_FAILED) { + perror("mmap p2j failed while reading from shared memory"); + close(p2jFd); + return nullptr; + } + + p2jLayout = static_cast(p2jPtr); + + char *data_startInitialEnvironment = + static_cast(p2jPtr) + p2jLayout->initial_environment_offset; + size_t data_size = p2jLayout->initial_environment_size; + + std::cout << "Java read data_size: " << p2jLayout->initial_environment_size + << std::endl; + std::cout << "Java initial environment offset:" + << p2jLayout->initial_environment_offset << std::endl; + std::cout << "Java layout size:" << p2jLayout->layout_size << std::endl; + std::cout << "Java action offset:" << p2jLayout->action_offset << std::endl; + std::cout << "Java action size:" << p2jLayout->action_size << std::endl; + std::cout << "Java read data_size: " << data_size << std::endl; + + jbyteArray byteArray = env->NewByteArray(data_size); + if (byteArray == nullptr || env->ExceptionCheck()) { + munmap( + p2jPtr, + sizeof(SharedMemoryLayout) + action_size + initial_environment_size + ); + close(p2jFd); + return nullptr; + } + std::cout << "Java read array: "; + printHex(data_startInitialEnvironment, data_size); + env->SetByteArrayRegion( + byteArray, + 0, + data_size, + reinterpret_cast(data_startInitialEnvironment) + ); + return byteArray; +} + +jbyteArray read_action( + JNIEnv *env, + jclass clazz, + const std::string &p2j_memory_name, + jbyteArray data +) { + std::cout << "Reading action from shared memory 1" << std::endl; + int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); + if (p2jFd == -1) { + perror("shm_open p2j failed while reading from shared memory"); + return nullptr; + } + void *p2jPtr = mmap( + 0, + sizeof(SharedMemoryLayout), + PROT_READ | PROT_WRITE, + MAP_SHARED, + p2jFd, + 0 + ); + if (p2jPtr == MAP_FAILED) { + perror("mmap p2j failed while reading from shared memory"); + close(p2jFd); + return nullptr; + } + + SharedMemoryLayout *p2jHeader = static_cast(p2jPtr); + size_t action_size = p2jHeader->action_size; + + std::cout << "Reading action from shared memory 2" << std::endl; + + munmap(p2jPtr, sizeof(SharedMemoryLayout)); + p2jPtr = mmap( + 0, + sizeof(SharedMemoryLayout) + action_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, + p2jFd, + 0 + ); + + if (p2jPtr == MAP_FAILED) { + perror("mmap p2j failed while reading from shared memory"); + close(p2jFd); + return nullptr; + } + p2jHeader = static_cast(p2jPtr); + + char *data_start = static_cast(p2jPtr) + p2jHeader->action_offset; + + std::cout << "Reading action from shared memory: Acquired Lock" + << std::endl; + + rk_sema_wait(&p2jHeader->sem_action_ready); + if (data != nullptr) { + jsize oldSize = env->GetArrayLength(data); + if (oldSize != p2jHeader->action_size) { + std::cout << "Resizing byte array to" + << std::to_string(p2jHeader->action_size) << std::endl; + env->DeleteLocalRef(data); + data = nullptr; + data = env->NewByteArray(p2jHeader->action_size); + } + } else { + std::cout << "Creating new byte array" + << std::to_string(p2jHeader->action_size) << std::endl; + data = env->NewByteArray(p2jHeader->action_size); + } + + if (data == nullptr || env->ExceptionCheck()) { + munmap(p2jPtr, sizeof(SharedMemoryLayout) + action_size); + close(p2jFd); + return nullptr; + } + // Read the action message + env->SetByteArrayRegion( + data, 0, p2jHeader->action_size, reinterpret_cast(data_start) + ); + // Notify that the action has been read + rk_sema_post(&p2jHeader->sem_action_ready); + std::cout << "Read action from shared memory 3" << std::endl; + return data; +} + +void write_observation( + const std::string &p2j_memory_name, + const std::string &j2p_memory_name, + const char *data, + const size_t observation_size +) { + std::cout << "Writing observation to shared memory 1" << std::endl; + int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); + if (p2jFd == -1) { + perror("shm_open p2j failed while writing to shared memory"); + return; + } + void *p2jPtr = mmap( + 0, + sizeof(SharedMemoryLayout), + PROT_READ | PROT_WRITE, + MAP_SHARED, + p2jFd, + 0 + ); + if (p2jPtr == MAP_FAILED) { + perror("mmap p2j failed while writing to shared memory"); + close(p2jFd); + return; + } + SharedMemoryLayout *p2jLayout = static_cast(p2jPtr); + + rk_sema_wait(&p2jLayout->sem_obs_ready); + + std::cout << "Writing observation to shared memory 2" << std::endl; + + int j2pFd = shm_open(j2p_memory_name.c_str(), O_RDWR, 0666); + if (j2pFd == -1) { + perror("shm_open j2p failed while writing to shared memory"); + munmap(p2jPtr, sizeof(SharedMemoryLayout)); + return; + } + + void *j2pPtr = mmap( + 0, + sizeof(J2PSharedMemoryLayout), + PROT_READ | PROT_WRITE, + MAP_SHARED, + j2pFd, + 0 + ); + + if (j2pPtr == MAP_FAILED) { + perror("mmap j2p failed while writing to shared memory"); + munmap(p2jPtr, sizeof(SharedMemoryLayout)); + close(p2jFd); + close(j2pFd); + return; + } + J2PSharedMemoryLayout *j2pLayout = + static_cast(j2pPtr); + + struct stat statbuf; + if (fstat(j2pFd, &statbuf) == -1) { + perror("fstat failed while getting shared memory size"); + munmap(j2pPtr, sizeof(J2PSharedMemoryLayout)); + munmap(p2jPtr, sizeof(SharedMemoryLayout)); + close(j2pFd); + close(p2jFd); + return; + } + const size_t current_shmem_size = statbuf.st_size; + size_t requiredSize = observation_size + sizeof(J2PSharedMemoryLayout); + // requiredSize = requiredSize > 1024 ? requiredSize : 1024; + + if (current_shmem_size < requiredSize) { + // Unmap existing memory before resizing + munmap(j2pPtr, sizeof(J2PSharedMemoryLayout)); + + // Resize the shared memory + if (ftruncate(j2pFd, requiredSize) == -1) { + perror("ftruncate failed while resizing shared memory"); + munmap(p2jPtr, sizeof(SharedMemoryLayout)); + close(j2pFd); + close(p2jFd); + return; + } + + // Remap with new size + j2pPtr = + mmap(0, requiredSize, PROT_READ | PROT_WRITE, MAP_SHARED, j2pFd, 0); + if (j2pPtr == MAP_FAILED) { + perror("mmap failed after resizing shared memory"); + munmap(p2jPtr, sizeof(SharedMemoryLayout)); + close(j2pFd); + close(p2jFd); + return; + } + + // Initialize the header + j2pLayout = static_cast(j2pPtr); + j2pLayout->layout_size = sizeof(J2PSharedMemoryLayout); + j2pLayout->data_offset = sizeof(J2PSharedMemoryLayout); + j2pLayout->data_size = observation_size; + std::cout << "Resized shared memory to " << requiredSize << std::endl; + } + std::cout << "Writing observation to shared memory 2" << std::endl; + // Write the observation to shared memory + char *data_start = static_cast(j2pPtr) + j2pLayout->data_offset; + std::memcpy(data_start, data, observation_size); + j2pLayout->data_size = observation_size; + + // Notify Python that the observation is ready + rk_sema_post(&p2jLayout->sem_obs_ready); + + // Clean up resources + munmap(j2pPtr, requiredSize); + munmap(p2jPtr, sizeof(SharedMemoryLayout)); + close(j2pFd); + close(p2jFd); +} + +extern "C" JNIEXPORT jobject JNICALL +Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readInitialEnvironmentImpl( + JNIEnv *env, jclass clazz, jstring p2j_memory_name +) { + const char *p2j_memory_name_cstr = + env->GetStringUTFChars(p2j_memory_name, nullptr); + jobject result = nullptr; + try { + result = read_initial_environment(env, clazz, p2j_memory_name_cstr); + } catch (const std::exception &e) { + env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what()); + return nullptr; + } + env->ReleaseStringUTFChars(p2j_memory_name, p2j_memory_name_cstr); + return result; +} + +// fun readAction(action_memory_name: String, action_data: ByteArray?): +// ByteArray +extern "C" JNIEXPORT jbyteArray JNICALL +Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readActionImpl( + JNIEnv *env, jclass clazz, jstring p2j_memory_name, jbyteArray action_data +) { + const char *j2p_memory_name_cstr = + env->GetStringUTFChars(p2j_memory_name, nullptr); + + size_t data_size = 0; + jbyteArray data; + try { + data = read_action(env, clazz, j2p_memory_name_cstr, action_data); + } catch (const std::exception &e) { + env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what()); + return nullptr; + } + env->ReleaseStringUTFChars(p2j_memory_name, j2p_memory_name_cstr); + return data; +} + +// fun writeObservation( +// observation_memory_name: String, +// synchronization_memory_name: String, +// observation_data: ByteArray +// ) +extern "C" JNIEXPORT void JNICALL +Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_writeObservationImpl( + JNIEnv *env, + jclass clazz, + jstring p2j_memory_name, + jstring j2p_memory_name, + jbyteArray observation_data +) { + const char *j2p_memory_name_cstr = + env->GetStringUTFChars(j2p_memory_name, nullptr); + const char *p2j_memory_name_cstr = + env->GetStringUTFChars(p2j_memory_name, nullptr); + jbyte *observation_data_ptr = + env->GetByteArrayElements(observation_data, nullptr); + jsize observation_data_size = env->GetArrayLength(observation_data); + + try { + write_observation( + p2j_memory_name_cstr, + j2p_memory_name_cstr, + reinterpret_cast(observation_data_ptr), + static_cast(observation_data_size) + ); + } catch (const std::exception &e) { + env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what()); + } + + env->ReleaseStringUTFChars(j2p_memory_name, j2p_memory_name_cstr); + env->ReleaseStringUTFChars(p2j_memory_name, p2j_memory_name_cstr); + env->ReleaseByteArrayElements( + observation_data, observation_data_ptr, JNI_ABORT + ); +} \ No newline at end of file diff --git a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt index 7b9fd9b0..bdc791e4 100644 --- a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt +++ b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt @@ -192,8 +192,14 @@ class NamedPipeMessageIO( class SharedMemoryMessageIO( val port: Int, ) : MessageIO { - private val p2jMemoryName = "craftground_${port}_p2j" - private val j2pMemoryName = "craftground_${port}_j2p" + val shmemPrefix = + if (System.getProperty("os.name").contains("Windows", ignoreCase = true)) { + "Global\\" + } else { + "/" + } + private val p2jMemoryName = "${shmemPrefix}craftground_${port}_p2j" + private val j2pMemoryName = "${shmemPrefix}craftground_${port}_j2p" override fun readAction(): ActionSpace.ActionSpaceMessageV2 = FramebufferCapturer.readAction(p2jMemoryName) diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index ed7318a6..b022c9b8 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -1,5 +1,8 @@ +import platform import time +from ..proto.observation_space_pb2 import ObservationSpaceMessage + from ..csv_logger import CsvLogger from ..environment.ipc_interface import IPCInterface from ..proto.action_space_pb2 import ActionSpaceMessageV2 @@ -19,6 +22,8 @@ shared_memory_exists, # noqa ) +from ..environment.action_space import no_op_v2, action_v2_dict_to_message + class BoostIPC(IPCInterface): def __init__( @@ -33,8 +38,9 @@ def __init__( initial_environment_bytes: bytes = initial_environment.SerializeToString() # Get the length of the action space message - dummy_action: ActionSpaceMessageV2 = ActionSpaceMessageV2() + dummy_action: ActionSpaceMessageV2 = action_v2_dict_to_message(no_op_v2()) dummy_action_bytes: bytes = dummy_action.SerializeToString() + print(f"Length of Dummy_action_bytes: {len(dummy_action_bytes)}") self.find_free_port = find_free_port self.port = initialize_shared_memory( int(self.port), @@ -43,23 +49,29 @@ def __init__( len(dummy_action_bytes), find_free_port, ) - self.p2j_shared_memory_name = f"craftground_{self.port}_p2j" - self.j2p_shared_memory_name = f"craftground_{self.port}_j2p" + self.SHMEM_PREFIX = "Global\\" if platform.system() == "Windows" else "/" + self.p2j_shared_memory_name = f"{self.SHMEM_PREFIX}craftground_{self.port}_p2j" + self.j2p_shared_memory_name = f"{self.SHMEM_PREFIX}craftground_{self.port}_j2p" def send_action(self, action: ActionSpaceMessageV2): action_bytes: bytes = action.SerializeToString() - self.logger.log("Sending action to shared memory") - write_to_shared_memory(self.p2j_shared_memory_name, action_bytes) + self.logger.log(f"Sending action to shared memory: {len(action_bytes)} bytes") + write_to_shared_memory( + self.p2j_shared_memory_name, action_bytes, len(action_bytes) + ) - def read_observation(self) -> bytes: + def read_observation(self) -> ObservationSpaceMessage: self.logger.log("Reading observation from shared memory") - return read_from_shared_memory( + observation_space = ObservationSpaceMessage() + data_bytes = read_from_shared_memory( self.p2j_shared_memory_name, self.j2p_shared_memory_name ) + observation_space.ParseFromString(data_bytes) + return observation_space def destroy(self): - destroy_shared_memory(self.p2j_shared_memory_name) - destroy_shared_memory(self.j2p_shared_memory_name) + destroy_shared_memory(self.p2j_shared_memory_name, True) + destroy_shared_memory(self.j2p_shared_memory_name, True) # Java destroys the initial environment shared memory # destroy_shared_memory(self.initial_environment_shared_memory_name) From 815cbe11ce11b2449ed0d1c2160dacf96eb78d85 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Thu, 30 Jan 2025 22:05:00 +0900 Subject: [PATCH 70/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20all=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/cross_semaphore.h | 45 ++++++--- src/cpp/ipc_noboost.cpp | 47 ++++----- .../src/main/cpp/cross_semaphore.h | 45 ++++++--- .../MinecraftEnv/src/main/cpp/noboost_ipc.cpp | 97 ++++++++++++++----- .../minecraftenv/FramebufferCapturer.kt | 12 ++- .../kyhsgeekcode/minecraftenv/MessageIO.kt | 26 +++-- src/craftground/environment/boost_ipc.py | 23 ++++- 7 files changed, 202 insertions(+), 93 deletions(-) diff --git a/src/cpp/cross_semaphore.h b/src/cpp/cross_semaphore.h index 1c707329..369a0d73 100644 --- a/src/cpp/cross_semaphore.h +++ b/src/cpp/cross_semaphore.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #if IS_WINDOWS #include @@ -13,9 +14,11 @@ struct rk_sema { #if IS_WINDOWS - HANDLE sem; + HANDLE sem_python; + HANDLE sem_java; #else - sem_t *sem; + sem_t *sem_python; + sem_t *sem_java; char name[30]; // Save the name of the semaphore #endif }; @@ -26,38 +29,53 @@ static inline void rk_sema_init( #if IS_WINDOWS s->sem = CreateSemaphore(NULL, value, max, name); #else - snprintf(s->name, sizeof(s->name), "/%s", name); + snprintf(s->name, sizeof(s->name), "%s", name); sem_unlink(s->name); // Remove any existing semaphore with the same name - s->sem = sem_open(s->name, O_CREAT, 0644, value); // Binary semaphore - if (s->sem == SEM_FAILED) { - perror("sem_open failed"); + s->sem_python = sem_open(s->name, O_CREAT, 0644, value); // Binary semaphore + if (s->sem_python == SEM_FAILED) { + perror("sem_open failed in create"); return; } #endif } -static inline void rk_sema_wait(struct rk_sema *s) { +static inline void rk_sema_open(struct rk_sema *s) { +#if IS_WINDOWS + s->sem_python = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, s->name); +#else + s->sem_python = sem_open(s->name, 0); // Open existing semaphore + if (s->sem_python == SEM_FAILED) { + std::cout << "sem_open failed in python open" << s->name << std::endl; + perror("sem_open failed in open"); + return; + } +#endif +} + +static inline int rk_sema_wait(struct rk_sema *s) { #if IS_WINDOWS DWORD r; do { - r = WaitForSingleObject(s->sem, INFINITE); + r = WaitForSingleObject(s->sem_python, INFINITE); } while (r == WAIT_FAILED && GetLastError() == ERROR_INTERRUPT); + return r; #else int r; do { - r = sem_wait(s->sem); + r = sem_wait(s->sem_python); } while (r == -1 && errno == EINTR); + return r; #endif } -static inline void rk_sema_post(struct rk_sema *s) { +static inline int rk_sema_post(struct rk_sema *s) { #if IS_WINDOWS - ReleaseSemaphore(s->sem, 1, NULL); + return ReleaseSemaphore(s->sem_python, 1, NULL); #else - sem_post(s->sem); + return sem_post(s->sem_python); #endif } @@ -65,7 +83,8 @@ static inline void rk_sema_destroy(struct rk_sema *s) { #if IS_WINDOWS CloseHandle(s->sem); #else - sem_close(s->sem); + sem_close(s->sem_python); + sem_close(s->sem_java); sem_unlink(s->name); // Named semaphores are removed after unlinking #endif } \ No newline at end of file diff --git a/src/cpp/ipc_noboost.cpp b/src/cpp/ipc_noboost.cpp index 8be6ac7e..f2cc65ed 100644 --- a/src/cpp/ipc_noboost.cpp +++ b/src/cpp/ipc_noboost.cpp @@ -97,17 +97,19 @@ int create_shared_memory_impl( SharedMemoryLayout *p2jLayout = static_cast(ptr); new (p2jLayout) SharedMemoryLayout(); - std::string sema_obs_ready_name = make_shared_memory_name( - port, "cg_sem_obs" + std::to_string(port) - ); - std::string sema_action_ready_name = make_shared_memory_name( - port, "cg_sem_act" + std::to_string(port) - ); + std::string sema_obs_ready_name = + SHMEM_PREFIX "cg_sem_obs" + std::to_string(port); + std::string sema_action_ready_name = + SHMEM_PREFIX "cg_sem_act" + std::to_string(port); rk_sema_init(&p2jLayout->sem_obs_ready, sema_obs_ready_name.c_str(), 0, 1); rk_sema_init( &p2jLayout->sem_action_ready, sema_action_ready_name.c_str(), 0, 1 ); + std::cout << "Initialized semaphore for Python" + << p2jLayout->sem_obs_ready.name << std::endl; + std::cout << "Initialized semaphore for Python" + << p2jLayout->sem_action_ready.name << std::endl; p2jLayout->action_offset = sizeof(SharedMemoryLayout); p2jLayout->action_size = action_size; @@ -142,7 +144,7 @@ void write_to_shared_memory_impl( int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); void *ptr = mmap( 0, - sizeof(SharedMemoryLayout), + sizeof(SharedMemoryLayout) + action_size, PROT_READ | PROT_WRITE, MAP_SHARED, p2jFd, @@ -153,24 +155,14 @@ void write_to_shared_memory_impl( close(p2jFd); return; } - SharedMemoryLayout *layout = static_cast(ptr); layout->action_size = action_size; - const size_t action_offset = layout->action_offset; - munmap(ptr, sizeof(SharedMemoryLayout)); - ptr = mmap( - 0, - sizeof(SharedMemoryLayout) + action_size, - PROT_READ | PROT_WRITE, - MAP_SHARED, - p2jFd, - 0 - ); - layout = static_cast(ptr); - std::cout << "Writing action to shared memory" << std::endl; - rk_sema_wait(&layout->sem_action_ready); + layout->action_offset = sizeof(SharedMemoryLayout); std::memcpy((char *)ptr + layout->action_offset, data, layout->action_size); - rk_sema_post(&layout->sem_action_ready); + rk_sema_open(&layout->sem_obs_ready); + if (rk_sema_post(&layout->sem_action_ready) < 0) { + perror("rk_sema_post failed while notifying java"); + } std::cout << "Wrote action to shared memory" << std::endl; munmap(ptr, sizeof(SharedMemoryLayout) + action_size); close(p2jFd); @@ -179,7 +171,6 @@ void write_to_shared_memory_impl( py::bytes read_from_shared_memory_impl( const std::string &p2j_memory_name, const std::string &j2p_memory_name ) { - std::cout << "Reading observation from shared memory 1" << std::endl; int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); if (p2jFd == -1) { perror("shm_open p2j failed while reading from shared memory"); @@ -203,9 +194,12 @@ py::bytes read_from_shared_memory_impl( size_t action_size = layout->action_size; size_t data_size = layout->initial_environment_size; - std::cout << "Reading observation from shared memory 2" << std::endl; + std::cout << "Waiting for java to write observation" << std::endl; // Wait for the observation to be ready + rk_sema_open(&layout->sem_obs_ready); + rk_sema_open(&layout->sem_action_ready); + rk_sema_post(&layout->sem_action_ready); rk_sema_wait(&layout->sem_obs_ready); int j2pFd = shm_open(j2p_memory_name.c_str(), O_RDWR, 0666); @@ -259,9 +253,10 @@ py::bytes read_from_shared_memory_impl( const char *data_start = (char *)j2pPtr + j2pLayout->data_offset; py::bytes data(data_start, j2pLayout->data_size); - rk_sema_post(&layout->sem_obs_ready); // Notify that the observation is read - std::cout << "Read observation from shared memory" << std::endl; + std::cout << "Read observation from shared memory. Notified to java read " + "finish observation" + << std::endl; munmap(j2pPtr, sizeof(J2PSharedMemoryLayout) + obs_length); munmap(p2jPtr, sizeof(SharedMemoryLayout)); close(p2jFd); diff --git a/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h b/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h index 77ed1925..d5ada681 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h +++ b/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h @@ -4,6 +4,7 @@ #include #include #include +#include #if IS_WINDOWS #include #else @@ -12,9 +13,11 @@ struct rk_sema { #if IS_WINDOWS - HANDLE sem; + HANDLE sem_python; + HANDLE sem_java; #else - sem_t *sem; + sem_t *sem_python; + sem_t *sem_java; char name[30]; // Save the name of the semaphore #endif }; @@ -23,13 +26,26 @@ static inline void rk_sema_init( struct rk_sema *s, const char *name, uint32_t value, uint32_t max ) { #if IS_WINDOWS - s->sem = CreateSemaphore(NULL, value, max, name); + // TODO: Open the semaphore with the same name + s->sem_java = CreateSemaphore(NULL, value, max, name); #else - snprintf(s->name, sizeof(s->name), "/%s", name); - sem_unlink(s->name); // Remove any existing semaphore with the same name - s->sem = sem_open(s->name, O_CREAT, 0644, value); // Binary semaphore - if (s->sem == SEM_FAILED) { - perror("sem_open failed"); + snprintf(s->name, sizeof(s->name), "%s", name); + s->sem_java = sem_open(s->name, O_CREAT); // Binary semaphore + if (s->sem_java == SEM_FAILED) { + perror("sem_open failed in java init"); + return; + } +#endif +} + +static inline void rk_sema_open(struct rk_sema *s) { +#if IS_WINDOWS + s->sem_java = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, s->name); +#else + s->sem_java = sem_open(s->name, 0); // Open existing semaphore + if (s->sem_java == SEM_FAILED) { + std::cout << "sem_open failed in java open" << s->name << std::endl; + perror("sem_open failed in java open"); return; } #endif @@ -40,23 +56,23 @@ static inline void rk_sema_wait(struct rk_sema *s) { #if IS_WINDOWS DWORD r; do { - r = WaitForSingleObject(s->sem, INFINITE); + r = WaitForSingleObject(s->sem_java, INFINITE); } while (r == WAIT_FAILED && GetLastError() == ERROR_INTERRUPT); #else int r; do { - r = sem_wait(s->sem); + r = sem_wait(s->sem_java); } while (r == -1 && errno == EINTR); #endif } -static inline void rk_sema_post(struct rk_sema *s) { +static inline int rk_sema_post(struct rk_sema *s) { #if IS_WINDOWS - ReleaseSemaphore(s->sem, 1, NULL); + return ReleaseSemaphore(s->sem_java, 1, NULL); #else - sem_post(s->sem); + return sem_post(s->sem_java); #endif } @@ -64,7 +80,8 @@ static inline void rk_sema_destroy(struct rk_sema *s) { #if IS_WINDOWS CloseHandle(s->sem); #else - sem_close(s->sem); + sem_close(s->sem_java); + sem_close(s->sem_python); sem_unlink(s->name); // Named semaphores are removed after unlinking #endif } \ No newline at end of file diff --git a/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp index f65cdaae..4ac194a9 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -48,9 +49,13 @@ void printHex(const char *data, size_t data_size) { std::cout << std::dec << std::endl; // Reset the output format } +std::string make_shared_memory_name(int port, const std::string &suffix) { + return SHMEM_PREFIX "craftground_" + std::to_string(port) + "_" + suffix; +} + // Returns ByteArray object containing the initial environment message jobject read_initial_environment( - JNIEnv *env, jclass clazz, const std::string &p2j_memory_name + JNIEnv *env, jclass clazz, const std::string &p2j_memory_name, int port ) { std::cout << "Reading initial environment from shared memory 1" << std::endl; @@ -129,6 +134,20 @@ jobject read_initial_environment( data_size, reinterpret_cast(data_startInitialEnvironment) ); + + std::string sema_obs_ready_name = + SHMEM_PREFIX "cg_sem_obs" + std::to_string(port); + std::string sema_action_ready_name = + SHMEM_PREFIX "cg_sem_act" + std::to_string(port); + + rk_sema_init(&p2jLayout->sem_obs_ready, sema_obs_ready_name.c_str(), 0, 1); + rk_sema_init( + &p2jLayout->sem_action_ready, sema_action_ready_name.c_str(), 0, 1 + ); + std::cout << "Initialied semaphore for Java" + << p2jLayout->sem_obs_ready.name << std::endl; + std::cout << "Initialied semaphore for Java" + << p2jLayout->sem_action_ready.name << std::endl; return byteArray; } @@ -138,7 +157,6 @@ jbyteArray read_action( const std::string &p2j_memory_name, jbyteArray data ) { - std::cout << "Reading action from shared memory 1" << std::endl; int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); if (p2jFd == -1) { perror("shm_open p2j failed while reading from shared memory"); @@ -161,8 +179,6 @@ jbyteArray read_action( SharedMemoryLayout *p2jHeader = static_cast(p2jPtr); size_t action_size = p2jHeader->action_size; - std::cout << "Reading action from shared memory 2" << std::endl; - munmap(p2jPtr, sizeof(SharedMemoryLayout)); p2jPtr = mmap( 0, @@ -182,9 +198,10 @@ jbyteArray read_action( char *data_start = static_cast(p2jPtr) + p2jHeader->action_offset; - std::cout << "Reading action from shared memory: Acquired Lock" - << std::endl; - + std::cout << "Waiting for Python to write the action" << std::endl; + printHex((const char *)p2jHeader, sizeof(SharedMemoryLayout)); + // OK + rk_sema_open(&p2jHeader->sem_action_ready); rk_sema_wait(&p2jHeader->sem_action_ready); if (data != nullptr) { jsize oldSize = env->GetArrayLength(data); @@ -207,23 +224,25 @@ jbyteArray read_action( return nullptr; } // Read the action message - env->SetByteArrayRegion( - data, 0, p2jHeader->action_size, reinterpret_cast(data_start) - ); - // Notify that the action has been read - rk_sema_post(&p2jHeader->sem_action_ready); - std::cout << "Read action from shared memory 3" << std::endl; + if (action_size > 0) { + env->SetByteArrayRegion( + data, + 0, + p2jHeader->action_size, + reinterpret_cast(data_start) + ); + } + std::cout << "Read action from shared memory" << std::endl; return data; } void write_observation( - const std::string &p2j_memory_name, - const std::string &j2p_memory_name, + const char *p2j_memory_name, + const char *j2p_memory_name, const char *data, const size_t observation_size ) { - std::cout << "Writing observation to shared memory 1" << std::endl; - int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); + int p2jFd = shm_open(p2j_memory_name, O_RDWR, 0666); if (p2jFd == -1) { perror("shm_open p2j failed while writing to shared memory"); return; @@ -243,11 +262,7 @@ void write_observation( } SharedMemoryLayout *p2jLayout = static_cast(p2jPtr); - rk_sema_wait(&p2jLayout->sem_obs_ready); - - std::cout << "Writing observation to shared memory 2" << std::endl; - - int j2pFd = shm_open(j2p_memory_name.c_str(), O_RDWR, 0666); + int j2pFd = shm_open(j2p_memory_name, O_RDWR, 0666); if (j2pFd == -1) { perror("shm_open j2p failed while writing to shared memory"); munmap(p2jPtr, sizeof(SharedMemoryLayout)); @@ -270,8 +285,13 @@ void write_observation( close(j2pFd); return; } + std::cout << "Writing observation to shared memory adfsadf" << std::endl; J2PSharedMemoryLayout *j2pLayout = static_cast(j2pPtr); + j2pLayout->data_offset = sizeof(J2PSharedMemoryLayout); + + std::cout << "Writing observation to shared memory adfsad222sdsfsasdff" + << std::endl; struct stat statbuf; if (fstat(j2pFd, &statbuf) == -1) { @@ -281,6 +301,8 @@ void write_observation( close(j2pFd); close(p2jFd); return; + } else { + std::cout << "Shared memory size: " << statbuf.st_size << std::endl; } const size_t current_shmem_size = statbuf.st_size; size_t requiredSize = observation_size + sizeof(J2PSharedMemoryLayout); @@ -317,14 +339,20 @@ void write_observation( j2pLayout->data_size = observation_size; std::cout << "Resized shared memory to " << requiredSize << std::endl; } - std::cout << "Writing observation to shared memory 2" << std::endl; + std::cout << "Writing observation to shared memory KKKAKAAKAK" << std::endl; + // Write the observation to shared memory char *data_start = static_cast(j2pPtr) + j2pLayout->data_offset; std::memcpy(data_start, data, observation_size); j2pLayout->data_size = observation_size; // Notify Python that the observation is ready - rk_sema_post(&p2jLayout->sem_obs_ready); + printHex((const char *)p2jLayout, sizeof(SharedMemoryLayout)); + rk_sema_open(&p2jLayout->sem_obs_ready); + if (rk_sema_post(&p2jLayout->sem_obs_ready) < 0) { + perror("rk_sema_post failed while notifying python"); + } + std::cout << "Wrote and notified observation to python" << std::endl; // Clean up resources munmap(j2pPtr, requiredSize); @@ -335,13 +363,14 @@ void write_observation( extern "C" JNIEXPORT jobject JNICALL Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_readInitialEnvironmentImpl( - JNIEnv *env, jclass clazz, jstring p2j_memory_name + JNIEnv *env, jclass clazz, jstring p2j_memory_name, jint port ) { const char *p2j_memory_name_cstr = env->GetStringUTFChars(p2j_memory_name, nullptr); jobject result = nullptr; try { - result = read_initial_environment(env, clazz, p2j_memory_name_cstr); + result = + read_initial_environment(env, clazz, p2j_memory_name_cstr, port); } catch (const std::exception &e) { env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what()); return nullptr; @@ -388,9 +417,22 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_writeObservationImpl( env->GetStringUTFChars(j2p_memory_name, nullptr); const char *p2j_memory_name_cstr = env->GetStringUTFChars(p2j_memory_name, nullptr); + + if (j2p_memory_name_cstr == nullptr || p2j_memory_name_cstr == nullptr) { + std::cerr << "Failed to get memory name: " << j2p_memory_name_cstr + << ";" << p2j_memory_name_cstr << std::endl; + return; + } jbyte *observation_data_ptr = env->GetByteArrayElements(observation_data, nullptr); + + if (observation_data_ptr == nullptr) { + std::cerr << "Failed to get observation data" << std::endl; + return; + } jsize observation_data_size = env->GetArrayLength(observation_data); + std::cout << "Writing observation to shared memory with length" + << std::to_string(observation_data_size) << std::endl; try { write_observation( @@ -403,8 +445,11 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_writeObservationImpl( env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what()); } + std::cout << "Releasing utf chars 1" << std::endl; env->ReleaseStringUTFChars(j2p_memory_name, j2p_memory_name_cstr); + std::cout << "Releasing utf chars 1" << std::endl; env->ReleaseStringUTFChars(p2j_memory_name, p2j_memory_name_cstr); + std::cout << "Releasing array elements" << std::endl; env->ReleaseByteArrayElements( observation_data, observation_data_ptr, JNI_ABORT ); diff --git a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt index aa9fde30..f288dc6a 100644 --- a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt +++ b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/FramebufferCapturer.kt @@ -149,7 +149,10 @@ object FramebufferCapturer { private var actionBuffer: ByteArray? = null - external fun readInitialEnvironmentImpl(p2jMemoryName: String): ByteArray + external fun readInitialEnvironmentImpl( + p2jMemoryName: String, + port: Int, + ): ByteArray external fun readActionImpl( p2jMemoryName: String, @@ -162,8 +165,11 @@ object FramebufferCapturer { observationData: ByteArray, ) - fun readInitialEnvironment(p2jMemoryName: String): InitialEnvironment.InitialEnvironmentMessage = - InitialEnvironment.InitialEnvironmentMessage.parseFrom(readInitialEnvironmentImpl(p2jMemoryName)) + fun readInitialEnvironment( + p2jMemoryName: String, + port: Int, + ): InitialEnvironment.InitialEnvironmentMessage = + InitialEnvironment.InitialEnvironmentMessage.parseFrom(readInitialEnvironmentImpl(p2jMemoryName, port)) fun readAction(p2jMemoryName: String): ActionSpace.ActionSpaceMessageV2 { actionBuffer = readActionImpl(p2jMemoryName, actionBuffer) diff --git a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt index bdc791e4..2086a258 100644 --- a/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt +++ b/src/craftground/MinecraftEnv/src/main/java/com/kyhsgeekcode/minecraftenv/MessageIO.kt @@ -35,7 +35,7 @@ class TCPSocketMessageIO( inputStream.read(buffer.array()) val len = buffer.order(ByteOrder.LITTLE_ENDIAN).int val bytes = inputStream.readNBytes(len) -// println("Read action space bytes $len") + // println("Read action space bytes $len") val actionSpace = ActionSpace.ActionSpaceMessageV2.parseFrom(bytes) printWithTime("Read action space") return actionSpace @@ -51,8 +51,11 @@ class TCPSocketMessageIO( val len = buffer.order(ByteOrder.LITTLE_ENDIAN).int printWithTime("$len") val bytes = inputStream.readNBytes(len.toInt()) - val initialEnvironment = InitialEnvironment.InitialEnvironmentMessage.parseFrom(bytes) - printWithTime("Read initial environment ${initialEnvironment!!.imageSizeX} ${initialEnvironment!!.imageSizeY}") + val initialEnvironment = + InitialEnvironment.InitialEnvironmentMessage.parseFrom(bytes) + printWithTime( + "Read initial environment ${initialEnvironment!!.imageSizeX} ${initialEnvironment!!.imageSizeY}", + ) return initialEnvironment } catch (e: SocketTimeoutException) { println("Socket timeout") @@ -68,9 +71,9 @@ class TCPSocketMessageIO( printWithTime("Writing observation with size ${observationSpace.serializedSize}") val dataOutputStream = LittleEndianDataOutputStream(outputStream) dataOutputStream.writeInt(observationSpace.serializedSize) -// println("Wrote observation size ${observationSpace.serializedSize}") + // println("Wrote observation size ${observationSpace.serializedSize}") observationSpace.writeTo(outputStream) -// println("Wrote observation ${observationSpace.serializedSize}") + // println("Wrote observation ${observationSpace.serializedSize}") outputStream.flush() printWithTime("Flushed") } @@ -87,7 +90,7 @@ class DomainSocketMessageIO( buffer.flip() val len = buffer.order(ByteOrder.LITTLE_ENDIAN).int val bytes = socketChannel.readNBytes(len) -// println("Read action space bytes $len") + // println("Read action space bytes $len") val actionSpaceMessageV2 = ActionSpace.ActionSpaceMessageV2.parseFrom(bytes) printWithTime("Read action space") return actionSpaceMessageV2 @@ -105,8 +108,11 @@ class DomainSocketMessageIO( val len = buffer.order(ByteOrder.LITTLE_ENDIAN).int printWithTime("$len") val dataBuffer = socketChannel.readNBytes(len) - val initialEnvironment = InitialEnvironment.InitialEnvironmentMessage.parseFrom(dataBuffer) - printWithTime("Read initial environment ${initialEnvironment.imageSizeX} ${initialEnvironment.imageSizeY}") + val initialEnvironment = + InitialEnvironment.InitialEnvironmentMessage.parseFrom(dataBuffer) + printWithTime( + "Read initial environment ${initialEnvironment.imageSizeX} ${initialEnvironment.imageSizeY}", + ) return initialEnvironment } catch (e: SocketTimeoutException) { println("Socket timeout") @@ -204,8 +210,8 @@ class SharedMemoryMessageIO( override fun readAction(): ActionSpace.ActionSpaceMessageV2 = FramebufferCapturer.readAction(p2jMemoryName) override fun readInitialEnvironment(): InitialEnvironment.InitialEnvironmentMessage = - FramebufferCapturer.readInitialEnvironment(p2jMemoryName) + FramebufferCapturer.readInitialEnvironment(p2jMemoryName, port) override fun writeObservation(observation: ObservationSpace.ObservationSpaceMessage) = - FramebufferCapturer.writeObservation(j2pMemoryName, p2jMemoryName, observation) + FramebufferCapturer.writeObservation(p2jMemoryName, j2pMemoryName, observation) } diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index b022c9b8..42bedb18 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -1,5 +1,6 @@ import platform import time +from typing import List, Optional from ..proto.observation_space_pb2 import ObservationSpaceMessage @@ -53,7 +54,13 @@ def __init__( self.p2j_shared_memory_name = f"{self.SHMEM_PREFIX}craftground_{self.port}_p2j" self.j2p_shared_memory_name = f"{self.SHMEM_PREFIX}craftground_{self.port}_j2p" - def send_action(self, action: ActionSpaceMessageV2): + def send_action( + self, action: ActionSpaceMessageV2, commands: Optional[List[str]] = None + ): + if not commands: + commands = [] + action.commands.extend(commands) + action_bytes: bytes = action.SerializeToString() self.logger.log(f"Sending action to shared memory: {len(action_bytes)} bytes") write_to_shared_memory( @@ -81,6 +88,20 @@ def is_alive(self) -> bool: def remove_orphan_java_processes(self): pass + def send_fastreset2(self, extra_commands: List[str] = None): + extra_cmd_str = "" + if extra_commands is not None: + extra_cmd_str = ";".join(extra_commands) + self.send_commands([f"fastreset {extra_cmd_str}"]) + + def send_commands(self, commands: List[str]): + # print("Sending command") + action_space = action_v2_dict_to_message(no_op_v2()) + action_space.commands.extend(commands) + v = action_space.SerializeToString() + self.logger.log(f"Sending action to shared memory: {len(v)} bytes") + write_to_shared_memory(self.p2j_shared_memory_name, v, len(v)) + def start_communication(self): # wait until the j2p shared memory is created wait_time = 1 From 890ba1a1632f2b29f1830222761a79bb174e8bdb Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Thu, 30 Jan 2025 22:07:59 +0900 Subject: [PATCH 71/91] =?UTF-8?q?=F0=9F=92=9A=20Remove=20boost=20from=20te?= =?UTF-8?q?st?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fa22d05a..e355489c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,8 +184,8 @@ if(BUILD_TESTS) target_link_libraries(craftground PRIVATE CUDA::cudart CUDA::cudart_static) endif() endif() - target_link_libraries(craftground PRIVATE Boost::system Boost::thread Boost::interprocess) - target_include_directories(craftground PRIVATE ${Boost_INCLUDE_DIRS}) + # target_link_libraries(craftground PRIVATE Boost::system Boost::thread Boost::interprocess) + # target_include_directories(craftground PRIVATE ${Boost_INCLUDE_DIRS}) target_link_options(craftground PRIVATE "-Wl,--whole-archive" "-Wl,--no-whole-archive") target_include_directories(craftground PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp) From d78d3a795471ad8aa3fa9878cc925feb409ec63d Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Fri, 31 Jan 2025 15:42:14 +0900 Subject: [PATCH 72/91] =?UTF-8?q?=F0=9F=94=87=20Remove=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/cross_semaphore.h | 12 ++ src/cpp/ipc_noboost.cpp | 136 ++++++++++++++---- .../MinecraftEnv/src/main/cpp/noboost_ipc.cpp | 79 +++++----- src/craftground/environment/boost_ipc.py | 4 +- 4 files changed, 160 insertions(+), 71 deletions(-) diff --git a/src/cpp/cross_semaphore.h b/src/cpp/cross_semaphore.h index 369a0d73..6ca1a498 100644 --- a/src/cpp/cross_semaphore.h +++ b/src/cpp/cross_semaphore.h @@ -6,6 +6,7 @@ #include #include #include +#include #if IS_WINDOWS #include #else @@ -43,6 +44,9 @@ static inline void rk_sema_open(struct rk_sema *s) { #if IS_WINDOWS s->sem_python = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, s->name); #else + if (s->sem_python != nullptr) { + return; + } s->sem_python = sem_open(s->name, 0); // Open existing semaphore if (s->sem_python == SEM_FAILED) { std::cout << "sem_open failed in python open" << s->name << std::endl; @@ -79,6 +83,14 @@ static inline int rk_sema_post(struct rk_sema *s) { #endif } +static inline void async_rk_sema_post(struct rk_sema *s) { + std::thread([s]() { + if (rk_sema_post(s) < 0) { + perror("Failed to post semaphore"); + }; + }).detach(); +} + static inline void rk_sema_destroy(struct rk_sema *s) { #if IS_WINDOWS CloseHandle(s->sem); diff --git a/src/cpp/ipc_noboost.cpp b/src/cpp/ipc_noboost.cpp index f2cc65ed..0f970f05 100644 --- a/src/cpp/ipc_noboost.cpp +++ b/src/cpp/ipc_noboost.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -9,6 +10,10 @@ #include "cross_semaphore.h" #include "print_hex.h" +#ifndef MAP_POPULATE +#define MAP_POPULATE 0 +#endif + bool shared_memory_exists(const std::string &name) { int fd = shm_open(name.c_str(), O_RDONLY, 0666); if (fd == -1) { @@ -22,6 +27,72 @@ std::string make_shared_memory_name(int port, const std::string &suffix) { return SHMEM_PREFIX "craftground_" + std::to_string(port) + "_" + suffix; } +std::unordered_map shm_fd_cache; +std::mutex shm_fd_cache_mutex; + +int get_shared_memory_fd(const std::string &memory_name) { + std::lock_guard lock(shm_fd_cache_mutex); + if (shm_fd_cache.count(memory_name)) { + return shm_fd_cache[memory_name]; + } + + int fd = shm_open(memory_name.c_str(), O_RDWR, 0666); + if (fd == -1) { + perror(("shm_open failed for " + memory_name).c_str()); + return -1; + } + + shm_fd_cache[memory_name] = fd; + return fd; +} + +void close_shared_memory_fd(const std::string &memory_name) { + std::lock_guard lock(shm_fd_cache_mutex); + if (shm_fd_cache.count(memory_name)) { + close(shm_fd_cache[memory_name]); + shm_fd_cache.erase(memory_name); + } +} + +void *map_shared_memory(int fd, size_t size) { + void *ptr = + mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, 0); + if (ptr == MAP_FAILED) { + perror("mmap failed while mapping shared memory"); + return nullptr; + } + return ptr; +} + +std::unordered_map> + shm_map; +std::mutex shm_map_mutex; + +void signal_handler(int signal) { + std::cout << "Received signal " << signal + << ", cleaning up shared memory..." << std::endl; + std::lock_guard lock(shm_map_mutex); + + for (const auto &[tid, names] : shm_map) { + shm_unlink(names.first.c_str()); + shm_unlink(names.second.c_str()); + } + + exit(1); +} + +void register_signal_handlers() { + struct sigaction sa; + sa.sa_handler = signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + sigaction(SIGTERM, &sa, nullptr); // kill, systemd + sigaction(SIGINT, &sa, nullptr); // Ctrl+C + sigaction(SIGHUP, &sa, nullptr); // Terminal closed + sigaction(SIGQUIT, &sa, nullptr); // Quit Signal +} + int create_shared_memory_impl( int port, const char *initial_data, @@ -73,6 +144,9 @@ int create_shared_memory_impl( return -1; } + std::lock_guard lock(shm_map_mutex); + shm_map[std::this_thread::get_id()] = {p2j_memory_name, j2p_memory_name}; + if (ftruncate(j2pFd, sizeof(J2PSharedMemoryLayout) + data_size) == -1) { perror("ftruncate failed for j2pFd"); close(j2pFd); @@ -106,10 +180,10 @@ int create_shared_memory_impl( rk_sema_init( &p2jLayout->sem_action_ready, sema_action_ready_name.c_str(), 0, 1 ); - std::cout << "Initialized semaphore for Python" - << p2jLayout->sem_obs_ready.name << std::endl; - std::cout << "Initialized semaphore for Python" - << p2jLayout->sem_action_ready.name << std::endl; + // std::cout << "Initialized semaphore for Python" + // << p2jLayout->sem_obs_ready.name << std::endl; + // std::cout << "Initialized semaphore for Python" + // << p2jLayout->sem_action_ready.name << std::endl; p2jLayout->action_offset = sizeof(SharedMemoryLayout); p2jLayout->action_size = action_size; @@ -120,19 +194,21 @@ int create_shared_memory_impl( void *action_start = (char *)ptr + p2jLayout->action_offset; void *data_start = (char *)ptr + p2jLayout->initial_environment_offset; std::memcpy(data_start, initial_data, data_size); - std::cout << "Wrote initial data to shared memory:" << std::endl; - - printHex(initial_data, data_size); - std::cout << "Data size: " << data_size << std::endl; - std::cout << "Action size: " << action_size << std::endl; - std::cout << "Initial environment offset: " - << p2jLayout->initial_environment_offset << std::endl; - std::cout << "Initial environment size: " - << p2jLayout->initial_environment_size << std::endl; - std::cout << "Action offset: " << p2jLayout->action_offset << std::endl; - std::cout << "Action size: " << p2jLayout->action_size << std::endl; - - munmap(ptr, shared_memory_size); + // std::cout << "Wrote initial data to shared memory:" << std::endl; + + // printHex(initial_data, data_size); + // std::cout << "Data size: " << data_size << std::endl; + // std::cout << "Action size: " << action_size << std::endl; + // std::cout << "Initial environment offset: " + // << p2jLayout->initial_environment_offset << std::endl; + // std::cout << "Initial environment size: " + // << p2jLayout->initial_environment_size << std::endl; + // std::cout << "Action offset: " << p2jLayout->action_offset << std::endl; + // std::cout << "Action size: " << p2jLayout->action_size << std::endl; + + if (munmap(ptr, shared_memory_size) == -1) { + perror("munmap failed while creating shared memory"); + } close(p2jFd); close(j2pFd); return port; @@ -141,7 +217,7 @@ int create_shared_memory_impl( void write_to_shared_memory_impl( const std::string &p2j_memory_name, const char *data, size_t action_size ) { - int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); + int p2jFd = get_shared_memory_fd(p2j_memory_name.c_str()); void *ptr = mmap( 0, sizeof(SharedMemoryLayout) + action_size, @@ -160,10 +236,8 @@ void write_to_shared_memory_impl( layout->action_offset = sizeof(SharedMemoryLayout); std::memcpy((char *)ptr + layout->action_offset, data, layout->action_size); rk_sema_open(&layout->sem_obs_ready); - if (rk_sema_post(&layout->sem_action_ready) < 0) { - perror("rk_sema_post failed while notifying java"); - } - std::cout << "Wrote action to shared memory" << std::endl; + async_rk_sema_post(&layout->sem_action_ready); + // std::cout << "Wrote action to shared memory" << std::endl; munmap(ptr, sizeof(SharedMemoryLayout) + action_size); close(p2jFd); } @@ -171,7 +245,7 @@ void write_to_shared_memory_impl( py::bytes read_from_shared_memory_impl( const std::string &p2j_memory_name, const std::string &j2p_memory_name ) { - int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); + int p2jFd = get_shared_memory_fd(p2j_memory_name.c_str()); if (p2jFd == -1) { perror("shm_open p2j failed while reading from shared memory"); return py::bytes(); // return empty bytes @@ -194,12 +268,12 @@ py::bytes read_from_shared_memory_impl( size_t action_size = layout->action_size; size_t data_size = layout->initial_environment_size; - std::cout << "Waiting for java to write observation" << std::endl; + // std::cout << "Waiting for java to write observation" << std::endl; // Wait for the observation to be ready rk_sema_open(&layout->sem_obs_ready); - rk_sema_open(&layout->sem_action_ready); - rk_sema_post(&layout->sem_action_ready); + // rk_sema_open(&layout->sem_action_ready); + // rk_sema_post(&layout->sem_action_ready); rk_sema_wait(&layout->sem_obs_ready); int j2pFd = shm_open(j2p_memory_name.c_str(), O_RDWR, 0666); @@ -254,9 +328,10 @@ py::bytes read_from_shared_memory_impl( py::bytes data(data_start, j2pLayout->data_size); - std::cout << "Read observation from shared memory. Notified to java read " - "finish observation" - << std::endl; + // std::cout << "Read observation from shared memory. Notified to java read + // " + // "finish observation" + // << std::endl; munmap(j2pPtr, sizeof(J2PSharedMemoryLayout) + obs_length); munmap(p2jPtr, sizeof(SharedMemoryLayout)); close(p2jFd); @@ -269,9 +344,10 @@ void destroy_shared_memory_impl( const std::string &memory_name, bool release_semaphores ) { if (release_semaphores) { - int p2jFd = shm_open(memory_name.c_str(), O_RDWR, 0666); + int p2jFd = get_shared_memory_fd(memory_name.c_str()); if (p2jFd == -1) { perror("shm_open failed while destroying shared memory"); + return; } else { void *ptr = mmap( 0, diff --git a/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp index 4ac194a9..8b7595e4 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp @@ -57,8 +57,8 @@ std::string make_shared_memory_name(int port, const std::string &suffix) { jobject read_initial_environment( JNIEnv *env, jclass clazz, const std::string &p2j_memory_name, int port ) { - std::cout << "Reading initial environment from shared memory 1" - << std::endl; + // std::cout << "Reading initial environment from shared memory 1" + // << std::endl; int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); if (p2jFd == -1) { perror("shm_open p2j failed while reading from shared memory"); @@ -81,8 +81,8 @@ jobject read_initial_environment( const size_t initial_environment_size = p2jLayout->initial_environment_size; const size_t action_size = p2jLayout->action_size; - std::cout << "Reading initial environment from shared memory 2" - << std::endl; + // std::cout << "Reading initial environment from shared memory 2" + // << std::endl; munmap(p2jPtr, sizeof(SharedMemoryLayout)); // Note: action_size is 0 when dummy is provided; actually python overwrites @@ -108,14 +108,16 @@ jobject read_initial_environment( static_cast(p2jPtr) + p2jLayout->initial_environment_offset; size_t data_size = p2jLayout->initial_environment_size; - std::cout << "Java read data_size: " << p2jLayout->initial_environment_size - << std::endl; - std::cout << "Java initial environment offset:" - << p2jLayout->initial_environment_offset << std::endl; - std::cout << "Java layout size:" << p2jLayout->layout_size << std::endl; - std::cout << "Java action offset:" << p2jLayout->action_offset << std::endl; - std::cout << "Java action size:" << p2jLayout->action_size << std::endl; - std::cout << "Java read data_size: " << data_size << std::endl; + // std::cout << "Java read data_size: " << + // p2jLayout->initial_environment_size + // << std::endl; + // std::cout << "Java initial environment offset:" + // << p2jLayout->initial_environment_offset << std::endl; + // std::cout << "Java layout size:" << p2jLayout->layout_size << std::endl; + // std::cout << "Java action offset:" << p2jLayout->action_offset << + // std::endl; std::cout << "Java action size:" << p2jLayout->action_size << + // std::endl; std::cout << "Java read data_size: " << data_size << + // std::endl; jbyteArray byteArray = env->NewByteArray(data_size); if (byteArray == nullptr || env->ExceptionCheck()) { @@ -126,8 +128,8 @@ jobject read_initial_environment( close(p2jFd); return nullptr; } - std::cout << "Java read array: "; - printHex(data_startInitialEnvironment, data_size); + // std::cout << "Java read array: "; + // printHex(data_startInitialEnvironment, data_size); env->SetByteArrayRegion( byteArray, 0, @@ -144,10 +146,10 @@ jobject read_initial_environment( rk_sema_init( &p2jLayout->sem_action_ready, sema_action_ready_name.c_str(), 0, 1 ); - std::cout << "Initialied semaphore for Java" - << p2jLayout->sem_obs_ready.name << std::endl; - std::cout << "Initialied semaphore for Java" - << p2jLayout->sem_action_ready.name << std::endl; + // std::cout << "Initialied semaphore for Java" + // << p2jLayout->sem_obs_ready.name << std::endl; + // std::cout << "Initialied semaphore for Java" + // << p2jLayout->sem_action_ready.name << std::endl; return byteArray; } @@ -198,23 +200,23 @@ jbyteArray read_action( char *data_start = static_cast(p2jPtr) + p2jHeader->action_offset; - std::cout << "Waiting for Python to write the action" << std::endl; - printHex((const char *)p2jHeader, sizeof(SharedMemoryLayout)); + // std::cout << "Waiting for Python to write the action" << std::endl; + // printHex((const char *)p2jHeader, sizeof(SharedMemoryLayout)); // OK rk_sema_open(&p2jHeader->sem_action_ready); rk_sema_wait(&p2jHeader->sem_action_ready); if (data != nullptr) { jsize oldSize = env->GetArrayLength(data); if (oldSize != p2jHeader->action_size) { - std::cout << "Resizing byte array to" - << std::to_string(p2jHeader->action_size) << std::endl; + // std::cout << "Resizing byte array to" + // << std::to_string(p2jHeader->action_size) << std::endl; env->DeleteLocalRef(data); data = nullptr; data = env->NewByteArray(p2jHeader->action_size); } } else { - std::cout << "Creating new byte array" - << std::to_string(p2jHeader->action_size) << std::endl; + // std::cout << "Creating new byte array" + // << std::to_string(p2jHeader->action_size) << std::endl; data = env->NewByteArray(p2jHeader->action_size); } @@ -232,7 +234,7 @@ jbyteArray read_action( reinterpret_cast(data_start) ); } - std::cout << "Read action from shared memory" << std::endl; + // std::cout << "Read action from shared memory" << std::endl; return data; } @@ -285,13 +287,13 @@ void write_observation( close(j2pFd); return; } - std::cout << "Writing observation to shared memory adfsadf" << std::endl; + // std::cout << "Writing observation to shared memory adfsadf" << std::endl; J2PSharedMemoryLayout *j2pLayout = static_cast(j2pPtr); j2pLayout->data_offset = sizeof(J2PSharedMemoryLayout); - std::cout << "Writing observation to shared memory adfsad222sdsfsasdff" - << std::endl; + // std::cout << "Writing observation to shared memory adfsad222sdsfsasdff" + // << std::endl; struct stat statbuf; if (fstat(j2pFd, &statbuf) == -1) { @@ -302,11 +304,11 @@ void write_observation( close(p2jFd); return; } else { - std::cout << "Shared memory size: " << statbuf.st_size << std::endl; + // std::cout << "Shared memory size: " << statbuf.st_size << std::endl; } const size_t current_shmem_size = statbuf.st_size; size_t requiredSize = observation_size + sizeof(J2PSharedMemoryLayout); - // requiredSize = requiredSize > 1024 ? requiredSize : 1024; + requiredSize = requiredSize > 1024 ? requiredSize : 1024; if (current_shmem_size < requiredSize) { // Unmap existing memory before resizing @@ -337,9 +339,11 @@ void write_observation( j2pLayout->layout_size = sizeof(J2PSharedMemoryLayout); j2pLayout->data_offset = sizeof(J2PSharedMemoryLayout); j2pLayout->data_size = observation_size; - std::cout << "Resized shared memory to " << requiredSize << std::endl; + // std::cout << "Resized shared memory to " << requiredSize << + // std::endl; } - std::cout << "Writing observation to shared memory KKKAKAAKAK" << std::endl; + // std::cout << "Writing observation to shared memory KKKAKAAKAK" << + // std::endl; // Write the observation to shared memory char *data_start = static_cast(j2pPtr) + j2pLayout->data_offset; @@ -347,12 +351,12 @@ void write_observation( j2pLayout->data_size = observation_size; // Notify Python that the observation is ready - printHex((const char *)p2jLayout, sizeof(SharedMemoryLayout)); + // printHex((const char *)p2jLayout, sizeof(SharedMemoryLayout)); rk_sema_open(&p2jLayout->sem_obs_ready); if (rk_sema_post(&p2jLayout->sem_obs_ready) < 0) { perror("rk_sema_post failed while notifying python"); } - std::cout << "Wrote and notified observation to python" << std::endl; + // std::cout << "Wrote and notified observation to python" << std::endl; // Clean up resources munmap(j2pPtr, requiredSize); @@ -431,8 +435,8 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_writeObservationImpl( return; } jsize observation_data_size = env->GetArrayLength(observation_data); - std::cout << "Writing observation to shared memory with length" - << std::to_string(observation_data_size) << std::endl; + // std::cout << "Writing observation to shared memory with length" + // << std::to_string(observation_data_size) << std::endl; try { write_observation( @@ -445,11 +449,8 @@ Java_com_kyhsgeekcode_minecraftenv_FramebufferCapturer_writeObservationImpl( env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what()); } - std::cout << "Releasing utf chars 1" << std::endl; env->ReleaseStringUTFChars(j2p_memory_name, j2p_memory_name_cstr); - std::cout << "Releasing utf chars 1" << std::endl; env->ReleaseStringUTFChars(p2j_memory_name, p2j_memory_name_cstr); - std::cout << "Releasing array elements" << std::endl; env->ReleaseByteArrayElements( observation_data, observation_data_ptr, JNI_ABORT ); diff --git a/src/craftground/environment/boost_ipc.py b/src/craftground/environment/boost_ipc.py index 42bedb18..264d7ffd 100644 --- a/src/craftground/environment/boost_ipc.py +++ b/src/craftground/environment/boost_ipc.py @@ -62,13 +62,13 @@ def send_action( action.commands.extend(commands) action_bytes: bytes = action.SerializeToString() - self.logger.log(f"Sending action to shared memory: {len(action_bytes)} bytes") + # self.logger.log(f"Sending action to shared memory: {len(action_bytes)} bytes") write_to_shared_memory( self.p2j_shared_memory_name, action_bytes, len(action_bytes) ) def read_observation(self) -> ObservationSpaceMessage: - self.logger.log("Reading observation from shared memory") + # self.logger.log("Reading observation from shared memory") observation_space = ObservationSpaceMessage() data_bytes = read_from_shared_memory( self.p2j_shared_memory_name, self.j2p_shared_memory_name From 9841fb7e9f90cbe3a1d4d0d976baf3121b8f908b Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Fri, 31 Jan 2025 15:43:11 +0900 Subject: [PATCH 73/91] =?UTF-8?q?=F0=9F=90=9B=20Remove=20sys/semaphore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/cross_semaphore.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cpp/cross_semaphore.h b/src/cpp/cross_semaphore.h index 6ca1a498..5e35cb2a 100644 --- a/src/cpp/cross_semaphore.h +++ b/src/cpp/cross_semaphore.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #if IS_WINDOWS #include From 15f25da5e4dee71be0976196afdd8fe294922ea8 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Fri, 31 Jan 2025 15:44:37 +0900 Subject: [PATCH 74/91] =?UTF-8?q?=F0=9F=90=9B=20Add=20fnctl=20for=20O=5FCR?= =?UTF-8?q?EAT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/cross_semaphore.h | 1 + src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/cpp/cross_semaphore.h b/src/cpp/cross_semaphore.h index 5e35cb2a..f6ec9289 100644 --- a/src/cpp/cross_semaphore.h +++ b/src/cpp/cross_semaphore.h @@ -6,6 +6,7 @@ #include #include #include +#include #if IS_WINDOWS #include #else diff --git a/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h b/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h index d5ada681..9e8e512f 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h +++ b/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h @@ -5,6 +5,7 @@ #include #include #include +#include #if IS_WINDOWS #include #else From b78b6c3bc75dcc58fa64463b98f2e7a6ff3ca546 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Fri, 31 Jan 2025 17:29:32 +0900 Subject: [PATCH 75/91] =?UTF-8?q?=F0=9F=90=9B=20No=20sys/semaphore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_noboost.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cpp/ipc_noboost.cpp b/src/cpp/ipc_noboost.cpp index 0f970f05..d8ae219a 100644 --- a/src/cpp/ipc_noboost.cpp +++ b/src/cpp/ipc_noboost.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include From 051fdace8eb64aed800883665490fbd2ffdbd174 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 4 Feb 2025 21:51:10 +0900 Subject: [PATCH 76/91] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fix_deps.py => tools/fix_deps.py | 0 needed_objects.txt => tools/needed_objects.txt | 0 not_found_symbols.txt => tools/not_found_symbols.txt | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename fix_deps.py => tools/fix_deps.py (100%) rename needed_objects.txt => tools/needed_objects.txt (100%) rename not_found_symbols.txt => tools/not_found_symbols.txt (100%) diff --git a/fix_deps.py b/tools/fix_deps.py similarity index 100% rename from fix_deps.py rename to tools/fix_deps.py diff --git a/needed_objects.txt b/tools/needed_objects.txt similarity index 100% rename from needed_objects.txt rename to tools/needed_objects.txt diff --git a/not_found_symbols.txt b/tools/not_found_symbols.txt similarity index 100% rename from not_found_symbols.txt rename to tools/not_found_symbols.txt From af7917e5bb525420bdfc761488370869d1591cd7 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 4 Feb 2025 21:58:36 +0900 Subject: [PATCH 77/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20windows=20build=20er?= =?UTF-8?q?rors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/cross_semaphore.h | 19 ++++++++++++------- .../src/main/cpp/cross_semaphore.h | 18 ++++++++++++------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/cpp/cross_semaphore.h b/src/cpp/cross_semaphore.h index f6ec9289..c85f03a2 100644 --- a/src/cpp/cross_semaphore.h +++ b/src/cpp/cross_semaphore.h @@ -20,15 +20,15 @@ struct rk_sema { #else sem_t *sem_python; sem_t *sem_java; - char name[30]; // Save the name of the semaphore #endif + char name[30]; // Save the name of the semaphore }; static inline void rk_sema_init( struct rk_sema *s, const char *name, uint32_t value, uint32_t max ) { #if IS_WINDOWS - s->sem = CreateSemaphore(NULL, value, max, name); + s->sem_python = CreateSemaphore(NULL, value, max, name); #else snprintf(s->name, sizeof(s->name), "%s", name); sem_unlink(s->name); // Remove any existing semaphore with the same name @@ -42,7 +42,12 @@ static inline void rk_sema_init( static inline void rk_sema_open(struct rk_sema *s) { #if IS_WINDOWS - s->sem_python = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, s->name); + s->sem_python = OpenSemaphoreA( + SEMAPHORE_ALL_ACCESS, FALSE, s->name + ); // Open existing semaphore + if (s->sem_python == NULL) { + std::cerr << "OpenSemaphore failed: " << GetLastError() << std::endl; + } #else if (s->sem_python != nullptr) { return; @@ -57,16 +62,15 @@ static inline void rk_sema_open(struct rk_sema *s) { } static inline int rk_sema_wait(struct rk_sema *s) { - #if IS_WINDOWS DWORD r; do { r = WaitForSingleObject(s->sem_python, INFINITE); - } while (r == WAIT_FAILED && GetLastError() == ERROR_INTERRUPT); + } while (r == WAIT_FAILED && GetLastError() == ERROR_IO_PENDING + ); // 적절한 오류 코드로 변경 return r; #else int r; - do { r = sem_wait(s->sem_python); } while (r == -1 && errno == EINTR); @@ -93,7 +97,8 @@ static inline void async_rk_sema_post(struct rk_sema *s) { static inline void rk_sema_destroy(struct rk_sema *s) { #if IS_WINDOWS - CloseHandle(s->sem); + CloseHandle(s->sem_python); + CloseHandle(s->sem_java); #else sem_close(s->sem_python); sem_close(s->sem_java); diff --git a/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h b/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h index 9e8e512f..f2efac63 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h +++ b/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h @@ -19,8 +19,8 @@ struct rk_sema { #else sem_t *sem_python; sem_t *sem_java; - char name[30]; // Save the name of the semaphore #endif + char name[30]; // Save the name of the semaphore }; static inline void rk_sema_init( @@ -41,7 +41,12 @@ static inline void rk_sema_init( static inline void rk_sema_open(struct rk_sema *s) { #if IS_WINDOWS - s->sem_java = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, s->name); + s->sem_java = OpenSemaphoreA( + SEMAPHORE_ALL_ACCESS, FALSE, s->name + ); // Open existing semaphore + if (s->sem_java == NULL) { + std::cerr << "OpenSemaphore failed: " << GetLastError() << std::endl; + } #else s->sem_java = sem_open(s->name, 0); // Open existing semaphore if (s->sem_java == SEM_FAILED) { @@ -52,19 +57,20 @@ static inline void rk_sema_open(struct rk_sema *s) { #endif } -static inline void rk_sema_wait(struct rk_sema *s) { - +static inline int rk_sema_wait(struct rk_sema *s) { #if IS_WINDOWS DWORD r; do { r = WaitForSingleObject(s->sem_java, INFINITE); - } while (r == WAIT_FAILED && GetLastError() == ERROR_INTERRUPT); + } while (r == WAIT_FAILED && GetLastError() == ERROR_IO_PENDING + ); // 적절한 오류 코드로 변경 + return r; #else int r; - do { r = sem_wait(s->sem_java); } while (r == -1 && errno == EINTR); + return r; #endif } From ebfc672a226d115d75698b8e7659090c6890b69e Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 4 Feb 2025 22:06:36 +0900 Subject: [PATCH 78/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20build=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_noboost.cpp | 5 +++++ src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cpp/ipc_noboost.cpp b/src/cpp/ipc_noboost.cpp index d8ae219a..9ec07318 100644 --- a/src/cpp/ipc_noboost.cpp +++ b/src/cpp/ipc_noboost.cpp @@ -1,7 +1,12 @@ #include #include #include + +#if defined(WIN32) || defined(_WIN32) || \ + defined(__WIN32) && !defined(__CYGWIN__) +#else #include +#endif #include #include #include diff --git a/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp index 8b7595e4..897be99f 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #if defined(WIN32) || defined(_WIN32) || \ @@ -13,6 +12,7 @@ #define IS_WINDOWS 1 #define SHMEM_PREFIX "Global\\" #else +#include #define SHMEM_PREFIX "/" #define IS_WINDOWS 0 #endif From 7d5c1012aab6e8a16eda28ad0ec693333062a85a Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 4 Feb 2025 22:10:39 +0900 Subject: [PATCH 79/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20windows=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_noboost.cpp | 2 +- src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cpp/ipc_noboost.cpp b/src/cpp/ipc_noboost.cpp index 9ec07318..6ad7fb33 100644 --- a/src/cpp/ipc_noboost.cpp +++ b/src/cpp/ipc_noboost.cpp @@ -6,8 +6,8 @@ defined(__WIN32) && !defined(__CYGWIN__) #else #include -#endif #include +#endif #include #include #include "ipc_noboost.hpp" diff --git a/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp index 897be99f..10aa33e6 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp @@ -6,13 +6,13 @@ #include #include #include -#include #if defined(WIN32) || defined(_WIN32) || \ defined(__WIN32) && !defined(__CYGWIN__) #define IS_WINDOWS 1 #define SHMEM_PREFIX "Global\\" #else #include +#include #define SHMEM_PREFIX "/" #define IS_WINDOWS 0 #endif From a8682e9af6504e5713f015556554f8d7e44a74f6 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 4 Feb 2025 22:22:17 +0900 Subject: [PATCH 80/91] =?UTF-8?q?=F0=9F=92=9A=20Windows=20support=20for=20?= =?UTF-8?q?noboost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- src/cpp/ipc_noboost.cpp | 116 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 110 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 63a03ead..0ac17911 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ keywords = ["minecraft", "reinforcement learning", "environment"] license = {file = "LICENSE"} name = "craftground" readme = "README.md" -version = "2.7.0" +version = "2.6.5" dependencies = [ "gymnasium", diff --git a/src/cpp/ipc_noboost.cpp b/src/cpp/ipc_noboost.cpp index 6ad7fb33..48bb3635 100644 --- a/src/cpp/ipc_noboost.cpp +++ b/src/cpp/ipc_noboost.cpp @@ -1,19 +1,121 @@ #include #include #include - -#if defined(WIN32) || defined(_WIN32) || \ - defined(__WIN32) && !defined(__CYGWIN__) -#else -#include -#include -#endif #include #include #include "ipc_noboost.hpp" #include "cross_semaphore.h" #include "print_hex.h" +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + #include + #include + #include + + // Define some POSIX-like constants for use with our mmap_win + #ifndef PROT_READ + #define PROT_READ 0x1 + #endif + #ifndef PROT_WRITE + #define PROT_WRITE 0x2 + #endif + #ifndef MAP_SHARED + #define MAP_SHARED 0x01 + #endif + #ifndef MAP_FAILED + #define MAP_FAILED ((void*) -1) + #endif + + // Windows does not need an unlink; when all handles are closed the mapping is gone. + int shm_unlink_win(const char* name) { + return 0; + } + + // ftruncate is not needed on Windows because the size is set at creation. + int ftruncate_win(int /*fd*/, size_t /*size*/) { + return 0; + } + + // Our wrapper for opening/creating a shared memory “file” + // (Note: when creating, we pass in the desired size.) + int shm_open_wrapper(const char* name, int oflag, int mode, size_t size) { + HANDLE hMap; + DWORD dwDesiredAccess = FILE_MAP_READ | FILE_MAP_WRITE; + if (oflag & O_CREAT) { + hMap = CreateFileMapping( + INVALID_HANDLE_VALUE, + NULL, + PAGE_READWRITE, + (DWORD)((size >> 32) & 0xFFFFFFFF), + (DWORD)(size & 0xFFFFFFFF), + name + ); + if (hMap == NULL) { + fprintf(stderr, "CreateFileMapping failed for %s with error %lu\n", name, GetLastError()); + return -1; + } + } else { + hMap = OpenFileMapping(dwDesiredAccess, FALSE, name); + if (hMap == NULL) { + fprintf(stderr, "OpenFileMapping failed for %s with error %lu\n", name, GetLastError()); + return -1; + } + } + return (int)(intptr_t)hMap; + } + + // Our wrapper for mapping a view of the shared memory + void* mmap_win(void* /*addr*/, size_t length, int prot, int /*flags*/, int fd, size_t offset) { + HANDLE hMap = (HANDLE)(intptr_t)fd; + DWORD dwDesiredAccess = 0; + if (prot & PROT_READ) dwDesiredAccess |= FILE_MAP_READ; + if (prot & PROT_WRITE) dwDesiredAccess |= FILE_MAP_WRITE; + void* map = MapViewOfFile( + hMap, + dwDesiredAccess, + (DWORD)((offset >> 32) & 0xFFFFFFFF), + (DWORD)(offset & 0xFFFFFFFF), + length + ); + if (map == NULL) { + fprintf(stderr, "MapViewOfFile failed with error %lu\n", GetLastError()); + return MAP_FAILED; + } + return map; + } + + int munmap_win(void* addr, size_t /*length*/) { + if (!UnmapViewOfFile(addr)) { + fprintf(stderr, "UnmapViewOfFile failed with error %lu\n", GetLastError()); + return -1; + } + return 0; + } + + int close_win(int fd) { + HANDLE hMap = (HANDLE)(intptr_t)fd; + if (!CloseHandle(hMap)) { + fprintf(stderr, "CloseHandle failed with error %lu\n", GetLastError()); + return -1; + } + return 0; + } + + // To differentiate “create” versus “open” calls we define two macros: + #define shm_open_create(name, mode, size) shm_open_wrapper(name, O_CREAT | O_RDWR, mode, size) + #define shm_open_existing(name, mode) shm_open_wrapper(name, O_RDWR, mode, 0) + #define ftruncate(fd, size) ftruncate_win(fd, size) + #define mmap(addr, length, prot, flags, fd, offset) mmap_win(addr, length, prot, flags, fd, offset) + #define munmap(addr, length) munmap_win(addr, length) + #define close(fd) close_win(fd) + #define shm_unlink(name) shm_unlink_win(name) + +#else + // On POSIX systems use the normal headers. + #include + #include +#endif + #ifndef MAP_POPULATE #define MAP_POPULATE 0 #endif From 7c9fd4ff4e791cd4bf1c635c7b1c2730b93ff5b7 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 4 Feb 2025 22:33:25 +0900 Subject: [PATCH 81/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_noboost.cpp | 58 ++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/src/cpp/ipc_noboost.cpp b/src/cpp/ipc_noboost.cpp index 48bb3635..4a0536d9 100644 --- a/src/cpp/ipc_noboost.cpp +++ b/src/cpp/ipc_noboost.cpp @@ -121,12 +121,19 @@ #endif bool shared_memory_exists(const std::string &name) { +#ifdef _WIN32 + HANDLE hMap = OpenFileMapping(FILE_MAP_READ, FALSE, name.c_str()); + if (hMap == NULL) + return false; + CloseHandle(hMap); + return true; +#else int fd = shm_open(name.c_str(), O_RDONLY, 0666); - if (fd == -1) { - return false; // Shared memory does not exist - } - close(fd); // Close the file descriptor - return true; // Shared memory exists + if (fd == -1) + return false; + close(fd); + return true; +#endif } std::string make_shared_memory_name(int port, const std::string &suffix) { @@ -138,16 +145,17 @@ std::mutex shm_fd_cache_mutex; int get_shared_memory_fd(const std::string &memory_name) { std::lock_guard lock(shm_fd_cache_mutex); - if (shm_fd_cache.count(memory_name)) { + if (shm_fd_cache.count(memory_name)) return shm_fd_cache[memory_name]; - } - +#ifdef _WIN32 + int fd = shm_open_existing(memory_name.c_str(), 0666); +#else int fd = shm_open(memory_name.c_str(), O_RDWR, 0666); +#endif if (fd == -1) { perror(("shm_open failed for " + memory_name).c_str()); return -1; } - shm_fd_cache[memory_name] = fd; return fd; } @@ -229,26 +237,35 @@ int create_shared_memory_impl( found_free_port = true; } while (!found_free_port); +#ifdef _WIN32 + int p2jFd = shm_open_create(p2j_memory_name.c_str(), 0666, shared_memory_size); +#else int p2jFd = shm_open(p2j_memory_name.c_str(), O_CREAT | O_RDWR, 0666); - if (p2jFd == -1) { - perror("shm_open failed while creating shared memory p2j"); - return -1; - } - - if (ftruncate(p2jFd, shared_memory_size) == -1) { + if (p2jFd != -1 && ftruncate(p2jFd, shared_memory_size) == -1) { perror("ftruncate failed for p2jFd"); close(p2jFd); shm_unlink(p2j_memory_name.c_str()); return -1; } +#endif + if (p2jFd == -1) { + perror("shm_open failed while creating shared memory p2j"); + return -1; + } +#ifdef _WIN32 + int j2pFd = shm_open_create(j2p_memory_name.c_str(), 0666, sizeof(J2PSharedMemoryLayout) + data_size); +#else int j2pFd = shm_open(j2p_memory_name.c_str(), O_CREAT | O_RDWR, 0666); - if (j2pFd == -1) { + if (j2pFd != -1 && ftruncate(j2pFd, sizeof(J2PSharedMemoryLayout) + data_size) == -1) { + perror("ftruncate failed for j2pFd"); + close(j2pFd); close(p2jFd); + shm_unlink(j2p_memory_name.c_str()); shm_unlink(p2j_memory_name.c_str()); - perror("shm_open failed while creating shared memory j2p"); return -1; } +#endif std::lock_guard lock(shm_map_mutex); shm_map[std::this_thread::get_id()] = {p2j_memory_name, j2p_memory_name}; @@ -382,7 +399,12 @@ py::bytes read_from_shared_memory_impl( // rk_sema_post(&layout->sem_action_ready); rk_sema_wait(&layout->sem_obs_ready); - int j2pFd = shm_open(j2p_memory_name.c_str(), O_RDWR, 0666); + int j2pFd; +#ifdef _WIN32 + j2pFd = shm_open_existing(j2p_memory_name.c_str(), 0666); +#else + j2pFd = shm_open(j2p_memory_name.c_str(), O_RDWR, 0666); +#endif if (j2pFd == -1) { perror("shm_open j2p failed while reading from shared memory"); munmap(p2jPtr, sizeof(SharedMemoryLayout)); From 4f0db0afc9fd60e6057890007e7f45a8cc9574ca Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 4 Feb 2025 22:42:40 +0900 Subject: [PATCH 82/91] =?UTF-8?q?=F0=9F=92=9A=20Windows=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_noboost.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cpp/ipc_noboost.cpp b/src/cpp/ipc_noboost.cpp index 4a0536d9..fefb402b 100644 --- a/src/cpp/ipc_noboost.cpp +++ b/src/cpp/ipc_noboost.cpp @@ -196,6 +196,7 @@ void signal_handler(int signal) { } void register_signal_handlers() { + #ifdef _WIN32 struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); @@ -205,6 +206,7 @@ void register_signal_handlers() { sigaction(SIGINT, &sa, nullptr); // Ctrl+C sigaction(SIGHUP, &sa, nullptr); // Terminal closed sigaction(SIGQUIT, &sa, nullptr); // Quit Signal + #endif } int create_shared_memory_impl( From 8f70e6bf0bf20dcf2134e0a7a0a7c519566a11eb Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 4 Feb 2025 22:46:27 +0900 Subject: [PATCH 83/91] =?UTF-8?q?=F0=9F=8E=A8=20Format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_noboost.cpp | 247 ++++++++++++++++++++++------------------ 1 file changed, 137 insertions(+), 110 deletions(-) diff --git a/src/cpp/ipc_noboost.cpp b/src/cpp/ipc_noboost.cpp index fefb402b..d07149a4 100644 --- a/src/cpp/ipc_noboost.cpp +++ b/src/cpp/ipc_noboost.cpp @@ -7,113 +7,136 @@ #include "cross_semaphore.h" #include "print_hex.h" -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) - #include - #include - #include - - // Define some POSIX-like constants for use with our mmap_win - #ifndef PROT_READ - #define PROT_READ 0x1 - #endif - #ifndef PROT_WRITE - #define PROT_WRITE 0x2 - #endif - #ifndef MAP_SHARED - #define MAP_SHARED 0x01 - #endif - #ifndef MAP_FAILED - #define MAP_FAILED ((void*) -1) - #endif - - // Windows does not need an unlink; when all handles are closed the mapping is gone. - int shm_unlink_win(const char* name) { - return 0; - } - - // ftruncate is not needed on Windows because the size is set at creation. - int ftruncate_win(int /*fd*/, size_t /*size*/) { - return 0; - } - - // Our wrapper for opening/creating a shared memory “file” - // (Note: when creating, we pass in the desired size.) - int shm_open_wrapper(const char* name, int oflag, int mode, size_t size) { - HANDLE hMap; - DWORD dwDesiredAccess = FILE_MAP_READ | FILE_MAP_WRITE; - if (oflag & O_CREAT) { - hMap = CreateFileMapping( - INVALID_HANDLE_VALUE, - NULL, - PAGE_READWRITE, - (DWORD)((size >> 32) & 0xFFFFFFFF), - (DWORD)(size & 0xFFFFFFFF), - name - ); - if (hMap == NULL) { - fprintf(stderr, "CreateFileMapping failed for %s with error %lu\n", name, GetLastError()); - return -1; - } - } else { - hMap = OpenFileMapping(dwDesiredAccess, FALSE, name); - if (hMap == NULL) { - fprintf(stderr, "OpenFileMapping failed for %s with error %lu\n", name, GetLastError()); - return -1; - } - } - return (int)(intptr_t)hMap; - } - - // Our wrapper for mapping a view of the shared memory - void* mmap_win(void* /*addr*/, size_t length, int prot, int /*flags*/, int fd, size_t offset) { - HANDLE hMap = (HANDLE)(intptr_t)fd; - DWORD dwDesiredAccess = 0; - if (prot & PROT_READ) dwDesiredAccess |= FILE_MAP_READ; - if (prot & PROT_WRITE) dwDesiredAccess |= FILE_MAP_WRITE; - void* map = MapViewOfFile( - hMap, - dwDesiredAccess, - (DWORD)((offset >> 32) & 0xFFFFFFFF), - (DWORD)(offset & 0xFFFFFFFF), - length - ); - if (map == NULL) { - fprintf(stderr, "MapViewOfFile failed with error %lu\n", GetLastError()); - return MAP_FAILED; - } - return map; - } - - int munmap_win(void* addr, size_t /*length*/) { - if (!UnmapViewOfFile(addr)) { - fprintf(stderr, "UnmapViewOfFile failed with error %lu\n", GetLastError()); - return -1; - } - return 0; - } - - int close_win(int fd) { - HANDLE hMap = (HANDLE)(intptr_t)fd; - if (!CloseHandle(hMap)) { - fprintf(stderr, "CloseHandle failed with error %lu\n", GetLastError()); - return -1; - } - return 0; - } - - // To differentiate “create” versus “open” calls we define two macros: - #define shm_open_create(name, mode, size) shm_open_wrapper(name, O_CREAT | O_RDWR, mode, size) - #define shm_open_existing(name, mode) shm_open_wrapper(name, O_RDWR, mode, 0) - #define ftruncate(fd, size) ftruncate_win(fd, size) - #define mmap(addr, length, prot, flags, fd, offset) mmap_win(addr, length, prot, flags, fd, offset) - #define munmap(addr, length) munmap_win(addr, length) - #define close(fd) close_win(fd) - #define shm_unlink(name) shm_unlink_win(name) +#if defined(WIN32) || defined(_WIN32) || \ + defined(__WIN32) && !defined(__CYGWIN__) +#include +#include +#include + +// Define some POSIX-like constants for use with our mmap_win +#ifndef PROT_READ +#define PROT_READ 0x1 +#endif +#ifndef PROT_WRITE +#define PROT_WRITE 0x2 +#endif +#ifndef MAP_SHARED +#define MAP_SHARED 0x01 +#endif +#ifndef MAP_FAILED +#define MAP_FAILED ((void *)-1) +#endif + +// Windows does not need an unlink; when all handles are closed the mapping is +// gone. +int shm_unlink_win(const char *name) { return 0; } + +// ftruncate is not needed on Windows because the size is set at creation. +int ftruncate_win(int /*fd*/, size_t /*size*/) { return 0; } + +// Our wrapper for opening/creating a shared memory “file” +// (Note: when creating, we pass in the desired size.) +int shm_open_wrapper(const char *name, int oflag, int mode, size_t size) { + HANDLE hMap; + DWORD dwDesiredAccess = FILE_MAP_READ | FILE_MAP_WRITE; + if (oflag & O_CREAT) { + hMap = CreateFileMapping( + INVALID_HANDLE_VALUE, + NULL, + PAGE_READWRITE, + (DWORD)((size >> 32) & 0xFFFFFFFF), + (DWORD)(size & 0xFFFFFFFF), + name + ); + if (hMap == NULL) { + fprintf( + stderr, + "CreateFileMapping failed for %s with error %lu\n", + name, + GetLastError() + ); + return -1; + } + } else { + hMap = OpenFileMapping(dwDesiredAccess, FALSE, name); + if (hMap == NULL) { + fprintf( + stderr, + "OpenFileMapping failed for %s with error %lu\n", + name, + GetLastError() + ); + return -1; + } + } + return (int)(intptr_t)hMap; +} + +// Our wrapper for mapping a view of the shared memory +void *mmap_win( + void * /*addr*/, + size_t length, + int prot, + int /*flags*/, + int fd, + size_t offset +) { + HANDLE hMap = (HANDLE)(intptr_t)fd; + DWORD dwDesiredAccess = 0; + if (prot & PROT_READ) + dwDesiredAccess |= FILE_MAP_READ; + if (prot & PROT_WRITE) + dwDesiredAccess |= FILE_MAP_WRITE; + void *map = MapViewOfFile( + hMap, + dwDesiredAccess, + (DWORD)((offset >> 32) & 0xFFFFFFFF), + (DWORD)(offset & 0xFFFFFFFF), + length + ); + if (map == NULL) { + fprintf( + stderr, "MapViewOfFile failed with error %lu\n", GetLastError() + ); + return MAP_FAILED; + } + return map; +} + +int munmap_win(void *addr, size_t /*length*/) { + if (!UnmapViewOfFile(addr)) { + fprintf( + stderr, "UnmapViewOfFile failed with error %lu\n", GetLastError() + ); + return -1; + } + return 0; +} + +int close_win(int fd) { + HANDLE hMap = (HANDLE)(intptr_t)fd; + if (!CloseHandle(hMap)) { + fprintf(stderr, "CloseHandle failed with error %lu\n", GetLastError()); + return -1; + } + return 0; +} + +// To differentiate “create” versus “open” calls we define two macros: +#define shm_open_create(name, mode, size) \ + shm_open_wrapper(name, O_CREAT | O_RDWR, mode, size) +#define shm_open_existing(name, mode) shm_open_wrapper(name, O_RDWR, mode, 0) +#define ftruncate(fd, size) ftruncate_win(fd, size) +#define mmap(addr, length, prot, flags, fd, offset) \ + mmap_win(addr, length, prot, flags, fd, offset) +#define munmap(addr, length) munmap_win(addr, length) +#define close(fd) close_win(fd) +#define shm_unlink(name) shm_unlink_win(name) #else - // On POSIX systems use the normal headers. - #include - #include +// On POSIX systems use the normal headers. +#include +#include #endif #ifndef MAP_POPULATE @@ -196,7 +219,7 @@ void signal_handler(int signal) { } void register_signal_handlers() { - #ifdef _WIN32 +#ifdef _WIN32 struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); @@ -206,7 +229,7 @@ void register_signal_handlers() { sigaction(SIGINT, &sa, nullptr); // Ctrl+C sigaction(SIGHUP, &sa, nullptr); // Terminal closed sigaction(SIGQUIT, &sa, nullptr); // Quit Signal - #endif +#endif } int create_shared_memory_impl( @@ -240,7 +263,8 @@ int create_shared_memory_impl( } while (!found_free_port); #ifdef _WIN32 - int p2jFd = shm_open_create(p2j_memory_name.c_str(), 0666, shared_memory_size); + int p2jFd = + shm_open_create(p2j_memory_name.c_str(), 0666, shared_memory_size); #else int p2jFd = shm_open(p2j_memory_name.c_str(), O_CREAT | O_RDWR, 0666); if (p2jFd != -1 && ftruncate(p2jFd, shared_memory_size) == -1) { @@ -256,10 +280,13 @@ int create_shared_memory_impl( } #ifdef _WIN32 - int j2pFd = shm_open_create(j2p_memory_name.c_str(), 0666, sizeof(J2PSharedMemoryLayout) + data_size); + int j2pFd = shm_open_create( + j2p_memory_name.c_str(), 0666, sizeof(J2PSharedMemoryLayout) + data_size + ); #else int j2pFd = shm_open(j2p_memory_name.c_str(), O_CREAT | O_RDWR, 0666); - if (j2pFd != -1 && ftruncate(j2pFd, sizeof(J2PSharedMemoryLayout) + data_size) == -1) { + if (j2pFd != -1 && + ftruncate(j2pFd, sizeof(J2PSharedMemoryLayout) + data_size) == -1) { perror("ftruncate failed for j2pFd"); close(j2pFd); close(p2jFd); From 3d642bb81c0f897f0941da156a045b4ca0010000 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 4 Feb 2025 22:47:29 +0900 Subject: [PATCH 84/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20windows=20def?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_noboost.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpp/ipc_noboost.cpp b/src/cpp/ipc_noboost.cpp index d07149a4..971813ec 100644 --- a/src/cpp/ipc_noboost.cpp +++ b/src/cpp/ipc_noboost.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -9,6 +8,7 @@ #if defined(WIN32) || defined(_WIN32) || \ defined(__WIN32) && !defined(__CYGWIN__) +#define _WIN32 1 #include #include #include From c94bcfa9f9bfde5efce631689853b43011a7fc91 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 4 Feb 2025 22:51:40 +0900 Subject: [PATCH 85/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20macros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cpp/ipc_noboost.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cpp/ipc_noboost.cpp b/src/cpp/ipc_noboost.cpp index 971813ec..18529c47 100644 --- a/src/cpp/ipc_noboost.cpp +++ b/src/cpp/ipc_noboost.cpp @@ -137,6 +137,7 @@ int close_win(int fd) { // On POSIX systems use the normal headers. #include #include +#include #endif #ifndef MAP_POPULATE @@ -219,7 +220,7 @@ void signal_handler(int signal) { } void register_signal_handlers() { -#ifdef _WIN32 +#ifndef _WIN32 struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); From 25893c48e62706214b3f73471a775349671dd727 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Tue, 4 Feb 2025 23:38:10 +0900 Subject: [PATCH 86/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20ci=20for=20tet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e355489c..09bcf460 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,7 +103,7 @@ FetchContent_Declare( ) # FetchContent_MakeAvailable(Boost) -message(STATUS "Boost is now available") +# message(STATUS "Boost is now available") # Add the module pybind11_add_module(craftground_native ${CRAFTGROUND_PY_SOURCES}) # target_link_libraries(craftground_native PRIVATE Boost::system Boost::thread Boost::interprocess) @@ -133,6 +133,10 @@ elseif(CUDAToolkit_FOUND) target_link_libraries(craftground_native PRIVATE CUDA::cudart CUDA::cudart_static) endif() endif() +if(WIN32) + add_definitions(-D_WIN32) + target_link_libraries(craftground_native PRIVATE kernel32.lib user32.lib Synchronization.lib) +endif() target_include_directories(craftground_native PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp) @@ -186,6 +190,10 @@ if(BUILD_TESTS) endif() # target_link_libraries(craftground PRIVATE Boost::system Boost::thread Boost::interprocess) # target_include_directories(craftground PRIVATE ${Boost_INCLUDE_DIRS}) + if(WIN32) + add_definitions(-D_WIN32) + target_link_libraries(craftground PRIVATE kernel32.lib user32.lib Synchronization.lib) + endif() target_link_options(craftground PRIVATE "-Wl,--whole-archive" "-Wl,--no-whole-archive") target_include_directories(craftground PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp) From ebd141e9a36f0f59c661e3a56f532cd0fcbcddfd Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 5 Feb 2025 00:18:33 +0900 Subject: [PATCH 87/91] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Link=20library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/cpp/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt index 9eac3ab3..141ab3cd 100644 --- a/tests/cpp/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -83,6 +83,7 @@ elseif(WIN32) message(STATUS "CUDA is available in Windows") list(APPEND LINK_LIBRARIES CUDA::cudart) endif() + list(APPEND LINK_LIBRARIES kernel32.lib user32.lib Synchronization.lib) endif() # Apply the libraries to the target From d192373748da36b80698f2dc87f519ca5c8e3586 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 5 Feb 2025 01:03:15 +0900 Subject: [PATCH 88/91] =?UTF-8?q?=F0=9F=94=8A=20Add=20log=20for=20tets=5Fi?= =?UTF-8?q?pc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/cpp/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt index 141ab3cd..9123061e 100644 --- a/tests/cpp/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -84,6 +84,12 @@ elseif(WIN32) list(APPEND LINK_LIBRARIES CUDA::cudart) endif() list(APPEND LINK_LIBRARIES kernel32.lib user32.lib Synchronization.lib) + add_custom_command(TARGET test_ipc POST_BUILD + COMMAND dumpbin /DEPENDENTS $ > log.txt 2>&1 || echo "Dumpbin error ignored" + COMMAND type log.txt + COMMENT "Running dumpbin to check dependencies on Windows..." + VERBATIM + ) endif() # Apply the libraries to the target From 5a75f8e388d929bdc381ea4ef1808d72f7630c8b Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 5 Feb 2025 01:17:01 +0900 Subject: [PATCH 89/91] =?UTF-8?q?=F0=9F=92=9A=20Fix=20windows=20ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cmake-build.yml | 6 +++++- tests/cpp/CMakeLists.txt | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-build.yml b/.github/workflows/cmake-build.yml index d541cc1e..443f7edc 100644 --- a/.github/workflows/cmake-build.yml +++ b/.github/workflows/cmake-build.yml @@ -66,7 +66,6 @@ jobs: sudo mv libtorch $LIBTORCH - name: Install python < 3.13 if macos # https://groups.google.com/g/vim_dev/c/1I20UCzmtF4 - if: matrix.os == 'macos-latest' uses: actions/setup-python@v5 with: python-version: '3.12' @@ -98,6 +97,11 @@ jobs: -S ${{ github.workspace }} -DBUILD_TESTS=ON # Enable testing + # - name: Install Windows Universal CRT + # run: | + # curl -LO https://download.visualstudio.microsoft.com/download/pr/b6b7b0c1-4475-4a21-9c59-63722e3fdf18/7e2b0a8fc72e4f0ba9ea8c7ed1b7c13d/windows10.0-kb3118401-x64.msu + # start /wait wusa.exe windows10.0-kb3118401-x64.msu /quiet /norestart + - name: Build # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt index 9123061e..ce0a1483 100644 --- a/tests/cpp/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -10,7 +10,8 @@ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) find_package(CUDAToolkit QUIET) -find_package(Python3 REQUIRED COMPONENTS Development) +set(Python_FIND_VIRTUALENV FIRST) +find_package(Python3 QUIET COMPONENTS Interpreter Development) # Build Test directory add_executable(test_ipc test_ipc.cpp) From 0ee5c0d259a4f567b35a6e51ad17e3de8231c13a Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 5 Feb 2025 01:32:04 +0900 Subject: [PATCH 90/91] =?UTF-8?q?=E2=9C=A8=20Support=20windows=20noboost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MinecraftEnv/src/main/cpp/noboost_ipc.cpp | 265 +++++++++++------- 1 file changed, 156 insertions(+), 109 deletions(-) diff --git a/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp b/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp index 10aa33e6..91f6df77 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp +++ b/src/craftground/MinecraftEnv/src/main/cpp/noboost_ipc.cpp @@ -10,6 +10,7 @@ defined(__WIN32) && !defined(__CYGWIN__) #define IS_WINDOWS 1 #define SHMEM_PREFIX "Global\\" +#include #else #include #include @@ -57,13 +58,29 @@ std::string make_shared_memory_name(int port, const std::string &suffix) { jobject read_initial_environment( JNIEnv *env, jclass clazz, const std::string &p2j_memory_name, int port ) { - // std::cout << "Reading initial environment from shared memory 1" - // << std::endl; +#ifdef _WIN32 + HANDLE hMapFile = + OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, p2j_memory_name.c_str()); + if (!hMapFile) { + std::cerr << "OpenFileMapping failed: " << GetLastError() << std::endl; + return nullptr; + } + + void *p2jPtr = MapViewOfFile( + hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(SharedMemoryLayout) + ); + if (!p2jPtr) { + std::cerr << "MapViewOfFile failed: " << GetLastError() << std::endl; + CloseHandle(hMapFile); + return nullptr; + } +#else int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); if (p2jFd == -1) { perror("shm_open p2j failed while reading from shared memory"); return nullptr; } + void *p2jPtr = mmap( 0, sizeof(SharedMemoryLayout), @@ -77,16 +94,29 @@ jobject read_initial_environment( close(p2jFd); return nullptr; } - SharedMemoryLayout *p2jLayout = static_cast(p2jPtr); +#endif + SharedMemoryLayout *p2jLayout = static_cast(p2jPtr); const size_t initial_environment_size = p2jLayout->initial_environment_size; const size_t action_size = p2jLayout->action_size; - // std::cout << "Reading initial environment from shared memory 2" - // << std::endl; +#ifdef _WIN32 + UnmapViewOfFile(p2jPtr); + p2jPtr = MapViewOfFile( + hMapFile, + FILE_MAP_ALL_ACCESS, + 0, + 0, + sizeof(SharedMemoryLayout) + action_size + initial_environment_size + ); + if (!p2jPtr) { + std::cerr << "MapViewOfFile failed on second mapping: " + << GetLastError() << std::endl; + CloseHandle(hMapFile); + return nullptr; + } +#else munmap(p2jPtr, sizeof(SharedMemoryLayout)); - // Note: action_size is 0 when dummy is provided; actually python overwrites - // on initial environment section. p2jPtr = mmap( 0, sizeof(SharedMemoryLayout) + action_size + initial_environment_size, @@ -95,41 +125,33 @@ jobject read_initial_environment( p2jFd, 0 ); - if (p2jPtr == MAP_FAILED) { perror("mmap p2j failed while reading from shared memory"); close(p2jFd); return nullptr; } +#endif p2jLayout = static_cast(p2jPtr); - char *data_startInitialEnvironment = static_cast(p2jPtr) + p2jLayout->initial_environment_offset; size_t data_size = p2jLayout->initial_environment_size; - // std::cout << "Java read data_size: " << - // p2jLayout->initial_environment_size - // << std::endl; - // std::cout << "Java initial environment offset:" - // << p2jLayout->initial_environment_offset << std::endl; - // std::cout << "Java layout size:" << p2jLayout->layout_size << std::endl; - // std::cout << "Java action offset:" << p2jLayout->action_offset << - // std::endl; std::cout << "Java action size:" << p2jLayout->action_size << - // std::endl; std::cout << "Java read data_size: " << data_size << - // std::endl; - jbyteArray byteArray = env->NewByteArray(data_size); if (byteArray == nullptr || env->ExceptionCheck()) { +#ifdef _WIN32 + UnmapViewOfFile(p2jPtr); + CloseHandle(hMapFile); +#else munmap( p2jPtr, sizeof(SharedMemoryLayout) + action_size + initial_environment_size ); close(p2jFd); +#endif return nullptr; } - // std::cout << "Java read array: "; - // printHex(data_startInitialEnvironment, data_size); + env->SetByteArrayRegion( byteArray, 0, @@ -137,19 +159,17 @@ jobject read_initial_environment( reinterpret_cast(data_startInitialEnvironment) ); - std::string sema_obs_ready_name = - SHMEM_PREFIX "cg_sem_obs" + std::to_string(port); - std::string sema_action_ready_name = - SHMEM_PREFIX "cg_sem_act" + std::to_string(port); - - rk_sema_init(&p2jLayout->sem_obs_ready, sema_obs_ready_name.c_str(), 0, 1); - rk_sema_init( - &p2jLayout->sem_action_ready, sema_action_ready_name.c_str(), 0, 1 +#ifdef _WIN32 + UnmapViewOfFile(p2jPtr); + CloseHandle(hMapFile); +#else + munmap( + p2jPtr, + sizeof(SharedMemoryLayout) + action_size + initial_environment_size ); - // std::cout << "Initialied semaphore for Java" - // << p2jLayout->sem_obs_ready.name << std::endl; - // std::cout << "Initialied semaphore for Java" - // << p2jLayout->sem_action_ready.name << std::endl; + close(p2jFd); +#endif + return byteArray; } @@ -159,11 +179,29 @@ jbyteArray read_action( const std::string &p2j_memory_name, jbyteArray data ) { +#ifdef _WIN32 + HANDLE hMapFile = + OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, p2j_memory_name.c_str()); + if (!hMapFile) { + std::cerr << "OpenFileMapping failed: " << GetLastError() << std::endl; + return nullptr; + } + + void *p2jPtr = MapViewOfFile( + hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(SharedMemoryLayout) + ); + if (!p2jPtr) { + std::cerr << "MapViewOfFile failed: " << GetLastError() << std::endl; + CloseHandle(hMapFile); + return nullptr; + } +#else int p2jFd = shm_open(p2j_memory_name.c_str(), O_RDWR, 0666); if (p2jFd == -1) { perror("shm_open p2j failed while reading from shared memory"); return nullptr; } + void *p2jPtr = mmap( 0, sizeof(SharedMemoryLayout), @@ -177,10 +215,27 @@ jbyteArray read_action( close(p2jFd); return nullptr; } +#endif SharedMemoryLayout *p2jHeader = static_cast(p2jPtr); size_t action_size = p2jHeader->action_size; +#ifdef _WIN32 + UnmapViewOfFile(p2jPtr); + p2jPtr = MapViewOfFile( + hMapFile, + FILE_MAP_ALL_ACCESS, + 0, + 0, + sizeof(SharedMemoryLayout) + action_size + ); + if (!p2jPtr) { + std::cerr << "MapViewOfFile failed on second mapping: " + << GetLastError() << std::endl; + CloseHandle(hMapFile); + return nullptr; + } +#else munmap(p2jPtr, sizeof(SharedMemoryLayout)); p2jPtr = mmap( 0, @@ -190,42 +245,38 @@ jbyteArray read_action( p2jFd, 0 ); - if (p2jPtr == MAP_FAILED) { perror("mmap p2j failed while reading from shared memory"); close(p2jFd); return nullptr; } - p2jHeader = static_cast(p2jPtr); +#endif + p2jHeader = static_cast(p2jPtr); char *data_start = static_cast(p2jPtr) + p2jHeader->action_offset; - // std::cout << "Waiting for Python to write the action" << std::endl; - // printHex((const char *)p2jHeader, sizeof(SharedMemoryLayout)); - // OK - rk_sema_open(&p2jHeader->sem_action_ready); - rk_sema_wait(&p2jHeader->sem_action_ready); if (data != nullptr) { jsize oldSize = env->GetArrayLength(data); if (oldSize != p2jHeader->action_size) { - // std::cout << "Resizing byte array to" - // << std::to_string(p2jHeader->action_size) << std::endl; env->DeleteLocalRef(data); data = nullptr; data = env->NewByteArray(p2jHeader->action_size); } } else { - // std::cout << "Creating new byte array" - // << std::to_string(p2jHeader->action_size) << std::endl; data = env->NewByteArray(p2jHeader->action_size); } if (data == nullptr || env->ExceptionCheck()) { +#ifdef _WIN32 + UnmapViewOfFile(p2jPtr); + CloseHandle(hMapFile); +#else munmap(p2jPtr, sizeof(SharedMemoryLayout) + action_size); close(p2jFd); +#endif return nullptr; } - // Read the action message + if (action_size > 0) { env->SetByteArrayRegion( data, @@ -234,7 +285,14 @@ jbyteArray read_action( reinterpret_cast(data_start) ); } - // std::cout << "Read action from shared memory" << std::endl; + +#ifdef _WIN32 + UnmapViewOfFile(p2jPtr); + CloseHandle(hMapFile); +#else + munmap(p2jPtr, sizeof(SharedMemoryLayout) + action_size); + close(p2jFd); +#endif return data; } @@ -244,6 +302,24 @@ void write_observation( const char *data, const size_t observation_size ) { +#ifdef _WIN32 + HANDLE p2jFd = + OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, p2j_memory_name); + if (!p2jFd) { + std::cerr << "OpenFileMapping failed for p2j: " << GetLastError() + << std::endl; + return; + } + void *p2jPtr = MapViewOfFile( + p2jFd, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(SharedMemoryLayout) + ); + if (!p2jPtr) { + std::cerr << "MapViewOfFile failed for p2j: " << GetLastError() + << std::endl; + CloseHandle(p2jFd); + return; + } +#else int p2jFd = shm_open(p2j_memory_name, O_RDWR, 0666); if (p2jFd == -1) { perror("shm_open p2j failed while writing to shared memory"); @@ -262,15 +338,38 @@ void write_observation( close(p2jFd); return; } +#endif SharedMemoryLayout *p2jLayout = static_cast(p2jPtr); +#ifdef _WIN32 + HANDLE j2pFd = + OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, j2p_memory_name); + if (!j2pFd) { + std::cerr << "OpenFileMapping failed for j2p: " << GetLastError() + << std::endl; + UnmapViewOfFile(p2jPtr); + CloseHandle(p2jFd); + return; + } + void *j2pPtr = MapViewOfFile( + j2pFd, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(J2PSharedMemoryLayout) + ); + if (!j2pPtr) { + std::cerr << "MapViewOfFile failed for j2p: " << GetLastError() + << std::endl; + UnmapViewOfFile(p2jPtr); + CloseHandle(p2jFd); + CloseHandle(j2pFd); + return; + } +#else int j2pFd = shm_open(j2p_memory_name, O_RDWR, 0666); if (j2pFd == -1) { perror("shm_open j2p failed while writing to shared memory"); munmap(p2jPtr, sizeof(SharedMemoryLayout)); + close(p2jFd); return; } - void *j2pPtr = mmap( 0, sizeof(J2PSharedMemoryLayout), @@ -279,7 +378,6 @@ void write_observation( j2pFd, 0 ); - if (j2pPtr == MAP_FAILED) { perror("mmap j2p failed while writing to shared memory"); munmap(p2jPtr, sizeof(SharedMemoryLayout)); @@ -287,82 +385,31 @@ void write_observation( close(j2pFd); return; } - // std::cout << "Writing observation to shared memory adfsadf" << std::endl; +#endif J2PSharedMemoryLayout *j2pLayout = static_cast(j2pPtr); j2pLayout->data_offset = sizeof(J2PSharedMemoryLayout); - // std::cout << "Writing observation to shared memory adfsad222sdsfsasdff" - // << std::endl; - - struct stat statbuf; - if (fstat(j2pFd, &statbuf) == -1) { - perror("fstat failed while getting shared memory size"); - munmap(j2pPtr, sizeof(J2PSharedMemoryLayout)); - munmap(p2jPtr, sizeof(SharedMemoryLayout)); - close(j2pFd); - close(p2jFd); - return; - } else { - // std::cout << "Shared memory size: " << statbuf.st_size << std::endl; - } - const size_t current_shmem_size = statbuf.st_size; - size_t requiredSize = observation_size + sizeof(J2PSharedMemoryLayout); - requiredSize = requiredSize > 1024 ? requiredSize : 1024; - - if (current_shmem_size < requiredSize) { - // Unmap existing memory before resizing - munmap(j2pPtr, sizeof(J2PSharedMemoryLayout)); - - // Resize the shared memory - if (ftruncate(j2pFd, requiredSize) == -1) { - perror("ftruncate failed while resizing shared memory"); - munmap(p2jPtr, sizeof(SharedMemoryLayout)); - close(j2pFd); - close(p2jFd); - return; - } - - // Remap with new size - j2pPtr = - mmap(0, requiredSize, PROT_READ | PROT_WRITE, MAP_SHARED, j2pFd, 0); - if (j2pPtr == MAP_FAILED) { - perror("mmap failed after resizing shared memory"); - munmap(p2jPtr, sizeof(SharedMemoryLayout)); - close(j2pFd); - close(p2jFd); - return; - } - - // Initialize the header - j2pLayout = static_cast(j2pPtr); - j2pLayout->layout_size = sizeof(J2PSharedMemoryLayout); - j2pLayout->data_offset = sizeof(J2PSharedMemoryLayout); - j2pLayout->data_size = observation_size; - // std::cout << "Resized shared memory to " << requiredSize << - // std::endl; - } - // std::cout << "Writing observation to shared memory KKKAKAAKAK" << - // std::endl; - - // Write the observation to shared memory char *data_start = static_cast(j2pPtr) + j2pLayout->data_offset; std::memcpy(data_start, data, observation_size); j2pLayout->data_size = observation_size; - // Notify Python that the observation is ready - // printHex((const char *)p2jLayout, sizeof(SharedMemoryLayout)); rk_sema_open(&p2jLayout->sem_obs_ready); if (rk_sema_post(&p2jLayout->sem_obs_ready) < 0) { perror("rk_sema_post failed while notifying python"); } - // std::cout << "Wrote and notified observation to python" << std::endl; - // Clean up resources - munmap(j2pPtr, requiredSize); +#ifdef _WIN32 + UnmapViewOfFile(j2pPtr); + UnmapViewOfFile(p2jPtr); + CloseHandle(j2pFd); + CloseHandle(p2jFd); +#else + munmap(j2pPtr, sizeof(J2PSharedMemoryLayout)); munmap(p2jPtr, sizeof(SharedMemoryLayout)); close(j2pFd); close(p2jFd); +#endif } extern "C" JNIEXPORT jobject JNICALL From 4858004e4783c35ec24955809f43cdac78b1f139 Mon Sep 17 00:00:00 2001 From: Hyeonseo Yang Date: Wed, 5 Feb 2025 01:38:18 +0900 Subject: [PATCH 91/91] =?UTF-8?q?=F0=9F=90=9B=20Fix=20windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h b/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h index f2efac63..366db6c4 100644 --- a/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h +++ b/src/craftground/MinecraftEnv/src/main/cpp/cross_semaphore.h @@ -85,7 +85,8 @@ static inline int rk_sema_post(struct rk_sema *s) { static inline void rk_sema_destroy(struct rk_sema *s) { #if IS_WINDOWS - CloseHandle(s->sem); + CloseHandle(s->sem_java); + CloseHandle(s->sem_python); #else sem_close(s->sem_java); sem_close(s->sem_python);