godot/modules/gdscript/gd_function.cpp
Hein-Pieter van Braam 22358babda Implement Linux-style likely()/unlikely() macros
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.
2017-09-21 18:28:28 +02:00

1516 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();
}
}
}