def build_method(classname, meth): methname = meth['name'] # Flag METHOD_FLAG_VIRTUAL only available when compiling godot with DEBUG_METHODS_ENABLED methbind = lib.godot_method_bind_get_method(classname.encode(), methname.encode()) if meth['flags'] & lib.METHOD_FLAG_VIRTUAL or methbind == ffi.NULL: return None # def bind(self, *args): # raise NotImplementedError("Method %s.%s is virtual" % (classname, methname)) elif meth['flags'] & lib.METHOD_FLAG_VARARG: # Vararg methods are not supported by ptrcall, must use slower dynamic mode instead rettype = meth['return']['type'] fixargs_count = len(meth['args']) def bind(self, *args): # print('[PY->GD] Varargs call %s.%s (%s) on %s with %s' % (classname, methname, meth, self, args)) vaargs = [ convert_arg(meth_arg['type'], meth_arg['name'], arg, to_variant=True) for arg, meth_arg in zip(args, meth['args']) ] vaargs += [pyobj_to_variant(arg) for arg in args[fixargs_count:]] vavaargs = ffi.new("godot_variant*[]", vaargs) if vaargs else ffi.NULL # TODO: use `godot_variant_call_error` to raise exceptions varret = lib.godot_method_bind_call(methbind, self._gd_ptr, vavaargs, len(args), ffi.NULL) ret = variant_to_pyobj(ffi.addressof(varret)) # print('[PY->GD] returned:', ret) return ret else: # Use ptrcall for calling method rettype = meth['return']['type'] def bind(self, *args): if len(args) != len(meth['args']): raise TypeError( '%s() takes %s positional argument but %s were given' % (methname, len(meth['args']), len(args))) # TODO: check args number and type here (ptrcall means segfault on bad args...) # print('[PY->GD] Ptrcall %s.%s (%s) on %s with %s' % (classname, methname, meth, self, args)) raw_args = [ convert_arg(meth_arg['type'], meth_arg['name'], arg) for arg, meth_arg in zip(args, meth['args']) ] gdargs = ffi.new("void*[]", raw_args) if raw_args else ffi.NULL ret = new_uninitialized_gdobj(rettype) lib.godot_method_bind_ptrcall(methbind, self._gd_ptr, gdargs, ret) ret = gdobj_to_pyobj(rettype, ret) # print('[PY->GD] returned:', ret) return ret return bind
class ClassDB: _instance = lib.godot_global_get_singleton(b"ClassDB") _meth_instance = lib.godot_method_bind_get_method(b"_ClassDB", b"instance") _meth_get_class_list = lib.godot_method_bind_get_method( b"_ClassDB", b"get_class_list") _meth_get_method_list = lib.godot_method_bind_get_method( b"_ClassDB", b"class_get_method_list") _meth_get_parent_class = lib.godot_method_bind_get_method( b"_ClassDB", b"get_parent_class") _meth_get_property_list = lib.godot_method_bind_get_method( b"_ClassDB", b"class_get_property_list") _meth_get_property = lib.godot_method_bind_get_method( b"_ClassDB", b"class_get_property") _meth_set_property = lib.godot_method_bind_get_method( b"_ClassDB", b"class_set_property") _meth_get_integer_constant_list = lib.godot_method_bind_get_method( b"_ClassDB", b"class_get_integer_constant_list") _meth_get_integer_constant = lib.godot_method_bind_get_method( b"_ClassDB", b"class_get_integer_constant") @classmethod def get_class_list(cls): ret = godot_pool_string_array_alloc() lib.godot_method_bind_ptrcall(cls._meth_get_class_list, cls._instance, ffi.NULL, ret) # Convert Godot return into Python civilized stuff unordered = [] for i in range(lib.godot_pool_string_array_size(ret)): godot_str = lib.godot_pool_string_array_get(ret, i) raw_str = lib.godot_string_wide_str(ffi.addressof(godot_str)) unordered.append(ffi.string(raw_str)) # Order class to have a parent defined before their children classes = [] while len(unordered) != len(classes): for classname in unordered: parentname = cls.get_parent_class(classname) if not parentname or parentname in classes: if classname not in classes: classes.append(classname) return classes @classmethod def get_class_constructor(cls, classname): def constructor(self): gd_classname = godot_string_from_pyobj(classname) # TODO: alloc this on the stack (using _malloca ?) args = ffi.new("void*[]", [gd_classname]) ret = godot_variant_alloc() lib.godot_method_bind_ptrcall(cls._meth_instance, cls._instance, args, ret) objret = lib.godot_variant_as_object(ret) # Quick'n dirty fix to prevent Ressource objects from beeing automatically # freed when the variant is destroyed given it holds the only ref on it self._gd_var = ret return objret return constructor @classmethod def get_class_methods(cls, classname): methods = [] ret = godot_array_alloc() lib.godot_array_new(ret) gd_classname = godot_string_from_pyobj(classname) gd_true = godot_bool_alloc(True) args = ffi.new("void*[2]", [gd_classname, gd_true]) # 2nd arg should be false, which is what we get by not initializing it lib.godot_method_bind_ptrcall(cls._meth_get_method_list, cls._instance, args, ret) for i in range(lib.godot_array_size(ret)): var = lib.godot_array_get(ret, i) gddict = lib.godot_variant_as_dictionary(ffi.addressof(var)) methdict = Dictionary.build_from_gdobj(gddict) methods.append(methdict) return methods @classmethod def build_property_getset(cls, prop): propname = prop['name'] gd_propname = godot_string_from_pyobj(propname) def getter(self): ret = godot_variant_alloc() lib.godot_variant_new_nil(ret) args = ffi.new("void*[]", [self._gd_ptr, gd_propname]) lib.godot_method_bind_ptrcall(cls._meth_get_property, cls._instance, args, ret) return variant_to_pyobj(ret) def setter(self, value): gd_value = pyobj_to_variant(value) args = ffi.new("void*[]", [self._gd_ptr, gd_propname, gd_value]) ret = godot_variant_alloc() lib.godot_variant_new_nil(ret) lib.godot_method_bind_ptrcall(cls._meth_set_property, cls._instance, args, ret) return variant_to_pyobj(ret) return getter, setter @classmethod def get_class_properties(cls, classname): properties = [] ret = godot_array_alloc() lib.godot_array_new(ret) gd_classname = godot_string_from_pyobj(classname) gd_true = godot_bool_alloc(True) args = ffi.new("void*[2]", [gd_classname, gd_true]) # 2nd arg should be false, which what we get by not initializing it lib.godot_method_bind_ptrcall(cls._meth_get_property_list, cls._instance, args, ret) for i in range(lib.godot_array_size(ret)): var = lib.godot_array_get(ret, i) gddict = lib.godot_variant_as_dictionary(ffi.addressof(var)) propdict = Dictionary.build_from_gdobj(gddict) properties.append(propdict) return properties @classmethod def get_class_consts(cls, classname): consts = [] ret = godot_pool_string_array_alloc() lib.godot_pool_string_array_new(ret) gd_classname = godot_string_from_pyobj(classname) gd_true = godot_bool_alloc(True) args = ffi.new("void*[2]", [gd_classname, gd_true]) # 2nd arg should be false, which what we get by not initializing it lib.godot_method_bind_ptrcall(cls._meth_get_integer_constant_list, cls._instance, args, ret) for i in range(lib.godot_pool_string_array_size(ret)): godot_str = lib.godot_pool_string_array_get(ret, i) raw_str = lib.godot_string_wide_str(ffi.addressof(godot_str)) consts.append(ffi.string(raw_str)) return consts @classmethod def get_integer_constant(cls, classname, constname): ret = godot_int_alloc() gd_classname = godot_string_from_pyobj(classname) gd_constname = godot_string_from_pyobj(constname) args = ffi.new("void*[2]", [gd_classname, gd_constname]) # 2nd arg should be false, which what we get by not initializing it lib.godot_method_bind_ptrcall(cls._meth_get_integer_constant, cls._instance, args, ret) return int(ret[0]) @classmethod def get_parent_class(cls, classname): ret = godot_string_alloc() lib.godot_string_new(ret) gd_classname = godot_string_from_pyobj(classname) args = ffi.new("godot_string**", gd_classname) lib.godot_method_bind_ptrcall(cls._meth_get_parent_class, cls._instance, ffi.cast("void**", args), ret) raw_str = lib.godot_string_wide_str(ret) return ffi.string(raw_str)
def build_method(classname, meth): methname = meth["name"] # Flag METHOD_FLAG_VIRTUAL only available when compiling godot with DEBUG_METHODS_ENABLED methbind = lib.godot_method_bind_get_method(classname.encode(), methname.encode()) if meth["flags"] & lib.METHOD_FLAG_VIRTUAL or methbind == ffi.NULL: return None # def bind(self, *args): # raise NotImplementedError("Method %s.%s is virtual" % (classname, methname)) elif meth["flags"] & lib.METHOD_FLAG_VARARG: # Vararg methods are not supported by ptrcall, must use slower dynamic mode instead rettype = meth["return"]["type"] fixargs_count = len(meth["args"]) def bind(self, *args): # print('[PY->GD] Varargs call %s.%s (%s) on %s with %s' % (classname, methname, meth, self, args)) vaargs = [ convert_arg(meth_arg["type"], meth_arg["name"], arg, to_variant=True) for arg, meth_arg in zip(args, meth["args"]) ] vaargs += [pyobj_to_variant(arg) for arg in args[fixargs_count:]] vavaargs = ffi.new("godot_variant*[]", vaargs) if vaargs else ffi.NULL # TODO: use `godot_variant_call_error` to raise exceptions varret = lib.godot_method_bind_call(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 else: # Use ptrcall for calling method rettype = meth["return"]["type"] def bind(self, *args): # TODO: allow **kwargs # check number of args n_args, nm_args, nmd_args = len(args), len(meth["args"]), len( meth["default_args"]) nr_args = nm_args - nmd_args # number of required arguments if n_args < nr_args: # not enough args, raise error if nr_args - n_args == 1: raise TypeError( "%s() missing 1 required positional argument: '%s'" % (methname, meth["args"][nr_args - 1]["name"])) else: raise TypeError( "%s() missing %i required positional arguments: " % (methname, nr_args - n_args) + ", ".join( "'%s'" % (arg["name"]) for arg in meth["args"][n_args:nr_args - 1]) + " and '%s'" % (meth["args"][nr_args - 1]["name"])) if n_args > nm_args: # too many args, raise error if nmd_args == 0: raise TypeError( "%s() takes %i positional argument%s but %i were given" % (methname, nm_args, "s" if nm_args > 1 else "", n_args)) else: raise TypeError( "%s() takes from %i to %i positional arguments but %i were given" % (methname, nr_args, nm_args, n_args)) # complete missing optional args with default values diff = len(args) - len(meth["args"]) args = args + tuple(meth["default_args"][diff:]) # TODO: check args type here (ptrcall means segfault on bad args...) # print('[PY->GD] Ptrcall %s.%s (%s) on %s with %s' % (classname, methname, meth, self, args)) raw_args = [ convert_arg(meth_arg["type"], meth_arg["name"], arg) for arg, meth_arg in zip(args, meth["args"]) ] gdargs = ffi.new("void*[]", raw_args) if raw_args else ffi.NULL ret = new_uninitialized_gdobj(rettype) lib.godot_method_bind_ptrcall(methbind, self._gd_ptr, gdargs, ret) ret = gdobj_to_pyobj(rettype, ret) # print('[PY->GD] returned:', ret) return ret return bind