# ------------------------------------------------------------------------------ # List of methods that should not be overloaded when they are not defined # in the class being doubled. These either break things if they are # overloaded or do not have a "super" equivalent so we can't just pass # through. const BLACKLIST = [ '_draw', '_enter_tree', '_exit_tree', '_get_minimum_size', # Nonexistent function _get_minimum_size '_get', # probably '_input', '_notification', '_physics_process', '_process', '_set', '_to_string', # nonexistant function super._to_string '_unhandled_input', '_unhandled_key_input', 'draw_mesh', # issue with one parameter, value is `Null((..), (..), (..))`` 'emit_signal', # can't handle extra parameters to be sent with signal. 'get_path', 'get_script', 'get', 'has_method', 'print_orphan_nodes' ] # ------------------------------------------------------------------------------ # Combins the meta for the method with additional information. # * flag for whether the method is local # * adds a 'default' property to all parameters that can be easily checked per # parameter # ------------------------------------------------------------------------------ class ParsedMethod: var _meta = {} var meta = _meta : get: return _meta set(val): return; var _parameters = [] var is_local = false const NO_DEFAULT = '__no__default__' func _init(metadata): _meta = metadata var start_default = _meta.args.size() - _meta.default_args.size() for i in range(_meta.args.size()): var arg = _meta.args[i] # Add a "default" property to the metadata so we don't have to do # weird default position math again. if(i >= start_default): arg['default'] = _meta.default_args[start_default - i] else: arg['default'] = NO_DEFAULT _parameters.append(arg) func is_black_listed(): return BLACKLIST.find(_meta.name) != -1 func to_s(): var s = _meta.name + "(" for i in range(_meta.args.size()): var arg = _meta.args[i] if(str(arg.default) != NO_DEFAULT): var val = str(arg.default) if(val == ''): val = '""' s += str(arg.name, ' = ', val) else: s += str(arg.name) if(i != _meta.args.size() -1): s += ', ' s += ")" return s # ------------------------------------------------------------------------------ # Doesn't know if a method is local and in super, but not sure if that will # ever matter. # ------------------------------------------------------------------------------ class ParsedScript: # All methods indexed by name. var _methods_by_name = {} var _utils = load('res://addons/gut/utils.gd').get_instance() var _script_path = null var script_path = _script_path : get: return _script_path set(val): return; var _subpath = null var subpath = null : get: return _subpath set(val): return; var _resource = null var resource = null : get: return _resource set(val): return; var _native_instance = null var is_native = false : get: return _native_instance != null set(val): return; func unreference(): if(_native_instance != null): _native_instance.free() return super() func _init(script_or_inst, inner_class=null): var to_load = script_or_inst if(_utils.is_native_class(to_load)): _resource = to_load _native_instance = to_load.new() else: if(!script_or_inst is Resource): to_load = load(script_or_inst.get_script().get_path()) _script_path = to_load.resource_path if(inner_class != null): _subpath = _find_subpath(to_load, inner_class) if(inner_class == null): _resource = to_load else: _resource = inner_class to_load = inner_class _parse_methods(to_load) func _has_flag_to_be_ignored(flags): return false # I think this is getting anything that has the 1 flag set...I think return flags & (1 << 2) == 0 && \ flags & (1 << 4) == 0 && \ flags & (1 << 6) == 0 func _print_flags(meta): print(str(meta.name, ':').rpad(30), str(meta.flags).rpad(4), ' = ', _utils.dec2bistr(meta.flags, 10)) func _get_native_methods(base_type): var to_return = [] if(base_type != null): var source = str('extends ', base_type) var inst = _utils.create_script_from_source(source).new() to_return = inst.get_method_list() if(! inst is RefCounted): inst.free() return to_return func _parse_methods(thing): var methods = [] if(is_native): methods = _native_instance.get_method_list() else: var base_type = thing.get_instance_base_type() methods = _get_native_methods(base_type) for m in methods: if(!_has_flag_to_be_ignored(m.flags)): var parsed = ParsedMethod.new(m) _methods_by_name[m.name] = parsed # _init must always be included so that we can initialize # double_tools if(m.name == '_init'): parsed.is_local = true # This loop will overwrite all entries in _methods_by_name with the local # method object so there is only ever one listing for a function with # the right "is_local" flag. if(!is_native): methods = thing.get_script_method_list() for m in methods: var parsed_method = ParsedMethod.new(m) parsed_method.is_local = true _methods_by_name[m.name] = parsed_method func _find_subpath(parent_script, inner): var const_map = parent_script.get_script_constant_map() var consts = const_map.keys() var const_idx = 0 var found = false var to_return = null while(const_idx < consts.size() and !found): var key = consts[const_idx] var const_val = const_map[key] if(typeof(const_val) == TYPE_OBJECT): if(const_val == inner): found = true to_return = key else: to_return = _find_subpath(const_val, inner) if(to_return != null): to_return = str(key, '.', to_return) found = true const_idx += 1 return to_return func get_method(name): return _methods_by_name[name] func is_method_blacklisted(m_name): if(_methods_by_name.has(m_name)): return _methods_by_name[m_name].is_black_listed() func get_super_method(name): var to_return = get_method(name) if(to_return.is_local): to_return = null return to_return func get_local_method(name): var to_return = get_method(name) if(!to_return.is_local): to_return = null return to_return func get_sorted_method_names(): var keys = _methods_by_name.keys() keys.sort() return keys func get_local_method_names(): var names = [] for method in _methods_by_name: if(_methods_by_name[method].is_local): names.append(method) return names func get_super_method_names(): var names = [] for method in _methods_by_name: if(!_methods_by_name[method].is_local): names.append(method) return names func get_local_methods(): var to_return = [] for key in _methods_by_name: var method = _methods_by_name[key] if(method.is_local): to_return.append(method) return to_return func get_super_methods(): var to_return = [] for key in _methods_by_name: var method = _methods_by_name[key] if(!method.is_local): to_return.append(method) return to_return func get_extends_text(): var text = null if(is_native): text = str("extends ", _native_instance.get_class()) else: text = str("extends '", _script_path, "'") if(_subpath != null): text += '.' + _subpath return text # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ var scripts = {} var _utils = load('res://addons/gut/utils.gd').get_instance() func _get_instance_id(thing): var inst_id = null if(_utils.is_native_class(thing)): var id_str = str(thing).replace("<", '').replace(">", '').split('#')[1] inst_id = id_str.to_int() elif(typeof(thing) == TYPE_STRING): if(FileAccess.file_exists(thing)): inst_id = load(thing).get_instance_id() else: inst_id = thing.get_instance_id() return inst_id func parse(thing, inner_thing=null): var key = -1 if(inner_thing == null): key = _get_instance_id(thing) else: key = _get_instance_id(inner_thing) var parsed = null if(key != null): if(scripts.has(key)): parsed = scripts[key] else: var obj = instance_from_id(_get_instance_id(thing)) var inner = null if(inner_thing != null): inner = instance_from_id(_get_instance_id(inner_thing)) if(obj is Resource or _utils.is_native_class(obj)): parsed = ParsedScript.new(obj, inner) scripts[key] = parsed return parsed