Skip to content

Commit 3b7758c

Browse files
author
Roberto De Ioris
committed
2 parents 68fd1ac + d43f83c commit 3b7758c

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

docs/MemoryManagement.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# Managing Memory
2+
3+
## UObject
4+
5+
A UObject is one of the fundamental parts of Unreal Engine. It represents an engine/editor object tracked by Unreal Engine Garbage Collector.
6+
7+
Each UObject is constantly tracked, and whenever the Garbage Collector runs (generally every 60 seconds or in very specific parts of the engine/editor loop, like when entering PIE mode)
8+
it checks if the UObject has still references to other UObjects, and in negative case (no references) it will be destroyed.
9+
10+
You can trigger a Garbage Collector run from python with:
11+
12+
```python
13+
import unreal_engine as ue
14+
ue.console_exec('obj gc')
15+
```
16+
17+
## py_UEObject
18+
19+
this is the low-level C struct representing the python mapping between a PyObject (c struct representing a python object) and a UObject.
20+
Whenever you create a py_UEObject (from the UE python api) another GC (related to the python plugin) will start tracking it.
21+
22+
Whenever the UE GC runs, the UNrealEnginePython GC will run too, checking if a UObject mapped to a py_UEObject is still alive.
23+
24+
If the UObject mapped to a python object is dead, an exception will be triggered.
25+
26+
This is an example:
27+
28+
```python
29+
import unreal_engine as ue
30+
31+
from unreal_engine.classes import BlueprintFactory
32+
33+
factory = BlueprintFactory()
34+
# run GC
35+
ue.console_exec('obj gc')
36+
# this will raise an exception as the UObject mapped to factory has been destroyed by the GC run
37+
print(factory)
38+
```
39+
40+
By running this script you will end with something like this:
41+
42+
```
43+
PyUObject is in invalid state
44+
Traceback (most recent call last):
45+
File "<string>", line XX, in <module>
46+
Exception: PyUObject is in invalid state
47+
```
48+
49+
Very long scripts, that do lot of stuff, often triggering UE4 GC, could be blocked in the middle of their execution by this kind of errors. In such a case (like you would do in C++) you need to inform the UE GC on how to deal with them (for avoiding their destruction).
50+
51+
Pay attention, as once you tell the UE GC to not destroy a UObject, that UObject (and its python mapping) will stay in memory (so you will end with a leak)
52+
53+
## Strategy 1: Setting UObject flags to govern the GC
54+
55+
When you create a UObject (from the C++ side, via the NewObject<T> api call) you can specify a bitmask of flags. By default the python api only use the RF_Public flag:
56+
57+
https://api.unrealengine.com/INT/API/Runtime/CoreUObject/UObject/EObjectFlags/index.html
58+
59+
You can change this bitmask with the set_obj_flags() python function:
60+
61+
```python
62+
import unreal_engine as ue
63+
64+
from unreal_engine.classes import BlueprintFactory
65+
66+
factory = BlueprintFactory()
67+
# assign mask 0x00000001|0x00000002
68+
factory.set_obj_flags(ue.RF_PUBLIC|ue.RF_STANDALONE)
69+
# run GC
70+
ue.console_exec('obj gc')
71+
# this will normally print the UObject repr
72+
print(factory)
73+
```
74+
75+
The RF_Standalone flag (RF_STANDALONE in python api) will marks a UObject as 'standalone' so it will remain resident in memory forever.
76+
77+
Eventually you can reset/set the flags:
78+
79+
```python
80+
import unreal_engine as ue
81+
82+
factory = BlueprintFactory()
83+
factory.set_obj_flags(ue.RF_PUBLIC|ue.RF_STANDALONE)
84+
85+
86+
ue.console_exec('obj gc')
87+
88+
print(factory)
89+
90+
# the second True argument will reset the flags (otherwise set_obj_flags will work in append mode)
91+
# eventually you can call factory.reset_obj_flags()
92+
factory.set_obj_flags(ue.RF_PUBLIC, True)
93+
94+
ue.console_exec('obj gc')
95+
96+
print(factory)
97+
```
98+
99+
The second print will raise the error.
100+
101+
This is a pretty raw approach (unless you are sure that you need a resident object). For having more control the second strategy will be way more better...
102+
103+
## Strategy 2: The Root Set
104+
105+
The root set is a very specific part of the GC tree. If you want to hold control of a UObject lifecycle in an efficient way, you can use the related python api:
106+
107+
```python
108+
import unreal_engine as ue
109+
110+
factory = BlueprintFactory()
111+
factory.add_to_root()
112+
113+
ue.console_exec('obj gc')
114+
115+
print(factory)
116+
117+
factory.remove_from_root()
118+
119+
ue.console_exec('obj gc')
120+
121+
print(factory)
122+
```
123+
124+
as before, the first GC run will not destroy the UObject (as it is in the root set), while the second one will remove if from the memory as it is no more in the root set.
125+
126+
A funny approach to memory management of UObject from python is by using a Tracker object:
127+
128+
```python
129+
class Tracker:
130+
131+
def __init__(self):
132+
self.uobjects = []
133+
134+
def track(self, uobject):
135+
uobject.add_to_root()
136+
self.uobjects.append(uobject)
137+
return uobject
138+
139+
def __del__(self):
140+
for uobject in self.uobjects:
141+
uobject.remove_from_root()
142+
143+
tracker = Tracker()
144+
```
145+
146+
Now you can create UObject from python and track them automatically. When the python GC destroys the tracker object, all of the UObject's tracked by it will be destroyed too:
147+
148+
```python
149+
factory = tracker.track(BlueprintFactory())
150+
```
151+
152+
As an example when running a script multiple times, the 'tracker' id will be overwritten, triggering the destruction of the mapped python object (and its ```__del__``` method)
153+

0 commit comments

Comments
 (0)