22358babda
This implement branch prediction macros likely() and unlikely() like in Linux. When using these macros please ensure that when you use them the condition in the branch really is very, very likely or unlikely. Think 90+% of the time. Primarily useful for error checking. (And I implement these macros for all our error checking macros now) See this article for more information: https://kernelnewbies.org/FAQ/LikelyUnlikely There are more places where these macros may make sense in renderer and physics engine. Placing them will come in another commit down the line.
1515 lines
38 KiB
C++
1515 lines
38 KiB
C++
/*************************************************************************/
|
|
/* gd_function.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
|
/* */
|
|
/* 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 "gd_function.h"
|
|
|
|
#include "gd_functions.h"
|
|
#include "gd_script.h"
|
|
#include "os/os.h"
|
|
|
|
Variant *GDFunction::_get_variant(int p_address, GDInstance *p_instance, GDScript *p_script, Variant &self, Variant *p_stack, String &r_error) const {
|
|
|
|
int address = p_address & ADDR_MASK;
|
|
|
|
//sequential table (jump table generated by compiler)
|
|
switch ((p_address & ADDR_TYPE_MASK) >> ADDR_BITS) {
|
|
|
|
case ADDR_TYPE_SELF: {
|
|
|
|
if (unlikely(!p_instance)) {
|
|
r_error = "Cannot access self without instance.";
|
|
return NULL;
|
|
}
|
|
return &self;
|
|
} break;
|
|
case ADDR_TYPE_CLASS: {
|
|
|
|
return &p_script->_static_ref;
|
|
} break;
|
|
case ADDR_TYPE_MEMBER: {
|
|
//member indexing is O(1)
|
|
if (unlikely(!p_instance)) {
|
|
r_error = "Cannot access member without instance.";
|
|
return NULL;
|
|
}
|
|
return &p_instance->members[address];
|
|
} break;
|
|
case ADDR_TYPE_CLASS_CONSTANT: {
|
|
|
|
//todo change to index!
|
|
GDScript *o = p_script;
|
|
ERR_FAIL_INDEX_V(address, _global_names_count, NULL);
|
|
const StringName *sn = &_global_names_ptr[address];
|
|
|
|
while (o) {
|
|
GDScript *s = o;
|
|
while (s) {
|
|
|
|
Map<StringName, Variant>::Element *E = s->constants.find(*sn);
|
|
if (E) {
|
|
return &E->get();
|
|
}
|
|
s = s->_base;
|
|
}
|
|
o = o->_owner;
|
|
}
|
|
|
|
ERR_EXPLAIN("GDCompiler bug..");
|
|
ERR_FAIL_V(NULL);
|
|
} break;
|
|
case ADDR_TYPE_LOCAL_CONSTANT: {
|
|
ERR_FAIL_INDEX_V(address, _constant_count, NULL);
|
|
return &_constants_ptr[address];
|
|
} break;
|
|
case ADDR_TYPE_STACK:
|
|
case ADDR_TYPE_STACK_VARIABLE: {
|
|
ERR_FAIL_INDEX_V(address, _stack_size, NULL);
|
|
return &p_stack[address];
|
|
} break;
|
|
case ADDR_TYPE_GLOBAL: {
|
|
|
|
ERR_FAIL_INDEX_V(address, GDScriptLanguage::get_singleton()->get_global_array_size(), NULL);
|
|
|
|
return &GDScriptLanguage::get_singleton()->get_global_array()[address];
|
|
} break;
|
|
case ADDR_TYPE_NIL: {
|
|
return &nil;
|
|
} break;
|
|
}
|
|
|
|
ERR_EXPLAIN("Bad Code! (Addressing Mode)");
|
|
ERR_FAIL_V(NULL);
|
|
return NULL;
|
|
}
|
|
|
|
String GDFunction::_get_call_error(const Variant::CallError &p_err, const String &p_where, const Variant **argptrs) const {
|
|
|
|
String err_text;
|
|
|
|
if (p_err.error == Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) {
|
|
int errorarg = p_err.argument;
|
|
err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(p_err.expected) + ".";
|
|
} else if (p_err.error == Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) {
|
|
err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
|
|
} else if (p_err.error == Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) {
|
|
err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
|
|
} else if (p_err.error == Variant::CallError::CALL_ERROR_INVALID_METHOD) {
|
|
err_text = "Invalid call. Nonexistent " + p_where + ".";
|
|
} else if (p_err.error == Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL) {
|
|
err_text = "Attempt to call " + p_where + " on a null instance.";
|
|
} else {
|
|
err_text = "Bug, call error: #" + itos(p_err.error);
|
|
}
|
|
|
|
return err_text;
|
|
}
|
|
|
|
static String _get_var_type(const Variant *p_type) {
|
|
|
|
String basestr;
|
|
|
|
if (p_type->get_type() == Variant::OBJECT) {
|
|
Object *bobj = *p_type;
|
|
if (!bobj) {
|
|
basestr = "null instance";
|
|
} else {
|
|
#ifdef DEBUG_ENABLED
|
|
if (ObjectDB::instance_validate(bobj)) {
|
|
if (bobj->get_script_instance())
|
|
basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")";
|
|
else
|
|
basestr = bobj->get_class();
|
|
} else {
|
|
basestr = "previously freed instance";
|
|
}
|
|
|
|
#else
|
|
basestr = "Object";
|
|
#endif
|
|
}
|
|
|
|
} else {
|
|
basestr = Variant::get_type_name(p_type->get_type());
|
|
}
|
|
|
|
return basestr;
|
|
}
|
|
|
|
Variant GDFunction::call(GDInstance *p_instance, const Variant **p_args, int p_argcount, Variant::CallError &r_err, CallState *p_state) {
|
|
|
|
if (!_code_ptr) {
|
|
|
|
return Variant();
|
|
}
|
|
|
|
r_err.error = Variant::CallError::CALL_OK;
|
|
|
|
Variant self;
|
|
Variant retvalue;
|
|
Variant *stack = NULL;
|
|
Variant **call_args;
|
|
int defarg = 0;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
//GDScriptLanguage::get_singleton()->calls++;
|
|
|
|
#endif
|
|
|
|
uint32_t alloca_size = 0;
|
|
GDScript *_class;
|
|
int ip = 0;
|
|
int line = _initial_line;
|
|
|
|
if (p_state) {
|
|
//use existing (supplied) state (yielded)
|
|
stack = (Variant *)p_state->stack.ptr();
|
|
call_args = (Variant **)&p_state->stack.ptr()[sizeof(Variant) * p_state->stack_size]; //ptr() to avoid bounds check
|
|
line = p_state->line;
|
|
ip = p_state->ip;
|
|
alloca_size = p_state->stack.size();
|
|
_class = p_state->_class;
|
|
p_instance = p_state->instance;
|
|
defarg = p_state->defarg;
|
|
self = p_state->self;
|
|
//stack[p_state->result_pos]=p_state->result; //assign stack with result
|
|
|
|
} else {
|
|
|
|
if (p_argcount != _argument_count) {
|
|
|
|
if (p_argcount > _argument_count) {
|
|
|
|
r_err.error = Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
|
|
r_err.argument = _argument_count;
|
|
|
|
return Variant();
|
|
} else if (p_argcount < _argument_count - _default_arg_count) {
|
|
|
|
r_err.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
|
|
r_err.argument = _argument_count - _default_arg_count;
|
|
return Variant();
|
|
} else {
|
|
|
|
defarg = _argument_count - p_argcount;
|
|
}
|
|
}
|
|
|
|
alloca_size = sizeof(Variant *) * _call_size + sizeof(Variant) * _stack_size;
|
|
|
|
if (alloca_size) {
|
|
|
|
uint8_t *aptr = (uint8_t *)alloca(alloca_size);
|
|
|
|
if (_stack_size) {
|
|
|
|
stack = (Variant *)aptr;
|
|
for (int i = 0; i < p_argcount; i++)
|
|
memnew_placement(&stack[i], Variant(*p_args[i]));
|
|
for (int i = p_argcount; i < _stack_size; i++)
|
|
memnew_placement(&stack[i], Variant);
|
|
} else {
|
|
stack = NULL;
|
|
}
|
|
|
|
if (_call_size) {
|
|
|
|
call_args = (Variant **)&aptr[sizeof(Variant) * _stack_size];
|
|
} else {
|
|
|
|
call_args = NULL;
|
|
}
|
|
|
|
} else {
|
|
stack = NULL;
|
|
call_args = NULL;
|
|
}
|
|
|
|
if (p_instance) {
|
|
if (p_instance->base_ref && static_cast<Reference *>(p_instance->owner)->is_referenced()) {
|
|
|
|
self = REF(static_cast<Reference *>(p_instance->owner));
|
|
} else {
|
|
self = p_instance->owner;
|
|
}
|
|
_class = p_instance->script.ptr();
|
|
} else {
|
|
_class = _script;
|
|
}
|
|
}
|
|
|
|
String err_text;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
if (ScriptDebugger::get_singleton())
|
|
GDScriptLanguage::get_singleton()->enter_function(p_instance, this, stack, &ip, &line);
|
|
|
|
#define GD_ERR_BREAK(m_cond) ERR_BREAK(m_cond)
|
|
|
|
#define CHECK_SPACE(m_space) \
|
|
ERR_BREAK((ip + m_space) > _code_size)
|
|
|
|
#define GET_VARIANT_PTR(m_v, m_code_ofs) \
|
|
Variant *m_v; \
|
|
m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, _class, self, stack, err_text); \
|
|
if (unlikely(!m_v)) \
|
|
break;
|
|
|
|
#else
|
|
#define GD_ERR_BREAK(m_cond)
|
|
#define CHECK_SPACE(m_space)
|
|
#define GET_VARIANT_PTR(m_v, m_code_ofs) \
|
|
Variant *m_v; \
|
|
m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, _class, self, stack, err_text);
|
|
|
|
#endif
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
uint64_t function_start_time = 0;
|
|
uint64_t function_call_time = 0;
|
|
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
function_start_time = OS::get_singleton()->get_ticks_usec();
|
|
function_call_time = 0;
|
|
profile.call_count++;
|
|
profile.frame_call_count++;
|
|
}
|
|
#endif
|
|
bool exit_ok = false;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
while (ip < _code_size) {
|
|
int last_opcode = _code_ptr[ip];
|
|
#else
|
|
while (true) {
|
|
#endif
|
|
|
|
switch (_code_ptr[ip]) {
|
|
|
|
case OPCODE_OPERATOR: {
|
|
|
|
CHECK_SPACE(5);
|
|
|
|
bool valid;
|
|
Variant::Operator op = (Variant::Operator)_code_ptr[ip + 1];
|
|
GD_ERR_BREAK(op >= Variant::OP_MAX);
|
|
|
|
GET_VARIANT_PTR(a, 2);
|
|
GET_VARIANT_PTR(b, 3);
|
|
GET_VARIANT_PTR(dst, 4);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
Variant ret;
|
|
Variant::evaluate(op, *a, *b, ret, valid);
|
|
#else
|
|
Variant::evaluate(op, *a, *b, *dst, valid);
|
|
#endif
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
|
|
if (ret.get_type() == Variant::STRING) {
|
|
//return a string when invalid with the error
|
|
err_text = ret;
|
|
err_text += " in operator '" + Variant::get_operator_name(op) + "'.";
|
|
} else {
|
|
err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'.";
|
|
}
|
|
break;
|
|
}
|
|
*dst = ret;
|
|
#endif
|
|
ip += 5;
|
|
continue;
|
|
}
|
|
case OPCODE_EXTENDS_TEST: {
|
|
|
|
CHECK_SPACE(4);
|
|
|
|
GET_VARIANT_PTR(a, 1);
|
|
GET_VARIANT_PTR(b, 2);
|
|
GET_VARIANT_PTR(dst, 3);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (a->get_type() != Variant::OBJECT || a->operator Object *() == NULL) {
|
|
|
|
err_text = "Left operand of 'is' is not an instance of anything.";
|
|
break;
|
|
}
|
|
if (b->get_type() != Variant::OBJECT || b->operator Object *() == NULL) {
|
|
|
|
err_text = "Right operand of 'is' is not a class.";
|
|
break;
|
|
}
|
|
#endif
|
|
Object *obj_A = *a;
|
|
Object *obj_B = *b;
|
|
|
|
GDScript *scr_B = Object::cast_to<GDScript>(obj_B);
|
|
|
|
bool extends_ok = false;
|
|
|
|
if (scr_B) {
|
|
//if B is a script, the only valid condition is that A has an instance which inherits from the script
|
|
//in other situation, this shoul return false.
|
|
|
|
if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) {
|
|
|
|
GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr());
|
|
//bool found=false;
|
|
while (cmp) {
|
|
|
|
if (cmp == scr_B) {
|
|
//inherits from script, all ok
|
|
extends_ok = true;
|
|
break;
|
|
}
|
|
|
|
cmp = cmp->_base;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
GDNativeClass *nc = Object::cast_to<GDNativeClass>(obj_B);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (!nc) {
|
|
|
|
err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "').";
|
|
break;
|
|
}
|
|
#endif
|
|
extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name());
|
|
}
|
|
|
|
*dst = extends_ok;
|
|
ip += 4;
|
|
continue;
|
|
}
|
|
case OPCODE_SET: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(dst, 1);
|
|
GET_VARIANT_PTR(index, 2);
|
|
GET_VARIANT_PTR(value, 3);
|
|
|
|
bool valid;
|
|
dst->set(*index, *value, &valid);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
String v = index->operator String();
|
|
if (v != "") {
|
|
v = "'" + v + "'";
|
|
} else {
|
|
v = "of type '" + _get_var_type(index) + "'";
|
|
}
|
|
err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "').";
|
|
break;
|
|
}
|
|
#endif
|
|
ip += 4;
|
|
continue;
|
|
}
|
|
case OPCODE_GET: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(src, 1);
|
|
GET_VARIANT_PTR(index, 2);
|
|
GET_VARIANT_PTR(dst, 3);
|
|
|
|
bool valid;
|
|
#ifdef DEBUG_ENABLED
|
|
//allow better error message in cases where src and dst are the same stack position
|
|
Variant ret = src->get(*index, &valid);
|
|
#else
|
|
*dst = src->get(*index, &valid);
|
|
|
|
#endif
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
String v = index->operator String();
|
|
if (v != "") {
|
|
v = "'" + v + "'";
|
|
} else {
|
|
v = "of type '" + _get_var_type(index) + "'";
|
|
}
|
|
err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "').";
|
|
break;
|
|
}
|
|
*dst = ret;
|
|
#endif
|
|
ip += 4;
|
|
continue;
|
|
}
|
|
case OPCODE_SET_NAMED: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(dst, 1);
|
|
GET_VARIANT_PTR(value, 3);
|
|
|
|
int indexname = _code_ptr[ip + 2];
|
|
|
|
GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
|
|
const StringName *index = &_global_names_ptr[indexname];
|
|
|
|
bool valid;
|
|
dst->set_named(*index, *value, &valid);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
String err_type;
|
|
err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "').";
|
|
break;
|
|
}
|
|
#endif
|
|
ip += 4;
|
|
continue;
|
|
}
|
|
case OPCODE_GET_NAMED: {
|
|
|
|
CHECK_SPACE(4);
|
|
|
|
GET_VARIANT_PTR(src, 1);
|
|
GET_VARIANT_PTR(dst, 3);
|
|
|
|
int indexname = _code_ptr[ip + 2];
|
|
|
|
GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
|
|
const StringName *index = &_global_names_ptr[indexname];
|
|
|
|
bool valid;
|
|
#ifdef DEBUG_ENABLED
|
|
//allow better error message in cases where src and dst are the same stack position
|
|
Variant ret = src->get_named(*index, &valid);
|
|
|
|
#else
|
|
*dst = src->get_named(*index, &valid);
|
|
#endif
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
if (src->has_method(*index)) {
|
|
err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "'). Did you mean '." + index->operator String() + "()' ?";
|
|
} else {
|
|
err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "').";
|
|
}
|
|
break;
|
|
}
|
|
*dst = ret;
|
|
#endif
|
|
ip += 4;
|
|
continue;
|
|
}
|
|
case OPCODE_SET_MEMBER: {
|
|
|
|
CHECK_SPACE(3);
|
|
int indexname = _code_ptr[ip + 1];
|
|
GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
|
|
const StringName *index = &_global_names_ptr[indexname];
|
|
GET_VARIANT_PTR(src, 2);
|
|
|
|
bool valid;
|
|
bool ok = ClassDB::set_property(p_instance->owner, *index, *src, &valid);
|
|
#ifdef DEBUG_ENABLED
|
|
if (!ok) {
|
|
err_text = "Internal error setting property: " + String(*index);
|
|
break;
|
|
} else if (!valid) {
|
|
err_text = "Error setting property '" + String(*index) + "' with value of type " + Variant::get_type_name(src->get_type()) + ".";
|
|
break;
|
|
}
|
|
#endif
|
|
ip += 3;
|
|
continue;
|
|
}
|
|
case OPCODE_GET_MEMBER: {
|
|
|
|
CHECK_SPACE(3);
|
|
int indexname = _code_ptr[ip + 1];
|
|
GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
|
|
const StringName *index = &_global_names_ptr[indexname];
|
|
GET_VARIANT_PTR(dst, 2);
|
|
bool ok = ClassDB::get_property(p_instance->owner, *index, *dst);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (!ok) {
|
|
err_text = "Internal error getting property: " + String(*index);
|
|
break;
|
|
}
|
|
#endif
|
|
ip += 3;
|
|
continue;
|
|
}
|
|
case OPCODE_ASSIGN: {
|
|
|
|
CHECK_SPACE(3);
|
|
GET_VARIANT_PTR(dst, 1);
|
|
GET_VARIANT_PTR(src, 2);
|
|
|
|
*dst = *src;
|
|
|
|
ip += 3;
|
|
continue;
|
|
}
|
|
case OPCODE_ASSIGN_TRUE: {
|
|
|
|
CHECK_SPACE(2);
|
|
GET_VARIANT_PTR(dst, 1);
|
|
|
|
*dst = true;
|
|
|
|
ip += 2;
|
|
continue;
|
|
}
|
|
case OPCODE_ASSIGN_FALSE: {
|
|
|
|
CHECK_SPACE(2);
|
|
GET_VARIANT_PTR(dst, 1);
|
|
|
|
*dst = false;
|
|
|
|
ip += 2;
|
|
continue;
|
|
}
|
|
case OPCODE_CONSTRUCT: {
|
|
|
|
CHECK_SPACE(2);
|
|
Variant::Type t = Variant::Type(_code_ptr[ip + 1]);
|
|
int argc = _code_ptr[ip + 2];
|
|
CHECK_SPACE(argc + 2);
|
|
Variant **argptrs = call_args;
|
|
for (int i = 0; i < argc; i++) {
|
|
GET_VARIANT_PTR(v, 3 + i);
|
|
argptrs[i] = v;
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst, 3 + argc);
|
|
Variant::CallError err;
|
|
*dst = Variant::construct(t, (const Variant **)argptrs, argc, err);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (err.error != Variant::CallError::CALL_OK) {
|
|
|
|
err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
ip += 4 + argc;
|
|
//construct a basic type
|
|
continue;
|
|
}
|
|
case OPCODE_CONSTRUCT_ARRAY: {
|
|
|
|
CHECK_SPACE(1);
|
|
int argc = _code_ptr[ip + 1];
|
|
Array array; //arrays are always shared
|
|
array.resize(argc);
|
|
CHECK_SPACE(argc + 2);
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
GET_VARIANT_PTR(v, 2 + i);
|
|
array[i] = *v;
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst, 2 + argc);
|
|
|
|
*dst = array;
|
|
|
|
ip += 3 + argc;
|
|
continue;
|
|
}
|
|
case OPCODE_CONSTRUCT_DICTIONARY: {
|
|
|
|
CHECK_SPACE(1);
|
|
int argc = _code_ptr[ip + 1];
|
|
Dictionary dict; //arrays are always shared
|
|
|
|
CHECK_SPACE(argc * 2 + 2);
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
|
|
GET_VARIANT_PTR(k, 2 + i * 2 + 0);
|
|
GET_VARIANT_PTR(v, 2 + i * 2 + 1);
|
|
dict[*k] = *v;
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst, 2 + argc * 2);
|
|
|
|
*dst = dict;
|
|
|
|
ip += 3 + argc * 2;
|
|
continue;
|
|
}
|
|
case OPCODE_CALL_RETURN:
|
|
case OPCODE_CALL: {
|
|
|
|
CHECK_SPACE(4);
|
|
bool call_ret = _code_ptr[ip] == OPCODE_CALL_RETURN;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
GET_VARIANT_PTR(base, 2);
|
|
int nameg = _code_ptr[ip + 3];
|
|
|
|
GD_ERR_BREAK(nameg < 0 || nameg >= _global_names_count);
|
|
const StringName *methodname = &_global_names_ptr[nameg];
|
|
|
|
GD_ERR_BREAK(argc < 0);
|
|
ip += 4;
|
|
CHECK_SPACE(argc + 1);
|
|
Variant **argptrs = call_args;
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
GET_VARIANT_PTR(v, i);
|
|
argptrs[i] = v;
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
uint64_t call_time = 0;
|
|
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
call_time = OS::get_singleton()->get_ticks_usec();
|
|
}
|
|
|
|
#endif
|
|
Variant::CallError err;
|
|
if (call_ret) {
|
|
|
|
GET_VARIANT_PTR(ret, argc);
|
|
base->call_ptr(*methodname, (const Variant **)argptrs, argc, ret, err);
|
|
} else {
|
|
|
|
base->call_ptr(*methodname, (const Variant **)argptrs, argc, NULL, err);
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
|
|
}
|
|
|
|
if (err.error != Variant::CallError::CALL_OK) {
|
|
|
|
String methodstr = *methodname;
|
|
String basestr = _get_var_type(base);
|
|
|
|
if (methodstr == "call") {
|
|
if (argc >= 1) {
|
|
methodstr = String(*argptrs[0]) + " (via call)";
|
|
if (err.error == Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) {
|
|
err.argument -= 1;
|
|
}
|
|
}
|
|
} else if (methodstr == "free") {
|
|
|
|
if (err.error == Variant::CallError::CALL_ERROR_INVALID_METHOD) {
|
|
|
|
if (base->is_ref()) {
|
|
err_text = "Attempted to free a reference.";
|
|
break;
|
|
} else if (base->get_type() == Variant::OBJECT) {
|
|
|
|
err_text = "Attempted to free a locked object (calling or emitting).";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
//_call_func(NULL,base,*methodname,ip,argc,p_instance,stack);
|
|
ip += argc + 1;
|
|
continue;
|
|
}
|
|
case OPCODE_CALL_BUILT_IN: {
|
|
|
|
CHECK_SPACE(4);
|
|
|
|
GDFunctions::Function func = GDFunctions::Function(_code_ptr[ip + 1]);
|
|
int argc = _code_ptr[ip + 2];
|
|
GD_ERR_BREAK(argc < 0);
|
|
|
|
ip += 3;
|
|
CHECK_SPACE(argc + 1);
|
|
Variant **argptrs = call_args;
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
GET_VARIANT_PTR(v, i);
|
|
argptrs[i] = v;
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst, argc);
|
|
|
|
Variant::CallError err;
|
|
|
|
GDFunctions::call(func, (const Variant **)argptrs, argc, *dst, err);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (err.error != Variant::CallError::CALL_OK) {
|
|
|
|
String methodstr = GDFunctions::get_func_name(func);
|
|
if (dst->get_type() == Variant::STRING) {
|
|
//call provided error string
|
|
err_text = "Error calling built-in function '" + methodstr + "': " + String(*dst);
|
|
} else {
|
|
err_text = _get_call_error(err, "built-in function '" + methodstr + "'", (const Variant **)argptrs);
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
ip += argc + 1;
|
|
continue;
|
|
}
|
|
case OPCODE_CALL_SELF: {
|
|
|
|
break;
|
|
}
|
|
case OPCODE_CALL_SELF_BASE: {
|
|
|
|
CHECK_SPACE(2);
|
|
int self_fun = _code_ptr[ip + 1];
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (self_fun < 0 || self_fun >= _global_names_count) {
|
|
|
|
err_text = "compiler bug, function name not found";
|
|
break;
|
|
}
|
|
#endif
|
|
const StringName *methodname = &_global_names_ptr[self_fun];
|
|
|
|
int argc = _code_ptr[ip + 2];
|
|
|
|
CHECK_SPACE(2 + argc + 1);
|
|
|
|
Variant **argptrs = call_args;
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
GET_VARIANT_PTR(v, i + 3);
|
|
argptrs[i] = v;
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst, argc + 3);
|
|
|
|
const GDScript *gds = _script;
|
|
|
|
const Map<StringName, GDFunction *>::Element *E = NULL;
|
|
while (gds->base.ptr()) {
|
|
gds = gds->base.ptr();
|
|
E = gds->member_functions.find(*methodname);
|
|
if (E)
|
|
break;
|
|
}
|
|
|
|
Variant::CallError err;
|
|
|
|
if (E) {
|
|
|
|
*dst = E->get()->call(p_instance, (const Variant **)argptrs, argc, err);
|
|
} else if (gds->native.ptr()) {
|
|
|
|
if (*methodname != GDScriptLanguage::get_singleton()->strings._init) {
|
|
|
|
MethodBind *mb = ClassDB::get_method(gds->native->get_name(), *methodname);
|
|
if (!mb) {
|
|
err.error = Variant::CallError::CALL_ERROR_INVALID_METHOD;
|
|
} else {
|
|
*dst = mb->call(p_instance->owner, (const Variant **)argptrs, argc, err);
|
|
}
|
|
} else {
|
|
err.error = Variant::CallError::CALL_OK;
|
|
}
|
|
} else {
|
|
|
|
if (*methodname != GDScriptLanguage::get_singleton()->strings._init) {
|
|
err.error = Variant::CallError::CALL_ERROR_INVALID_METHOD;
|
|
} else {
|
|
err.error = Variant::CallError::CALL_OK;
|
|
}
|
|
}
|
|
|
|
if (err.error != Variant::CallError::CALL_OK) {
|
|
|
|
String methodstr = *methodname;
|
|
err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs);
|
|
|
|
break;
|
|
}
|
|
|
|
ip += 4 + argc;
|
|
continue;
|
|
}
|
|
case OPCODE_YIELD:
|
|
case OPCODE_YIELD_SIGNAL: {
|
|
|
|
int ipofs = 1;
|
|
if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) {
|
|
CHECK_SPACE(4);
|
|
ipofs += 2;
|
|
} else {
|
|
CHECK_SPACE(2);
|
|
}
|
|
|
|
Ref<GDFunctionState> gdfs = memnew(GDFunctionState);
|
|
gdfs->function = this;
|
|
|
|
gdfs->state.stack.resize(alloca_size);
|
|
//copy variant stack
|
|
for (int i = 0; i < _stack_size; i++) {
|
|
memnew_placement(&gdfs->state.stack[sizeof(Variant) * i], Variant(stack[i]));
|
|
}
|
|
gdfs->state.stack_size = _stack_size;
|
|
gdfs->state.self = self;
|
|
gdfs->state.alloca_size = alloca_size;
|
|
gdfs->state._class = _class;
|
|
gdfs->state.ip = ip + ipofs;
|
|
gdfs->state.line = line;
|
|
gdfs->state.instance_id = (p_instance && p_instance->get_owner()) ? p_instance->get_owner()->get_instance_id() : 0;
|
|
gdfs->state.script_id = _class->get_instance_id();
|
|
//gdfs->state.result_pos=ip+ipofs-1;
|
|
gdfs->state.defarg = defarg;
|
|
gdfs->state.instance = p_instance;
|
|
gdfs->function = this;
|
|
|
|
retvalue = gdfs;
|
|
|
|
if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) {
|
|
//do the oneshot connect
|
|
GET_VARIANT_PTR(argobj, 1);
|
|
GET_VARIANT_PTR(argname, 2);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (argobj->get_type() != Variant::OBJECT) {
|
|
err_text = "First argument of yield() not of type object.";
|
|
break;
|
|
}
|
|
if (argname->get_type() != Variant::STRING) {
|
|
err_text = "Second argument of yield() not a string (for signal name).";
|
|
break;
|
|
}
|
|
#endif
|
|
Object *obj = argobj->operator Object *();
|
|
String signal = argname->operator String();
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
if (!obj) {
|
|
err_text = "First argument of yield() is null.";
|
|
break;
|
|
}
|
|
if (ScriptDebugger::get_singleton()) {
|
|
if (!ObjectDB::instance_validate(obj)) {
|
|
err_text = "First argument of yield() is a previously freed instance.";
|
|
break;
|
|
}
|
|
}
|
|
if (signal.length() == 0) {
|
|
|
|
err_text = "Second argument of yield() is an empty string (for signal name).";
|
|
break;
|
|
}
|
|
|
|
#endif
|
|
Error err = obj->connect(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT);
|
|
#ifdef DEBUG_ENABLED
|
|
if (err != OK) {
|
|
err_text = "Error connecting to signal: " + signal + " during yield().";
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
exit_ok = true;
|
|
break;
|
|
}
|
|
case OPCODE_YIELD_RESUME: {
|
|
|
|
CHECK_SPACE(2);
|
|
#ifdef DEBUG_ENABLED
|
|
if (!p_state) {
|
|
err_text = ("Invalid Resume (bug?)");
|
|
break;
|
|
}
|
|
#endif
|
|
GET_VARIANT_PTR(result, 1);
|
|
*result = p_state->result;
|
|
ip += 2;
|
|
continue;
|
|
}
|
|
case OPCODE_JUMP: {
|
|
|
|
CHECK_SPACE(2);
|
|
int to = _code_ptr[ip + 1];
|
|
|
|
GD_ERR_BREAK(to < 0 || to > _code_size);
|
|
ip = to;
|
|
continue;
|
|
}
|
|
case OPCODE_JUMP_IF: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(test, 1);
|
|
|
|
bool result = test->booleanize();
|
|
|
|
if (result) {
|
|
int to = _code_ptr[ip + 2];
|
|
GD_ERR_BREAK(to < 0 || to > _code_size);
|
|
ip = to;
|
|
continue;
|
|
}
|
|
ip += 3;
|
|
continue;
|
|
}
|
|
case OPCODE_JUMP_IF_NOT: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(test, 1);
|
|
|
|
bool result = test->booleanize();
|
|
|
|
if (!result) {
|
|
int to = _code_ptr[ip + 2];
|
|
GD_ERR_BREAK(to < 0 || to > _code_size);
|
|
ip = to;
|
|
continue;
|
|
}
|
|
ip += 3;
|
|
continue;
|
|
}
|
|
case OPCODE_JUMP_TO_DEF_ARGUMENT: {
|
|
|
|
CHECK_SPACE(2);
|
|
ip = _default_arg_ptr[defarg];
|
|
continue;
|
|
}
|
|
case OPCODE_RETURN: {
|
|
|
|
CHECK_SPACE(2);
|
|
GET_VARIANT_PTR(r, 1);
|
|
retvalue = *r;
|
|
exit_ok = true;
|
|
break;
|
|
}
|
|
case OPCODE_ITERATE_BEGIN: {
|
|
|
|
CHECK_SPACE(8); //space for this a regular iterate
|
|
|
|
GET_VARIANT_PTR(counter, 1);
|
|
GET_VARIANT_PTR(container, 2);
|
|
|
|
bool valid;
|
|
if (!container->iter_init(*counter, valid)) {
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
err_text = "Unable to iterate on object of type " + Variant::get_type_name(container->get_type()) + "'.";
|
|
break;
|
|
}
|
|
#endif
|
|
int jumpto = _code_ptr[ip + 3];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
continue;
|
|
}
|
|
GET_VARIANT_PTR(iterator, 4);
|
|
|
|
*iterator = container->iter_get(*counter, valid);
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
err_text = "Unable to obtain iterator object of type " + Variant::get_type_name(container->get_type()) + "'.";
|
|
break;
|
|
}
|
|
#endif
|
|
ip += 5; //skip regular iterate which is always next
|
|
continue;
|
|
}
|
|
case OPCODE_ITERATE: {
|
|
|
|
CHECK_SPACE(4);
|
|
|
|
GET_VARIANT_PTR(counter, 1);
|
|
GET_VARIANT_PTR(container, 2);
|
|
|
|
bool valid;
|
|
if (!container->iter_next(*counter, valid)) {
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
err_text = "Unable to iterate on object of type " + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?).";
|
|
break;
|
|
}
|
|
#endif
|
|
int jumpto = _code_ptr[ip + 3];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
continue;
|
|
}
|
|
GET_VARIANT_PTR(iterator, 4);
|
|
|
|
*iterator = container->iter_get(*counter, valid);
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
err_text = "Unable to obtain iterator object of type " + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?).";
|
|
break;
|
|
}
|
|
#endif
|
|
ip += 5; //loop again
|
|
continue;
|
|
}
|
|
case OPCODE_ASSERT: {
|
|
CHECK_SPACE(2);
|
|
GET_VARIANT_PTR(test, 1);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
bool result = test->booleanize();
|
|
|
|
if (!result) {
|
|
|
|
err_text = "Assertion failed.";
|
|
break;
|
|
}
|
|
|
|
#endif
|
|
ip += 2;
|
|
continue;
|
|
}
|
|
case OPCODE_BREAKPOINT: {
|
|
#ifdef DEBUG_ENABLED
|
|
if (ScriptDebugger::get_singleton()) {
|
|
GDScriptLanguage::get_singleton()->debug_break("Breakpoint Statement", true);
|
|
}
|
|
#endif
|
|
ip += 1;
|
|
continue;
|
|
}
|
|
case OPCODE_LINE: {
|
|
CHECK_SPACE(2);
|
|
|
|
line = _code_ptr[ip + 1];
|
|
ip += 2;
|
|
|
|
if (ScriptDebugger::get_singleton()) {
|
|
// line
|
|
bool do_break = false;
|
|
|
|
if (ScriptDebugger::get_singleton()->get_lines_left() > 0) {
|
|
|
|
if (ScriptDebugger::get_singleton()->get_depth() <= 0)
|
|
ScriptDebugger::get_singleton()->set_lines_left(ScriptDebugger::get_singleton()->get_lines_left() - 1);
|
|
if (ScriptDebugger::get_singleton()->get_lines_left() <= 0)
|
|
do_break = true;
|
|
}
|
|
|
|
if (ScriptDebugger::get_singleton()->is_breakpoint(line, source))
|
|
do_break = true;
|
|
|
|
if (do_break) {
|
|
GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true);
|
|
}
|
|
|
|
ScriptDebugger::get_singleton()->line_poll();
|
|
}
|
|
continue;
|
|
}
|
|
case OPCODE_END: {
|
|
|
|
exit_ok = true;
|
|
break;
|
|
}
|
|
default: {
|
|
|
|
err_text = "Illegal opcode " + itos(_code_ptr[ip]) + " at address " + itos(ip);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (exit_ok)
|
|
break;
|
|
//error
|
|
// function, file, line, error, explanation
|
|
String err_file;
|
|
if (p_instance)
|
|
err_file = p_instance->script->path;
|
|
else if (_class)
|
|
err_file = _class->path;
|
|
if (err_file == "")
|
|
err_file = "<built-in>";
|
|
String err_func = name;
|
|
if (p_instance && p_instance->script->name != "")
|
|
err_func = p_instance->script->name + "." + err_func;
|
|
int err_line = line;
|
|
if (err_text == "") {
|
|
err_text = "Internal Script Error! - opcode #" + itos(last_opcode) + " (report please).";
|
|
}
|
|
|
|
if (!GDScriptLanguage::get_singleton()->debug_break(err_text, false)) {
|
|
// debugger break did not happen
|
|
|
|
_err_print_error(err_func.utf8().get_data(), err_file.utf8().get_data(), err_line, err_text.utf8().get_data(), ERR_HANDLER_SCRIPT);
|
|
}
|
|
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time;
|
|
profile.total_time += time_taken;
|
|
profile.self_time += time_taken - function_call_time;
|
|
profile.frame_total_time += time_taken;
|
|
profile.frame_self_time += time_taken - function_call_time;
|
|
GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
|
|
}
|
|
|
|
#endif
|
|
if (ScriptDebugger::get_singleton())
|
|
GDScriptLanguage::get_singleton()->exit_function();
|
|
|
|
if (_stack_size) {
|
|
//free stack
|
|
for (int i = 0; i < _stack_size; i++)
|
|
stack[i].~Variant();
|
|
}
|
|
|
|
return retvalue;
|
|
}
|
|
|
|
const int *GDFunction::get_code() const {
|
|
|
|
return _code_ptr;
|
|
}
|
|
int GDFunction::get_code_size() const {
|
|
|
|
return _code_size;
|
|
}
|
|
|
|
Variant GDFunction::get_constant(int p_idx) const {
|
|
|
|
ERR_FAIL_INDEX_V(p_idx, constants.size(), "<errconst>");
|
|
return constants[p_idx];
|
|
}
|
|
|
|
StringName GDFunction::get_global_name(int p_idx) const {
|
|
|
|
ERR_FAIL_INDEX_V(p_idx, global_names.size(), "<errgname>");
|
|
return global_names[p_idx];
|
|
}
|
|
|
|
int GDFunction::get_default_argument_count() const {
|
|
|
|
return default_arguments.size();
|
|
}
|
|
int GDFunction::get_default_argument_addr(int p_idx) const {
|
|
|
|
ERR_FAIL_INDEX_V(p_idx, default_arguments.size(), -1);
|
|
return default_arguments[p_idx];
|
|
}
|
|
|
|
StringName GDFunction::get_name() const {
|
|
|
|
return name;
|
|
}
|
|
|
|
int GDFunction::get_max_stack_size() const {
|
|
|
|
return _stack_size;
|
|
}
|
|
|
|
struct _GDFKC {
|
|
|
|
int order;
|
|
List<int> pos;
|
|
};
|
|
|
|
struct _GDFKCS {
|
|
|
|
int order;
|
|
StringName id;
|
|
int pos;
|
|
|
|
bool operator<(const _GDFKCS &p_r) const {
|
|
|
|
return order < p_r.order;
|
|
}
|
|
};
|
|
|
|
void GDFunction::debug_get_stack_member_state(int p_line, List<Pair<StringName, int> > *r_stackvars) const {
|
|
|
|
int oc = 0;
|
|
Map<StringName, _GDFKC> sdmap;
|
|
for (const List<StackDebug>::Element *E = stack_debug.front(); E; E = E->next()) {
|
|
|
|
const StackDebug &sd = E->get();
|
|
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.empty())
|
|
sdmap.erase(sd.identifier);
|
|
}
|
|
}
|
|
|
|
List<_GDFKCS> stackpositions;
|
|
for (Map<StringName, _GDFKC>::Element *E = sdmap.front(); E; E = E->next()) {
|
|
|
|
_GDFKCS spp;
|
|
spp.id = E->key();
|
|
spp.order = E->get().order;
|
|
spp.pos = E->get().pos.back()->get();
|
|
stackpositions.push_back(spp);
|
|
}
|
|
|
|
stackpositions.sort();
|
|
|
|
for (List<_GDFKCS>::Element *E = stackpositions.front(); E; E = E->next()) {
|
|
|
|
Pair<StringName, int> p;
|
|
p.first = E->get().id;
|
|
p.second = E->get().pos;
|
|
r_stackvars->push_back(p);
|
|
}
|
|
}
|
|
|
|
GDFunction::GDFunction()
|
|
: function_list(this) {
|
|
|
|
_stack_size = 0;
|
|
_call_size = 0;
|
|
rpc_mode = ScriptInstance::RPC_MODE_DISABLED;
|
|
name = "<anonymous>";
|
|
#ifdef DEBUG_ENABLED
|
|
_func_cname = NULL;
|
|
|
|
if (GDScriptLanguage::get_singleton()->lock) {
|
|
GDScriptLanguage::get_singleton()->lock->lock();
|
|
}
|
|
GDScriptLanguage::get_singleton()->function_list.add(&function_list);
|
|
|
|
if (GDScriptLanguage::get_singleton()->lock) {
|
|
GDScriptLanguage::get_singleton()->lock->unlock();
|
|
}
|
|
|
|
profile.call_count = 0;
|
|
profile.self_time = 0;
|
|
profile.total_time = 0;
|
|
profile.frame_call_count = 0;
|
|
profile.frame_self_time = 0;
|
|
profile.frame_total_time = 0;
|
|
profile.last_frame_call_count = 0;
|
|
profile.last_frame_self_time = 0;
|
|
profile.last_frame_total_time = 0;
|
|
|
|
#endif
|
|
}
|
|
|
|
GDFunction::~GDFunction() {
|
|
#ifdef DEBUG_ENABLED
|
|
if (GDScriptLanguage::get_singleton()->lock) {
|
|
GDScriptLanguage::get_singleton()->lock->lock();
|
|
}
|
|
GDScriptLanguage::get_singleton()->function_list.remove(&function_list);
|
|
|
|
if (GDScriptLanguage::get_singleton()->lock) {
|
|
GDScriptLanguage::get_singleton()->lock->unlock();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/////////////////////
|
|
|
|
Variant GDFunctionState::_signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) {
|
|
ERR_EXPLAIN("Resumed after yield, but class instance is gone");
|
|
ERR_FAIL_V(Variant());
|
|
}
|
|
|
|
if (state.script_id && !ObjectDB::get_instance(state.script_id)) {
|
|
ERR_EXPLAIN("Resumed after yield, but script is gone");
|
|
ERR_FAIL_V(Variant());
|
|
}
|
|
#endif
|
|
|
|
Variant arg;
|
|
r_error.error = Variant::CallError::CALL_OK;
|
|
|
|
ERR_FAIL_COND_V(!function, Variant());
|
|
|
|
if (p_argcount == 0) {
|
|
r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
|
|
r_error.argument = 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<GDFunctionState> self = *p_args[p_argcount - 1];
|
|
|
|
if (self.is_null()) {
|
|
r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
|
r_error.argument = p_argcount - 1;
|
|
r_error.expected = Variant::OBJECT;
|
|
return Variant();
|
|
}
|
|
|
|
state.result = arg;
|
|
Variant ret = function->call(NULL, NULL, 0, r_error, &state);
|
|
|
|
bool completed = true;
|
|
|
|
// If the return value is a GDFunctionState reference,
|
|
// then the function did yield again after resuming.
|
|
if (ret.is_ref()) {
|
|
GDFunctionState *gdfs = Object::cast_to<GDFunctionState>(ret);
|
|
if (gdfs && gdfs->function == function)
|
|
completed = false;
|
|
}
|
|
|
|
function = NULL; //cleaned up;
|
|
state.result = Variant();
|
|
|
|
if (completed) {
|
|
emit_signal("completed", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool GDFunctionState::is_valid(bool p_extended_check) const {
|
|
|
|
if (function == NULL)
|
|
return false;
|
|
|
|
if (p_extended_check) {
|
|
//class instance gone?
|
|
if (state.instance_id && !ObjectDB::get_instance(state.instance_id))
|
|
return false;
|
|
//script gone?
|
|
if (state.script_id && !ObjectDB::get_instance(state.script_id))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Variant GDFunctionState::resume(const Variant &p_arg) {
|
|
|
|
ERR_FAIL_COND_V(!function, Variant());
|
|
#ifdef DEBUG_ENABLED
|
|
if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) {
|
|
ERR_EXPLAIN("Resumed after yield, but class instance is gone");
|
|
ERR_FAIL_V(Variant());
|
|
}
|
|
|
|
if (state.script_id && !ObjectDB::get_instance(state.script_id)) {
|
|
ERR_EXPLAIN("Resumed after yield, but script is gone");
|
|
ERR_FAIL_V(Variant());
|
|
}
|
|
#endif
|
|
|
|
state.result = p_arg;
|
|
Variant::CallError err;
|
|
Variant ret = function->call(NULL, NULL, 0, err, &state);
|
|
|
|
bool completed = true;
|
|
|
|
// If the return value is a GDFunctionState reference,
|
|
// then the function did yield again after resuming.
|
|
if (ret.is_ref()) {
|
|
GDFunctionState *gdfs = Object::cast_to<GDFunctionState>(ret);
|
|
if (gdfs && gdfs->function == function)
|
|
completed = false;
|
|
}
|
|
|
|
function = NULL; //cleaned up;
|
|
state.result = Variant();
|
|
|
|
if (completed) {
|
|
emit_signal("completed", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void GDFunctionState::_bind_methods() {
|
|
|
|
ClassDB::bind_method(D_METHOD("resume", "arg"), &GDFunctionState::resume, DEFVAL(Variant()));
|
|
ClassDB::bind_method(D_METHOD("is_valid", "extended_check"), &GDFunctionState::is_valid, DEFVAL(false));
|
|
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "_signal_callback", &GDFunctionState::_signal_callback, MethodInfo("_signal_callback"));
|
|
|
|
ADD_SIGNAL(MethodInfo("completed", PropertyInfo(Variant::NIL, "result")));
|
|
}
|
|
|
|
GDFunctionState::GDFunctionState() {
|
|
|
|
function = NULL;
|
|
}
|
|
|
|
GDFunctionState::~GDFunctionState() {
|
|
|
|
if (function != NULL) {
|
|
//never called, deinitialize stack
|
|
for (int i = 0; i < state.stack_size; i++) {
|
|
Variant *v = (Variant *)&state.stack[sizeof(Variant) * i];
|
|
v->~Variant();
|
|
}
|
|
}
|
|
}
|