Skip to content

Commit 4ed788b

Browse files
authored
Merge pull request #69 from apple1417/master
make arrays and hook return values use standard property access semantics
2 parents d68c895 + eca4869 commit 4ed788b

File tree

7 files changed

+102
-68
lines changed

7 files changed

+102
-68
lines changed

CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cmake_minimum_required(VERSION 3.25)
22

3-
project(pyunrealsdk VERSION 1.5.2)
3+
project(pyunrealsdk VERSION 1.6.0)
44

55
function(_pyunrealsdk_add_base_target_args target_name)
66
target_compile_features(${target_name} PUBLIC cxx_std_20)

changelog.md

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changelog
22

3-
## Upcoming
3+
## v1.6.0
44

55
- `WrappedStruct` now supports being copied via the `copy` module.
66

@@ -11,10 +11,37 @@
1111

1212
[10bdc130](https://github.com/bl-sdk/pyunrealsdk/commit/10bdc130)
1313

14+
- Hook return values and array access now have the same semantics as normal property accesses. In
15+
practice this means:
16+
17+
- Getting an enum property will convert it to a python `IntFlag` enum (rather than an int).
18+
- Setting an array property will accept any sequence (rather than just wrapped arrays).
19+
20+
All other property types had the same semantics already, so this is backwards compatible.
21+
22+
[c52a807e](https://github.com/bl-sdk/pyunrealsdk/commit/c52a807e),
23+
[16ac1711](https://github.com/bl-sdk/pyunrealsdk/commit/16ac1711)
24+
1425
- Added a `_get_address` method to `WrappedArray`, `WrappedMulticastDelegate`, and `WrappedStruct`.
1526

1627
[1b3e9686](https://github.com/bl-sdk/pyunrealsdk/commit/1b3e9686)
1728

29+
30+
### unrealsdk v1.7.0
31+
For reference, the unrealsdk v1.7.0 changes this includes are:
32+
33+
> - `unrealsdk::unreal::cast` now copies the const-ness of its input object to its callbacks.
34+
>
35+
> [779d75ea](https://github.com/bl-sdk/unrealsdk/commit/779d75ea)
36+
>
37+
> - Reworked `PropertyProxy` to be based on `UnrealPointer` (and reworked it too). This fixes some
38+
> issues with ownership and possible use after frees.
39+
>
40+
> *This breaks binary compatibility*, though existing code should work pretty much as is after a
41+
> recompile.
42+
>
43+
> [49bff4a4](https://github.com/bl-sdk/unrealsdk/commit/49bff4a4)
44+
1845
## v1.5.2
1946

2047
### unrealsdk v1.6.1

src/pyunrealsdk/hook.cpp

+13-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "pyunrealsdk/exports.h"
44
#include "pyunrealsdk/hooks.h"
55
#include "pyunrealsdk/logging.h"
6+
#include "pyunrealsdk/unreal_bindings/property_access.h"
67
#include "unrealsdk/hook_manager.h"
78
#include "unrealsdk/unreal/cast.h"
89
#include "unrealsdk/unreal/prop_traits.h"
@@ -46,8 +47,9 @@ namespace {
4647
bool handle_py_hook(Details& hook, const py::object& callback) {
4748
py::object ret_arg;
4849
if (hook.ret.has_value()) {
49-
cast(hook.ret.prop, [&hook, &ret_arg]<typename T>(const T* /*prop*/) {
50-
ret_arg = py::cast(hook.ret.get<T>());
50+
cast(hook.ret.prop, [&hook, &ret_arg]<typename T>(T* prop) {
51+
ret_arg = pyunrealsdk::unreal::py_getattr(
52+
prop, reinterpret_cast<uintptr_t>(hook.ret.ptr.get()), hook.ret.ptr);
5153
});
5254
} else {
5355
ret_arg = py::type::of<Unset>();
@@ -76,9 +78,15 @@ bool handle_py_hook(Details& hook, const py::object& callback) {
7678
if (py::type::of<Unset>().is(ret_override) || py::isinstance<Unset>(ret_override)) {
7779
hook.ret.destroy();
7880
} else if (!py::ellipsis{}.equal(ret_override)) {
79-
cast(hook.ret.prop, [&hook, &ret_override]<typename T>(const T* /*prop*/) {
80-
auto value = py::cast<typename PropTraits<T>::Value>(ret_override);
81-
hook.ret.set<T>(value);
81+
cast(hook.ret.prop, [&hook, &ret_override]<typename T>(T* prop) {
82+
// Need to replicate PropertyProxy::set ourselves a bit, since we want to use
83+
// our custom python setter
84+
if (hook.ret.ptr.get() == nullptr) {
85+
hook.ret.ptr = UnrealPointer<void>(hook.ret.prop);
86+
}
87+
88+
pyunrealsdk::unreal::py_setattr_direct(
89+
prop, reinterpret_cast<uintptr_t>(hook.ret.ptr.get()), ret_override);
8290
});
8391
}
8492
}

src/pyunrealsdk/unreal_bindings/wrapped_array.h

-10
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,6 @@ py::object array_get(const WrappedArray& arr, size_t idx);
5252
*/
5353
void array_set(WrappedArray& arr, size_t idx, const py::object& value);
5454

55-
/**
56-
* @brief Ensures a value is compatible with the given array.
57-
* @note Intended to be used when there's extra, non-reversible, work to do before it's possible to
58-
* call array_set.
59-
*
60-
* @param arr The array to check against.
61-
* @param value The value to check.
62-
*/
63-
void array_validate_value(const WrappedArray& arr, const py::object& value);
64-
6555
/**
6656
* @brief Deletes a range of entries of arbitrary type in an array.
6757
*

src/pyunrealsdk/unreal_bindings/wrapped_array_helpers.cpp

+26-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "pyunrealsdk/pch.h"
2+
#include "pyunrealsdk/unreal_bindings/property_access.h"
23
#include "pyunrealsdk/unreal_bindings/wrapped_array.h"
34
#include "unrealsdk/unreal/cast.h"
5+
#include "unrealsdk/unreal/classes/uproperty.h"
46
#include "unrealsdk/unreal/wrappers/wrapped_array.h"
57

68
#ifdef PYUNREALSDK_INTERNAL
@@ -21,21 +23,35 @@ size_t convert_py_idx(const WrappedArray& arr, py::ssize_t idx) {
2123
}
2224

2325
py::object array_get(const WrappedArray& arr, size_t idx) {
24-
py::object ret{};
25-
cast(arr.type, [&]<typename T>(const T* /*prop*/) { ret = py::cast(arr.get_at<T>(idx)); });
26+
if (arr.type->Offset_Internal != 0) {
27+
throw std::runtime_error(
28+
"array inner property has non-zero offset, unsure how to handle, aborting!");
29+
}
30+
if (arr.type->ArrayDim != 1) {
31+
throw std::runtime_error(
32+
"array inner property is fixed array, unsure how to handle, aborting!");
33+
}
2634

27-
return ret;
35+
// Const cast is slightly naughty, but we know the internals aren't going to modify properties
36+
return py_getattr(
37+
const_cast<UProperty*>(arr.type), // NOLINT(cppcoreguidelines-pro-type-const-cast)
38+
reinterpret_cast<uintptr_t>(arr.base.get()->data) + (arr.type->ElementSize * idx),
39+
arr.base);
2840
}
2941

3042
void array_set(WrappedArray& arr, size_t idx, const py::object& value) {
31-
cast(arr.type, [&]<typename T>(const T* /*prop*/) {
32-
arr.set_at<T>(idx, py::cast<typename PropTraits<T>::Value>(value));
33-
});
34-
}
43+
if (arr.type->Offset_Internal != 0) {
44+
throw std::runtime_error(
45+
"array inner property has non-zero offset, unsure how to handle, aborting!");
46+
}
47+
if (arr.type->ArrayDim != 1) {
48+
throw std::runtime_error(
49+
"array inner property is fixed array, unsure how to handle, aborting!");
50+
}
3551

36-
void array_validate_value(const WrappedArray& arr, const py::object& value) {
37-
cast(arr.type,
38-
[&]<typename T>(const T* /*prop*/) { py::cast<typename PropTraits<T>::Value>(value); });
52+
py_setattr_direct(
53+
const_cast<UProperty*>(arr.type), // NOLINT(cppcoreguidelines-pro-type-const-cast)
54+
reinterpret_cast<uintptr_t>(arr.base.get()->data) + (arr.type->ElementSize * idx), value);
3955
}
4056

4157
void array_delete_range(WrappedArray& arr, size_t start, size_t stop) {

src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp

+33-40
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ using namespace unrealsdk::unreal;
1111
namespace pyunrealsdk::unreal::impl {
1212

1313
void array_py_append(WrappedArray& self, const py::object& value) {
14-
array_validate_value(self, value);
15-
1614
auto size = self.size();
1715
self.resize(size + 1);
18-
array_set(self, size, value);
16+
try {
17+
array_set(self, size, value);
18+
} catch (...) {
19+
self.resize(size);
20+
throw;
21+
}
1922
}
2023

2124
void array_py_clear(WrappedArray& self) {
@@ -61,8 +64,6 @@ size_t array_py_index(const WrappedArray& self,
6164
const py::object& value,
6265
py::ssize_t start,
6366
py::ssize_t stop) {
64-
array_validate_value(self, value);
65-
6667
// `list.index` method handles indexes a little differently to most methods. Essentially, any
6768
// index is allowed, and it's just implicitly clamped to the size of the array. You're allowed
6869
// 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,
9899
}
99100

100101
void array_py_insert(WrappedArray& self, py::ssize_t py_idx, const py::object& value) {
101-
array_validate_value(self, value);
102-
103102
auto size = self.size();
104103

105-
// Allow specifying one past the end, to insert at the end
106-
// insert(-1) should insert before the last element, so goes through the normal conversion
107-
auto idx = static_cast<size_t>(py_idx) == size ? py_idx : convert_py_idx(self, py_idx);
104+
if (static_cast<size_t>(py_idx) == size) {
105+
// We're just appending
106+
array_py_append(self, value);
107+
return;
108+
}
109+
110+
auto idx = convert_py_idx(self, py_idx);
108111

109112
self.resize(size + 1);
110113

111-
// Don't move if appending
112-
if (idx != size) {
113-
auto data = reinterpret_cast<uintptr_t>(self.base->data);
114-
auto element_size = self.type->ElementSize;
114+
auto data = reinterpret_cast<uintptr_t>(self.base->data);
115+
auto element_size = self.type->ElementSize;
116+
117+
auto src = data + (idx * element_size);
118+
auto remaining_size = (size - idx) * element_size;
119+
memmove(reinterpret_cast<void*>(src + element_size), reinterpret_cast<void*>(src),
120+
remaining_size);
115121

116-
auto src = data + (idx * element_size);
117-
auto remaining_size = (size - idx) * element_size;
118-
memmove(reinterpret_cast<void*>(src + element_size), reinterpret_cast<void*>(src),
122+
try {
123+
array_set(self, idx, value);
124+
} catch (...) {
125+
// Move it all back
126+
memmove(reinterpret_cast<void*>(src), reinterpret_cast<void*>(src + element_size),
119127
remaining_size);
128+
self.resize(size);
129+
throw;
120130
}
121-
122-
array_set(self, idx, value);
123131
}
124132

125133
py::object array_py_pop(WrappedArray& self, py::ssize_t py_idx) {
126134
auto idx = convert_py_idx(self, py_idx);
127135

128-
py::object ret{};
129-
cast(self.type, [&self, &ret, idx]<typename T>(const T* /*prop*/) {
130-
auto val = self.get_at<T>(idx);
131-
132-
// Explicitly make a copy
133-
// Some types (structs) are a reference by default, which will break once we
134-
// remove them otherwise
135-
// NOLINTNEXTLINE(misc-const-correctness)
136-
typename PropTraits<T>::Value val_copy = val;
137-
138-
ret = py::cast(val_copy);
139-
});
136+
py::object ret = array_get(self, idx);
140137

141138
array_delete_range(self, idx, idx + 1);
142139

@@ -171,15 +168,11 @@ void array_py_sort(WrappedArray& self, const py::object& key, bool reverse) {
171168
[]() { return py::module_::import("builtins").attr("sorted"); })
172169
.get_stored();
173170

174-
py::sequence sorted_array = sorted(self, "key"_a = key, "reverse"_a = reverse);
175-
176-
cast(self.type, [&self, &sorted_array]<typename T>(const T* /*prop*/) {
177-
auto size = self.size();
178-
for (size_t i = 0; i < size; i++) {
179-
auto val = py::cast<typename PropTraits<T>::Value>(sorted_array[i]);
180-
self.set_at<T>(i, val);
181-
}
182-
});
171+
const py::sequence sorted_array = sorted(self, "key"_a = key, "reverse"_a = reverse);
172+
auto size = self.size();
173+
for (size_t i = 0; i < size; i++) {
174+
array_set(self, i, sorted_array[i]);
175+
}
183176
}
184177

185178
uintptr_t array_py_getaddress(const WrappedArray& self) {

0 commit comments

Comments
 (0)