66#include " unrealsdk/format.h"
77#include " unrealsdk/unreal/classes/uclass.h"
88#include " unrealsdk/unreal/classes/uobject.h"
9+ #include " unrealsdk/unreal/classes/uproperty.h"
10+ #include " unrealsdk/unreal/find_class.h"
911#include " unrealsdk/unreal/structs/fname.h"
1012#include " unrealsdk/unrealsdk.h"
1113#include " unrealsdk/utils.h"
@@ -29,6 +31,11 @@ UObject* uobject_init(const py::args& /* args */, const py::kwargs& /* kwargs */
2931 throw py::type_error (" Cannot create new instances of unreal objects." );
3032}
3133
34+ // Dummy class to make the context manager on
35+ struct ContextManager {};
36+
37+ size_t should_notify_counter = 0 ;
38+
3239} // namespace
3340
3441void register_uobject (py::module_& mod) {
@@ -126,8 +133,12 @@ void register_uobject(py::module_& mod) {
126133 }
127134 }
128135
129- py_setattr_direct (py_find_field (py::cast<FName>(name), self->Class ),
130- reinterpret_cast <uintptr_t >(self), value);
136+ auto field = py_find_field (py::cast<FName>(name), self->Class );
137+ py_setattr_direct (field, reinterpret_cast <uintptr_t >(self), value);
138+
139+ if (should_notify_counter > 0 && field->is_instance (find_class<UProperty>())) {
140+ self->post_edit_change_property (reinterpret_cast <UProperty*>(field));
141+ }
131142 },
132143 " Writes a value to an unreal field on the object.\n "
133144 " \n "
@@ -144,6 +155,10 @@ void register_uobject(py::module_& mod) {
144155 throw py::attribute_error (" cannot access null attribute" );
145156 }
146157 py_setattr_direct (field, reinterpret_cast <uintptr_t >(self), value);
158+
159+ if (should_notify_counter > 0 && field->is_instance (find_class<UProperty>())) {
160+ self->post_edit_change_property (reinterpret_cast <UProperty*>(field));
161+ }
147162 },
148163 " Writes a value to an unreal field on the object.\n "
149164 " \n "
@@ -162,11 +177,68 @@ void register_uobject(py::module_& mod) {
162177 " \n "
163178 " Returns:\n "
164179 " This object's address." )
180+ .def (
181+ " _post_edit_change_property" ,
182+ [](UObject* self, std::variant<FName, UProperty*> prop) {
183+ std::visit ([self](auto && val) { self->post_edit_change_property (val); }, prop);
184+ },
185+ " Notifies the engine that we've made an external change to a property.\n "
186+ " \n "
187+ " This only works on top level properties, those directly on the object.\n "
188+ " \n "
189+ " Also see the notify_changes() context manager, which calls this automatically.\n "
190+ " \n "
191+ " Args:\n "
192+ " prop: The property, or the name of the property, which was changed." ,
193+ " args" _a)
194+ .def (
195+ " _post_edit_change_chain_property" ,
196+ [](UObject* self, UProperty* prop, const py::args& args) {
197+ std::vector<UProperty*> chain;
198+ chain.reserve (args.size ());
199+
200+ for (const auto & val : args) {
201+ chain.push_back (py::cast<UProperty*>(val));
202+ }
203+ self->post_edit_change_chain_property (prop, chain);
204+ },
205+ " Notifies the engine that we've made an external change to a chain of properties.\n "
206+ " \n "
207+ " This version allows notifying about changes inside (nested) structs.\n "
208+ " \n "
209+ " Args:\n "
210+ " prop: The property which was changed.\n "
211+ " *chain: The chain of properties to follow." ,
212+ " prop" _a)
165213 .def_readwrite (" ObjectFlags" , &UObject::ObjectFlags)
166214 .def_readwrite (" InternalIndex" , &UObject::InternalIndex)
167215 .def_readwrite (" Class" , &UObject::Class)
168216 .def_readwrite (" Name" , &UObject::Name)
169217 .def_readwrite (" Outer" , &UObject::Outer);
218+
219+ // Create under an empty handle to prevent this type being normally accessible
220+ py::class_<ContextManager>(py::handle (), " context_manager" , pybind11::module_local ())
221+ .def (" __enter__" , [](const py::object& /* self*/ ) { should_notify_counter++; })
222+ .def (" __exit__" , [](const py::object& /* self */ , const py::object& /* exc_type*/ ,
223+ const py::object& /* exc_value*/ , const py::object& /* traceback*/ ) {
224+ if (should_notify_counter > 0 ) {
225+ should_notify_counter--;
226+ }
227+ });
228+
229+ mod.def (
230+ " notify_changes" , []() { return ContextManager{}; },
231+ " Context manager to automatically notify the engine when you edit an object.\n "
232+ " \n "
233+ " This essentially just automatically calls obj._post_edit_change_property() after\n "
234+ " every setattr.\n "
235+ " \n "
236+ " Note that this only tracks top-level changes, it cannot track changes to inner\n "
237+ " struct fields, You will have to manually call obj._post_edit_chain_property()\n "
238+ " for them.\n "
239+ " \n "
240+ " Returns:\n "
241+ " A new context manager." );
170242}
171243
172244} // namespace pyunrealsdk::unreal
0 commit comments