Skip to content

Commit

Permalink
Improve memory management
Browse files Browse the repository at this point in the history
  • Loading branch information
touilleMan committed Apr 29, 2018
1 parent e990bb7 commit f3d70f4
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 72 deletions.
52 changes: 31 additions & 21 deletions pythonscript/embedded/godot/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,40 +20,40 @@ class Array(BaseBuiltinWithGDObjOwnership):

@staticmethod
def _copy_gdobj(gdobj):
cpy_gdobj = godot_array_alloc()
cpy_gdobj = godot_array_alloc(initialized=False)
lib.godot_array_new_copy(cpy_gdobj, gdobj)
return cpy_gdobj

def __init__(self, items=()):
if not items:
self._gd_ptr = godot_array_alloc()
self._gd_ptr = godot_array_alloc(initialized=False)
lib.godot_array_new(self._gd_ptr)
elif isinstance(items, Array):
self._gd_ptr = godot_array_alloc()
self._gd_ptr = godot_array_alloc(initialized=False)
lib.godot_array_new_copy(self._gd_ptr, items._gd_ptr)
elif isinstance(items, PoolColorArray):
self._gd_ptr = godot_array_alloc()
self._gd_ptr = godot_array_alloc(initialized=False)
lib.godot_array_new_pool_color_array(self._gd_ptr, items._gd_ptr)
elif isinstance(items, PoolVector3Array):
self._gd_ptr = godot_array_alloc()
self._gd_ptr = godot_array_alloc(initialized=False)
lib.godot_array_new_pool_vector3_array(self._gd_ptr, items._gd_ptr)
elif isinstance(items, PoolVector2Array):
self._gd_ptr = godot_array_alloc()
self._gd_ptr = godot_array_alloc(initialized=False)
lib.godot_array_new_pool_vector2_array(self._gd_ptr, items._gd_ptr)
elif isinstance(items, PoolStringArray):
self._gd_ptr = godot_array_alloc()
self._gd_ptr = godot_array_alloc(initialized=False)
lib.godot_array_new_pool_string_array(self._gd_ptr, items._gd_ptr)
elif isinstance(items, PoolRealArray):
self._gd_ptr = godot_array_alloc()
self._gd_ptr = godot_array_alloc(initialized=False)
lib.godot_array_new_pool_real_array(self._gd_ptr, items._gd_ptr)
elif isinstance(items, PoolIntArray):
self._gd_ptr = godot_array_alloc()
self._gd_ptr = godot_array_alloc(initialized=False)
lib.godot_array_new_pool_int_array(self._gd_ptr, items._gd_ptr)
elif isinstance(items, PoolByteArray):
self._gd_ptr = godot_array_alloc()
self._gd_ptr = godot_array_alloc(initialized=False)
lib.godot_array_new_pool_byte_array(self._gd_ptr, items._gd_ptr)
elif hasattr(items, "__iter__") and not isinstance(items, (str, bytes)):
self._gd_ptr = godot_array_alloc()
self._gd_ptr = godot_array_alloc(initialized=False)
lib.godot_array_new(self._gd_ptr)
for x in items:
self.append(x)
Expand Down Expand Up @@ -87,8 +87,10 @@ def __getitem__(self, idx):
if abs(idx) >= size:
raise IndexError("list index out of range")

ret = lib.godot_array_get(self._gd_ptr, idx)
return variant_to_pyobj(ffi.addressof(ret))
gdvar = lib.godot_array_get(self._gd_ptr, idx)
ret = variant_to_pyobj(ffi.addressof(gdvar))
lib.godot_variant_destroy(ffi.addressof(gdvar))
return ret

def __setitem__(self, idx, value):
size = len(self)
Expand Down Expand Up @@ -152,12 +154,16 @@ def erase(self, value):
lib.godot_array_erase(self._gd_ptr, var)

def front(self):
ret = lib.godot_array_front(self._gd_ptr)
return variant_to_pyobj(ffi.addressof(ret))
gdvar = lib.godot_array_front(self._gd_ptr)
ret = variant_to_pyobj(ffi.addressof(gdvar))
lib.godot_variant_destroy(ffi.addressof(gdvar))
return ret

def back(self):
ret = lib.godot_array_back(self._gd_ptr)
return variant_to_pyobj(ffi.addressof(ret))
gdvar = lib.godot_array_back(self._gd_ptr)
ret = variant_to_pyobj(ffi.addressof(gdvar))
lib.godot_variant_destroy(ffi.addressof(gdvar))
return ret

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

def pop_back(self):
ret = lib.godot_array_pop_back(self._gd_ptr)
return variant_to_pyobj(ffi.addressof(ret))
gdvar = lib.godot_array_pop_back(self._gd_ptr)
ret = variant_to_pyobj(ffi.addressof(gdvar))
lib.godot_variant_destroy(ffi.addressof(gdvar))
return ret

def pop_front(self):
ret = lib.godot_array_pop_front(self._gd_ptr)
return variant_to_pyobj(ffi.addressof(ret))
gdvar = lib.godot_array_pop_front(self._gd_ptr)
ret = variant_to_pyobj(ffi.addressof(gdvar))
lib.godot_variant_destroy(ffi.addressof(gdvar))
return ret

def push_back(self, value):
var = pyobj_to_variant(value)
Expand Down
16 changes: 9 additions & 7 deletions pythonscript/embedded/godot/dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ class Dictionary(BaseBuiltinWithGDObjOwnership):

@staticmethod
def _copy_gdobj(gdobj):
cpy_gdobj = godot_dictionary_alloc()
cpy_gdobj = godot_dictionary_alloc(initialized=False)
lib.godot_dictionary_new_copy(cpy_gdobj, gdobj)
return cpy_gdobj

def __init__(self, items=None, **kwargs):
if not items:
self._gd_ptr = godot_dictionary_alloc()
self._gd_ptr = godot_dictionary_alloc(initialized=False)
lib.godot_dictionary_new(self._gd_ptr)
elif isinstance(items, Dictionary):
self._gd_ptr = godot_dictionary_alloc()
self._gd_ptr = godot_dictionary_alloc(initialized=False)
lib.godot_dictionary_new_copy(self._gd_ptr, items._gd_ptr)
elif isinstance(items, dict):
self._gd_ptr = godot_dictionary_alloc()
self._gd_ptr = godot_dictionary_alloc(initialized=False)
lib.godot_dictionary_new(self._gd_ptr)
for k, v in items.items():
self[k] = v
Expand Down Expand Up @@ -59,8 +59,10 @@ def __iter__(self):

def __getitem__(self, key):
var = pyobj_to_variant(key)
retvar = lib.godot_dictionary_get(self._gd_ptr, var)
return variant_to_pyobj(ffi.addressof(retvar))
gdvar = lib.godot_dictionary_get(self._gd_ptr, var)
ret = variant_to_pyobj(ffi.addressof(gdvar))
lib.godot_variant_destroy(ffi.addressof(gdvar))
return ret

def __setitem__(self, key, value):
varkey = pyobj_to_variant(key)
Expand All @@ -76,7 +78,7 @@ def __delitem__(self, key):
# Methods

def copy(self):
gd_ptr = godot_dictionary_alloc()
gd_ptr = godot_dictionary_alloc(initialized=False)
lib.godot_dictionary_new_copy(gd_ptr, self._gd_ptr)
return Dictionary.build_from_gdobj(gd_ptr, steal=True)

Expand Down
2 changes: 1 addition & 1 deletion pythonscript/embedded/godot/hazmat/allocator.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def alloc(initialized=True):
)
godot_node_path_alloc = alloc_with_destructor_factory(
"godot_node_path*",
lambda path=godot_string_alloc(): lib.godot_node_path_new,
lambda data, path=godot_string_alloc(): lib.godot_node_path_new(data, path),
lib.godot_node_path_destroy,
)
godot_dictionary_alloc = alloc_with_destructor_factory(
Expand Down
1 change: 1 addition & 0 deletions pythonscript/embedded/godot/hazmat/lazy_bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ def bind(self, *args):
methbind, self._gd_ptr, vavaargs, len(args), ffi.NULL
)
ret = variant_to_pyobj(ffi.addressof(varret))
lib.godot_variant_destroy(ffi.addressof(varret))
# print('[PY->GD] returned:', ret)
return ret

Expand Down
58 changes: 35 additions & 23 deletions pythonscript/embedded/godot/hazmat/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ def variant_to_pyobj(p_gdvar):

elif gdtype == lib.GODOT_VARIANT_TYPE_STRING:
raw = lib.godot_variant_as_string(p_gdvar)
return godot_string_to_pyobj(ffi.addressof(raw))
ret = godot_string_to_pyobj(ffi.addressof(raw))
lib.godot_string_destroy(ffi.addressof(raw))
return ret

elif gdtype == lib.GODOT_VARIANT_TYPE_VECTOR2:
raw = lib.godot_variant_as_vector2(p_gdvar)
Expand Down Expand Up @@ -129,8 +131,9 @@ def variant_to_pyobj(p_gdvar):
return godot_bindings_module.Color.build_from_gdobj(raw)

elif gdtype == lib.GODOT_VARIANT_TYPE_NODE_PATH:
raw = lib.godot_variant_as_node_path(p_gdvar)
return godot_bindings_module.NodePath.build_from_gdobj(raw)
p_raw = godot_node_path_alloc(initialized=False)
p_raw[0] = lib.godot_variant_as_node_path(p_gdvar)
return godot_bindings_module.NodePath.build_from_gdobj(p_raw, steal=True)

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

elif gdtype == lib.GODOT_VARIANT_TYPE_DICTIONARY:
raw = lib.godot_variant_as_dictionary(p_gdvar)
return godot_bindings_module.Dictionary.build_from_gdobj(raw)
p_raw = godot_dictionary_alloc(initialized=False)
p_raw[0] = lib.godot_variant_as_dictionary(p_gdvar)
return godot_bindings_module.Dictionary.build_from_gdobj(p_raw, steal=True)

elif gdtype == lib.GODOT_VARIANT_TYPE_ARRAY:
raw = lib.godot_variant_as_array(p_gdvar)
return godot_bindings_module.Array.build_from_gdobj(raw)
p_raw = godot_array_alloc(initialized=False)
p_raw[0] = lib.godot_variant_as_array(p_gdvar)
return godot_bindings_module.Array.build_from_gdobj(p_raw, steal=True)

elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY:
raw = lib.godot_variant_as_pool_byte_array(p_gdvar)
return godot_bindings_module.PoolByteArray.build_from_gdobj(raw)
p_raw = godot_pool_byte_array_alloc(initialized=False)
p_raw[0] = lib.godot_variant_as_pool_byte_array(p_gdvar)
return godot_bindings_module.PoolByteArray.build_from_gdobj(p_raw, steal=True)

elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_INT_ARRAY:
raw = lib.godot_variant_as_pool_int_array(p_gdvar)
return godot_bindings_module.PoolIntArray.build_from_gdobj(raw)
p_raw = godot_pool_int_array_alloc(initialized=False)
p_raw[0] = lib.godot_variant_as_pool_int_array(p_gdvar)
return godot_bindings_module.PoolIntArray.build_from_gdobj(p_raw, steal=True)

elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_REAL_ARRAY:
raw = lib.godot_variant_as_pool_real_array(p_gdvar)
return godot_bindings_module.PoolRealArray.build_from_gdobj(raw)
p_raw = godot_pool_real_array_alloc(initialized=False)
p_raw[0] = lib.godot_variant_as_pool_real_array(p_gdvar)
return godot_bindings_module.PoolRealArray.build_from_gdobj(p_raw, steal=True)

elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_STRING_ARRAY:
raw = lib.godot_variant_as_pool_string_array(p_gdvar)
return godot_bindings_module.PoolStringArray.build_from_gdobj(raw)
p_raw = godot_pool_string_array_alloc(initialized=False)
p_raw[0] = lib.godot_variant_as_pool_string_array(p_gdvar)
return godot_bindings_module.PoolStringArray.build_from_gdobj(p_raw, steal=True)

elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_VECTOR2_ARRAY:
raw = lib.godot_variant_as_pool_vector2_array(p_gdvar)
return godot_bindings_module.PoolVector2Array.build_from_gdobj(raw)
p_raw = godot_pool_vector2_array_alloc(initialized=False)
p_raw[0] = lib.godot_variant_as_pool_vector2_array(p_gdvar)
return godot_bindings_module.PoolVector2Array.build_from_gdobj(p_raw, steal=True)

elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_VECTOR3_ARRAY:
raw = lib.godot_variant_as_pool_vector3_array(p_gdvar)
return godot_bindings_module.PoolVector3Array.build_from_gdobj(raw)
p_raw = godot_pool_vector3_array_alloc(initialized=False)
p_raw[0] = lib.godot_variant_as_pool_vector3_array(p_gdvar)
return godot_bindings_module.PoolVector3Array.build_from_gdobj(p_raw, steal=True)

elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_COLOR_ARRAY:
raw = lib.godot_variant_as_pool_color_array(p_gdvar)
return godot_bindings_module.PoolColorArray.build_from_gdobj(raw)
p_raw = godot_pool_color_array_alloc(initialized=False)
p_raw[0] = lib.godot_variant_as_pool_color_array(p_gdvar)
return godot_bindings_module.PoolColorArray.build_from_gdobj(p_raw, steal=True)

else:
raise TypeError(
Expand Down Expand Up @@ -208,7 +220,7 @@ def pyobj_to_variant(pyobj, p_gdvar=None, for_ffi_return=False):
elif (isinstance(pyobj, float)):
lib.godot_variant_new_real(p_gdvar, pyobj)
elif (isinstance(pyobj, str)):
gdstr = godot_string_alloc()
gdstr = godot_string_alloc(initialized=False)
lib.godot_string_new_with_wide_string(gdstr, pyobj, len(pyobj))
lib.godot_variant_new_string(p_gdvar, gdstr)
elif isinstance(pyobj, BaseBuiltin):
Expand Down Expand Up @@ -486,7 +498,7 @@ def pyobj_to_gdobj(pyobj, steal_gdobj=True):
return godot_real_alloc(pyobj)

elif isinstance(pyobj, str):
gdobj = godot_string_alloc()
gdobj = godot_string_alloc(initialized=False)
lib.godot_string_new_with_wide_string(gdobj, pyobj, -1)
return gdobj

Expand Down
2 changes: 1 addition & 1 deletion pythonscript/embedded/godot/pool_arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def _generate_pool_array(clsname, pycls, gdname, py_to_gd=None, gd_to_py=None):
godot_x_new_copy = getattr(lib, "godot_%s_new_copy" % gdname)

def _copy_gdobj(gdobj):
cpy_gdobj = godot_x_alloc()
cpy_gdobj = godot_x_alloc(initialized=False)
godot_x_new_copy(cpy_gdobj, gdobj)
return cpy_gdobj

Expand Down
42 changes: 23 additions & 19 deletions tests/bindings/test_memory_leaks.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import pytest
from contextlib import contextmanager

from godot import bindings
from godot.bindings import OS


@contextmanager
def check_memory_leak():
def check_memory_leak(fn):
dynamic_mem_start = OS.get_dynamic_memory_usage()
static_mem_start = OS.get_static_memory_usage()

yield
fn()

# TODO: force garbage collection on pypy

Expand Down Expand Up @@ -44,54 +42,60 @@ def test_base_dynamic_memory_leak_check():


def test_base_builtin_memory_leak():
with check_memory_leak():

def fn():
v = bindings.Vector3()
v.x = 42
v.y

check_memory_leak(fn)

def test_dictionary_memory_leak():
with check_memory_leak():

def test_dictionary_memory_leak():
def fn():
v = bindings.Dictionary()
v['foo'] = 42
v['foo'] = OS
v.update({'a': 1, 'b': 2.0, 'c': 'three'})
v['foo']
[x for x in v]
[x for x in v.items()]
del v['a']

check_memory_leak(fn)

def test_array_memory_leak():
with check_memory_leak():

def test_array_memory_leak():
def fn():
v = bindings.Array()
v.append('x')
v += [1, 2, 3]
v[0]
[x for x in v]

check_memory_leak(fn)

def test_pool_int_array_memory_leak():
with check_memory_leak():

def test_pool_int_array_memory_leak():
def fn():
v = bindings.PoolIntArray()
v.append(42)
v.resize(1000)
v.pop()

check_memory_leak(fn)

def test_pool_string_array_memory_leak():
with check_memory_leak():

def test_pool_string_array_memory_leak():
def fn():
v = bindings.PoolStringArray()
v.append("fooo")
v.resize(1000)
# v.resize(1000) # TODO: when uncommenting this, the test pass...
v.pop()

check_memory_leak(fn)

def test_object_memory_leak():
with check_memory_leak():

def test_object_memory_leak():
def fn():
v = bindings.Node()
v.free()

check_memory_leak(fn)

0 comments on commit f3d70f4

Please sign in to comment.