1/**************************************************************************/
2/* gdscript_function.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "gdscript_function.h"
32
33#include "gdscript.h"
34
35Variant GDScriptFunction::get_constant(int p_idx) const {
36 ERR_FAIL_INDEX_V(p_idx, constants.size(), "<errconst>");
37 return constants[p_idx];
38}
39
40StringName GDScriptFunction::get_global_name(int p_idx) const {
41 ERR_FAIL_INDEX_V(p_idx, global_names.size(), "<errgname>");
42 return global_names[p_idx];
43}
44
45struct _GDFKC {
46 int order = 0;
47 List<int> pos;
48};
49
50struct _GDFKCS {
51 int order = 0;
52 StringName id;
53 int pos = 0;
54
55 bool operator<(const _GDFKCS &p_r) const {
56 return order < p_r.order;
57 }
58};
59
60void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const {
61 int oc = 0;
62 HashMap<StringName, _GDFKC> sdmap;
63 for (const StackDebug &sd : stack_debug) {
64 if (sd.line >= p_line) {
65 break;
66 }
67
68 if (sd.added) {
69 if (!sdmap.has(sd.identifier)) {
70 _GDFKC d;
71 d.order = oc++;
72 d.pos.push_back(sd.pos);
73 sdmap[sd.identifier] = d;
74
75 } else {
76 sdmap[sd.identifier].pos.push_back(sd.pos);
77 }
78 } else {
79 ERR_CONTINUE(!sdmap.has(sd.identifier));
80
81 sdmap[sd.identifier].pos.pop_back();
82 if (sdmap[sd.identifier].pos.is_empty()) {
83 sdmap.erase(sd.identifier);
84 }
85 }
86 }
87
88 List<_GDFKCS> stackpositions;
89 for (const KeyValue<StringName, _GDFKC> &E : sdmap) {
90 _GDFKCS spp;
91 spp.id = E.key;
92 spp.order = E.value.order;
93 spp.pos = E.value.pos.back()->get();
94 stackpositions.push_back(spp);
95 }
96
97 stackpositions.sort();
98
99 for (_GDFKCS &E : stackpositions) {
100 Pair<StringName, int> p;
101 p.first = E.id;
102 p.second = E.pos;
103 r_stackvars->push_back(p);
104 }
105}
106
107GDScriptFunction::GDScriptFunction() {
108 name = "<anonymous>";
109#ifdef DEBUG_ENABLED
110 {
111 MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
112 GDScriptLanguage::get_singleton()->function_list.add(&function_list);
113 }
114#endif
115}
116
117GDScriptFunction::~GDScriptFunction() {
118 get_script()->member_functions.erase(name);
119
120 for (int i = 0; i < lambdas.size(); i++) {
121 memdelete(lambdas[i]);
122 }
123
124 for (int i = 0; i < argument_types.size(); i++) {
125 argument_types.write[i].script_type_ref = Ref<Script>();
126 }
127 return_type.script_type_ref = Ref<Script>();
128
129#ifdef DEBUG_ENABLED
130 MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
131 GDScriptLanguage::get_singleton()->function_list.remove(&function_list);
132#endif
133}
134
135/////////////////////
136
137Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
138 Variant arg;
139 r_error.error = Callable::CallError::CALL_OK;
140
141 if (p_argcount == 0) {
142 r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
143 r_error.argument = 1;
144 return Variant();
145 } else if (p_argcount == 1) {
146 //noooneee
147 } else if (p_argcount == 2) {
148 arg = *p_args[0];
149 } else {
150 Array extra_args;
151 for (int i = 0; i < p_argcount - 1; i++) {
152 extra_args.push_back(*p_args[i]);
153 }
154 arg = extra_args;
155 }
156
157 Ref<GDScriptFunctionState> self = *p_args[p_argcount - 1];
158
159 if (self.is_null()) {
160 r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
161 r_error.argument = p_argcount - 1;
162 r_error.expected = Variant::OBJECT;
163 return Variant();
164 }
165
166 return resume(arg);
167}
168
169bool GDScriptFunctionState::is_valid(bool p_extended_check) const {
170 if (function == nullptr) {
171 return false;
172 }
173
174 if (p_extended_check) {
175 MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
176
177 // Script gone?
178 if (!scripts_list.in_list()) {
179 return false;
180 }
181 // Class instance gone? (if not static function)
182 if (state.instance && !instances_list.in_list()) {
183 return false;
184 }
185 }
186
187 return true;
188}
189
190Variant GDScriptFunctionState::resume(const Variant &p_arg) {
191 ERR_FAIL_COND_V(!function, Variant());
192 {
193 MutexLock lock(GDScriptLanguage::singleton->mutex);
194
195 if (!scripts_list.in_list()) {
196#ifdef DEBUG_ENABLED
197 ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but script is gone. At script: " + state.script_path + ":" + itos(state.line));
198#else
199 return Variant();
200#endif
201 }
202 if (state.instance && !instances_list.in_list()) {
203#ifdef DEBUG_ENABLED
204 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));
205#else
206 return Variant();
207#endif
208 }
209 // Do these now to avoid locking again after the call
210 scripts_list.remove_from_list();
211 instances_list.remove_from_list();
212 }
213
214 state.result = p_arg;
215 Callable::CallError err;
216 Variant ret = function->call(nullptr, nullptr, 0, err, &state);
217
218 bool completed = true;
219
220 // If the return value is a GDScriptFunctionState reference,
221 // then the function did await again after resuming.
222 if (ret.is_ref_counted()) {
223 GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret);
224 if (gdfs && gdfs->function == function) {
225 completed = false;
226 gdfs->first_state = first_state.is_valid() ? first_state : Ref<GDScriptFunctionState>(this);
227 }
228 }
229
230 function = nullptr; //cleaned up;
231 state.result = Variant();
232
233 if (completed) {
234 if (first_state.is_valid()) {
235 first_state->emit_signal(SNAME("completed"), ret);
236 } else {
237 emit_signal(SNAME("completed"), ret);
238 }
239
240#ifdef DEBUG_ENABLED
241 if (EngineDebugger::is_active()) {
242 GDScriptLanguage::get_singleton()->exit_function();
243 }
244
245 _clear_stack();
246#endif
247 }
248
249 return ret;
250}
251
252void GDScriptFunctionState::_clear_stack() {
253 if (state.stack_size) {
254 Variant *stack = (Variant *)state.stack.ptr();
255 // The first 3 are special addresses and not copied to the state, so we skip them here.
256 for (int i = 3; i < state.stack_size; i++) {
257 stack[i].~Variant();
258 }
259 state.stack_size = 0;
260 }
261}
262
263void GDScriptFunctionState::_clear_connections() {
264 List<Object::Connection> conns;
265 get_signals_connected_to_this(&conns);
266
267 for (Object::Connection &c : conns) {
268 c.signal.disconnect(c.callable);
269 }
270}
271
272void GDScriptFunctionState::_bind_methods() {
273 ClassDB::bind_method(D_METHOD("resume", "arg"), &GDScriptFunctionState::resume, DEFVAL(Variant()));
274 ClassDB::bind_method(D_METHOD("is_valid", "extended_check"), &GDScriptFunctionState::is_valid, DEFVAL(false));
275 ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "_signal_callback", &GDScriptFunctionState::_signal_callback, MethodInfo("_signal_callback"));
276
277 ADD_SIGNAL(MethodInfo("completed", PropertyInfo(Variant::NIL, "result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
278}
279
280GDScriptFunctionState::GDScriptFunctionState() :
281 scripts_list(this),
282 instances_list(this) {
283}
284
285GDScriptFunctionState::~GDScriptFunctionState() {
286 {
287 MutexLock lock(GDScriptLanguage::singleton->mutex);
288 scripts_list.remove_from_list();
289 instances_list.remove_from_list();
290 }
291}
292