def pybind_instance_call_method(handle, p_method, p_args, p_argcount, r_error): instance = ffi.from_handle(handle) # TODO: improve this by using a dict lookup using string_name method = lib.godot_string_name_get_name(p_method) methname = godot_string_to_pyobj(ffi.addressof(method)) lib.godot_string_destroy(ffi.addressof(method)) try: meth = getattr(instance, methname) except AttributeError: r_error.error = lib.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD # TODO: Keep this object cached instead of recreating everytime return pyobj_to_variant(None, for_ffi_return=True)[0] # print('[GD->PY] Calling %s on %s ==> %s' % (methname, instance, meth)) pyargs = [variant_to_pyobj(p_args[i]) for i in range(p_argcount)] try: pyret = meth(*pyargs) ret = pyobj_to_variant(pyret, for_ffi_return=True) r_error.error = lib.GODOT_CALL_ERROR_CALL_OK # print('[GD->PY] result: %s (%s)' % (pyret, ret[0])) return ret[0] except NotImplementedError: # print('[GD->PY] not implemented !') r_error.error = lib.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD except TypeError: traceback.print_exc() # TODO: handle errors here r_error.error = lib.GODOT_CALL_ERROR_CALL_ERROR_INVALID_ARGUMENT r_error.argument = 1 r_error.expected = lib.GODOT_VARIANT_TYPE_NIL # Something bad occured, return a default None variant # TODO: Keep this object cached instead of recreating it everytime return pyobj_to_variant(None, for_ffi_return=True)[0]
def __getitem__(self, idx): if isinstance(idx, slice): return Array(list(self)[idx]) size = len(self) idx = size + idx if idx < 0 else idx if abs(idx) >= size: raise IndexError("list index out of range") 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 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
def pybind_get_signal_list(handle): # Lazily generate the list of exported properties' names cls_or_instance = ffi.from_handle(handle) cls = cls_or_instance if isinstance(cls_or_instance, type) else type(cls_or_instance) # Need to store the cached list with a per-class name to avoid shadowing # from a parent class field = '_%s__signal_raw_list' % cls.__name__ raw_list = getattr(cls, field, None) if not raw_list: # Build the list of signals, ready to be access by godot raw_list = ffi.new('godot_string[]', len(cls.__signals) + 1) for i, name in enumerate(cls.__signals.keys()): lib.godot_string_new_unicode_data(ffi.addressof(raw_list[i]), name, -1) # Last entry is an empty string lib.godot_string_new(ffi.addressof(raw_list[len(cls.__signals)])) setattr(cls, field, raw_list) return raw_list
def pybind_get_meth_list(handle): # Lazily generate the list of methods' names cls_or_instance = ffi.from_handle(handle) cls = cls_or_instance if isinstance(cls_or_instance, type) else type(cls_or_instance) # Need to store the cached list with a per-class name to avoid shadowing # from a parent class field = '_%s__meth_raw_list' % cls.__name__ raw_list = getattr(cls, field, None) if not raw_list: meths = [ k for k in dir(cls) if not k.startswith('__') and callable(getattr(cls, k)) ] raw_list = ffi.new('godot_string[]', len(meths) + 1) for i, name in enumerate(meths): lib.godot_string_new_unicode_data(ffi.addressof(raw_list[i]), name, -1) # Last entry is an empty string lib.godot_string_new(ffi.addressof(raw_list[len(meths)])) setattr(cls, field, raw_list) return raw_list
def build_from_gdobj(cls, gdobj, steal=False): # Avoid calling cls.__init__ by first instanciating a placeholder, then # overloading it __class__ to turn it into an instance of the right class ret = BuiltinInitPlaceholder() if steal: assert ffi.typeof(gdobj).kind == 'pointer' ret._gd_ptr = gdobj else: if ffi.typeof(gdobj).kind == 'pointer': ret._gd_ptr = cls._copy_gdobj(gdobj) else: ret._gd_ptr = cls._copy_gdobj(ffi.addressof(gdobj)) ret.__class__ = cls return ret
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
def pybind_profiling_get_frame_data(handle, info, info_max): print('get_frame_data') # Sort function to make sure we can display the most consuming ones sorted_and_limited = sorted( profiler.per_meth_profiling.items(), key=lambda x: -x[1].last_frame_self_time)[:info_max] for i, item in enumerate(sorted_and_limited): signature, profile = item # TODO: should be able to use lib.godot_string_new_with_wide_string directly lib.godot_string_name_new(ffi.addressof(info[i].signature), godot_string_from_pyobj(signature)) info[i].call_count = profile.last_frame_call_count info[i].total_time = int(profile.last_frame_total_time * 1e6) info[i].self_time = int(profile.last_frame_self_time * 1e6) return len(sorted_and_limited)
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
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
def back(self): ret = lib.godot_array_back(self._gd_ptr) return variant_to_pyobj(ffi.addressof(ret))
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))
def get_subname(self, idx): self._check_param_type("idx", idx, int) subname = lib.godot_node_path_get_subname(self._gd_ptr, idx) return godot_string_to_pyobj(ffi.addressof(subname))
def pop_front(self): ret = lib.godot_array_pop_front(self._gd_ptr) return variant_to_pyobj(ffi.addressof(ret))
def path(self): gd_repr = lib.godot_node_path_as_string(self._gd_ptr) return ffi.string(lib.godot_string_wide_str(ffi.addressof(gd_repr)))
def get_concatenated_subnames(self): concatenated = lib.godot_node_path_get_concatenated_subnames( self._gd_ptr) return godot_string_to_pyobj(ffi.addressof(concatenated))
def __getitem__(self, key): var = pyobj_to_variant(key) 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 _string_gd_to_py(self, value): return godot_string_to_pyobj(ffi.addressof(value))
def __repr__(self): gd_repr = lib.godot_transform2d_as_string(self._gd_ptr) raw_str = lib.godot_string_wide_str(ffi.addressof(gd_repr)) return "<%s(%s)>" % (type(self).__name__, ffi.string(raw_str))
def to_json(self): raw = lib.godot_dictionary_to_json(self._gd_ptr) return godot_string_to_pyobj(ffi.addressof(raw))
def to_html(self, with_alpha=True): gdstr = lib.godot_color_to_html(self._gd_ptr, with_alpha) return ffi.string(lib.godot_string_wide_str(ffi.addressof(gdstr)))
def variant_to_pyobj(p_gdvar): """ Convert Godot variant to regular Python object :param p_gdvar: Godot variant as ``<cdata 'struct godot_variant *'>`` (note the pointer) """ gdtype = lib.godot_variant_get_type(p_gdvar) if gdtype == lib.GODOT_VARIANT_TYPE_NIL: return None elif gdtype == lib.GODOT_VARIANT_TYPE_BOOL: return bool(lib.godot_variant_as_bool(p_gdvar)) elif gdtype == lib.GODOT_VARIANT_TYPE_INT: return int(lib.godot_variant_as_int(p_gdvar)) elif gdtype == lib.GODOT_VARIANT_TYPE_REAL: return float(lib.godot_variant_as_real(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)) elif gdtype == lib.GODOT_VARIANT_TYPE_VECTOR2: raw = lib.godot_variant_as_vector2(p_gdvar) return Vector2.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_RECT2: raw = lib.godot_variant_as_rec2(p_gdvar) return Rect2.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_VECTOR3: raw = lib.godot_variant_as_vector3(p_gdvar) return Vector3.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_TRANSFORM2D: raw = lib.godot_variant_as_transform2d(p_gdvar) return Transform2D.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_PLANE: raw = lib.godot_variant_as_plane(p_gdvar) return Plane.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_QUAT: raw = lib.godot_variant_as_quat(p_gdvar) return Quat.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_AABB: raw = lib.godot_variant_as_aabb(p_gdvar) return AABB.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_BASIS: raw = lib.godot_variant_as_basis(p_gdvar) return Basis.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_TRANSFORM: raw = lib.godot_variant_as_transform(p_gdvar) return Transform.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_COLOR: raw = lib.godot_variant_as_color(p_gdvar) return Color.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_NODE_PATH: raw = lib.godot_variant_as_node_path(p_gdvar) return NodePath.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_RID: raw = lib.godot_variant_as_rid(p_gdvar) return RID.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_OBJECT: p_raw = lib.godot_variant_as_object(p_gdvar) # TODO: optimize this tmpobj = godot_bindings_module.Object(p_raw) 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 Dictionary.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_ARRAY: raw = lib.godot_variant_as_array(p_gdvar) return Array.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY: raw = lib.godot_variant_as_pool_byte_array(p_gdvar) return PoolByteArray.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_INT_ARRAY: raw = lib.godot_variant_as_pool_int_array(p_gdvar) return PoolIntArray.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_REAL_ARRAY: raw = lib.godot_variant_as_pool_real_array(p_gdvar) return PoolRealArray.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_STRING_ARRAY: raw = lib.godot_variant_as_pool_string_array(p_gdvar) return PoolStringArray.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_VECTOR2_ARRAY: raw = lib.godot_variant_as_pool_vector2_array(p_gdvar) return PoolVector2Array.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_VECTOR3_ARRAY: raw = lib.godot_variant_as_pool_vector3_array(p_gdvar) return PoolVector3Array.build_from_gdobj(raw) elif gdtype == lib.GODOT_VARIANT_TYPE_POOL_COLOR_ARRAY: raw = lib.godot_variant_as_pool_color_array(p_gdvar) return PoolColorArray.build_from_gdobj(raw) else: raise TypeError( "Unknown Variant type `%s` (this should never happen !)" % gdtype)
def _build_script_manifest(cls): from godot.bindings import Dictionary, Array def _build_signal_info(signal): methinfo = Dictionary() methinfo['name'] = signal.name # Dummy data, only name is important here methinfo['args'] = Array() methinfo['default_args'] = Array() methinfo['return'] = None methinfo['flags'] = lib.METHOD_FLAG_FROM_SCRIPT return methinfo def _build_method_info(meth, methname): spec = inspect.getfullargspec(meth) methinfo = Dictionary() methinfo['name'] = methname # TODO: Handle classmethod/staticmethod methinfo['args'] = Array(spec.args) methinfo['default_args'] = Array() # TODO # TODO: use annotation to determine return type ? methinfo['return'] = None methinfo['flags'] = lib.METHOD_FLAG_FROM_SCRIPT methinfo['rpc_mode'] = getattr(meth, '__rpc', lib.GODOT_METHOD_RPC_MODE_DISABLED) return methinfo def _build_property_info(prop): propinfo = Dictionary() propinfo['name'] = prop.name propinfo['type'] = py_to_gd_type(prop.type) propinfo['hint'] = prop.hint propinfo['hint_string'] = prop.hint_string propinfo['usage'] = prop.usage propinfo['default_value'] = prop.default propinfo['rset_mode'] = prop.rpc return propinfo manifest = ffi.new('godot_pluginscript_script_manifest*') manifest.data = connect_handle(cls) gdname = godot_string_from_pyobj(cls.__name__) lib.godot_string_name_new(ffi.addressof(manifest.name), gdname) if cls.__bases__ and issubclass(cls.__bases__[0], BaseObject): gdbase = godot_string_from_pyobj(cls.__bases__[0].__name__) lib.godot_string_name_new(ffi.addressof(manifest.base), gdbase) manifest.is_tool = cls.__tool lib.godot_dictionary_new(ffi.addressof(manifest.member_lines)) lib.godot_array_new(ffi.addressof(manifest.methods)) methods = Array() # TODO: include inherited in exposed methods ? Expose Godot base class' ones ? # for methname in vars(cls): for methname in dir(cls): meth = getattr(cls, methname) if not inspect.isfunction(meth) or meth.__name__.startswith('__'): continue methinfo = _build_method_info(meth, methname) methods.append(methinfo) signals = Array() for signal in cls.__signals.values(): signalinfo = _build_signal_info(signal) signals.append(signalinfo) properties = Array() for prop in cls.__exported.values(): property_info = _build_property_info(prop) properties.append(property_info) lib.godot_array_new_copy(ffi.addressof(manifest.methods), methods._gd_ptr) lib.godot_array_new_copy(ffi.addressof(manifest.signals), signals._gd_ptr) lib.godot_array_new_copy(ffi.addressof(manifest.properties), properties._gd_ptr) return manifest
def pop_front(self): 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 _build_script_manifest(cls): def _build_signal_info(signal): methinfo = Dictionary() methinfo["name"] = signal.name # Dummy data, only name is important here methinfo["args"] = Array() methinfo["default_args"] = Array() methinfo["return"] = None methinfo["flags"] = lib.METHOD_FLAG_FROM_SCRIPT return methinfo def _build_method_info(meth, methname): spec = inspect.getfullargspec(meth) methinfo = Dictionary() methinfo["name"] = methname # TODO: Handle classmethod/staticmethod methinfo["args"] = Array(spec.args) methinfo["default_args"] = Array() # TODO # TODO: use annotation to determine return type ? methinfo["return"] = None methinfo["flags"] = lib.METHOD_FLAG_FROM_SCRIPT methinfo["rpc_mode"] = getattr(meth, "__rpc", lib.GODOT_METHOD_RPC_MODE_DISABLED) return methinfo def _build_property_info(prop): propinfo = Dictionary() propinfo["name"] = prop.name propinfo["type"] = py_to_gd_type(prop.type) propinfo["hint"] = prop.hint propinfo["hint_string"] = prop.hint_string propinfo["usage"] = prop.usage propinfo["default_value"] = prop.default propinfo["rset_mode"] = prop.rpc return propinfo manifest = ffi.new("godot_pluginscript_script_manifest*") manifest.data = connect_handle(cls) # TODO: should be able to use lib.godot_string_new_with_wide_string directly gdname = godot_string_from_pyobj(cls.__name__) lib.godot_string_name_new(ffi.addressof(manifest.name), gdname) if cls.__bases__: # Only one Godot parent class (checked at class definition time) godot_parent_class = next( (b for b in cls.__bases__ if issubclass(b, BaseObject))) if godot_parent_class.__dict__.get("__is_godot_native_class"): path = godot_parent_class.__name__ else: # Pluginscript wants us to return the parent as a path path = "res://%s.py" % "/".join( cls.__bases__[0].__module__.split(".")) gdbase = godot_string_from_pyobj(path) lib.godot_string_name_new(ffi.addressof(manifest.base), gdbase) manifest.is_tool = cls.__tool lib.godot_dictionary_new(ffi.addressof(manifest.member_lines)) lib.godot_array_new(ffi.addressof(manifest.methods)) methods = Array() # TODO: include inherited in exposed methods ? Expose Godot base class' ones ? # for methname in vars(cls): for methname in dir(cls): meth = getattr(cls, methname) if not inspect.isfunction(meth) or meth.__name__.startswith("__"): continue methinfo = _build_method_info(meth, methname) methods.append(methinfo) signals = Array() for signal in cls.__signals.values(): signalinfo = _build_signal_info(signal) signals.append(signalinfo) properties = Array() for prop in cls.__exported.values(): property_info = _build_property_info(prop) properties.append(property_info) lib.godot_array_new_copy(ffi.addressof(manifest.methods), methods._gd_ptr) lib.godot_array_new_copy(ffi.addressof(manifest.signals), signals._gd_ptr) lib.godot_array_new_copy(ffi.addressof(manifest.properties), properties._gd_ptr) return manifest