From c52a807ede641ff4b5d6e3d09826c229b59c362e Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sun, 19 Jan 2025 14:35:56 +1300 Subject: [PATCH 1/4] make hook return values use standard property access logic --- libs/unrealsdk | 2 +- src/pyunrealsdk/hook.cpp | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/libs/unrealsdk b/libs/unrealsdk index a316c28..fbf29de 160000 --- a/libs/unrealsdk +++ b/libs/unrealsdk @@ -1 +1 @@ -Subproject commit a316c2867fe15000e786d2a3446559095e4a80c7 +Subproject commit fbf29dedc5b9dabbb521f90eeedd2f1a3fb7d349 diff --git a/src/pyunrealsdk/hook.cpp b/src/pyunrealsdk/hook.cpp index 1b5ac98..763d903 100644 --- a/src/pyunrealsdk/hook.cpp +++ b/src/pyunrealsdk/hook.cpp @@ -3,6 +3,7 @@ #include "pyunrealsdk/exports.h" #include "pyunrealsdk/hooks.h" #include "pyunrealsdk/logging.h" +#include "pyunrealsdk/unreal_bindings/property_access.h" #include "unrealsdk/hook_manager.h" #include "unrealsdk/unreal/cast.h" #include "unrealsdk/unreal/prop_traits.h" @@ -46,8 +47,9 @@ namespace { bool handle_py_hook(Details& hook, const py::object& callback) { py::object ret_arg; if (hook.ret.has_value()) { - cast(hook.ret.prop, [&hook, &ret_arg](const T* /*prop*/) { - ret_arg = py::cast(hook.ret.get()); + cast(hook.ret.prop, [&hook, &ret_arg](T* prop) { + ret_arg = pyunrealsdk::unreal::py_getattr( + prop, reinterpret_cast(hook.ret.ptr.get()), hook.ret.ptr); }); } else { ret_arg = py::type::of(); @@ -76,9 +78,15 @@ bool handle_py_hook(Details& hook, const py::object& callback) { if (py::type::of().is(ret_override) || py::isinstance(ret_override)) { hook.ret.destroy(); } else if (!py::ellipsis{}.equal(ret_override)) { - cast(hook.ret.prop, [&hook, &ret_override](const T* /*prop*/) { - auto value = py::cast::Value>(ret_override); - hook.ret.set(value); + cast(hook.ret.prop, [&hook, &ret_override](T* prop) { + // Need to replicate PropertyProxy::set ourselves a bit, since we want to use + // our custom python setter + if (hook.ret.ptr.get() == nullptr) { + hook.ret.ptr = UnrealPointer(hook.ret.prop); + } + + pyunrealsdk::unreal::py_setattr_direct( + prop, reinterpret_cast(hook.ret.ptr.get()), ret_override); }); } } From 16ac171118b2fd092b5c01ef0747eec3d243286d Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sun, 19 Jan 2025 14:36:03 +1300 Subject: [PATCH 2/4] make arrays use standard property access logic --- .../unreal_bindings/wrapped_array.h | 10 --- .../unreal_bindings/wrapped_array_helpers.cpp | 36 +++++++--- .../unreal_bindings/wrapped_array_methods.cpp | 71 +++++++++---------- 3 files changed, 58 insertions(+), 59 deletions(-) diff --git a/src/pyunrealsdk/unreal_bindings/wrapped_array.h b/src/pyunrealsdk/unreal_bindings/wrapped_array.h index a5dea51..5b9cb44 100644 --- a/src/pyunrealsdk/unreal_bindings/wrapped_array.h +++ b/src/pyunrealsdk/unreal_bindings/wrapped_array.h @@ -52,16 +52,6 @@ py::object array_get(const WrappedArray& arr, size_t idx); */ void array_set(WrappedArray& arr, size_t idx, const py::object& value); -/** - * @brief Ensures a value is compatible with the given array. - * @note Intended to be used when there's extra, non-reversible, work to do before it's possible to - * call array_set. - * - * @param arr The array to check against. - * @param value The value to check. - */ -void array_validate_value(const WrappedArray& arr, const py::object& value); - /** * @brief Deletes a range of entries of arbitrary type in an array. * diff --git a/src/pyunrealsdk/unreal_bindings/wrapped_array_helpers.cpp b/src/pyunrealsdk/unreal_bindings/wrapped_array_helpers.cpp index 575173f..8b6b8e7 100644 --- a/src/pyunrealsdk/unreal_bindings/wrapped_array_helpers.cpp +++ b/src/pyunrealsdk/unreal_bindings/wrapped_array_helpers.cpp @@ -1,6 +1,8 @@ #include "pyunrealsdk/pch.h" +#include "pyunrealsdk/unreal_bindings/property_access.h" #include "pyunrealsdk/unreal_bindings/wrapped_array.h" #include "unrealsdk/unreal/cast.h" +#include "unrealsdk/unreal/classes/uproperty.h" #include "unrealsdk/unreal/wrappers/wrapped_array.h" #ifdef PYUNREALSDK_INTERNAL @@ -21,21 +23,35 @@ size_t convert_py_idx(const WrappedArray& arr, py::ssize_t idx) { } py::object array_get(const WrappedArray& arr, size_t idx) { - py::object ret{}; - cast(arr.type, [&](const T* /*prop*/) { ret = py::cast(arr.get_at(idx)); }); + if (arr.type->Offset_Internal != 0) { + throw std::runtime_error( + "array inner property has non-zero offset, unsure how to handle, aborting!"); + } + if (arr.type->ArrayDim != 1) { + throw std::runtime_error( + "array inner property is fixed array, unsure how to handle, aborting!"); + } - return ret; + // Const cast is slightly naughty, but we know the internals aren't going to modify properties + return py_getattr( + const_cast(arr.type), // NOLINT(cppcoreguidelines-pro-type-const-cast) + reinterpret_cast(arr.base.get()->data) + (arr.type->ElementSize * idx), + arr.base); } void array_set(WrappedArray& arr, size_t idx, const py::object& value) { - cast(arr.type, [&](const T* /*prop*/) { - arr.set_at(idx, py::cast::Value>(value)); - }); -} + if (arr.type->Offset_Internal != 0) { + throw std::runtime_error( + "array inner property has non-zero offset, unsure how to handle, aborting!"); + } + if (arr.type->ArrayDim != 1) { + throw std::runtime_error( + "array inner property is fixed array, unsure how to handle, aborting!"); + } -void array_validate_value(const WrappedArray& arr, const py::object& value) { - cast(arr.type, - [&](const T* /*prop*/) { py::cast::Value>(value); }); + py_setattr_direct( + const_cast(arr.type), // NOLINT(cppcoreguidelines-pro-type-const-cast) + reinterpret_cast(arr.base.get()->data) + (arr.type->ElementSize * idx), value); } void array_delete_range(WrappedArray& arr, size_t start, size_t stop) { diff --git a/src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp b/src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp index c051dea..d8592f5 100644 --- a/src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp +++ b/src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp @@ -11,11 +11,14 @@ using namespace unrealsdk::unreal; namespace pyunrealsdk::unreal::impl { void array_py_append(WrappedArray& self, const py::object& value) { - array_validate_value(self, value); - auto size = self.size(); self.resize(size + 1); - array_set(self, size, value); + try { + array_set(self, size, value); + } catch (...) { + self.resize(size); + throw; + } } void array_py_clear(WrappedArray& self) { @@ -61,8 +64,6 @@ size_t array_py_index(const WrappedArray& self, const py::object& value, py::ssize_t start, py::ssize_t stop) { - array_validate_value(self, value); - // `list.index` method handles indexes a little differently to most methods. Essentially, any // index is allowed, and it's just implicitly clamped to the size of the array. You're allowed // to do some stupid things like `["a"].index("a", -100, -200)`, it just gives a not in list @@ -98,45 +99,41 @@ size_t array_py_index(const WrappedArray& self, } void array_py_insert(WrappedArray& self, py::ssize_t py_idx, const py::object& value) { - array_validate_value(self, value); - auto size = self.size(); - // Allow specifying one past the end, to insert at the end - // insert(-1) should insert before the last element, so goes through the normal conversion - auto idx = static_cast(py_idx) == size ? py_idx : convert_py_idx(self, py_idx); + if (static_cast(py_idx) == size) { + // We're just appending + array_py_append(self, value); + return; + } + + auto idx = convert_py_idx(self, py_idx); self.resize(size + 1); - // Don't move if appending - if (idx != size) { - auto data = reinterpret_cast(self.base->data); - auto element_size = self.type->ElementSize; + auto data = reinterpret_cast(self.base->data); + auto element_size = self.type->ElementSize; + + auto src = data + (idx * element_size); + auto remaining_size = (size - idx) * element_size; + memmove(reinterpret_cast(src + element_size), reinterpret_cast(src), + remaining_size); - auto src = data + (idx * element_size); - auto remaining_size = (size - idx) * element_size; - memmove(reinterpret_cast(src + element_size), reinterpret_cast(src), + try { + array_set(self, idx, value); + } catch (...) { + // Move it all back + memmove(reinterpret_cast(src), reinterpret_cast(src + element_size), remaining_size); + self.resize(size); + throw; } - - array_set(self, idx, value); } py::object array_py_pop(WrappedArray& self, py::ssize_t py_idx) { auto idx = convert_py_idx(self, py_idx); - py::object ret{}; - cast(self.type, [&self, &ret, idx](const T* /*prop*/) { - auto val = self.get_at(idx); - - // Explicitly make a copy - // Some types (structs) are a reference by default, which will break once we - // remove them otherwise - // NOLINTNEXTLINE(misc-const-correctness) - typename PropTraits::Value val_copy = val; - - ret = py::cast(val_copy); - }); + py::object ret = array_get(self, idx); array_delete_range(self, idx, idx + 1); @@ -172,14 +169,10 @@ void array_py_sort(WrappedArray& self, const py::object& key, bool reverse) { .get_stored(); py::sequence sorted_array = sorted(self, "key"_a = key, "reverse"_a = reverse); - - cast(self.type, [&self, &sorted_array](const T* /*prop*/) { - auto size = self.size(); - for (size_t i = 0; i < size; i++) { - auto val = py::cast::Value>(sorted_array[i]); - self.set_at(i, val); - } - }); + auto size = self.size(); + for (size_t i = 0; i < size; i++) { + array_set(self, i, sorted_array[i]); + } } uintptr_t array_py_getaddress(const WrappedArray& self) { From 162411dae7d86aa1574e0636e81b9615f0bd820c Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sun, 19 Jan 2025 14:49:21 +1300 Subject: [PATCH 3/4] update changelog/bump version number --- CMakeLists.txt | 2 +- changelog.md | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a8dc76e..23a258f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.25) -project(pyunrealsdk VERSION 1.5.2) +project(pyunrealsdk VERSION 1.6.0) function(_pyunrealsdk_add_base_target_args target_name) target_compile_features(${target_name} PUBLIC cxx_std_20) diff --git a/changelog.md b/changelog.md index 9a42bca..55df488 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,6 @@ # Changelog -## Upcoming +## v1.6.0 - `WrappedStruct` now supports being copied via the `copy` module. @@ -11,10 +11,37 @@ [10bdc130](https://github.com/bl-sdk/pyunrealsdk/commit/10bdc130) +- Hook return values and array access now have the same semantics as normal property accesses. In + practice this means: + + - Getting an enum property will convert it to a python `IntFlag` enum (rather than an int). + - Setting an array property will accept any sequence (rather than just wrapped arrays). + + All other property types had the same semantics already, so this is backwards compatible. + + [c52a807e](https://github.com/bl-sdk/pyunrealsdk/commit/c52a807e), + [16ac1711](https://github.com/bl-sdk/pyunrealsdk/commit/16ac1711) + - Added a `_get_address` method to `WrappedArray`, `WrappedMulticastDelegate`, and `WrappedStruct`. [1b3e9686](https://github.com/bl-sdk/pyunrealsdk/commit/1b3e9686) + +### unrealsdk v1.7.0 +For reference, the unrealsdk v1.7.0 changes this includes are: + +> - `unrealsdk::unreal::cast` now copies the const-ness of its input object to its callbacks. +> +> [779d75ea](https://github.com/bl-sdk/unrealsdk/commit/779d75ea) +> +> - Reworked `PropertyProxy` to be based on `UnrealPointer` (and reworked it too). This fixes some +> issues with ownership and possible use after frees. +> +> *This breaks binary compatibility*, though existing code should work pretty much as is after a +> recompile. +> +> [49bff4a4](https://github.com/bl-sdk/unrealsdk/commit/49bff4a4) + ## v1.5.2 ### unrealsdk v1.6.1 From eca4869e440e0e7aff3ea4555cd6c58198132542 Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sun, 19 Jan 2025 15:05:53 +1300 Subject: [PATCH 4/4] fix linting --- src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp b/src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp index d8592f5..432c99b 100644 --- a/src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp +++ b/src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp @@ -168,7 +168,7 @@ void array_py_sort(WrappedArray& self, const py::object& key, bool reverse) { []() { return py::module_::import("builtins").attr("sorted"); }) .get_stored(); - py::sequence sorted_array = sorted(self, "key"_a = key, "reverse"_a = reverse); + const py::sequence sorted_array = sorted(self, "key"_a = key, "reverse"_a = reverse); auto size = self.size(); for (size_t i = 0; i < size; i++) { array_set(self, i, sorted_array[i]);