/**************************************************************************/ /* gdscript_function.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ #include "gdscript_function.h" #include "gdscript.h" Variant GDScriptFunction::get_constant(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, constants.size(), "<errconst>"); return constants[p_idx]; } StringName GDScriptFunction::get_global_name(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, global_names.size(), "<errgname>"); return global_names[p_idx]; } struct _GDFKC { int order = 0; List<int> pos; }; struct _GDFKCS { int order = 0; StringName id; int pos = 0; bool operator<(const _GDFKCS &p_r) const { return order < p_r.order; } }; void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const { int oc = 0; HashMap<StringName, _GDFKC> sdmap; for (const StackDebug &sd : stack_debug) { if (sd.line >= p_line) { break; } if (sd.added) { if (!sdmap.has(sd.identifier)) { _GDFKC d; d.order = oc++; d.pos.push_back(sd.pos); sdmap[sd.identifier] = d; } else { sdmap[sd.identifier].pos.push_back(sd.pos); } } else { ERR_CONTINUE(!sdmap.has(sd.identifier)); sdmap[sd.identifier].pos.pop_back(); if (sdmap[sd.identifier].pos.is_empty()) { sdmap.erase(sd.identifier); } } } List<_GDFKCS> stackpositions; for (const KeyValue<StringName, _GDFKC> &E : sdmap) { _GDFKCS spp; spp.id = E.key; spp.order = E.value.order; spp.pos = E.value.pos.back()->get(); stackpositions.push_back(spp); } stackpositions.sort(); for (_GDFKCS &E : stackpositions) { Pair<StringName, int> p; p.first = E.id; p.second = E.pos; r_stackvars->push_back(p); } } GDScriptFunction::GDScriptFunction() { name = "<anonymous>"; #ifdef DEBUG_ENABLED { MutexLock lock(GDScriptLanguage::get_singleton()->mutex); GDScriptLanguage::get_singleton()->function_list.add(&function_list); } #endif } GDScriptFunction::~GDScriptFunction() { get_script()->member_functions.erase(name); for (int i = 0; i < lambdas.size(); i++) { memdelete(lambdas[i]); } for (int i = 0; i < argument_types.size(); i++) { argument_types.write[i].script_type_ref = Ref<Script>(); } return_type.script_type_ref = Ref<Script>(); #ifdef DEBUG_ENABLED MutexLock lock(GDScriptLanguage::get_singleton()->mutex); GDScriptLanguage::get_singleton()->function_list.remove(&function_list); #endif } ///////////////////// Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { Variant arg; r_error.error = Callable::CallError::CALL_OK; if (p_argcount == 0) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.expected = 1; return Variant(); } else if (p_argcount == 1) { //noooneee } else if (p_argcount == 2) { arg = *p_args[0]; } else { Array extra_args; for (int i = 0; i < p_argcount - 1; i++) { extra_args.push_back(*p_args[i]); } arg = extra_args; } Ref<GDScriptFunctionState> self = *p_args[p_argcount - 1]; if (self.is_null()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = p_argcount - 1; r_error.expected = Variant::OBJECT; return Variant(); } return resume(arg); } bool GDScriptFunctionState::is_valid(bool p_extended_check) const { if (function == nullptr) { return false; } if (p_extended_check) { MutexLock lock(GDScriptLanguage::get_singleton()->mutex); // Script gone? if (!scripts_list.in_list()) { return false; } // Class instance gone? (if not static function) if (state.instance && !instances_list.in_list()) { return false; } } return true; } Variant GDScriptFunctionState::resume(const Variant &p_arg) { ERR_FAIL_NULL_V(function, Variant()); { MutexLock lock(GDScriptLanguage::singleton->mutex); if (!scripts_list.in_list()) { #ifdef DEBUG_ENABLED ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but script is gone. At script: " + state.script_path + ":" + itos(state.line)); #else return Variant(); #endif } if (state.instance && !instances_list.in_list()) { #ifdef DEBUG_ENABLED ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line)); #else return Variant(); #endif } // Do these now to avoid locking again after the call scripts_list.remove_from_list(); instances_list.remove_from_list(); } state.result = p_arg; Callable::CallError err; Variant ret = function->call(nullptr, nullptr, 0, err, &state); bool completed = true; // If the return value is a GDScriptFunctionState reference, // then the function did await again after resuming. if (ret.is_ref_counted()) { GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret); if (gdfs && gdfs->function == function) { completed = false; gdfs->first_state = first_state.is_valid() ? first_state : Ref<GDScriptFunctionState>(this); } } function = nullptr; //cleaned up; state.result = Variant(); if (completed) { if (first_state.is_valid()) { first_state->emit_signal(SNAME("completed"), ret); } else { emit_signal(SNAME("completed"), ret); } #ifdef DEBUG_ENABLED if (EngineDebugger::is_active()) { GDScriptLanguage::get_singleton()->exit_function(); } _clear_stack(); #endif } return ret; } void GDScriptFunctionState::_clear_stack() { if (state.stack_size) { Variant *stack = (Variant *)state.stack.ptr(); // The first 3 are special addresses and not copied to the state, so we skip them here. for (int i = 3; i < state.stack_size; i++) { stack[i].~Variant(); } state.stack_size = 0; } } void GDScriptFunctionState::_clear_connections() { List<Object::Connection> conns; get_signals_connected_to_this(&conns); for (Object::Connection &c : conns) { c.signal.disconnect(c.callable); } } void GDScriptFunctionState::_bind_methods() { ClassDB::bind_method(D_METHOD("resume", "arg"), &GDScriptFunctionState::resume, DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("is_valid", "extended_check"), &GDScriptFunctionState::is_valid, DEFVAL(false)); ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "_signal_callback", &GDScriptFunctionState::_signal_callback, MethodInfo("_signal_callback")); ADD_SIGNAL(MethodInfo("completed", PropertyInfo(Variant::NIL, "result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT))); } GDScriptFunctionState::GDScriptFunctionState() : scripts_list(this), instances_list(this) { } GDScriptFunctionState::~GDScriptFunctionState() { { MutexLock lock(GDScriptLanguage::singleton->mutex); scripts_list.remove_from_list(); instances_list.remove_from_list(); } }