diff --git a/CMakeLists.txt b/CMakeLists.txt index ed316aa..ab97d56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,110 @@ -cmake_minimum_required (VERSION 3.8) +cmake_minimum_required(VERSION 3.17.0) -project("RocketSim") +project(RocketSim VERSION 2.1.0) -# Add all headers and code files -file(GLOB_RECURSE FILES_ROCKETSIM "${PROJECT_SOURCE_DIR}/src/*.cpp" "${PROJECT_SOURCE_DIR}/src/*.h") +include(GNUInstallDirs) -# Only include bullet headers when using MSVC, otherwise just code files -file(GLOB_RECURSE FILES_BULLET "${PROJECT_SOURCE_DIR}/libsrc/bullet3-3.24/*.cpp" "${PROJECT_SOURCE_DIR}/libsrc/bullet3-3.24/*.h") +include(CheckIPOSupported) +check_ipo_supported(RESULT IPO_SUPPORTED) -add_library(RocketSim ${FILES_ROCKETSIM} ${FILES_BULLET}) +file(GLOB_RECURSE ROCKETSIM_BULLET3_SOURCES libsrc/bullet3-3.24/*.cpp) -set_target_properties(RocketSim PROPERTIES LINKER_LANGUAGE CXX) -set_target_properties(RocketSim PROPERTIES CXX_STANDARD 20) +file(GLOB_RECURSE ROCKETSIM_SOURCES src/*.cpp) + +add_library(RocketSim-static STATIC) +target_sources(RocketSim-static PRIVATE ${ROCKETSIM_BULLET3_SOURCES} ${ROCKETSIM_SOURCES}) + +if(NOT NINTENDO_SWITCH AND NOT NINTENDO_3DS) + add_library(RocketSim SHARED) + target_sources(RocketSim PRIVATE ${ROCKETSIM_BULLET3_SOURCES} ${ROCKETSIM_SOURCES}) +endif() + +foreach(ROCKETSIM_LIBRARY RocketSim RocketSim-static) + if((${ROCKETSIM_LIBRARY} STREQUAL "RocketSim") AND (NINTENDO_SWITCH OR NINTENDO_3DS)) + continue() + endif() + + if(IPO_SUPPORTED) + set_target_properties(${ROCKETSIM_LIBRARY} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION TRUE + INTERPROCEDURAL_OPTIMIZATION_DEBUG FALSE + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE + INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE + ) + endif() + + target_compile_features(${ROCKETSIM_LIBRARY} PRIVATE cxx_std_20) + + if(MSVC) + target_compile_options(${ROCKETSIM_LIBRARY} PUBLIC /arch:AVX2) + target_compile_definitions(${ROCKETSIM_LIBRARY} PUBLIC _USE_MATH_DEFINES) + elseif(NOT NINTENDO_SWITCH AND NOT NINTENDO_3DS) + target_compile_options(${ROCKETSIM_LIBRARY} PUBLIC -msse4.1) + #target_compile_options(${ROCKETSIM_LIBRARY} PUBLIC --coverage) + #target_link_options(${ROCKETSIM_LIBRARY} PUBLIC --coverage) + target_compile_options(${ROCKETSIM_LIBRARY} PUBLIC -fno-omit-frame-pointer) + target_link_options(${ROCKETSIM_LIBRARY} PUBLIC -fno-omit-frame-pointer) + target_compile_options(${ROCKETSIM_LIBRARY} PUBLIC -Wno-stringop-overflow -Wno-volatile) + target_link_options(${ROCKETSIM_LIBRARY} PUBLIC -Wno-stringop-overflow -Wno-volatile) + #target_compile_options(${ROCKETSIM_LIBRARY} PUBLIC -Wsuggest-override -Werror) + target_compile_options(${ROCKETSIM_LIBRARY} PUBLIC -Wsuggest-final-types -Wsuggest-final-methods) + endif() + + set_target_properties(${ROCKETSIM_LIBRARY} PROPERTIES C_VISIBILITY_PRESET hidden) + set_target_properties(${ROCKETSIM_LIBRARY} PROPERTIES CXX_VISIBILITY_PRESET hidden) + + target_include_directories(${ROCKETSIM_LIBRARY} PUBLIC + $ + $ + $ + ) +endforeach() + +# only export explicitly +if(NOT NINTENDO_SWITCH AND NOT NINTENDO_3DS) + target_compile_definitions(RocketSim PRIVATE ROCKETSIM_EXPORTS) +endif() +target_compile_definitions(RocketSim-static PUBLIC ROCKETSIM_STATIC) + + + + +if(NOT NINTENDO_SWITCH AND NOT NINTENDO_3DS) + ########################### + ### Example application ### + ########################### + + #find_package(RocketSim REQUIRED) + find_package(Eigen3 REQUIRED) + add_executable(RocketSimTest) + target_compile_features(RocketSimTest PRIVATE cxx_std_20) + + target_sources(RocketSimTest PRIVATE test.cpp) + + # choose shared or static lib here + #target_link_libraries(RocketSimTest PRIVATE RocketSim) + target_link_libraries(RocketSimTest PRIVATE RocketSim-static Eigen3::Eigen) + + if(IPO_SUPPORTED) + set_target_properties(RocketSimTest PROPERTIES + INTERPROCEDURAL_OPTIMIZATION TRUE + INTERPROCEDURAL_OPTIMIZATION_DEBUG FALSE + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE + INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE + ) + endif() + + set_target_properties(RocketSimTest PROPERTIES C_VISIBILITY_PRESET hidden) + set_target_properties(RocketSimTest PROPERTIES CXX_VISIBILITY_PRESET hidden) +endif() + +if(NINTENDO_3DS) +add_subdirectory(visualize-3ds) +else() +add_subdirectory(visualize) +#add_subdirectory(visualize-vk) +#add_subdirectory(train_ppo) +#add_subdirectory(train_ppo2) +endif() diff --git a/python-mtheall/Arena.cpp b/python-mtheall/Arena.cpp index 9ab40f4..d09a972 100644 --- a/python-mtheall/Arena.cpp +++ b/python-mtheall/Arena.cpp @@ -128,7 +128,7 @@ auto const hoopsBoostMapping = buildBoostMapping (hoopsIndexMapping); int getBoostPadIndex (RocketSim::BoostPad const *pad_, std::unordered_map const &map_) noexcept { - auto const it = map_.find (makeKey (pad_->pos.x, pad_->pos.y)); + auto const it = map_.find (makeKey (pad_->config.pos.x, pad_->config.pos.y)); if (it == std::end (map_)) return -1; @@ -145,7 +145,7 @@ std::unordered_map const &getBoostMapping (RocketSim::G bool ensureBoostPadByIndex (RocketSim::Python::Arena *arena_) noexcept { - if (arena_->arena->GetArenaConfig ().customBoostPads) + if (arena_->arena->GetArenaConfig ().useCustomBoostPads) return true; if (arena_->boostPads->empty ()) @@ -1790,7 +1790,8 @@ PyObject *Arena::CloneInto (Arena *self_, PyObject *args_, PyObject *kwds_) noex for (unsigned i = 0; i < padsCount; ++i) { - if (padsB[i]->pad->isBig != padsA[i]->pad->isBig || padsB[i]->pad->pos != padsA[i]->pad->pos) + if (padsB[i]->pad->config.isBig != padsA[i]->pad->config.isBig || + padsB[i]->pad->config.pos != padsA[i]->pad->config.pos) { PyErr_SetString (PyExc_ValueError, "Boost pad mismatch"); return nullptr; diff --git a/python-mtheall/ArenaConfig.cpp b/python-mtheall/ArenaConfig.cpp index e70ce48..e6aeea5 100644 --- a/python-mtheall/ArenaConfig.cpp +++ b/python-mtheall/ArenaConfig.cpp @@ -16,7 +16,7 @@ bool checkCustomBoostPads (PyObject *input_, bool setError_) noexcept for (int i = 0; i < size; ++i) { - if (!Py_IS_TYPE (PySequence_GetItem (input_, i), RocketSim::Python::Vec::Type)) + if (!Py_IS_TYPE (PySequence_GetItem (input_, i), RocketSim::Python::BoostPadConfig::Type)) { if (setError_) PyErr_SetString (PyExc_TypeError, "Invalid type for custom boost pads"); @@ -28,7 +28,7 @@ bool checkCustomBoostPads (PyObject *input_, bool setError_) noexcept return true; } -RocketSim::Python::PyObjectRef convert (std::vector const &input_) noexcept +RocketSim::Python::PyObjectRef convert (std::vector const &input_) noexcept { using namespace RocketSim::Python; @@ -38,35 +38,35 @@ RocketSim::Python::PyObjectRef convert (std::vector const &input for (unsigned i = 0; i < input_.size (); ++i) { - auto vec = Vec::NewFromVec (input_[i]); - if (!vec) + auto config = BoostPadConfig::NewFromBoostPadConfig (input_[i]); + if (!config) return nullptr; // steals ref - if (PyList_SetItem (output.borrow (), i, vec.giftObject ()) < 0) + if (PyList_SetItem (output.borrow (), i, config.giftObject ()) < 0) return nullptr; } return output; } -std::vector convert (PyObject *input_) noexcept +std::vector convert (PyObject *input_) noexcept { using namespace RocketSim::Python; - std::vector output; + std::vector output; output.resize (PySequence_Size (input_)); for (unsigned i = 0; i < output.size (); ++i) { - auto const vec = PySequence_GetItem (input_, i); + auto const config = PySequence_GetItem (input_, i); - assert (Py_IS_TYPE (vec, Vec::Type)); - if (!Py_IS_TYPE (vec, Vec::Type)) + assert (Py_IS_TYPE (config, BoostPadConfig::Type)); + if (!Py_IS_TYPE (config, BoostPadConfig::Type)) return {}; - output[i] = Vec::ToVec (PyCast (vec)); + output[i] = BoostPadConfig::ToBoostPadConfig (PyCast (config)); } return output; @@ -99,16 +99,11 @@ PyMemberDef ArenaConfig::Members[] = { .offset = offsetof (ArenaConfig, config) + offsetof (RocketSim::ArenaConfig, maxObjects), .flags = 0, .doc = "Maximum number of objects"}, - {.name = "custom_big_boost_pads", + {.name = "custom_boost_pads", .type = T_OBJECT, - .offset = offsetof (ArenaConfig, customBigBoostPads), + .offset = offsetof (ArenaConfig, customBoostPads), .flags = 0, - .doc = "Custom big boost pads"}, - {.name = "custom_small_boost_pads", - .type = T_OBJECT, - .offset = offsetof (ArenaConfig, customSmallBoostPads), - .flags = 0, - .doc = "Custom big boost pads"}, + .doc = "Custom boost pads"}, {.name = nullptr, .type = 0, .offset = 0, .flags = 0, .doc = nullptr}, }; @@ -145,7 +140,7 @@ PyType_Slot ArenaConfig::Slots[] = { {Py_tp_members, &ArenaConfig::Members}, {Py_tp_methods, &ArenaConfig::Methods}, {Py_tp_getset, &ArenaConfig::GetSet}, - {Py_tp_doc, (void *)R"(ArenaConfig + {Py_tp_doc, (void *)R"(Arena config __init__(self, memory_weight_mode: int = RocketSim.MemoryWeightMode.HEAVY, min_pos: RocketSim.Vec = RocketSim.Vec(-4500.0, -6000.0, 0.0), @@ -153,7 +148,8 @@ __init__(self, max_aabb_len: float = 370.0, no_ball_rot: bool = True, use_custom_broadphase: bool = True, - max_objects: int = 512))"}, + max_objects: int = 512), + custom_boost_pads: Union[None, Sequence[RocketSim.BoostPadConfig]]))"}, {0, nullptr}, }; @@ -176,27 +172,24 @@ PyRef ArenaConfig::NewFromArenaConfig (RocketSim::ArenaConfig const bool ArenaConfig::InitFromArenaConfig (ArenaConfig *self_, RocketSim::ArenaConfig const &config_) noexcept { - auto minPos = Vec::NewFromVec (config_.minPos); - auto maxPos = Vec::NewFromVec (config_.maxPos); - auto customBigBoostPads = PyObjectRef::incRef (Py_None); - auto customSmallBoostPads = PyObjectRef::incRef (Py_None); + auto minPos = Vec::NewFromVec (config_.minPos); + auto maxPos = Vec::NewFromVec (config_.maxPos); + auto customBoostPads = PyObjectRef::incRef (Py_None); if (!minPos || !maxPos) return false; - if (config_.customBoostPads) + if (config_.useCustomBoostPads) { - customBigBoostPads = convert (config_.customBigBoostPads); - customSmallBoostPads = convert (config_.customSmallBoostPads); + customBoostPads = convert (config_.customBoostPads); - if (!customBigBoostPads || !customSmallBoostPads) + if (!customBoostPads) return false; } PyRef::assign (self_->minPos, minPos.borrowObject ()); PyRef::assign (self_->maxPos, maxPos.borrowObject ()); - PyObjectRef::assign (self_->customBigBoostPads, customBigBoostPads.borrow ()); - PyObjectRef::assign (self_->customSmallBoostPads, customSmallBoostPads.borrow ()); + PyObjectRef::assign (self_->customBoostPads, customBoostPads.borrow ()); self_->config = config_; @@ -210,32 +203,17 @@ std::optional ArenaConfig::ToArenaConfig (ArenaConfig *s config.minPos = Vec::ToVec (self_->minPos); config.maxPos = Vec::ToVec (self_->maxPos); - config.customBoostPads = false; - config.customBigBoostPads.clear (); - config.customSmallBoostPads.clear (); + config.useCustomBoostPads = false; - if (self_->customBigBoostPads && self_->customSmallBoostPads && - (!Py_IsNone (self_->customBigBoostPads) || !Py_IsNone (self_->customSmallBoostPads))) + if (self_->customBoostPads && !Py_IsNone (self_->customBoostPads)) { try { - config.customBoostPads = true; - - if (!Py_IsNone (self_->customBigBoostPads)) - { - if (!checkCustomBoostPads (self_->customBigBoostPads, true)) - return std::nullopt; - - config.customBigBoostPads = convert (self_->customBigBoostPads); - } - - if (!Py_IsNone (self_->customSmallBoostPads)) - { - if (!checkCustomBoostPads (self_->customSmallBoostPads, true)) - return std::nullopt; + if (!checkCustomBoostPads (self_->customBoostPads, true)) + return std::nullopt; - config.customSmallBoostPads = convert (self_->customSmallBoostPads); - } + config.useCustomBoostPads = true; + config.customBoostPads = convert (self_->customBoostPads); } catch (...) { @@ -255,10 +233,9 @@ PyObject *ArenaConfig::New (PyTypeObject *subtype_, PyObject *args_, PyObject *k if (!self) return nullptr; - self->minPos = nullptr; - self->maxPos = nullptr; - self->customBigBoostPads = nullptr; - self->customSmallBoostPads = nullptr; + self->minPos = nullptr; + self->maxPos = nullptr; + self->customBoostPads = nullptr; return self.giftObject (); } @@ -267,23 +244,21 @@ int ArenaConfig::Init (ArenaConfig *self_, PyObject *args_, PyObject *kwds_) noe { RocketSim::ArenaConfig config{}; - PyObject *minPos = nullptr; // borrowed references - PyObject *maxPos = nullptr; - PyObject *noBallRot = nullptr; - PyObject *useCustomBroadphase = nullptr; - PyObject *customBigBoostPads = Py_None; - PyObject *customSmallBoostPads = Py_None; - int memoryWeightMode = static_cast (config.memWeightMode); - - static char memoryWeightModeKwd[] = "memory_weight_mode"; - static char minPosKwd[] = "min_pos"; - static char maxPosKwd[] = "max_pos"; - static char maxAABBLenKwd[] = "max_aabb_len"; - static char noBallRotKwd[] = "no_ball_rot"; - static char useCustomBroadphaseKwd[] = "use_custom_broadphase"; - static char maxObjectsKwd[] = "max_objects"; - static char customBigBoostPadsKwd[] = "custom_big_boost_pads"; - static char customSmallBoostPadsKwd[] = "custom_small_boost_pads"; + PyObject *minPos = nullptr; // borrowed references + PyObject *maxPos = nullptr; + PyObject *noBallRot = nullptr; + PyObject *useCustomBroadphase = nullptr; + PyObject *customBoostPads = Py_None; + int memoryWeightMode = static_cast (config.memWeightMode); + + static char memoryWeightModeKwd[] = "memory_weight_mode"; + static char minPosKwd[] = "min_pos"; + static char maxPosKwd[] = "max_pos"; + static char maxAABBLenKwd[] = "max_aabb_len"; + static char noBallRotKwd[] = "no_ball_rot"; + static char useCustomBroadphaseKwd[] = "use_custom_broadphase"; + static char maxObjectsKwd[] = "max_objects"; + static char customBoostPadsKwd[] = "custom_boost_pads"; static char *dict[] = {memoryWeightModeKwd, minPosKwd, @@ -292,8 +267,7 @@ int ArenaConfig::Init (ArenaConfig *self_, PyObject *args_, PyObject *kwds_) noe noBallRotKwd, useCustomBroadphaseKwd, maxObjectsKwd, - customBigBoostPadsKwd, - customSmallBoostPadsKwd, + customBoostPadsKwd, nullptr}; if (!PyArg_ParseTupleAndKeywords (args_, @@ -309,8 +283,7 @@ int ArenaConfig::Init (ArenaConfig *self_, PyObject *args_, PyObject *kwds_) noe &noBallRot, &useCustomBroadphase, &config.maxObjects, - &customBigBoostPads, - &customSmallBoostPads)) + &customBoostPads)) return -1; config.memWeightMode = static_cast (memoryWeightMode); @@ -338,25 +311,14 @@ int ArenaConfig::Init (ArenaConfig *self_, PyObject *args_, PyObject *kwds_) noe if (useCustomBroadphase) config.useCustomBroadphase = PyObject_IsTrue (useCustomBroadphase); - if (!Py_IsNone (customBigBoostPads)) - { - if (!checkCustomBoostPads (customBigBoostPads, true)) - return -1; - - config.customBigBoostPads = convert (customBigBoostPads); - } - - if (!Py_IsNone (customSmallBoostPads)) + if (!Py_IsNone (customBoostPads)) { - if (!checkCustomBoostPads (customSmallBoostPads, true)) + if (!checkCustomBoostPads (customBoostPads, true)) return -1; - config.customSmallBoostPads = convert (customSmallBoostPads); + config.customBoostPads = convert (customBoostPads); } - if (!Py_IsNone (customBigBoostPads) || !Py_IsNone (customSmallBoostPads)) - config.customBoostPads = true; - if (!InitFromArenaConfig (self_, config)) return -1; @@ -367,8 +329,7 @@ void ArenaConfig::Dealloc (ArenaConfig *self_) noexcept { Py_XDECREF (self_->minPos); Py_XDECREF (self_->maxPos); - Py_XDECREF (self_->customBigBoostPads); - Py_XDECREF (self_->customSmallBoostPads); + Py_XDECREF (self_->customBoostPads); auto const tp_free = (freefunc)PyType_GetSlot (Type, Py_tp_free); tp_free (self_); @@ -395,12 +356,8 @@ PyObject *ArenaConfig::Pickle (ArenaConfig *self_) noexcept !DictSetValue (dict.borrow (), "max_pos", PyNewRef (self_->maxPos))) return nullptr; - if (self_->customBigBoostPads && checkCustomBoostPads (self_->customBigBoostPads, false) && - !DictSetValue (dict.borrow (), "custom_big_boost_pads", PyNewRef (self_->customBigBoostPads))) - return nullptr; - - if (self_->customSmallBoostPads && checkCustomBoostPads (self_->customSmallBoostPads, false) && - !DictSetValue (dict.borrow (), "custom_small_boost_pads", PyNewRef (self_->customSmallBoostPads))) + if (self_->customBoostPads && checkCustomBoostPads (self_->customBoostPads, false) && + !DictSetValue (dict.borrow (), "custom_boost_pads", PyNewRef (self_->customBoostPads))) return nullptr; if (self_->config.maxAABBLen != model.maxAABBLen && @@ -448,8 +405,7 @@ PyObject *ArenaConfig::Copy (ArenaConfig *self_) noexcept PyRef::assign (config->minPos, reinterpret_cast (self_->minPos)); PyRef::assign (config->maxPos, reinterpret_cast (self_->maxPos)); - PyObjectRef::assign (config->customBigBoostPads, self_->customBigBoostPads); - PyObjectRef::assign (config->customSmallBoostPads, self_->customSmallBoostPads); + PyObjectRef::assign (config->customBoostPads, self_->customBoostPads); auto result = ToArenaConfig (self_); if (!result.has_value ()) @@ -474,23 +430,14 @@ PyObject *ArenaConfig::DeepCopy (ArenaConfig *self_, PyObject *memo_) noexcept if (!config->maxPos) return nullptr; - if (self_->customBigBoostPads && !Py_IsNone (self_->customBigBoostPads)) - { - PyObjectRef::assign (config->customBigBoostPads, PyDeepCopy (self_->customBigBoostPads, memo_)); - if (!config->customBigBoostPads) - return nullptr; - } - else - PyObjectRef::assign (config->customBigBoostPads, Py_None); - - if (self_->customSmallBoostPads && !Py_IsNone (self_->customSmallBoostPads)) + if (self_->customBoostPads && !Py_IsNone (self_->customBoostPads)) { - PyObjectRef::assign (config->customSmallBoostPads, PyDeepCopy (self_->customSmallBoostPads, memo_)); - if (!config->customSmallBoostPads) + PyObjectRef::assign (config->customBoostPads, PyDeepCopy (self_->customBoostPads, memo_)); + if (!config->customBoostPads) return nullptr; } else - PyObjectRef::assign (config->customSmallBoostPads, Py_None); + PyObjectRef::assign (config->customBoostPads, Py_None); auto result = ToArenaConfig (self_); if (!result.has_value ()) diff --git a/python-mtheall/BallState.cpp b/python-mtheall/BallState.cpp index 685cd37..b8f27f2 100644 --- a/python-mtheall/BallState.cpp +++ b/python-mtheall/BallState.cpp @@ -177,7 +177,7 @@ int BallState::Init (BallState *self_, PyObject *args_, PyObject *kwds_) noexcep RocketSim::BallState state{}; PyObject *pos = nullptr; // borrowed references - PyObject *rotMat = nullptr; // borrowed references + PyObject *rotMat = nullptr; PyObject *vel = nullptr; PyObject *angVel = nullptr; diff --git a/python-mtheall/BoostPad.cpp b/python-mtheall/BoostPad.cpp index 96e0102..099ec03 100644 --- a/python-mtheall/BoostPad.cpp +++ b/python-mtheall/BoostPad.cpp @@ -84,7 +84,7 @@ void BoostPad::Dealloc (BoostPad *self_) noexcept PyObject *BoostPad::Getis_big (BoostPad *self_, void *) noexcept { - if (self_->pad->isBig) + if (self_->pad->config.isBig) Py_RETURN_TRUE; Py_RETURN_FALSE; @@ -92,7 +92,7 @@ PyObject *BoostPad::Getis_big (BoostPad *self_, void *) noexcept PyObject *BoostPad::GetPos (BoostPad *self_) noexcept { - auto pos = Vec::NewFromVec (self_->pad->pos); + auto pos = Vec::NewFromVec (self_->pad->config.pos); if (!pos) return nullptr; diff --git a/python-mtheall/BoostPadConfig.cpp b/python-mtheall/BoostPadConfig.cpp new file mode 100644 index 0000000..0ad306b --- /dev/null +++ b/python-mtheall/BoostPadConfig.cpp @@ -0,0 +1,242 @@ +#include "Module.h" + +#include +#include + +namespace RocketSim::Python +{ +PyTypeObject *BoostPadConfig::Type = nullptr; + +PyMemberDef BoostPadConfig::Members[] = { + {.name = "is_big", + .type = TypeHelper::type, + .offset = offsetof (BoostPadConfig, config) + offsetof (RocketSim::BoostPadConfig, isBig), + .flags = 0, + .doc = "Whether boost pad is big"}, + {.name = nullptr, .type = 0, .offset = 0, .flags = 0, .doc = nullptr}, +}; + +PyMethodDef BoostPadConfig::Methods[] = { + {.ml_name = "__getstate__", + .ml_meth = (PyCFunction)&BoostPadConfig::Pickle, + .ml_flags = METH_NOARGS, + .ml_doc = nullptr}, + {.ml_name = "__setstate__", + .ml_meth = (PyCFunction)&BoostPadConfig::Unpickle, + .ml_flags = METH_O, + .ml_doc = nullptr}, + {.ml_name = "__copy__", + .ml_meth = (PyCFunction)&BoostPadConfig::Copy, + .ml_flags = METH_NOARGS, + .ml_doc = R"(__copy__(self) -> RocketSim.BoostPadConfig +Shallow copy)"}, + {.ml_name = "__deepcopy__", + .ml_meth = (PyCFunction)&BoostPadConfig::DeepCopy, + .ml_flags = METH_O, + .ml_doc = R"(__deepcopy__(self, memo) -> RocketSim.BoostPadConfig +Deep copy)"}, + {.ml_name = nullptr, .ml_meth = nullptr, .ml_flags = 0, .ml_doc = nullptr}, +}; + +PyGetSetDef BoostPadConfig::GetSet[] = { + GETSET_ENTRY (BoostPadConfig, pos, "Position"), + {.name = nullptr, .get = nullptr, .set = nullptr, .doc = nullptr, .closure = nullptr}, +}; + +PyType_Slot BoostPadConfig::Slots[] = { + {Py_tp_new, (void *)(&BoostPadConfig::New)}, + {Py_tp_init, (void *)(&BoostPadConfig::Init)}, + {Py_tp_dealloc, (void *)(&BoostPadConfig::Dealloc)}, + {Py_tp_members, &BoostPadConfig::Members}, + {Py_tp_methods, &BoostPadConfig::Methods}, + {Py_tp_getset, &BoostPadConfig::GetSet}, + {Py_tp_doc, (void *)R"(Boost pad config +__init__(self, + pos: RocketSim.Vec = RocketSim.Vec(), + is_big: bool = False))"}, + {0, nullptr}, +}; + +PyType_Spec BoostPadConfig::Spec = { + .name = "RocketSim.BoostPadConfig", + .basicsize = sizeof (BoostPadConfig), + .itemsize = 0, + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE, + .slots = BoostPadConfig::Slots, +}; + +PyRef BoostPadConfig::NewFromBoostPadConfig (RocketSim::BoostPadConfig const &pad_) noexcept +{ + auto const self = PyRef::stealObject (BoostPadConfig::New (BoostPadConfig::Type, nullptr, nullptr)); + if (!self || !InitFromBoostPadConfig (self.borrow (), pad_)) + return nullptr; + + return self; +} + +bool BoostPadConfig::InitFromBoostPadConfig (BoostPadConfig *const self_, + RocketSim::BoostPadConfig const &config_) noexcept +{ + auto pos = Vec::NewFromVec (config_.pos); + + if (!pos) + return false; + + PyRef::assign (self_->pos, pos.borrowObject ()); + + self_->config = config_; + + return true; +} + +RocketSim::BoostPadConfig BoostPadConfig::ToBoostPadConfig (BoostPadConfig *self_) noexcept +{ + auto config = self_->config; + + config.pos = Vec::ToVec (self_->pos); + + return config; +} + +PyObject *BoostPadConfig::New (PyTypeObject *subtype_, PyObject *args_, PyObject *kwds_) noexcept +{ + auto const tp_alloc = (allocfunc)PyType_GetSlot (subtype_, Py_tp_alloc); + + auto self = PyRef::stealObject (tp_alloc (subtype_, 0)); + if (!self) + return nullptr; + + new (&self->config) RocketSim::BoostPadConfig{}; + + self->pos = nullptr; + + return self.giftObject (); +} + +int BoostPadConfig::Init (BoostPadConfig *self_, PyObject *args_, PyObject *kwds_) noexcept +{ + static char posKwd[] = "pos"; + static char isBigKwd[] = "is_big"; + + static char *dict[] = {posKwd, isBigKwd, nullptr}; + + RocketSim::BoostPadConfig config{}; + + PyObject *pos = nullptr; // borrowed references + PyObject *isBig = nullptr; + + if (!PyArg_ParseTupleAndKeywords (args_, kwds_, "|O!O", dict, Vec::Type, &pos, &isBig)) + return -1; + + if (pos) + config.pos = Vec::ToVec (PyCast (pos)); + if (isBig) + config.isBig = PyObject_IsTrue (isBig); + + if (!InitFromBoostPadConfig (self_, config)) + return -1; + + return 0; +} + +void BoostPadConfig::Dealloc (BoostPadConfig *self_) noexcept +{ + Py_XDECREF (self_->pos); + + self_->config.~BoostPadConfig (); + + auto const tp_free = (freefunc)PyType_GetSlot (Type, Py_tp_free); + tp_free (self_); +} + +PyObject *BoostPadConfig::Pickle (BoostPadConfig *self_) noexcept +{ + auto dict = PyObjectRef::steal (PyDict_New ()); + if (!dict) + return nullptr; + + RocketSim::BoostPadConfig const model{}; + auto const config = ToBoostPadConfig (self_); + + if (Vec::ToVec (self_->pos) != model.pos && !DictSetValue (dict.borrow (), "pos", PyNewRef (self_->pos))) + return nullptr; + + if (config.isBig != model.isBig && !DictSetValue (dict.borrow (), "is_big", PyBool_FromLong (config.isBig))) + return nullptr; + + return dict.gift (); +} + +PyObject *BoostPadConfig::Unpickle (BoostPadConfig *self_, PyObject *dict_) noexcept +{ + if (!PyDict_Check (dict_)) + { + PyErr_SetString (PyExc_ValueError, "Pickled object is not a dict"); + return nullptr; + } + + auto const args = PyObjectRef::steal (PyTuple_New (0)); + if (!args) + return nullptr; + + if (Init (self_, args.borrow (), dict_) != 0) + return nullptr; + + Py_RETURN_NONE; +} + +PyObject *BoostPadConfig::Copy (BoostPadConfig *self_) noexcept +{ + auto config = PyRef::stealObject (New (Type, nullptr, nullptr)); + if (!config) + return nullptr; + + PyRef::assign (config->pos, reinterpret_cast (self_->pos)); + + config->config = ToBoostPadConfig (self_); + + return config.giftObject (); +} + +PyObject *BoostPadConfig::DeepCopy (BoostPadConfig *self_, PyObject *memo_) noexcept +{ + auto config = PyRef::stealObject (New (Type, nullptr, nullptr)); + if (!config) + return nullptr; + + PyRef::assign (config->pos, PyDeepCopy (self_->pos, memo_)); + if (!config->pos) + return nullptr; + + config->config = ToBoostPadConfig (self_); + + return config.giftObject (); +} + +PyObject *BoostPadConfig::Getpos (BoostPadConfig *self_, void *) noexcept +{ + return PyRef::incRef (self_->pos).giftObject (); +} + +int BoostPadConfig::Setpos (BoostPadConfig *self_, PyObject *value_, void *) noexcept +{ + if (!value_) + { + PyErr_SetString (PyExc_AttributeError, "can't delete 'pos' attribute of 'RocketSim.BoostPadConfig' objects"); + return -1; + } + + if (!Py_IS_TYPE (value_, Vec::Type)) + { + PyErr_SetString (PyExc_TypeError, "attribute value type must be RocketSim.Vec"); + return -1; + } + + if (value_ == (PyObject *)self_->pos) + return 0; + + PyRef::assign (self_->pos, value_); + + return 0; +} +} diff --git a/python-mtheall/Makefile b/python-mtheall/Makefile index 1dc88b5..44e4f10 100644 --- a/python-mtheall/Makefile +++ b/python-mtheall/Makefile @@ -5,13 +5,13 @@ PYTHON ?= python3 all: build build: - $(PYTHON) ../setup.py build --debug -j 4 + $(PYTHON) ../setup.py build -j $(shell nproc) wheel: build $(PYTHON) ../setup.py bdist_wheel --py-limited-api=cp34 install: wheel - $(PYTHON) -m pip install --force-reinstall --user ./dist/RocketSim-2.1.1-cp34-abi3-linux_x86_64.whl + $(PYTHON) -m pip install --force-reinstall --user ./dist/RocketSim-2.1.1.post1-cp34-abi3-linux_x86_64.whl clean: $(RM) -r build/ dist/ RocketSim.egg-info/ diff --git a/python-mtheall/Module.cpp b/python-mtheall/Module.cpp index 9eb5f55..b34a56a 100644 --- a/python-mtheall/Module.cpp +++ b/python-mtheall/Module.cpp @@ -182,6 +182,7 @@ extern "C" Py_EXPORTED_SYMBOL PyObject *PyInit_RocketSim () noexcept MAKE_TYPE (BallPredictor); MAKE_TYPE (BallState); MAKE_TYPE (BoostPad); + MAKE_TYPE (BoostPadConfig); MAKE_TYPE (BoostPadState); MAKE_TYPE (Car); MAKE_TYPE (CarConfig); diff --git a/python-mtheall/Module.h b/python-mtheall/Module.h index 9ef0c90..2d21e04 100644 --- a/python-mtheall/Module.h +++ b/python-mtheall/Module.h @@ -405,6 +405,35 @@ struct Ball static PyObject *SetState (Ball *self_, PyObject *args_, PyObject *kwds_) noexcept; }; +struct BoostPadConfig +{ + PyObject_HEAD; + + RocketSim::BoostPadConfig config; + Vec *pos; + + static PyTypeObject *Type; + static PyMemberDef Members[]; + static PyMethodDef Methods[]; + static PyGetSetDef GetSet[]; + static PyType_Slot Slots[]; + static PyType_Spec Spec; + + static PyRef NewFromBoostPadConfig (RocketSim::BoostPadConfig const &config_ = {}) noexcept; + static bool InitFromBoostPadConfig (BoostPadConfig *self_, RocketSim::BoostPadConfig const &config_ = {}) noexcept; + static RocketSim::BoostPadConfig ToBoostPadConfig (BoostPadConfig *self_) noexcept; + + static PyObject *New (PyTypeObject *subtype_, PyObject *args_, PyObject *kwds_) noexcept; + static int Init (BoostPadConfig *self_, PyObject *args_, PyObject *kwds_) noexcept; + static void Dealloc (BoostPadConfig *self_) noexcept; + static PyObject *Pickle (BoostPadConfig *self_) noexcept; + static PyObject *Unpickle (BoostPadConfig *self_, PyObject *dict_) noexcept; + static PyObject *Copy (BoostPadConfig *self_) noexcept; + static PyObject *DeepCopy (BoostPadConfig *self_, PyObject *memo_) noexcept; + + GETSET_DECLARE (BoostPadConfig, pos); +}; + struct BoostPadState { PyObject_HEAD; @@ -720,8 +749,7 @@ struct ArenaConfig Vec *minPos; Vec *maxPos; - PyObject *customBigBoostPads; - PyObject *customSmallBoostPads; + PyObject *customBoostPads; static PyTypeObject *Type; static PyMemberDef Members[]; diff --git a/python-mtheall/unit_test.py b/python-mtheall/unit_test.py index 57cd169..7e42884 100755 --- a/python-mtheall/unit_test.py +++ b/python-mtheall/unit_test.py @@ -432,6 +432,41 @@ def test_create(self): with self.assertRaises(TypeError): rs.Ball() +class TestBoostPadConfig(FuzzyTestCase): + def compare(self, config_a, config_b): + self.assertEqual(config_a.pos, config_b.pos) + self.assertEqual(config_a.is_big, config_b.is_big) + + @staticmethod + def random(): + return rs.BoostPadConfig(pos=random_vec(-4000, 4000), is_big=random_bool()) + + def test_create(self): + config = rs.BoostPadConfig() + self.assertEqual(config.pos, rs.Vec(0, 0, 0)) + self.assertEqual(config.is_big, False) + + def test_pickle(self): + for i in range(10): + config_a = TestBoostPadConfig.random() + config_b = pickled(config_a) + self.compare(config_a, config_b) + self.assertIsNot(config_a.pos, config_b.pos) + + def test_copy(self): + for i in range(10): + config_a = TestBoostPadConfig.random() + config_b = copy.copy(config_a) + self.compare(config_a, config_b) + self.assertIs(config_a.pos, config_b.pos) + + def test_deep_copy(self): + for i in range(10): + config_a = TestBoostPadConfig.random() + config_b = copy.deepcopy(config_a) + self.compare(config_a, config_b) + self.assertIsNot(config_a.pos, config_b.pos) + class TestBoostPadState(FuzzyTestCase): def compare(self, state_a, state_b): self.assertEqual(state_a.is_active, state_b.is_active) @@ -1105,30 +1140,22 @@ def test_deep_copy(self): class TestArenaConfig(FuzzyTestCase): def compare(self, config_a, config_b): - self.assertEqual(config_a.memory_weight_mode, config_b.memory_weight_mode) - self.assertEqual(config_a.min_pos, config_a.min_pos) - self.assertEqual(config_a.max_pos, config_a.max_pos) - self.assertEqual(config_a.max_aabb_len, config_a.max_aabb_len) - self.assertEqual(config_a.no_ball_rot, config_a.no_ball_rot) - self.assertEqual(config_a.use_custom_broadphase, config_a.use_custom_broadphase) - self.assertEqual(config_a.max_objects, config_a.max_objects) - - if config_a.custom_big_boost_pads is None and config_a.custom_small_boost_pads is None: - self.assertIsNone(config_b.custom_big_boost_pads) - self.assertIsNone(config_b.custom_small_boost_pads) - else: - if not config_a.custom_big_boost_pads is None: - self.assertEqual(len(config_a.custom_big_boost_pads), len(config_b.custom_big_boost_pads)) - for a, b in zip(config_a.custom_big_boost_pads, config_b.custom_big_boost_pads): - self.assertEqual(a, b) - - if not config_a.custom_small_boost_pads is None: - self.assertEqual(len(config_a.custom_small_boost_pads), len(config_b.custom_small_boost_pads)) - for a, b in zip(config_a.custom_small_boost_pads, config_b.custom_small_boost_pads): - self.assertEqual(a, b) + self.assertEqual(config_a.memory_weight_mode, config_b.memory_weight_mode) + self.assertEqual(config_a.min_pos, config_a.min_pos) + self.assertEqual(config_a.max_pos, config_a.max_pos) + self.assertEqual(config_a.max_aabb_len, config_a.max_aabb_len) + self.assertEqual(config_a.no_ball_rot, config_a.no_ball_rot) + self.assertEqual(config_a.use_custom_broadphase, config_a.use_custom_broadphase) + self.assertEqual(config_a.max_objects, config_a.max_objects) + self.assertEqual(config_a.custom_boost_pads is None, config_b.custom_boost_pads is None) + + if not config_a.custom_boost_pads is None: + self.assertEqual(len(config_a.custom_boost_pads), len(config_b.custom_boost_pads)) + for a, b in zip(config_a.custom_boost_pads, config_b.custom_boost_pads): + self.assertEqual(a, b) @staticmethod - def random(): + def random(custom_boost_pads: bool = False): a = random_vec(-5000.0, -4000.0) b = rs.Vec(abs(a.x), abs(a.y), abs(a.z)) return rs.ArenaConfig( @@ -1141,6 +1168,9 @@ def random(): max_objects = random_int() ) + if custom_boost_pads: + config.custom_boost_pads = [TestBoostPadConfig.random() for i in range(random.randint(0, 10))] + def test_basic(self): config = rs.ArenaConfig() @@ -1151,34 +1181,28 @@ def test_basic(self): def test_custom_boost_pads(self): for i in range(10): - config = TestArenaConfig.random() - - if random_bool(): - config.custom_big_boost_pads = [random_vec(-4000, 4000) for i in range(random.randint(0, 3))] - - if random_bool(): - config.custom_small_boost_pads = [random_vec(-4000, 4000) for i in range(random.randint(0, 3))] + config = TestArenaConfig.random(random_bool()) arena = rs.Arena(config=config) # make sure this doesn't throw any errors arena.get_gym_state() - if config.custom_big_boost_pads is None and config.custom_small_boost_pads is None: + if config.custom_boost_pads is None: continue - big = config.custom_big_boost_pads or [] - small = config.custom_small_boost_pads or [] - pads = arena.get_boost_pads() - self.assertEqual(len(pads), len(big) + len(small)) + self.assertEqual(len(pads), len(config.custom_boost_pads)) - for a, b in zip(pads, big + small): - self.assertEqual(a.get_pos(), b) + for a, b in zip(pads, config.custom_boost_pads): + self.assertEqual(a.get_pos(), b.pos) + self.assertEqual(a.is_big, b.is_big) config = TestArenaConfig.random() - config.custom_big_boost_pads = [rs.Vec(-2000.0, -2000.0, 73.0)] - config.custom_small_boost_pads = [rs.Vec( 2000.0, 2000.0, 70.0)] + config.custom_boost_pads = [ + rs.BoostPadConfig(pos=rs.Vec(-2000.0, -2000.0, 73.0), is_big=True), + rs.BoostPadConfig(pos=rs.Vec( 2000.0, 2000.0, 73.0), is_big=False) + ] arena = rs.Arena(config=config) car = arena.add_car(rs.Team.BLUE) @@ -1186,12 +1210,12 @@ def test_custom_boost_pads(self): # drive toward the big boost but deplete while car.get_state().boost > 0: - target_chase(config.custom_small_boost_pads[0], car) + target_chase(config.custom_boost_pads[0].pos, car) arena.step() # grab the big boost for i in range(1000): - target_chase(config.custom_big_boost_pads[0], car) + target_chase(config.custom_boost_pads[0].pos, car) arena.step() if car.get_state().boost > 0: break @@ -1200,12 +1224,12 @@ def test_custom_boost_pads(self): # drive toward the small boost but deplete while car.get_state().boost > 0: - target_chase(config.custom_small_boost_pads[0], car) + target_chase(config.custom_boost_pads[1].pos, car) arena.step() # grab the small boost for i in range(1000): - target_chase(config.custom_small_boost_pads[0], car) + target_chase(config.custom_boost_pads[1].pos, car) arena.step() if car.get_state().boost > 0: break @@ -1214,72 +1238,36 @@ def test_custom_boost_pads(self): def test_pickle(self): for i in range(10): - config_a = TestArenaConfig.random() + config_a = TestArenaConfig.random(random_bool()) config_b = pickled(config_a) self.assertIsNot(config_a.min_pos, config_b.min_pos) self.assertIsNot(config_a.max_pos, config_b.max_pos) self.compare(config_a, config_b) - if random_bool(): - config_a.custom_big_boost_pads = [random_vec() for i in range(random_int())] - - if random_bool(): - config_a.custom_small_boost_pads = [random_vec() for i in range(random_int())] - - config_b = pickled(config_a) - self.assertIsNot(config_a.min_pos, config_b.min_pos) - self.assertIsNot(config_a.max_pos, config_b.max_pos) - if not config_a.custom_big_boost_pads is None: - self.assertIsNot(config_a.custom_big_boost_pads, config_b.custom_big_boost_pads) - if not config_a.custom_small_boost_pads is None: - self.assertIsNot(config_a.custom_small_boost_pads, config_b.custom_small_boost_pads) - self.compare(config_a, config_b) + if not config_a.custom_boost_pads is None: + self.assertIsNot(config_a.custom_boost_pads, config_b.custom_boost_pads) def test_copy(self): for i in range(10): - config_a = TestArenaConfig.random() + config_a = TestArenaConfig.random(random_bool()) config_b = copy.copy(config_a) self.assertIs(config_a.min_pos, config_b.min_pos) self.assertIs(config_a.max_pos, config_b.max_pos) self.compare(config_a, config_b) - if random_bool(): - config_a.custom_big_boost_pads = [random_vec() for i in range(random_int())] - - if random_bool(): - config_a.custom_small_boost_pads = [random_vec() for i in range(random_int())] - - config_b = copy.copy(config_a) - self.assertIs(config_a.min_pos, config_b.min_pos) - self.assertIs(config_a.max_pos, config_b.max_pos) - if not config_a.custom_big_boost_pads is None: - self.assertIs(config_a.custom_big_boost_pads, config_b.custom_big_boost_pads) - if not config_a.custom_small_boost_pads is None: - self.assertIs(config_a.custom_small_boost_pads, config_b.custom_small_boost_pads) - self.compare(config_a, config_b) + if not config_a.custom_boost_pads is None: + self.assertIs(config_a.custom_boost_pads, config_b.custom_boost_pads) def test_deep_copy(self): for i in range(10): - config_a = TestArenaConfig.random() + config_a = TestArenaConfig.random(random_bool()) config_b = copy.deepcopy(config_a) self.assertIsNot(config_a.min_pos, config_b.min_pos) self.assertIsNot(config_a.max_pos, config_b.max_pos) self.compare(config_a, config_b) - if random_bool(): - config_a.custom_big_boost_pads = [random_vec() for i in range(random_int())] - - if random_bool(): - config_a.custom_small_boost_pads = [random_vec() for i in range(random_int())] - - config_b = copy.deepcopy(config_a) - self.assertIsNot(config_a.min_pos, config_b.min_pos) - self.assertIsNot(config_a.max_pos, config_b.max_pos) - if not config_a.custom_big_boost_pads is None: - self.assertIsNot(config_a.custom_big_boost_pads, config_b.custom_big_boost_pads) - if not config_a.custom_small_boost_pads is None: - self.assertIsNot(config_a.custom_small_boost_pads, config_b.custom_small_boost_pads) - self.compare(config_a, config_b) + if not config_a.custom_boost_pads is None: + self.assertIsNot(config_a.custom_boost_pads, config_b.custom_boost_pads) class TestArena(FuzzyTestCase): def compare(self, arena_a, arena_b): diff --git a/setup.py b/setup.py index 7f8e051..c2f9ee3 100755 --- a/setup.py +++ b/setup.py @@ -88,7 +88,7 @@ def build_extension(self, ext): setup( name = "RocketSim", - version = "2.1.1", + version = "2.1.1.post1", description = "This is Rocket League!", cmdclass = {"build_ext": build_ext_ex}, ext_modules = [RocketSim], diff --git a/src/Sim/Arena/Arena.cpp b/src/Sim/Arena/Arena.cpp index 67970fb..11c5aa6 100644 --- a/src/Sim/Arena/Arena.cpp +++ b/src/Sim/Arena/Arena.cpp @@ -11,10 +11,8 @@ #include "../../../libsrc/bullet3-3.24/BulletCollision/CollisionShapes/btBoxShape.h" #include "../../../libsrc/bullet3-3.24/BulletCollision/CollisionShapes/btSphereShape.h" -#include -#include - RS_NS_START + RSAPI void Arena::SetMutatorConfig(const MutatorConfig& mutatorConfig) { bool @@ -546,31 +544,37 @@ Arena::Arena(GameMode gameMode, const ArenaConfig& config, float tickRate) : _mu if (loadArenaStuff) { // Initialize boost pads using namespace RLConst::BoostPads; - bool isHoops = gameMode == GameMode::HOOPS; + if (_config.useCustomBoostPads) { + for (auto& padConfig : _config.customBoostPads) { + BoostPad* pad = BoostPad::_AllocBoostPad(); + pad->_Setup(padConfig); - auto toSpan = [](auto &val) { return std::span (&*std::begin (val), &*std::end (val)); }; + _boostPads.push_back(pad); + } + } else { + bool isHoops = gameMode == GameMode::HOOPS; - std::span big = isHoops ? toSpan (LOCS_BIG_HOOPS) : toSpan (LOCS_BIG_SOCCAR); - std::span small = isHoops ? toSpan (LOCS_SMALL_HOOPS) : toSpan (LOCS_SMALL_SOCCAR); + int amountSmall = isHoops ? LOCS_AMOUNT_SMALL_HOOPS : LOCS_AMOUNT_SMALL_SOCCAR; + _boostPads.reserve(LOCS_AMOUNT_BIG + amountSmall); - if (config.customBoostPads) - { - big = toSpan (config.customBigBoostPads); - small = toSpan (config.customSmallBoostPads); - } + for (int i = 0; i < (LOCS_AMOUNT_BIG + amountSmall); i++) { - _boostPads.reserve(big.size() + small.size()); + BoostPadConfig padConfig; + + padConfig.isBig = i < LOCS_AMOUNT_BIG; + + btVector3 pos; + if (isHoops) { + padConfig.pos = padConfig.isBig ? LOCS_BIG_HOOPS[i] : LOCS_SMALL_HOOPS[i - LOCS_AMOUNT_BIG]; + } else { + padConfig.pos = padConfig.isBig ? LOCS_BIG_SOCCAR[i] : LOCS_SMALL_SOCCAR[i - LOCS_AMOUNT_BIG]; + } - for (auto const &pads : {big, small}) - { - for (auto const &pos : pads) - { BoostPad* pad = BoostPad::_AllocBoostPad(); - pad->_Setup(pads.data () == big.data (), pos); + pad->_Setup(padConfig); _boostPads.push_back(pad); - if (!config.customBoostPads) - _boostPadGrid.Add(pad); + _boostPadGrid.Add(pad); } } } @@ -807,13 +811,14 @@ void Arena::Step(int ticksToSimulate) { car->_PostTickUpdate(gameMode, tickTime, _mutatorConfig); car->_FinishPhysicsTick(_mutatorConfig); if (hasArenaStuff) { - if (_config.customBoostPads) - { - for (auto &pad : _boostPads) - pad->_CheckCollide(car); - } - else + if (_config.useCustomBoostPads) { + // TODO: This is quite slow, we should use a sorting method of some sort + for (auto& boostPad : _boostPads) { + boostPad->_CheckCollide(car); + } + } else { _boostPadGrid.CheckCollision(car); + } } } diff --git a/src/Sim/Arena/ArenaConfig/ArenaConfig.cpp b/src/Sim/Arena/ArenaConfig/ArenaConfig.cpp index 688e48d..0456fdc 100644 --- a/src/Sim/Arena/ArenaConfig/ArenaConfig.cpp +++ b/src/Sim/Arena/ArenaConfig/ArenaConfig.cpp @@ -4,10 +4,34 @@ RS_NS_START void ArenaConfig::Serialize(DataStreamOut& out) const { out.WriteMultiple(ARENA_CONFIG_SERIALIZATION_FIELDS); + + out.Write(useCustomBoostPads); + if (useCustomBoostPads) { + out.Write(customBoostPads.size()); + for (auto& boostPad : customBoostPads) + boostPad.Serialize(out); + } } void ArenaConfig::Deserialize(DataStreamIn& in) { in.ReadMultiple(ARENA_CONFIG_SERIALIZATION_FIELDS); + + useCustomBoostPads = in.Read(); + if (useCustomBoostPads) { + uint32_t numPads = in.Read(); + + // NOTE: Not reserving/resizing customBoostPads from numPads just in case its a horrible corrupted value + + for (uint32_t i = 0; i < numPads; i++) { + BoostPadConfig config; + config.Deserialize(in); + + if (in.IsOverflown()) + RS_ERR_CLOSE("Overflow after reading custom boost (" << (i + 1) << " / " << numPads << ")"); + + customBoostPads.push_back(config); + } + } } RS_NS_END \ No newline at end of file diff --git a/src/Sim/Arena/ArenaConfig/ArenaConfig.h b/src/Sim/Arena/ArenaConfig/ArenaConfig.h index 83558b1..64eede2 100644 --- a/src/Sim/Arena/ArenaConfig/ArenaConfig.h +++ b/src/Sim/Arena/ArenaConfig/ArenaConfig.h @@ -3,7 +3,7 @@ #include "../../../DataStream/DataStreamOut.h" #include "../../../DataStream/DataStreamIn.h" -#include +#include "../../BoostPad/BoostPad.h" RS_NS_START @@ -15,6 +15,12 @@ enum class ArenaMemWeightMode : byte { // Measurements last updated 2024/5/9 }; +// Custom boost pad +struct CustomBoostPad { + Vec pos = Vec(0, 0, 0); + bool isBig = false; +}; + struct ArenaConfig { ArenaMemWeightMode memWeightMode = ArenaMemWeightMode::HEAVY; @@ -40,9 +46,10 @@ struct ArenaConfig { // Maximum number of objects int maxObjects = 512; - bool customBoostPads = false; - std::vector customBigBoostPads; - std::vector customSmallBoostPads; + // Use a custom list of boost pads (customBoostPads) instead of the normal one + // NOTE: This will disable the boost pad grid and will thus worsen performance + bool useCustomBoostPads = false; + std::vector customBoostPads = {}; // Custom boost pads to use, if useCustomBoostPads void Serialize(DataStreamOut& out) const; void Deserialize(DataStreamIn& in); diff --git a/src/Sim/BoostPad/BoostPad.cpp b/src/Sim/BoostPad/BoostPad.cpp index 0cb272c..32bdd2d 100644 --- a/src/Sim/BoostPad/BoostPad.cpp +++ b/src/Sim/BoostPad/BoostPad.cpp @@ -6,20 +6,43 @@ RS_NS_START +void BoostPadConfig::Serialize(DataStreamOut& out) const { + out.WriteMultiple( + BOOSTPADCONFIG_SERIALIZATION_FIELDS + ); +} + +void BoostPadConfig::Deserialize(DataStreamIn& in) { + in.ReadMultiple( + BOOSTPADCONFIG_SERIALIZATION_FIELDS + ); +} + +void BoostPadState::Serialize(DataStreamOut& out) const { + out.WriteMultiple( + BOOSTPAD_SERIALIZATION_FIELDS + ); +} + +void BoostPadState::Deserialize(DataStreamIn& in) { + in.ReadMultiple( + BOOSTPAD_SERIALIZATION_FIELDS + ); +} + BoostPad* BoostPad::_AllocBoostPad() { return new BoostPad(); } -void BoostPad::_Setup(bool isBig, Vec pos) { - this->isBig = isBig; - this->pos = pos; +void BoostPad::_Setup(const BoostPadConfig& config) { + this->config = config; - this->_posBT = pos * UU_TO_BT; + this->_posBT = config.pos * UU_TO_BT; { using namespace RLConst::BoostPads; - float boxRad = (isBig ? BOX_RAD_BIG : BOX_RAD_SMALL) * UU_TO_BT; + float boxRad = (config.isBig ? BOX_RAD_BIG : BOX_RAD_SMALL) * UU_TO_BT; this->_boxMinBT = this->_posBT - Vec(boxRad, boxRad, 0); this->_boxMaxBT = this->_posBT + Vec(boxRad, boxRad, BOX_HEIGHT * UU_TO_BT); } @@ -53,7 +76,7 @@ void BoostPad::_CheckCollide(Car* car) { } else { // Check with cylinder-origin collision - float rad = (isBig ? CYL_RAD_BIG : CYL_RAD_SMALL) * UU_TO_BT; + float rad = (config.isBig ? CYL_RAD_BIG : CYL_RAD_SMALL) * UU_TO_BT; if (carPosBT.DistSq2D(this->_posBT) < (rad * rad)) colliding = abs(carPosBT.z - this->_posBT.z) < (CYL_HEIGHT * UU_TO_BT); } @@ -71,11 +94,11 @@ bool BoostPad::_PostTickUpdate(float tickTime, const MutatorConfig& mutatorConfi lockedCarID = _internalState.curLockedCar->id; if (_internalState.isActive) { - float boostToAdd = isBig ? BOOST_AMOUNT_BIG : BOOST_AMOUNT_SMALL; + float boostToAdd = config.isBig ? BOOST_AMOUNT_BIG : BOOST_AMOUNT_SMALL; _internalState.curLockedCar->_internalState.boost = RS_MIN(_internalState.curLockedCar->_internalState.boost + boostToAdd, 100); _internalState.isActive = false; - _internalState.cooldown = isBig ? mutatorConfig.boostPadCooldown_Big : mutatorConfig.boostPadCooldown_Small; + _internalState.cooldown = config.isBig ? mutatorConfig.boostPadCooldown_Big : mutatorConfig.boostPadCooldown_Small; pickedUp = true; } @@ -86,16 +109,4 @@ bool BoostPad::_PostTickUpdate(float tickTime, const MutatorConfig& mutatorConfi return pickedUp; } -void BoostPadState::Serialize(DataStreamOut& out) const { - out.WriteMultiple( - BOOSTPAD_SERIALIZATION_FIELDS - ); -} - -void BoostPadState::Deserialize(DataStreamIn& in) { - in.ReadMultiple( - BOOSTPAD_SERIALIZATION_FIELDS - ); -} - -RS_NS_END \ No newline at end of file +RS_NS_END diff --git a/src/Sim/BoostPad/BoostPad.h b/src/Sim/BoostPad/BoostPad.h index 652bfa6..fa73f4b 100644 --- a/src/Sim/BoostPad/BoostPad.h +++ b/src/Sim/BoostPad/BoostPad.h @@ -10,6 +10,16 @@ RS_NS_START +struct BoostPadConfig { + Vec pos; + bool isBig = false; + + void Serialize(DataStreamOut& out) const; + void Deserialize(DataStreamIn& in); +}; +#define BOOSTPADCONFIG_SERIALIZATION_FIELDS \ +pos, isBig + struct BoostPadState { bool isActive = true; float cooldown = 0; @@ -25,8 +35,7 @@ isActive, cooldown, prevLockedCarID class BoostPad { public: - bool isBig; - Vec pos; + BoostPadConfig config; Vec _posBT; Vec _boxMinBT, _boxMaxBT; @@ -38,7 +47,7 @@ class BoostPad { // For construction by Arena static BoostPad* _AllocBoostPad(); - void _Setup(bool isBig, Vec pos); + void _Setup(const BoostPadConfig& config); void _CheckCollide(Car* car); diff --git a/src/Sim/BoostPad/BoostPadGrid/BoostPadGrid.cpp b/src/Sim/BoostPad/BoostPadGrid/BoostPadGrid.cpp index dc7ffeb..986ccf4 100644 --- a/src/Sim/BoostPad/BoostPadGrid/BoostPadGrid.cpp +++ b/src/Sim/BoostPad/BoostPadGrid/BoostPadGrid.cpp @@ -25,14 +25,14 @@ void BoostPadGrid::CheckCollision(Car* car) { } void BoostPadGrid::Add(BoostPad* pad) { - int indexX = pad->pos.x / CELL_SIZE_X + (CELLS_X / 2); - int indexY = pad->pos.y / CELL_SIZE_Y + (CELLS_Y / 2); + int indexX = pad->config.pos.x / CELL_SIZE_X + (CELLS_X / 2); + int indexY = pad->config.pos.y / CELL_SIZE_Y + (CELLS_Y / 2); BoostPad*& ptrInArray = pads[indexX][indexY]; if (ptrInArray != NULL) { RS_ERR_CLOSE( "BoostPadGrid::Add(): Failed to add a boost pad where there already was one " << - "(old: " << ptrInArray->pos << ", new: " << pad->pos << ") -> " << + "(old: " << ptrInArray->config.pos << ", new: " << pad->config.pos << ") -> " << "[" << indexX << ", " << indexY << "]" ); } else {