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 pybind_get_prop_type(handle, propname, prop_type): instance = ffi.from_handle(handle) prop = instance._exported.get(ffi.string(propname), None) if not prop: return False else: prop_type[0] = prop.gd_type return True
def pybind_set_prop(handle, propname, val): instance = ffi.from_handle(handle) try: pyval = variant_to_pyobj(val) setattr(instance, ffi.string(propname), pyval) return True except Exception: traceback.print_exc() return False
def pybind_get_prop_default_value(handle, propname, r_val): cls_or_instance = ffi.from_handle(handle) cls = cls_or_instance if isinstance(cls_or_instance, type) else type(cls_or_instance) prop = cls.__exported.get(ffi.string(propname), None) if not prop: return False pyobj_to_variant(prop.default, r_val) return True
def pybind_get_prop(handle, propname, ret): instance = ffi.from_handle(handle) try: pyret = getattr(instance, ffi.string(propname)) pyobj_to_variant(pyret, ret) return True except Exception: traceback.print_exc() return False
def pybind_instance_get_prop(instance_handle, p_name, r_ret): instance = ffi.from_handle(instance_handle) try: name = godot_string_to_pyobj(p_name) pyret = getattr(instance, name) pyobj_to_variant(pyret, r_ret) return True except Exception: traceback.print_exc() return False
def pybind_instance_set_prop(instance_handle, p_name, p_value): instance = ffi.from_handle(instance_handle) try: pyval = variant_to_pyobj(p_value) name = godot_string_to_pyobj(p_name) # print('[GD->PY] Set %s to %s (%s)' % (name, pyval, p_value)) setattr(instance, name, pyval) return True except Exception: traceback.print_exc() return False
def pybind_get_meth_info(handle, methname, r_argcount): cls_or_instance = ffi.from_handle(handle) cls = cls_or_instance if isinstance(cls_or_instance, type) else type(cls_or_instance) meth = getattr(cls, ffi.string(methname), None) if not meth: return False spec = inspect.getfullargspec(meth) # Cannot pass keyword only arguments through godot r_argcount[0] = len(spec.args) return True
def pybind_instance_notification(instance_handle, notification): # Godot's notification should call all parent `_notification` # methods (better not use `super()._notification` in those methods...) instance = ffi.from_handle(instance_handle) cls = type(instance) # TODO: cache the methods to call ? for parentcls in inspect.getmro(cls): try: parentcls.__dict__["_notification"](instance, notification) except (KeyError, NotImplementedError): pass
def pybind_get_prop_info(handle, propname, r_prop_info): cls_or_instance = ffi.from_handle(handle) cls = cls_or_instance if isinstance(cls_or_instance, type) else type(cls_or_instance) prop = cls.__exported.get(ffi.string(propname), None) if not prop: return False r_prop_info.type = prop.gd_type r_prop_info.hint = prop.gd_hint r_prop_info.name = prop.gd_name[0] r_prop_info.hint_string = prop.gd_hint_string[0] r_prop_info.usage = prop.gd_usage return True
def pybind_get_signal_info(handle, signalname, r_argcount): cls_or_instance = ffi.from_handle(handle) cls = cls_or_instance if isinstance(cls_or_instance, type) else type(cls_or_instance) signal = cls.__signals.get(signalname, None) if not signal: return False # TODO: finish this r_argcount[0] = 0 # spec = inspect.getfullargspec(signal) # # Cannot pass keyword only arguments through godot # r_argcount[0] = len(spec.args) return True
def pybind_get_rset_mode(handle, varname): cls_or_instance = ffi.from_handle(handle) cls = cls_or_instance if isinstance(cls_or_instance, type) else type(cls_or_instance) # TODO: it seems if gdstript find a method with RPC_MODE_DISABLED, it tries # to find a parent with rpc enabled... for parentcls in inspect.getmro(cls): try: mode = parentcls._exported[varname].rpc if mode != lib.GODOT_METHOD_RPC_MODE_DISABLED: return mode except (ValueError, KeyError): pass return lib.GODOT_METHOD_RPC_MODE_DISABLED
def pybind_get_rpc_mode(handle, methname): cls_or_instance = ffi.from_handle(handle) cls = cls_or_instance if isinstance(cls_or_instance, type) else type(cls_or_instance) # TODO: it seems if gdstript find a method with RPC_MODE_DISABLED, it tries # to find a parent with rpc enabled... for parentcls in inspect.getmro(cls): try: mode = parentcls.__dict__[ffi.string(methname)].__rpc if mode != lib.GODOT_METHOD_RPC_MODE_DISABLED: return mode except (KeyError, AttributeError): pass return lib.GODOT_METHOD_RPC_MODE_DISABLED
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_call_meth(handle, methname, args, argcount, ret, error): instance = ffi.from_handle(handle) try: meth = getattr(instance, ffi.string(methname)) except AttributeError: error[0] = CALL_METH_ERROR_INVALID_METHOD return # print('[GD->PY] Calling %s on %s ==> %s' % (ffi.string(methname), instance, meth)) pyargs = [variant_to_pyobj(args[i]) for i in range(argcount)] # error is an hacky int compressing Variant::CallError values try: pyret = meth(*pyargs) pyobj_to_variant(pyret, ret) error[0] = CALL_METH_OK except NotImplementedError: error[0] = CALL_METH_ERROR_INVALID_METHOD except TypeError: traceback.print_exc() error[0] = 1 | CALL_METH_ERROR_INVALID_ARGUMENT | CALL_METH_TYPE_NIL
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 pybind_instance_finish(instance_handle): instance = ffi.from_handle(instance_handle) protect_from_gc.unregister(instance)
def pybind_has_meth(handle, methname): cls_or_instance = ffi.from_handle(handle) cls = cls_or_instance if isinstance(cls_or_instance, type) else type(cls_or_instance) meth = getattr(cls, ffi.string(methname), None) return callable(meth)
def pybind_instance_init(cls_handle, gdobj): instance = ffi.from_handle(cls_handle)(gdobj) protect_from_gc.register(instance) return connect_handle(instance)
def pybind_is_tool(handle): instance = ffi.from_handle(handle) return getattr(instance, '__tool', False)
def pybind_release_instance(handle): instance = ffi.from_handle(handle) protect_from_gc.unregister(instance)
def pybind_wrap_gdobj_with_class(cls_handle, gdobj): instance = ffi.from_handle(cls_handle)(gdobj) protect_from_gc.register(instance) return connect_handle(instance)
def pybind_get_class_name(handle, r_name): cls_or_instance = ffi.from_handle(handle) cls = cls_or_instance if isinstance(cls_or_instance, type) else type(cls_or_instance) lib.godot_string_new_unicode_data(r_name, cls.__name__, -1)
def pybind_has_signal(handle, signalname): cls_or_instance = ffi.from_handle(handle) cls = cls_or_instance if isinstance(cls_or_instance, type) else type(cls_or_instance) return ffi.string(signalname) in cls.__signals