Skip to content

Commit 8a93833

Browse files
author
Roberto De Ioris
committed
added canary
1 parent 5ad1931 commit 8a93833

17 files changed

Lines changed: 186 additions & 141 deletions

‎Source/UnrealEnginePython/Private/PyActor.cpp‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,6 @@ APyActor::~APyActor()
284284
{
285285
FScopePythonGIL gil;
286286

287-
ue_pydelegates_cleanup(py_uobject);
288-
289287
#if defined(UEPY_MEMORY_DEBUG)
290288
if (py_actor_instance && py_actor_instance->ob_refcnt != 1)
291289
{
@@ -299,5 +297,9 @@ APyActor::~APyActor()
299297
#endif
300298

301299
// this could trigger the destruction of the python/uobject mapper
300+
if (py_uobject)
301+
{
302+
UE_LOG(LogPython, Error, TEXT("UObject refcnt = %d"), py_uobject->ob_base.ob_refcnt);
303+
}
302304
Py_XDECREF(py_uobject);
303305
}

‎Source/UnrealEnginePython/Private/PyCharacter.cpp‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,8 +443,6 @@ APyCharacter::~APyCharacter()
443443
{
444444
FScopePythonGIL gil;
445445

446-
ue_pydelegates_cleanup(py_uobject);
447-
448446
#if defined(UEPY_MEMORY_DEBUG)
449447
if (py_character_instance && py_character_instance->ob_refcnt != 1) {
450448
UE_LOG(LogPython, Error, TEXT("Inconsistent Python ACharacter wrapper refcnt = %d"), py_character_instance->ob_refcnt);

‎Source/UnrealEnginePython/Private/PyHUD.cpp‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,6 @@ APyHUD::~APyHUD()
242242
{
243243
FScopePythonGIL gil;
244244

245-
ue_pydelegates_cleanup(py_uobject);
246-
247245
#if defined(UEPY_MEMORY_DEBUG)
248246
if (py_hud_instance && py_hud_instance->ob_refcnt != 1)
249247
{

‎Source/UnrealEnginePython/Private/PyPawn.cpp‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,6 @@ APyPawn::~APyPawn()
239239
{
240240
FScopePythonGIL gil;
241241

242-
ue_pydelegates_cleanup(py_uobject);
243-
244242
#if defined(UEPY_MEMORY_DEBUG)
245243
if (py_pawn_instance && py_pawn_instance->ob_refcnt != 1) {
246244
UE_LOG(LogPython, Error, TEXT("Inconsistent Python APawn wrapper refcnt = %d"), py_pawn_instance->ob_refcnt);

‎Source/UnrealEnginePython/Private/PyUserWidget.cpp‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,6 @@ UPyUserWidget::~UPyUserWidget()
355355
{
356356
FScopePythonGIL gil;
357357

358-
ue_pydelegates_cleanup(py_uobject);
359-
360358
#if defined(UEPY_MEMORY_DEBUG)
361359
if (py_user_widget_instance && py_user_widget_instance->ob_refcnt != 1) {
362360
UE_LOG(LogPython, Error, TEXT("Inconsistent Python UUserWidget wrapper refcnt = %d"), py_user_widget_instance->ob_refcnt);

‎Source/UnrealEnginePython/Private/PythonComponent.cpp‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,8 +556,6 @@ UPythonComponent::~UPythonComponent()
556556
{
557557
FScopePythonGIL gil;
558558

559-
ue_pydelegates_cleanup(py_uobject);
560-
561559
#if defined(UEPY_MEMORY_DEBUG)
562560
if (py_component_instance && py_component_instance->ob_refcnt != 1) {
563561
UE_LOG(LogPython, Error, TEXT("Inconsistent Python UActorComponent wrapper refcnt = %d"), py_component_instance->ob_refcnt);

‎Source/UnrealEnginePython/Private/PythonSmartDelegate.cpp‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ bool FPythonSmartDelegate::Tick(float DeltaTime)
3333
return false;
3434
}
3535

36+
void FPythonSmartDelegate::Void()
37+
{
38+
FScopePythonGIL gil;
39+
PyObject *ret = PyObject_CallFunction(py_callable, nullptr);
40+
if (!ret)
41+
{
42+
unreal_engine_py_log_error();
43+
return;
44+
}
45+
Py_DECREF(ret);
46+
}
47+
3648
#if WITH_EDITOR
3749
void FPythonSmartDelegate::PyFOnAssetPostImport(UFactory *factory, UObject *u_object)
3850
{

‎Source/UnrealEnginePython/Private/UEPyModule.cpp‎

Lines changed: 95 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,76 @@ static PyObject *init_unreal_engine(void)
8181
}
8282
#endif
8383

84-
std::map<UObject *, ue_PyUObject *> ue_python_gc;
84+
namespace UnrealEnginePythonHouseKeeper
85+
{
86+
std::map<UObject *, ue_PyUObject *> u_object_py_mapping;
87+
88+
struct FPythonDelegateTracker
89+
{
90+
FWeakObjectPtr owner;
91+
UPythonDelegate *delegate;
92+
93+
FPythonDelegateTracker(UPythonDelegate *delegate_to_track, UObject *delegate_owner) : owner(delegate_owner), delegate(delegate_to_track)
94+
{
95+
}
96+
97+
~FPythonDelegateTracker()
98+
{
99+
}
100+
};
101+
102+
std::list<FPythonDelegateTracker> py_delegates_tracker;
103+
};
104+
105+
static void ue_py_delegates_gc()
106+
{
107+
auto &lst = UnrealEnginePythonHouseKeeper::py_delegates_tracker;
108+
for (auto itr = lst.begin(); itr != lst.end(); /*noop*/)
109+
//for (auto itr = lst.begin(); itr != lst.end(); ++itr)
110+
{
111+
/*if (itr->owner.IsValid())
112+
{
113+
UE_LOG(LogPython, Error, TEXT("Delegate for %s"), *itr->owner.Get()->GetName());
114+
}
115+
else
116+
{
117+
UE_LOG(LogPython, Error, TEXT("Found bad delegate"));
118+
}
119+
*/
120+
if (!itr->owner.IsValid())
121+
{
122+
itr->delegate->RemoveFromRoot();
123+
itr = lst.erase(itr);
124+
}
125+
else
126+
++itr;
127+
}
128+
}
129+
130+
UPythonDelegate *ue_py_new_delegate(UObject *owner, PyObject *py_callable, UFunction *signature)
131+
{
132+
// TODO: do a round of gc only if enough time passed
133+
ue_py_delegates_gc();
134+
135+
UPythonDelegate *py_delegate = NewObject<UPythonDelegate>();
136+
if (!py_delegate)
137+
return nullptr;
138+
139+
py_delegate->AddToRoot();
140+
py_delegate->SetPyCallable(py_callable);
141+
py_delegate->SetSignature(signature);
142+
143+
UnrealEnginePythonHouseKeeper::FPythonDelegateTracker tracker(py_delegate, owner);
144+
UnrealEnginePythonHouseKeeper::py_delegates_tracker.push_back(tracker);
145+
146+
return py_delegate;
147+
}
85148

86149

87150
static PyObject *py_unreal_engine_py_gc(PyObject * self, PyObject * args)
88151
{
89152
std::list<UObject *> broken_list;
90-
for (auto it : ue_python_gc)
153+
for (auto it : UnrealEnginePythonHouseKeeper::u_object_py_mapping)
91154
{
92155
#if defined(UEPY_MEMORY_DEBUG)
93156
UE_LOG(LogPython, Warning, TEXT("Checking for UObject at %p"), it.first);
@@ -100,12 +163,17 @@ static PyObject *py_unreal_engine_py_gc(PyObject * self, PyObject * args)
100163
#endif
101164
broken_list.push_back(u_obj);
102165
}
166+
else
167+
{
168+
UE_LOG(LogPython, Error, TEXT("UObject at %p %s is in use"), u_obj, *u_obj->GetName());
169+
}
103170
}
171+
/*
104172
for (UObject *u_obj : broken_list)
105173
{
106-
ue_PyUObject *py_obj = ue_python_gc.at(u_obj);
174+
ue_PyUObject *py_obj = UnrealEnginePythonHouseKeeper::u_object_py_mapping.at(u_obj);
107175
Py_DECREF(py_obj);
108-
}
176+
}*/
109177

110178
return PyLong_FromLong(broken_list.size());
111179

@@ -975,43 +1043,21 @@ static PyMethodDef ue_PyUObject_methods[] = {
9751043
{ NULL } /* Sentinel */
9761044
};
9771045

978-
void ue_pydelegates_cleanup(ue_PyUObject *self)
1046+
1047+
// destructor
1048+
static void ue_pyobject_dealloc(ue_PyUObject *self)
9791049
{
980-
// this could happen during engine shutdown, so have mercy
981-
// start deallocating delegates mapped to the object
982-
if (!self || !self->python_delegates_gc)
983-
return;
984-
UE_LOG(LogPython, Warning, TEXT("Delegates = %d"), self->python_delegates_gc->size());
985-
for (UPythonDelegate *py_delegate : *(self->python_delegates_gc))
986-
{
987-
if (py_delegate && py_delegate->IsValidLowLevel())
988-
{
1050+
PyObject_GC_UnTrack(self);
9891051
#if defined(UEPY_MEMORY_DEBUG)
990-
UE_LOG(LogPython, Warning, TEXT("Removing UPythonDelegate %p from ue_PyUObject %p mapped to UObject %p"), py_delegate, self, self->ue_object);
1052+
UE_LOG(LogPython, Warning, TEXT("Destroying ue_PyUObject %p mapped to UObject %p"), self, self->ue_object);
9911053
#endif
992-
py_delegate->RemoveFromRoot();
993-
}
994-
}
995-
self->python_delegates_gc->clear();
996-
delete self->python_delegates_gc;
997-
self->python_delegates_gc = nullptr;
998-
999-
if (self->auto_rooted && self->ue_object->IsRooted())
1054+
if (self->auto_rooted && (self->ue_object && self->ue_object->IsValidLowLevel() && self->ue_object->IsRooted()))
10001055
{
10011056
self->ue_object->RemoveFromRoot();
10021057
}
10031058

10041059
Py_XDECREF(self->py_dict);
1005-
}
1006-
1007-
// destructor
1008-
static void ue_pyobject_dealloc(ue_PyUObject *self)
1009-
{
1010-
#if defined(UEPY_MEMORY_DEBUG)
1011-
UE_LOG(LogPython, Warning, TEXT("Destroying ue_PyUObject %p mapped to UObject %p"), self, self->ue_object);
1012-
#endif
1013-
ue_pydelegates_cleanup(self);
1014-
ue_python_gc.erase(self->ue_object);
1060+
UnrealEnginePythonHouseKeeper::u_object_py_mapping.erase(self->ue_object);
10151061
Py_TYPE(self)->tp_free((PyObject *)self);
10161062
}
10171063

@@ -1162,6 +1208,12 @@ static PyObject *ue_PyUObject_str(ue_PyUObject *self)
11621208
#endif
11631209
}
11641210

1211+
static int ue_PyUObject_traverse(ue_PyUObject *self, visitproc visit, void *arg)
1212+
{
1213+
UE_LOG(LogPython, Error, TEXT("TRAVERSING %p"), self);
1214+
return 0;
1215+
}
1216+
11651217
static PyObject *ue_PyUObject_call(ue_PyUObject *self, PyObject *args, PyObject *kw)
11661218
{
11671219
// if it is a class, create a new object
@@ -1277,9 +1329,9 @@ static PyTypeObject ue_PyUObjectType = {
12771329
(getattrofunc)ue_PyUObject_getattro, /* tp_getattro */
12781330
(setattrofunc)ue_PyUObject_setattro, /* tp_setattro */
12791331
0, /* tp_as_buffer */
1280-
Py_TPFLAGS_DEFAULT, /* tp_flags */
1332+
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, /* tp_flags */
12811333
"Unreal Engine generic UObject", /* tp_doc */
1282-
0, /* tp_traverse */
1334+
(traverseproc)ue_PyUObject_traverse, /* tp_traverse */
12831335
0, /* tp_clear */
12841336
0, /* tp_richcompare */
12851337
0, /* tp_weaklistoffset */
@@ -1727,12 +1779,7 @@ static int unreal_engine_py_init(ue_PyUObject *self, PyObject *args, PyObject *k
17271779
FMulticastScriptDelegate multiscript_delegate = casted_prop->GetPropertyValue_InContainer(ObjectInitializer.GetObj());
17281780

17291781
FScriptDelegate script_delegate;
1730-
UPythonDelegate *py_delegate = NewObject<UPythonDelegate>();
1731-
py_delegate->SetPyCallable(mc_value);
1732-
py_delegate->SetSignature(casted_prop->SignatureFunction);
1733-
// avoid delegates to be destroyed by the GC
1734-
py_delegate->AddToRoot();
1735-
1782+
UPythonDelegate *py_delegate = ue_py_new_delegate(ObjectInitializer.GetObj(), mc_value, casted_prop->SignatureFunction);
17361783
// fake UFUNCTION for bypassing checks
17371784
script_delegate.BindUFunction(py_delegate, FName("PyFakeCallable"));
17381785

@@ -2100,21 +2147,21 @@ ue_PyUObject *ue_get_python_uobject(UObject *ue_obj)
21002147
{
21012148
if (!ue_obj || !ue_obj->IsValidLowLevel() || ue_obj->IsPendingKillOrUnreachable())
21022149
return nullptr;
2103-
std::map<UObject *, ue_PyUObject *>::iterator it = ue_python_gc.find(ue_obj);
2150+
std::map<UObject *, ue_PyUObject *>::iterator it = UnrealEnginePythonHouseKeeper::u_object_py_mapping.find(ue_obj);
21042151
// not found ??
2105-
if (it == ue_python_gc.end())
2152+
if (it == UnrealEnginePythonHouseKeeper::u_object_py_mapping.end())
21062153
{
21072154

2108-
ue_PyUObject *ue_py_object = (ue_PyUObject *)PyObject_New(ue_PyUObject, &ue_PyUObjectType);
2155+
//ue_PyUObject *ue_py_object = (ue_PyUObject *)PyObject_New(ue_PyUObject, &ue_PyUObjectType);
2156+
ue_PyUObject *ue_py_object = (ue_PyUObject *)PyObject_GC_New(ue_PyUObject, &ue_PyUObjectType);
21092157
if (!ue_py_object)
21102158
{
21112159
return nullptr;
21122160
}
21132161
ue_py_object->ue_object = ue_obj;
2114-
ue_py_object->python_delegates_gc = new std::list<UPythonDelegate *>;
21152162
ue_py_object->py_dict = PyDict_New();
21162163

2117-
ue_python_gc[ue_obj] = ue_py_object;
2164+
UnrealEnginePythonHouseKeeper::u_object_py_mapping[ue_obj] = ue_py_object;
21182165

21192166
#if defined(UEPY_MEMORY_DEBUG)
21202167
UE_LOG(LogPython, Warning, TEXT("CREATED UPyObject at %p for %p %s"), ue_py_object, ue_obj, *ue_obj->GetName());
@@ -2211,7 +2258,7 @@ void unreal_engine_py_log_error()
22112258
}
22122259

22132260
PyErr_Clear();
2214-
}
2261+
}
22152262

22162263
// retrieve a UWorld from a generic UObject (if possible)
22172264
UWorld *ue_get_uworld(ue_PyUObject *py_obj)
@@ -3235,13 +3282,7 @@ PyObject *ue_bind_pyevent(ue_PyUObject *u_obj, FString event_name, PyObject *py_
32353282
FMulticastScriptDelegate multiscript_delegate = casted_prop->GetPropertyValue_InContainer(u_obj->ue_object);
32363283

32373284
FScriptDelegate script_delegate;
3238-
UPythonDelegate *py_delegate = NewObject<UPythonDelegate>();
3239-
py_delegate->SetPyCallable(py_callable);
3240-
py_delegate->SetSignature(casted_prop->SignatureFunction);
3241-
// avoid delegates to be destroyed by the GC
3242-
py_delegate->AddToRoot();
3243-
u_obj->python_delegates_gc->push_back(py_delegate);
3244-
3285+
UPythonDelegate *py_delegate = ue_py_new_delegate(u_obj->ue_object, py_callable, casted_prop->SignatureFunction);
32453286
// fake UFUNCTION for bypassing checks
32463287
script_delegate.BindUFunction(py_delegate, FName("PyFakeCallable"));
32473288

‎Source/UnrealEnginePython/Private/UEPyModule.h‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,28 @@
44
#include "PythonDelegate.h"
55
#include "PythonSmartDelegate.h"
66
#include "UEPyUScriptStruct.h"
7-
#include <map>
8-
#include <list>
7+
8+
99

1010
//#include "UEPyModule.generated.h"
1111

1212
typedef struct
1313
{
1414
PyObject_HEAD
15-
/* Type-specific fields go here. */
16-
UObject *ue_object;
15+
/* Type-specific fields go here. */
16+
UObject *ue_object;
1717
// reference to proxy class (can be null)
1818
PyObject *py_proxy;
19-
// list of exposed delegates
20-
std::list<UPythonDelegate*> *python_delegates_gc;
2119
// the __dict__
2220
PyObject *py_dict;
2321
// if true RemoveFromRoot will be called at object destruction time
2422
int auto_rooted;
2523
} ue_PyUObject;
2624

2725

26+
27+
28+
2829
void unreal_engine_py_log_error();
2930
ue_PyUObject *ue_get_python_uobject(UObject *);
3031
ue_PyUObject *ue_get_python_uobject_inc(UObject *);
@@ -41,8 +42,6 @@ PyObject *ue_bind_pyevent(ue_PyUObject *, FString, PyObject *, bool);
4142

4243
PyObject *py_ue_ufunction_call(UFunction *, UObject *, PyObject *, int, PyObject *);
4344

44-
void ue_pydelegates_cleanup(ue_PyUObject *);
45-
4645
UClass *unreal_engine_new_uclass(char *, UClass *);
4746
UFunction *unreal_engine_add_function(UClass *, char *, PyObject *, uint32);
4847

@@ -89,3 +88,5 @@ template <typename T> bool ue_py_check_childstruct(PyObject *py_obj)
8988
}
9089

9190
FGuid *ue_py_check_fguid(PyObject *);
91+
92+
UPythonDelegate *ue_py_new_delegate(UObject *, PyObject *p_callable, UFunction *);

‎Source/UnrealEnginePython/Private/UEPyTimer.cpp‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,10 @@ PyObject *py_ue_set_timer(ue_PyUObject *self, PyObject * args)
108108
return PyErr_Format(PyExc_Exception, "unable to retrieve UWorld from uobject");
109109

110110
FTimerDelegate timer_delegate;
111-
UPythonDelegate *py_delegate = NewObject<UPythonDelegate>();
111+
TSharedRef<FPythonSmartDelegate> py_delegate = MakeShareable(new FPythonSmartDelegate);
112112
py_delegate->SetPyCallable(py_callable);
113-
// fake UFUNCTION for bypassing checks
114-
timer_delegate.BindUFunction(py_delegate, FName("PyFakeCallable"));
115113

116-
// allow the delegate to not be destroyed
117-
py_delegate->AddToRoot();
118-
self->python_delegates_gc->push_back(py_delegate);
114+
timer_delegate.BindSP(py_delegate, &FPythonSmartDelegate::Void);
119115

120116
FTimerHandle thandle;
121117
world->GetTimerManager().SetTimer(thandle, timer_delegate, rate, loop, first_delay);

0 commit comments

Comments
 (0)