Skip to content

Commit f3d70f4

Browse files
committed
Improve memory management
1 parent e990bb7 commit f3d70f4

File tree

7 files changed

+101
-72
lines changed

7 files changed

+101
-72
lines changed

pythonscript/embedded/godot/array.py

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,40 +20,40 @@ class Array(BaseBuiltinWithGDObjOwnership):
2020

2121
@staticmethod
2222
def _copy_gdobj(gdobj):
23-
cpy_gdobj = godot_array_alloc()
23+
cpy_gdobj = godot_array_alloc(initialized=False)
2424
lib.godot_array_new_copy(cpy_gdobj, gdobj)
2525
return cpy_gdobj
2626

2727
def __init__(self, items=()):
2828
if not items:
29-
self._gd_ptr = godot_array_alloc()
29+
self._gd_ptr = godot_array_alloc(initialized=False)
3030
lib.godot_array_new(self._gd_ptr)
3131
elif isinstance(items, Array):
32-
self._gd_ptr = godot_array_alloc()
32+
self._gd_ptr = godot_array_alloc(initialized=False)
3333
lib.godot_array_new_copy(self._gd_ptr, items._gd_ptr)
3434
elif isinstance(items, PoolColorArray):
35-
self._gd_ptr = godot_array_alloc()
35+
self._gd_ptr = godot_array_alloc(initialized=False)
3636
lib.godot_array_new_pool_color_array(self._gd_ptr, items._gd_ptr)
3737
elif isinstance(items, PoolVector3Array):
38-
self._gd_ptr = godot_array_alloc()
38+
self._gd_ptr = godot_array_alloc(initialized=False)
3939
lib.godot_array_new_pool_vector3_array(self._gd_ptr, items._gd_ptr)
4040
elif isinstance(items, PoolVector2Array):
41-
self._gd_ptr = godot_array_alloc()
41+
self._gd_ptr = godot_array_alloc(initialized=False)
4242
lib.godot_array_new_pool_vector2_array(self._gd_ptr, items._gd_ptr)
4343
elif isinstance(items, PoolStringArray):
44-
self._gd_ptr = godot_array_alloc()
44+
self._gd_ptr = godot_array_alloc(initialized=False)
4545
lib.godot_array_new_pool_string_array(self._gd_ptr, items._gd_ptr)
4646
elif isinstance(items, PoolRealArray):
47-
self._gd_ptr = godot_array_alloc()
47+
self._gd_ptr = godot_array_alloc(initialized=False)
4848
lib.godot_array_new_pool_real_array(self._gd_ptr, items._gd_ptr)
4949
elif isinstance(items, PoolIntArray):
50-
self._gd_ptr = godot_array_alloc()
50+
self._gd_ptr = godot_array_alloc(initialized=False)
5151
lib.godot_array_new_pool_int_array(self._gd_ptr, items._gd_ptr)
5252
elif isinstance(items, PoolByteArray):
53-
self._gd_ptr = godot_array_alloc()
53+
self._gd_ptr = godot_array_alloc(initialized=False)
5454
lib.godot_array_new_pool_byte_array(self._gd_ptr, items._gd_ptr)
5555
elif hasattr(items, "__iter__") and not isinstance(items, (str, bytes)):
56-
self._gd_ptr = godot_array_alloc()
56+
self._gd_ptr = godot_array_alloc(initialized=False)
5757
lib.godot_array_new(self._gd_ptr)
5858
for x in items:
5959
self.append(x)
@@ -87,8 +87,10 @@ def __getitem__(self, idx):
8787
if abs(idx) >= size:
8888
raise IndexError("list index out of range")
8989

90-
ret = lib.godot_array_get(self._gd_ptr, idx)
91-
return variant_to_pyobj(ffi.addressof(ret))
90+
gdvar = lib.godot_array_get(self._gd_ptr, idx)
91+
ret = variant_to_pyobj(ffi.addressof(gdvar))
92+
lib.godot_variant_destroy(ffi.addressof(gdvar))
93+
return ret
9294

9395
def __setitem__(self, idx, value):
9496
size = len(self)
@@ -152,12 +154,16 @@ def erase(self, value):
152154
lib.godot_array_erase(self._gd_ptr, var)
153155

154156
def front(self):
155-
ret = lib.godot_array_front(self._gd_ptr)
156-
return variant_to_pyobj(ffi.addressof(ret))
157+
gdvar = lib.godot_array_front(self._gd_ptr)
158+
ret = variant_to_pyobj(ffi.addressof(gdvar))
159+
lib.godot_variant_destroy(ffi.addressof(gdvar))
160+
return ret
157161

158162
def back(self):
159-
ret = lib.godot_array_back(self._gd_ptr)
160-
return variant_to_pyobj(ffi.addressof(ret))
163+
gdvar = lib.godot_array_back(self._gd_ptr)
164+
ret = variant_to_pyobj(ffi.addressof(gdvar))
165+
lib.godot_variant_destroy(ffi.addressof(gdvar))
166+
return ret
161167

162168
def find(self, what, from_):
163169
var = pyobj_to_variant(what)
@@ -182,12 +188,16 @@ def invert(self):
182188
lib.godot_array_invert(self._gd_ptr)
183189

184190
def pop_back(self):
185-
ret = lib.godot_array_pop_back(self._gd_ptr)
186-
return variant_to_pyobj(ffi.addressof(ret))
191+
gdvar = lib.godot_array_pop_back(self._gd_ptr)
192+
ret = variant_to_pyobj(ffi.addressof(gdvar))
193+
lib.godot_variant_destroy(ffi.addressof(gdvar))
194+
return ret
187195

188196
def pop_front(self):
189-
ret = lib.godot_array_pop_front(self._gd_ptr)
190-
return variant_to_pyobj(ffi.addressof(ret))
197+
gdvar = lib.godot_array_pop_front(self._gd_ptr)
198+
ret = variant_to_pyobj(ffi.addressof(gdvar))
199+
lib.godot_variant_destroy(ffi.addressof(gdvar))
200+
return ret
191201

192202
def push_back(self, value):
193203
var = pyobj_to_variant(value)

pythonscript/embedded/godot/dictionary.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ class Dictionary(BaseBuiltinWithGDObjOwnership):
1212

1313
@staticmethod
1414
def _copy_gdobj(gdobj):
15-
cpy_gdobj = godot_dictionary_alloc()
15+
cpy_gdobj = godot_dictionary_alloc(initialized=False)
1616
lib.godot_dictionary_new_copy(cpy_gdobj, gdobj)
1717
return cpy_gdobj
1818

1919
def __init__(self, items=None, **kwargs):
2020
if not items:
21-
self._gd_ptr = godot_dictionary_alloc()
21+
self._gd_ptr = godot_dictionary_alloc(initialized=False)
2222
lib.godot_dictionary_new(self._gd_ptr)
2323
elif isinstance(items, Dictionary):
24-
self._gd_ptr = godot_dictionary_alloc()
24+
self._gd_ptr = godot_dictionary_alloc(initialized=False)
2525
lib.godot_dictionary_new_copy(self._gd_ptr, items._gd_ptr)
2626
elif isinstance(items, dict):
27-
self._gd_ptr = godot_dictionary_alloc()
27+
self._gd_ptr = godot_dictionary_alloc(initialized=False)
2828
lib.godot_dictionary_new(self._gd_ptr)
2929
for k, v in items.items():
3030
self[k] = v
@@ -59,8 +59,10 @@ def __iter__(self):
5959

6060
def __getitem__(self, key):
6161
var = pyobj_to_variant(key)
62-
retvar = lib.godot_dictionary_get(self._gd_ptr, var)
63-
return variant_to_pyobj(ffi.addressof(retvar))
62+
gdvar = lib.godot_dictionary_get(self._gd_ptr, var)
63+
ret = variant_to_pyobj(ffi.addressof(gdvar))
64+
lib.godot_variant_destroy(ffi.addressof(gdvar))
65+
return ret
6466

6567
def __setitem__(self, key, value):
6668
varkey = pyobj_to_variant(key)
@@ -76,7 +78,7 @@ def __delitem__(self, key):
7678
# Methods
7779

7880
def copy(self):
79-
gd_ptr = godot_dictionary_alloc()
81+
gd_ptr = godot_dictionary_alloc(initialized=False)
8082
lib.godot_dictionary_new_copy(gd_ptr, self._gd_ptr)
8183
return Dictionary.build_from_gdobj(gd_ptr, steal=True)
8284

pythonscript/embedded/godot/hazmat/allocator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def alloc(initialized=True):
5353
)
5454
godot_node_path_alloc = alloc_with_destructor_factory(
5555
"godot_node_path*",
56-
lambda path=godot_string_alloc(): lib.godot_node_path_new,
56+
lambda data, path=godot_string_alloc(): lib.godot_node_path_new(data, path),
5757
lib.godot_node_path_destroy,
5858
)
5959
godot_dictionary_alloc = alloc_with_destructor_factory(

pythonscript/embedded/godot/hazmat/lazy_bindings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ def bind(self, *args):
260260
methbind, self._gd_ptr, vavaargs, len(args), ffi.NULL
261261
)
262262
ret = variant_to_pyobj(ffi.addressof(varret))
263+
lib.godot_variant_destroy(ffi.addressof(varret))
263264
# print('[PY->GD] returned:', ret)
264265
return ret
265266

pythonscript/embedded/godot/hazmat/tools.py

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ def variant_to_pyobj(p_gdvar):
8686

8787
elif gdtype == lib.GODOT_VARIANT_TYPE_STRING:
8888
raw = lib.godot_variant_as_string(p_gdvar)
89-
return godot_string_to_pyobj(ffi.addressof(raw))
89+
ret = godot_string_to_pyobj(ffi.addressof(raw))
90+
lib.godot_string_destroy(ffi.addressof(raw))
91+
return ret
9092

9193
elif gdtype == lib.GODOT_VARIANT_TYPE_VECTOR2:
9294
raw = lib.godot_variant_as_vector2(p_gdvar)
@@ -129,8 +131,9 @@ def variant_to_pyobj(p_gdvar):
129131
return godot_bindings_module.Color.build_from_gdobj(raw)
130132

131133
elif gdtype == lib.GODOT_VARIANT_TYPE_NODE_PATH:
132-
raw = lib.godot_variant_as_node_path(p_gdvar)
133-
return godot_bindings_module.NodePath.build_from_gdobj(raw)
134+
p_raw = godot_node_path_alloc(initialized=False)
135+
p_raw[0] = lib.godot_variant_as_node_path(p_gdvar)
136+
return godot_bindings_module.NodePath.build_from_gdobj(p_raw, steal=True)
134137

135138
elif gdtype == lib.GODOT_VARIANT_TYPE_RID:
136139
raw = lib.godot_variant_as_rid(p_gdvar)
@@ -143,40 +146,49 @@ def variant_to_pyobj(p_gdvar):
143146
return getattr(godot_bindings_module, tmpobj.get_class())(p_raw)
144147

145148
elif gdtype == lib.GODOT_VARIANT_TYPE_DICTIONARY:
146-
raw = lib.godot_variant_as_dictionary(p_gdvar)
147-
return godot_bindings_module.Dictionary.build_from_gdobj(raw)
149+
p_raw = godot_dictionary_alloc(initialized=False)
150+
p_raw[0] = lib.godot_variant_as_dictionary(p_gdvar)
151+
return godot_bindings_module.Dictionary.build_from_gdobj(p_raw, steal=True)
148152

149153
elif gdtype == lib.GODOT_VARIANT_TYPE_ARRAY:
150-
raw = lib.godot_variant_as_array(p_gdvar)
151-
return godot_bindings_module.Array.build_from_gdobj(raw)
154+
p_raw = godot_array_alloc(initialized=False)
155+
p_raw[0] = lib.godot_variant_as_array(p_gdvar)
156+
return godot_bindings_module.Array.build_from_gdobj(p_raw, steal=True)
152157

153158
elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY:
154-
raw = lib.godot_variant_as_pool_byte_array(p_gdvar)
155-
return godot_bindings_module.PoolByteArray.build_from_gdobj(raw)
159+
p_raw = godot_pool_byte_array_alloc(initialized=False)
160+
p_raw[0] = lib.godot_variant_as_pool_byte_array(p_gdvar)
161+
return godot_bindings_module.PoolByteArray.build_from_gdobj(p_raw, steal=True)
156162

157163
elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_INT_ARRAY:
158-
raw = lib.godot_variant_as_pool_int_array(p_gdvar)
159-
return godot_bindings_module.PoolIntArray.build_from_gdobj(raw)
164+
p_raw = godot_pool_int_array_alloc(initialized=False)
165+
p_raw[0] = lib.godot_variant_as_pool_int_array(p_gdvar)
166+
return godot_bindings_module.PoolIntArray.build_from_gdobj(p_raw, steal=True)
160167

161168
elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_REAL_ARRAY:
162-
raw = lib.godot_variant_as_pool_real_array(p_gdvar)
163-
return godot_bindings_module.PoolRealArray.build_from_gdobj(raw)
169+
p_raw = godot_pool_real_array_alloc(initialized=False)
170+
p_raw[0] = lib.godot_variant_as_pool_real_array(p_gdvar)
171+
return godot_bindings_module.PoolRealArray.build_from_gdobj(p_raw, steal=True)
164172

165173
elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_STRING_ARRAY:
166-
raw = lib.godot_variant_as_pool_string_array(p_gdvar)
167-
return godot_bindings_module.PoolStringArray.build_from_gdobj(raw)
174+
p_raw = godot_pool_string_array_alloc(initialized=False)
175+
p_raw[0] = lib.godot_variant_as_pool_string_array(p_gdvar)
176+
return godot_bindings_module.PoolStringArray.build_from_gdobj(p_raw, steal=True)
168177

169178
elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_VECTOR2_ARRAY:
170-
raw = lib.godot_variant_as_pool_vector2_array(p_gdvar)
171-
return godot_bindings_module.PoolVector2Array.build_from_gdobj(raw)
179+
p_raw = godot_pool_vector2_array_alloc(initialized=False)
180+
p_raw[0] = lib.godot_variant_as_pool_vector2_array(p_gdvar)
181+
return godot_bindings_module.PoolVector2Array.build_from_gdobj(p_raw, steal=True)
172182

173183
elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_VECTOR3_ARRAY:
174-
raw = lib.godot_variant_as_pool_vector3_array(p_gdvar)
175-
return godot_bindings_module.PoolVector3Array.build_from_gdobj(raw)
184+
p_raw = godot_pool_vector3_array_alloc(initialized=False)
185+
p_raw[0] = lib.godot_variant_as_pool_vector3_array(p_gdvar)
186+
return godot_bindings_module.PoolVector3Array.build_from_gdobj(p_raw, steal=True)
176187

177188
elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_COLOR_ARRAY:
178-
raw = lib.godot_variant_as_pool_color_array(p_gdvar)
179-
return godot_bindings_module.PoolColorArray.build_from_gdobj(raw)
189+
p_raw = godot_pool_color_array_alloc(initialized=False)
190+
p_raw[0] = lib.godot_variant_as_pool_color_array(p_gdvar)
191+
return godot_bindings_module.PoolColorArray.build_from_gdobj(p_raw, steal=True)
180192

181193
else:
182194
raise TypeError(
@@ -208,7 +220,7 @@ def pyobj_to_variant(pyobj, p_gdvar=None, for_ffi_return=False):
208220
elif (isinstance(pyobj, float)):
209221
lib.godot_variant_new_real(p_gdvar, pyobj)
210222
elif (isinstance(pyobj, str)):
211-
gdstr = godot_string_alloc()
223+
gdstr = godot_string_alloc(initialized=False)
212224
lib.godot_string_new_with_wide_string(gdstr, pyobj, len(pyobj))
213225
lib.godot_variant_new_string(p_gdvar, gdstr)
214226
elif isinstance(pyobj, BaseBuiltin):
@@ -486,7 +498,7 @@ def pyobj_to_gdobj(pyobj, steal_gdobj=True):
486498
return godot_real_alloc(pyobj)
487499

488500
elif isinstance(pyobj, str):
489-
gdobj = godot_string_alloc()
501+
gdobj = godot_string_alloc(initialized=False)
490502
lib.godot_string_new_with_wide_string(gdobj, pyobj, -1)
491503
return gdobj
492504

pythonscript/embedded/godot/pool_arrays.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def _generate_pool_array(clsname, pycls, gdname, py_to_gd=None, gd_to_py=None):
141141
godot_x_new_copy = getattr(lib, "godot_%s_new_copy" % gdname)
142142

143143
def _copy_gdobj(gdobj):
144-
cpy_gdobj = godot_x_alloc()
144+
cpy_gdobj = godot_x_alloc(initialized=False)
145145
godot_x_new_copy(cpy_gdobj, gdobj)
146146
return cpy_gdobj
147147

tests/bindings/test_memory_leaks.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import pytest
2-
from contextlib import contextmanager
32

43
from godot import bindings
54
from godot.bindings import OS
65

76

8-
@contextmanager
9-
def check_memory_leak():
7+
def check_memory_leak(fn):
108
dynamic_mem_start = OS.get_dynamic_memory_usage()
119
static_mem_start = OS.get_static_memory_usage()
1210

13-
yield
11+
fn()
1412

1513
# TODO: force garbage collection on pypy
1614

@@ -44,54 +42,60 @@ def test_base_dynamic_memory_leak_check():
4442

4543

4644
def test_base_builtin_memory_leak():
47-
with check_memory_leak():
48-
45+
def fn():
4946
v = bindings.Vector3()
5047
v.x = 42
5148
v.y
5249

50+
check_memory_leak(fn)
5351

54-
def test_dictionary_memory_leak():
55-
with check_memory_leak():
5652

53+
def test_dictionary_memory_leak():
54+
def fn():
5755
v = bindings.Dictionary()
58-
v['foo'] = 42
56+
v['foo'] = OS
5957
v.update({'a': 1, 'b': 2.0, 'c': 'three'})
6058
v['foo']
61-
[x for x in v]
59+
[x for x in v.items()]
6260
del v['a']
6361

62+
check_memory_leak(fn)
6463

65-
def test_array_memory_leak():
66-
with check_memory_leak():
6764

65+
def test_array_memory_leak():
66+
def fn():
6867
v = bindings.Array()
6968
v.append('x')
7069
v += [1, 2, 3]
7170
v[0]
7271
[x for x in v]
7372

73+
check_memory_leak(fn)
7474

75-
def test_pool_int_array_memory_leak():
76-
with check_memory_leak():
7775

76+
def test_pool_int_array_memory_leak():
77+
def fn():
7878
v = bindings.PoolIntArray()
7979
v.append(42)
8080
v.resize(1000)
8181
v.pop()
8282

83+
check_memory_leak(fn)
8384

84-
def test_pool_string_array_memory_leak():
85-
with check_memory_leak():
8685

86+
def test_pool_string_array_memory_leak():
87+
def fn():
8788
v = bindings.PoolStringArray()
8889
v.append("fooo")
89-
v.resize(1000)
90+
# v.resize(1000) # TODO: when uncommenting this, the test pass...
9091
v.pop()
9192

93+
check_memory_leak(fn)
9294

93-
def test_object_memory_leak():
94-
with check_memory_leak():
9595

96+
def test_object_memory_leak():
97+
def fn():
9698
v = bindings.Node()
9799
v.free()
100+
101+
check_memory_leak(fn)

0 commit comments

Comments
 (0)