mirror of
https://github.com/godotengine/godot.git
synced 2024-12-09 10:09:20 +08:00
cf4079cb5f
There's now only 3 addressing modes: stack, constant, and member. Self, class, and nil are now present respectively in the first 3 stack slots. Global and class constants are moved to local constants when compiling. Named globals is only present on editor to use on tool singletons, so its use now emits a new instruction to copy the global to the stack. This allow us to further optimize the VM later by embedding the addressing modes in the instructions themselves, which is better done with less permutations.
3128 lines
97 KiB
C++
3128 lines
97 KiB
C++
/*************************************************************************/
|
|
/* gdscript_vm.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2021 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 "gdscript_function.h"
|
|
|
|
#include "core/core_string_names.h"
|
|
#include "core/os/os.h"
|
|
#include "gdscript.h"
|
|
|
|
Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, 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_STACK: {
|
|
#ifdef DEBUG_ENABLED
|
|
ERR_FAIL_INDEX_V(address, _stack_size, nullptr);
|
|
#endif
|
|
return &p_stack[address];
|
|
} break;
|
|
case ADDR_TYPE_CONSTANT: {
|
|
#ifdef DEBUG_ENABLED
|
|
ERR_FAIL_INDEX_V(address, _constant_count, nullptr);
|
|
#endif
|
|
return &_constants_ptr[address];
|
|
} break;
|
|
case ADDR_TYPE_MEMBER: {
|
|
#ifdef DEBUG_ENABLED
|
|
if (unlikely(!p_instance)) {
|
|
r_error = "Cannot access member without instance.";
|
|
return nullptr;
|
|
}
|
|
#endif
|
|
//member indexing is O(1)
|
|
return &p_instance->members.write[address];
|
|
} break;
|
|
}
|
|
|
|
ERR_FAIL_V_MSG(nullptr, "Bad code! (unknown addressing mode).");
|
|
return nullptr;
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
static String _get_script_name(const Ref<Script> p_script) {
|
|
Ref<GDScript> gdscript = p_script;
|
|
if (gdscript.is_valid()) {
|
|
return gdscript->get_script_class_name();
|
|
} else if (p_script->get_name().is_empty()) {
|
|
return p_script->get_path().get_file();
|
|
} else {
|
|
return p_script->get_name();
|
|
}
|
|
}
|
|
|
|
static String _get_var_type(const Variant *p_var) {
|
|
String basestr;
|
|
|
|
if (p_var->get_type() == Variant::OBJECT) {
|
|
bool was_freed;
|
|
Object *bobj = p_var->get_validated_object_with_check(was_freed);
|
|
if (!bobj) {
|
|
if (was_freed) {
|
|
basestr = "null instance";
|
|
} else {
|
|
basestr = "previously freed";
|
|
}
|
|
} else {
|
|
basestr = bobj->get_class();
|
|
if (bobj->get_script_instance()) {
|
|
basestr += " (" + _get_script_name(bobj->get_script_instance()->get_script()) + ")";
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if (p_var->get_type() == Variant::ARRAY) {
|
|
basestr = "Array";
|
|
const Array *p_array = VariantInternal::get_array(p_var);
|
|
Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin();
|
|
StringName native_type = p_array->get_typed_class_name();
|
|
Ref<Script> script_type = p_array->get_typed_script();
|
|
|
|
if (script_type.is_valid() && script_type->is_valid()) {
|
|
basestr += "[" + _get_script_name(script_type) + "]";
|
|
} else if (native_type != StringName()) {
|
|
basestr += "[" + native_type.operator String() + "]";
|
|
} else if (builtin_type != Variant::NIL) {
|
|
basestr += "[" + Variant::get_type_name(builtin_type) + "]";
|
|
}
|
|
} else {
|
|
basestr = Variant::get_type_name(p_var->get_type());
|
|
}
|
|
}
|
|
|
|
return basestr;
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
|
|
String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const {
|
|
String err_text;
|
|
|
|
if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
|
|
int errorarg = p_err.argument;
|
|
// Handle the Object to Object case separately as we don't have further class details.
|
|
#ifdef DEBUG_ENABLED
|
|
if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) {
|
|
err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class.";
|
|
} else
|
|
#endif // DEBUG_ENABLED
|
|
{
|
|
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(Variant::Type(p_err.expected)) + ".";
|
|
}
|
|
} else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) {
|
|
err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
|
|
} else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) {
|
|
err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
|
|
} else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
|
|
err_text = "Invalid call. Nonexistent " + p_where + ".";
|
|
} else if (p_err.error == Callable::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;
|
|
}
|
|
|
|
#if defined(__GNUC__)
|
|
#define OPCODES_TABLE \
|
|
static const void *switch_table_ops[] = { \
|
|
&&OPCODE_OPERATOR, \
|
|
&&OPCODE_OPERATOR_VALIDATED, \
|
|
&&OPCODE_EXTENDS_TEST, \
|
|
&&OPCODE_IS_BUILTIN, \
|
|
&&OPCODE_SET_KEYED, \
|
|
&&OPCODE_SET_KEYED_VALIDATED, \
|
|
&&OPCODE_SET_INDEXED_VALIDATED, \
|
|
&&OPCODE_GET_KEYED, \
|
|
&&OPCODE_GET_KEYED_VALIDATED, \
|
|
&&OPCODE_GET_INDEXED_VALIDATED, \
|
|
&&OPCODE_SET_NAMED, \
|
|
&&OPCODE_SET_NAMED_VALIDATED, \
|
|
&&OPCODE_GET_NAMED, \
|
|
&&OPCODE_GET_NAMED_VALIDATED, \
|
|
&&OPCODE_SET_MEMBER, \
|
|
&&OPCODE_GET_MEMBER, \
|
|
&&OPCODE_ASSIGN, \
|
|
&&OPCODE_ASSIGN_TRUE, \
|
|
&&OPCODE_ASSIGN_FALSE, \
|
|
&&OPCODE_ASSIGN_TYPED_BUILTIN, \
|
|
&&OPCODE_ASSIGN_TYPED_ARRAY, \
|
|
&&OPCODE_ASSIGN_TYPED_NATIVE, \
|
|
&&OPCODE_ASSIGN_TYPED_SCRIPT, \
|
|
&&OPCODE_CAST_TO_BUILTIN, \
|
|
&&OPCODE_CAST_TO_NATIVE, \
|
|
&&OPCODE_CAST_TO_SCRIPT, \
|
|
&&OPCODE_CONSTRUCT, \
|
|
&&OPCODE_CONSTRUCT_VALIDATED, \
|
|
&&OPCODE_CONSTRUCT_ARRAY, \
|
|
&&OPCODE_CONSTRUCT_TYPED_ARRAY, \
|
|
&&OPCODE_CONSTRUCT_DICTIONARY, \
|
|
&&OPCODE_CALL, \
|
|
&&OPCODE_CALL_RETURN, \
|
|
&&OPCODE_CALL_ASYNC, \
|
|
&&OPCODE_CALL_UTILITY, \
|
|
&&OPCODE_CALL_UTILITY_VALIDATED, \
|
|
&&OPCODE_CALL_GDSCRIPT_UTILITY, \
|
|
&&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \
|
|
&&OPCODE_CALL_SELF_BASE, \
|
|
&&OPCODE_CALL_METHOD_BIND, \
|
|
&&OPCODE_CALL_METHOD_BIND_RET, \
|
|
&&OPCODE_CALL_PTRCALL_NO_RETURN, \
|
|
&&OPCODE_CALL_PTRCALL_BOOL, \
|
|
&&OPCODE_CALL_PTRCALL_INT, \
|
|
&&OPCODE_CALL_PTRCALL_FLOAT, \
|
|
&&OPCODE_CALL_PTRCALL_STRING, \
|
|
&&OPCODE_CALL_PTRCALL_VECTOR2, \
|
|
&&OPCODE_CALL_PTRCALL_VECTOR2I, \
|
|
&&OPCODE_CALL_PTRCALL_RECT2, \
|
|
&&OPCODE_CALL_PTRCALL_RECT2I, \
|
|
&&OPCODE_CALL_PTRCALL_VECTOR3, \
|
|
&&OPCODE_CALL_PTRCALL_VECTOR3I, \
|
|
&&OPCODE_CALL_PTRCALL_TRANSFORM2D, \
|
|
&&OPCODE_CALL_PTRCALL_PLANE, \
|
|
&&OPCODE_CALL_PTRCALL_QUAT, \
|
|
&&OPCODE_CALL_PTRCALL_AABB, \
|
|
&&OPCODE_CALL_PTRCALL_BASIS, \
|
|
&&OPCODE_CALL_PTRCALL_TRANSFORM, \
|
|
&&OPCODE_CALL_PTRCALL_COLOR, \
|
|
&&OPCODE_CALL_PTRCALL_STRING_NAME, \
|
|
&&OPCODE_CALL_PTRCALL_NODE_PATH, \
|
|
&&OPCODE_CALL_PTRCALL_RID, \
|
|
&&OPCODE_CALL_PTRCALL_OBJECT, \
|
|
&&OPCODE_CALL_PTRCALL_CALLABLE, \
|
|
&&OPCODE_CALL_PTRCALL_SIGNAL, \
|
|
&&OPCODE_CALL_PTRCALL_DICTIONARY, \
|
|
&&OPCODE_CALL_PTRCALL_ARRAY, \
|
|
&&OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY, \
|
|
&&OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY, \
|
|
&&OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY, \
|
|
&&OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY, \
|
|
&&OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY, \
|
|
&&OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY, \
|
|
&&OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY, \
|
|
&&OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY, \
|
|
&&OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, \
|
|
&&OPCODE_AWAIT, \
|
|
&&OPCODE_AWAIT_RESUME, \
|
|
&&OPCODE_JUMP, \
|
|
&&OPCODE_JUMP_IF, \
|
|
&&OPCODE_JUMP_IF_NOT, \
|
|
&&OPCODE_JUMP_TO_DEF_ARGUMENT, \
|
|
&&OPCODE_RETURN, \
|
|
&&OPCODE_RETURN_TYPED_BUILTIN, \
|
|
&&OPCODE_RETURN_TYPED_ARRAY, \
|
|
&&OPCODE_RETURN_TYPED_NATIVE, \
|
|
&&OPCODE_RETURN_TYPED_SCRIPT, \
|
|
&&OPCODE_ITERATE_BEGIN, \
|
|
&&OPCODE_ITERATE_BEGIN_INT, \
|
|
&&OPCODE_ITERATE_BEGIN_FLOAT, \
|
|
&&OPCODE_ITERATE_BEGIN_VECTOR2, \
|
|
&&OPCODE_ITERATE_BEGIN_VECTOR2I, \
|
|
&&OPCODE_ITERATE_BEGIN_VECTOR3, \
|
|
&&OPCODE_ITERATE_BEGIN_VECTOR3I, \
|
|
&&OPCODE_ITERATE_BEGIN_STRING, \
|
|
&&OPCODE_ITERATE_BEGIN_DICTIONARY, \
|
|
&&OPCODE_ITERATE_BEGIN_ARRAY, \
|
|
&&OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, \
|
|
&&OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, \
|
|
&&OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, \
|
|
&&OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, \
|
|
&&OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, \
|
|
&&OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, \
|
|
&&OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, \
|
|
&&OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, \
|
|
&&OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, \
|
|
&&OPCODE_ITERATE_BEGIN_OBJECT, \
|
|
&&OPCODE_ITERATE, \
|
|
&&OPCODE_ITERATE_INT, \
|
|
&&OPCODE_ITERATE_FLOAT, \
|
|
&&OPCODE_ITERATE_VECTOR2, \
|
|
&&OPCODE_ITERATE_VECTOR2I, \
|
|
&&OPCODE_ITERATE_VECTOR3, \
|
|
&&OPCODE_ITERATE_VECTOR3I, \
|
|
&&OPCODE_ITERATE_STRING, \
|
|
&&OPCODE_ITERATE_DICTIONARY, \
|
|
&&OPCODE_ITERATE_ARRAY, \
|
|
&&OPCODE_ITERATE_PACKED_BYTE_ARRAY, \
|
|
&&OPCODE_ITERATE_PACKED_INT32_ARRAY, \
|
|
&&OPCODE_ITERATE_PACKED_INT64_ARRAY, \
|
|
&&OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, \
|
|
&&OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, \
|
|
&&OPCODE_ITERATE_PACKED_STRING_ARRAY, \
|
|
&&OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, \
|
|
&&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \
|
|
&&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \
|
|
&&OPCODE_ITERATE_OBJECT, \
|
|
&&OPCODE_STORE_NAMED_GLOBAL, \
|
|
&&OPCODE_ASSERT, \
|
|
&&OPCODE_BREAKPOINT, \
|
|
&&OPCODE_LINE, \
|
|
&&OPCODE_END \
|
|
}; \
|
|
static_assert((sizeof(switch_table_ops) / sizeof(switch_table_ops[0]) == (OPCODE_END + 1)), "Opcodes in jump table aren't the same as opcodes in enum.");
|
|
|
|
#define OPCODE(m_op) \
|
|
m_op:
|
|
#define OPCODE_WHILE(m_test) \
|
|
OPSWHILE:
|
|
#define OPCODES_END \
|
|
OPSEXIT:
|
|
#define OPCODES_OUT \
|
|
OPSOUT:
|
|
#define DISPATCH_OPCODE goto OPSWHILE
|
|
#define OPCODE_SWITCH(m_test) goto *switch_table_ops[m_test];
|
|
#define OPCODE_BREAK goto OPSEXIT
|
|
#define OPCODE_OUT goto OPSOUT
|
|
#else
|
|
#define OPCODES_TABLE
|
|
#define OPCODE(m_op) case m_op:
|
|
#define OPCODE_WHILE(m_test) while (m_test)
|
|
#define OPCODES_END
|
|
#define OPCODES_OUT
|
|
#define DISPATCH_OPCODE continue
|
|
#define OPCODE_SWITCH(m_test) switch (m_test)
|
|
#define OPCODE_BREAK break
|
|
#define OPCODE_OUT break
|
|
#endif
|
|
|
|
// Helpers for VariantInternal methods in macros.
|
|
#define OP_GET_BOOL get_bool
|
|
#define OP_GET_INT get_int
|
|
#define OP_GET_FLOAT get_float
|
|
#define OP_GET_VECTOR2 get_vector2
|
|
#define OP_GET_VECTOR2I get_vector2i
|
|
#define OP_GET_VECTOR3 get_vector3
|
|
#define OP_GET_VECTOR3I get_vector3i
|
|
#define OP_GET_RECT2 get_rect2
|
|
#define OP_GET_RECT2I get_rect2i
|
|
#define OP_GET_QUAT get_quat
|
|
#define OP_GET_COLOR get_color
|
|
#define OP_GET_STRING get_string
|
|
#define OP_GET_STRING_NAME get_string_name
|
|
#define OP_GET_NODE_PATH get_node_path
|
|
#define OP_GET_CALLABLE get_callable
|
|
#define OP_GET_SIGNAL get_signal
|
|
#define OP_GET_ARRAY get_array
|
|
#define OP_GET_DICTIONARY get_dictionary
|
|
#define OP_GET_PACKED_BYTE_ARRAY get_byte_array
|
|
#define OP_GET_PACKED_INT32_ARRAY get_int32_array
|
|
#define OP_GET_PACKED_INT64_ARRAY get_int64_array
|
|
#define OP_GET_PACKED_FLOAT32_ARRAY get_float32_array
|
|
#define OP_GET_PACKED_FLOAT64_ARRAY get_float64_array
|
|
#define OP_GET_PACKED_STRING_ARRAY get_string_array
|
|
#define OP_GET_PACKED_VECTOR2_ARRAY get_vector2_array
|
|
#define OP_GET_PACKED_VECTOR3_ARRAY get_vector3_array
|
|
#define OP_GET_PACKED_COLOR_ARRAY get_color_array
|
|
#define OP_GET_TRANSFORM get_transform
|
|
#define OP_GET_TRANSFORM2D get_transform2d
|
|
#define OP_GET_PLANE get_plane
|
|
#define OP_GET_AABB get_aabb
|
|
#define OP_GET_BASIS get_basis
|
|
#define OP_GET_RID get_rid
|
|
|
|
Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) {
|
|
OPCODES_TABLE;
|
|
|
|
if (!_code_ptr) {
|
|
return Variant();
|
|
}
|
|
|
|
r_err.error = Callable::CallError::CALL_OK;
|
|
|
|
Variant retvalue;
|
|
Variant *stack = nullptr;
|
|
Variant **instruction_args = nullptr;
|
|
const void **call_args_ptr = nullptr;
|
|
int defarg = 0;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
//GDScriptLanguage::get_singleton()->calls++;
|
|
|
|
#endif
|
|
|
|
uint32_t alloca_size = 0;
|
|
GDScript *script;
|
|
int ip = 0;
|
|
int line = _initial_line;
|
|
|
|
if (p_state) {
|
|
//use existing (supplied) state (awaited)
|
|
stack = (Variant *)p_state->stack.ptr();
|
|
instruction_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();
|
|
script = p_state->script;
|
|
p_instance = p_state->instance;
|
|
defarg = p_state->defarg;
|
|
|
|
} else {
|
|
if (p_argcount != _argument_count) {
|
|
if (p_argcount > _argument_count) {
|
|
r_err.error = Callable::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 = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
|
|
r_err.argument = _argument_count - _default_arg_count;
|
|
return Variant();
|
|
} else {
|
|
defarg = _argument_count - p_argcount;
|
|
}
|
|
}
|
|
|
|
// Add 3 here for self, class, and nil.
|
|
alloca_size = sizeof(Variant *) * 3 + sizeof(Variant *) * _instruction_args_size + sizeof(Variant) * _stack_size;
|
|
|
|
uint8_t *aptr = (uint8_t *)alloca(alloca_size);
|
|
stack = (Variant *)aptr;
|
|
|
|
for (int i = 0; i < p_argcount; i++) {
|
|
if (!argument_types[i].has_type) {
|
|
memnew_placement(&stack[i + 3], Variant(*p_args[i]));
|
|
continue;
|
|
}
|
|
|
|
if (!argument_types[i].is_type(*p_args[i], true)) {
|
|
r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
|
r_err.argument = i;
|
|
r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT;
|
|
return Variant();
|
|
}
|
|
if (argument_types[i].kind == GDScriptDataType::BUILTIN) {
|
|
Variant arg;
|
|
Variant::construct(argument_types[i].builtin_type, arg, &p_args[i], 1, r_err);
|
|
memnew_placement(&stack[i + 3], Variant(arg));
|
|
} else {
|
|
memnew_placement(&stack[i + 3], Variant(*p_args[i]));
|
|
}
|
|
}
|
|
for (int i = p_argcount + 3; i < _stack_size; i++) {
|
|
memnew_placement(&stack[i], Variant);
|
|
}
|
|
|
|
memnew_placement(&stack[ADDR_STACK_NIL], Variant);
|
|
|
|
if (_instruction_args_size) {
|
|
instruction_args = (Variant **)&aptr[sizeof(Variant) * _stack_size];
|
|
} else {
|
|
instruction_args = nullptr;
|
|
}
|
|
|
|
if (p_instance) {
|
|
memnew_placement(&stack[ADDR_STACK_SELF], Variant(p_instance->owner));
|
|
script = p_instance->script.ptr();
|
|
} else {
|
|
memnew_placement(&stack[ADDR_STACK_SELF], Variant);
|
|
script = _script;
|
|
}
|
|
}
|
|
if (_ptrcall_args_size) {
|
|
call_args_ptr = (const void **)alloca(_ptrcall_args_size * sizeof(void *));
|
|
} else {
|
|
call_args_ptr = nullptr;
|
|
}
|
|
|
|
memnew_placement(&stack[ADDR_STACK_CLASS], Variant(script));
|
|
|
|
String err_text;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
if (EngineDebugger::is_active()) {
|
|
GDScriptLanguage::get_singleton()->enter_function(p_instance, this, stack, &ip, &line);
|
|
}
|
|
|
|
#define GD_ERR_BREAK(m_cond) \
|
|
{ \
|
|
if (unlikely(m_cond)) { \
|
|
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition ' " _STR(m_cond) " ' is true. Breaking..:"); \
|
|
OPCODE_BREAK; \
|
|
} \
|
|
}
|
|
|
|
#define CHECK_SPACE(m_space) \
|
|
GD_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, stack, err_text); \
|
|
if (unlikely(!m_v)) \
|
|
OPCODE_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, stack, err_text);
|
|
|
|
#endif
|
|
|
|
#define GET_INSTRUCTION_ARG(m_v, m_idx) \
|
|
Variant *m_v = instruction_args[m_idx]
|
|
|
|
#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++;
|
|
}
|
|
bool exit_ok = false;
|
|
bool awaited = false;
|
|
#endif
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
OPCODE_WHILE(ip < _code_size) {
|
|
int last_opcode = _code_ptr[ip] & INSTR_MASK;
|
|
#else
|
|
OPCODE_WHILE(true) {
|
|
#endif
|
|
// Load arguments for the instruction before each instruction.
|
|
int instr_arg_count = ((_code_ptr[ip]) & INSTR_ARGS_MASK) >> INSTR_BITS;
|
|
for (int i = 0; i < instr_arg_count; i++) {
|
|
GET_VARIANT_PTR(v, i + 1);
|
|
instruction_args[i] = v;
|
|
}
|
|
|
|
OPCODE_SWITCH(_code_ptr[ip] & INSTR_MASK) {
|
|
OPCODE(OPCODE_OPERATOR) {
|
|
CHECK_SPACE(5);
|
|
|
|
bool valid;
|
|
Variant::Operator op = (Variant::Operator)_code_ptr[ip + 4];
|
|
GD_ERR_BREAK(op >= Variant::OP_MAX);
|
|
|
|
GET_INSTRUCTION_ARG(a, 0);
|
|
GET_INSTRUCTION_ARG(b, 1);
|
|
GET_INSTRUCTION_ARG(dst, 2);
|
|
|
|
#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) + "'.";
|
|
}
|
|
OPCODE_BREAK;
|
|
}
|
|
*dst = ret;
|
|
#endif
|
|
ip += 5;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_OPERATOR_VALIDATED) {
|
|
CHECK_SPACE(5);
|
|
|
|
int operator_idx = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(operator_idx < 0 || operator_idx >= _operator_funcs_count);
|
|
Variant::ValidatedOperatorEvaluator operator_func = _operator_funcs_ptr[operator_idx];
|
|
|
|
GET_INSTRUCTION_ARG(a, 0);
|
|
GET_INSTRUCTION_ARG(b, 1);
|
|
GET_INSTRUCTION_ARG(dst, 2);
|
|
|
|
operator_func(a, b, dst);
|
|
|
|
ip += 5;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_EXTENDS_TEST) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(a, 0);
|
|
GET_INSTRUCTION_ARG(b, 1);
|
|
GET_INSTRUCTION_ARG(dst, 2);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) {
|
|
err_text = "Right operand of 'is' is not a class.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
|
|
bool extends_ok = false;
|
|
if (a->get_type() == Variant::OBJECT && a->operator Object *() != nullptr) {
|
|
#ifdef DEBUG_ENABLED
|
|
bool was_freed;
|
|
Object *obj_A = a->get_validated_object_with_check(was_freed);
|
|
|
|
if (was_freed) {
|
|
err_text = "Left operand of 'is' is a previously freed instance.";
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
Object *obj_B = b->get_validated_object_with_check(was_freed);
|
|
|
|
if (was_freed) {
|
|
err_text = "Right operand of 'is' is a previously freed instance.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#else
|
|
|
|
Object *obj_A = *a;
|
|
Object *obj_B = *b;
|
|
#endif // DEBUG_ENABLED
|
|
|
|
GDScript *scr_B = Object::cast_to<GDScript>(obj_B);
|
|
|
|
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 should 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 {
|
|
GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (!nc) {
|
|
err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "').";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name());
|
|
}
|
|
}
|
|
|
|
*dst = extends_ok;
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_IS_BUILTIN) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(value, 0);
|
|
GET_INSTRUCTION_ARG(dst, 1);
|
|
Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3];
|
|
|
|
GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX);
|
|
|
|
*dst = value->get_type() == var_type;
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_SET_KEYED) {
|
|
CHECK_SPACE(3);
|
|
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
GET_INSTRUCTION_ARG(index, 1);
|
|
GET_INSTRUCTION_ARG(value, 2);
|
|
|
|
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) + "') with value of type '" + _get_var_type(value) + "'";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_SET_KEYED_VALIDATED) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
GET_INSTRUCTION_ARG(index, 1);
|
|
GET_INSTRUCTION_ARG(value, 2);
|
|
|
|
int index_setter = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(index_setter < 0 || index_setter >= _keyed_setters_count);
|
|
const Variant::ValidatedKeyedSetter setter = _keyed_setters_ptr[index_setter];
|
|
|
|
bool valid;
|
|
setter(dst, 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) + "') with value of type '" + _get_var_type(value) + "'";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
ip += 5;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_SET_INDEXED_VALIDATED) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
GET_INSTRUCTION_ARG(index, 1);
|
|
GET_INSTRUCTION_ARG(value, 2);
|
|
|
|
int index_setter = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(index_setter < 0 || index_setter >= _indexed_setters_count);
|
|
const Variant::ValidatedIndexedSetter setter = _indexed_setters_ptr[index_setter];
|
|
|
|
int64_t int_index = *VariantInternal::get_int(index);
|
|
|
|
bool oob;
|
|
setter(dst, int_index, value, &oob);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (oob) {
|
|
String v = index->operator String();
|
|
if (v != "") {
|
|
v = "'" + v + "'";
|
|
} else {
|
|
v = "of type '" + _get_var_type(index) + "'";
|
|
}
|
|
err_text = "Out of bounds set index " + v + " (on base: '" + _get_var_type(dst) + "')";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
ip += 5;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_GET_KEYED) {
|
|
CHECK_SPACE(3);
|
|
|
|
GET_INSTRUCTION_ARG(src, 0);
|
|
GET_INSTRUCTION_ARG(index, 1);
|
|
GET_INSTRUCTION_ARG(dst, 2);
|
|
|
|
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) + "').";
|
|
OPCODE_BREAK;
|
|
}
|
|
*dst = ret;
|
|
#endif
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_GET_KEYED_VALIDATED) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(src, 0);
|
|
GET_INSTRUCTION_ARG(key, 1);
|
|
GET_INSTRUCTION_ARG(dst, 2);
|
|
|
|
int index_getter = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(index_getter < 0 || index_getter >= _keyed_getters_count);
|
|
const Variant::ValidatedKeyedGetter getter = _keyed_getters_ptr[index_getter];
|
|
|
|
bool valid;
|
|
#ifdef DEBUG_ENABLED
|
|
// Allow better error message in cases where src and dst are the same stack position.
|
|
Variant ret;
|
|
getter(src, key, &ret, &valid);
|
|
#else
|
|
getter(src, key, dst, &valid);
|
|
#endif
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
String v = key->operator String();
|
|
if (v != "") {
|
|
v = "'" + v + "'";
|
|
} else {
|
|
v = "of type '" + _get_var_type(key) + "'";
|
|
}
|
|
err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "').";
|
|
OPCODE_BREAK;
|
|
}
|
|
*dst = ret;
|
|
#endif
|
|
ip += 5;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_GET_INDEXED_VALIDATED) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(src, 0);
|
|
GET_INSTRUCTION_ARG(index, 1);
|
|
GET_INSTRUCTION_ARG(dst, 2);
|
|
|
|
int index_getter = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(index_getter < 0 || index_getter >= _indexed_getters_count);
|
|
const Variant::ValidatedIndexedGetter getter = _indexed_getters_ptr[index_getter];
|
|
|
|
int64_t int_index = *VariantInternal::get_int(index);
|
|
|
|
bool oob;
|
|
getter(src, int_index, dst, &oob);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (oob) {
|
|
String v = index->operator String();
|
|
if (v != "") {
|
|
v = "'" + v + "'";
|
|
} else {
|
|
v = "of type '" + _get_var_type(index) + "'";
|
|
}
|
|
err_text = "Out of bounds get index " + v + " (on base: '" + _get_var_type(src) + "')";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
ip += 5;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_SET_NAMED) {
|
|
CHECK_SPACE(3);
|
|
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
GET_INSTRUCTION_ARG(value, 1);
|
|
|
|
int indexname = _code_ptr[ip + 3];
|
|
|
|
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) + "') with value of type '" + _get_var_type(value) + "'.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_SET_NAMED_VALIDATED) {
|
|
CHECK_SPACE(3);
|
|
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
GET_INSTRUCTION_ARG(value, 1);
|
|
|
|
int index_setter = _code_ptr[ip + 3];
|
|
GD_ERR_BREAK(index_setter < 0 || index_setter >= _setters_count);
|
|
const Variant::ValidatedSetter setter = _setters_ptr[index_setter];
|
|
|
|
setter(dst, value);
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_GET_NAMED) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(src, 0);
|
|
GET_INSTRUCTION_ARG(dst, 1);
|
|
|
|
int indexname = _code_ptr[ip + 3];
|
|
|
|
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() + "()' or funcref(obj, \"" + index->operator String() + "\") ?";
|
|
} else {
|
|
err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "').";
|
|
}
|
|
OPCODE_BREAK;
|
|
}
|
|
*dst = ret;
|
|
#endif
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_GET_NAMED_VALIDATED) {
|
|
CHECK_SPACE(3);
|
|
|
|
GET_INSTRUCTION_ARG(src, 0);
|
|
GET_INSTRUCTION_ARG(dst, 1);
|
|
|
|
int index_getter = _code_ptr[ip + 3];
|
|
GD_ERR_BREAK(index_getter < 0 || index_getter >= _getters_count);
|
|
const Variant::ValidatedGetter getter = _getters_ptr[index_getter];
|
|
|
|
getter(src, dst);
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_SET_MEMBER) {
|
|
CHECK_SPACE(3);
|
|
GET_INSTRUCTION_ARG(src, 0);
|
|
int indexname = _code_ptr[ip + 2];
|
|
GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
|
|
const StringName *index = &_global_names_ptr[indexname];
|
|
|
|
bool valid;
|
|
#ifndef DEBUG_ENABLED
|
|
ClassDB::set_property(p_instance->owner, *index, *src, &valid);
|
|
#else
|
|
bool ok = ClassDB::set_property(p_instance->owner, *index, *src, &valid);
|
|
if (!ok) {
|
|
err_text = "Internal error setting property: " + String(*index);
|
|
OPCODE_BREAK;
|
|
} else if (!valid) {
|
|
err_text = "Error setting property '" + String(*index) + "' with value of type " + Variant::get_type_name(src->get_type()) + ".";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_GET_MEMBER) {
|
|
CHECK_SPACE(3);
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
int indexname = _code_ptr[ip + 2];
|
|
GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
|
|
const StringName *index = &_global_names_ptr[indexname];
|
|
#ifndef DEBUG_ENABLED
|
|
ClassDB::get_property(p_instance->owner, *index, *dst);
|
|
#else
|
|
bool ok = ClassDB::get_property(p_instance->owner, *index, *dst);
|
|
if (!ok) {
|
|
err_text = "Internal error getting property: " + String(*index);
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ASSIGN) {
|
|
CHECK_SPACE(3);
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
GET_INSTRUCTION_ARG(src, 1);
|
|
|
|
*dst = *src;
|
|
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ASSIGN_TRUE) {
|
|
CHECK_SPACE(2);
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
|
|
*dst = true;
|
|
|
|
ip += 2;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ASSIGN_FALSE) {
|
|
CHECK_SPACE(2);
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
|
|
*dst = false;
|
|
|
|
ip += 2;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) {
|
|
CHECK_SPACE(4);
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
GET_INSTRUCTION_ARG(src, 1);
|
|
|
|
Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3];
|
|
GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX);
|
|
|
|
if (src->get_type() != var_type) {
|
|
#ifdef DEBUG_ENABLED
|
|
if (Variant::can_convert_strict(src->get_type(), var_type)) {
|
|
#endif // DEBUG_ENABLED
|
|
Callable::CallError ce;
|
|
Variant::construct(var_type, *dst, const_cast<const Variant **>(&src), 1, ce);
|
|
} else {
|
|
#ifdef DEBUG_ENABLED
|
|
err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
|
|
"' to a variable of type '" + Variant::get_type_name(var_type) + "'.";
|
|
OPCODE_BREAK;
|
|
}
|
|
} else {
|
|
#endif // DEBUG_ENABLED
|
|
*dst = *src;
|
|
}
|
|
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ASSIGN_TYPED_ARRAY) {
|
|
CHECK_SPACE(3);
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
GET_INSTRUCTION_ARG(src, 1);
|
|
|
|
Array *dst_arr = VariantInternal::get_array(dst);
|
|
|
|
if (src->get_type() != Variant::ARRAY) {
|
|
#ifdef DEBUG_ENABLED
|
|
err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
|
|
"' to a variable of type '" + +"'.";
|
|
#endif
|
|
OPCODE_BREAK;
|
|
}
|
|
if (!dst_arr->typed_assign(*src)) {
|
|
#ifdef DEBUG_ENABLED
|
|
err_text = "Trying to assign a typed array with an array of different type.'";
|
|
#endif
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) {
|
|
CHECK_SPACE(4);
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
GET_INSTRUCTION_ARG(src, 1);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
GET_INSTRUCTION_ARG(type, 2);
|
|
GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *());
|
|
GD_ERR_BREAK(!nc);
|
|
if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
|
|
err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
|
|
"' to a variable of type '" + nc->get_name() + "'.";
|
|
OPCODE_BREAK;
|
|
}
|
|
Object *src_obj = src->operator Object *();
|
|
|
|
if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) {
|
|
err_text = "Trying to assign value of type '" + src_obj->get_class_name() +
|
|
"' to a variable of type '" + nc->get_name() + "'.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
*dst = *src;
|
|
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) {
|
|
CHECK_SPACE(4);
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
GET_INSTRUCTION_ARG(src, 1);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
GET_INSTRUCTION_ARG(type, 2);
|
|
Script *base_type = Object::cast_to<Script>(type->operator Object *());
|
|
|
|
GD_ERR_BREAK(!base_type);
|
|
|
|
if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
|
|
err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'.";
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) {
|
|
ScriptInstance *scr_inst = src->operator Object *()->get_script_instance();
|
|
if (!scr_inst) {
|
|
err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() +
|
|
"' to a variable of type '" + base_type->get_path().get_file() + "'.";
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr();
|
|
bool valid = false;
|
|
|
|
while (src_type) {
|
|
if (src_type == base_type) {
|
|
valid = true;
|
|
break;
|
|
}
|
|
src_type = src_type->get_base_script().ptr();
|
|
}
|
|
|
|
if (!valid) {
|
|
err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() +
|
|
"' to a variable of type '" + base_type->get_path().get_file() + "'.";
|
|
OPCODE_BREAK;
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
|
|
*dst = *src;
|
|
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CAST_TO_BUILTIN) {
|
|
CHECK_SPACE(4);
|
|
GET_INSTRUCTION_ARG(src, 0);
|
|
GET_INSTRUCTION_ARG(dst, 1);
|
|
Variant::Type to_type = (Variant::Type)_code_ptr[ip + 3];
|
|
|
|
GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX);
|
|
|
|
Callable::CallError err;
|
|
Variant::construct(to_type, *dst, (const Variant **)&src, 1, err);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (err.error != Callable::CallError::CALL_OK) {
|
|
err_text = "Invalid cast: could not convert value to '" + Variant::get_type_name(to_type) + "'.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CAST_TO_NATIVE) {
|
|
CHECK_SPACE(4);
|
|
GET_INSTRUCTION_ARG(src, 0);
|
|
GET_INSTRUCTION_ARG(dst, 1);
|
|
GET_INSTRUCTION_ARG(to_type, 2);
|
|
|
|
GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(to_type->operator Object *());
|
|
GD_ERR_BREAK(!nc);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
|
|
err_text = "Invalid cast: can't convert a non-object value to an object type.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
Object *src_obj = src->operator Object *();
|
|
|
|
if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) {
|
|
*dst = Variant(); // invalid cast, assign NULL
|
|
} else {
|
|
*dst = *src;
|
|
}
|
|
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CAST_TO_SCRIPT) {
|
|
CHECK_SPACE(4);
|
|
GET_INSTRUCTION_ARG(src, 0);
|
|
GET_INSTRUCTION_ARG(dst, 1);
|
|
GET_INSTRUCTION_ARG(to_type, 2);
|
|
|
|
Script *base_type = Object::cast_to<Script>(to_type->operator Object *());
|
|
|
|
GD_ERR_BREAK(!base_type);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
|
|
err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
|
|
bool valid = false;
|
|
|
|
if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) {
|
|
ScriptInstance *scr_inst = src->operator Object *()->get_script_instance();
|
|
|
|
if (scr_inst) {
|
|
Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr();
|
|
|
|
while (src_type) {
|
|
if (src_type == base_type) {
|
|
valid = true;
|
|
break;
|
|
}
|
|
src_type = src_type->get_base_script().ptr();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (valid) {
|
|
*dst = *src; // Valid cast, copy the source object
|
|
} else {
|
|
*dst = Variant(); // invalid cast, assign NULL
|
|
}
|
|
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CONSTRUCT) {
|
|
CHECK_SPACE(2 + instr_arg_count);
|
|
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
|
|
Variant::Type t = Variant::Type(_code_ptr[ip + 2]);
|
|
Variant **argptrs = instruction_args;
|
|
|
|
GET_INSTRUCTION_ARG(dst, argc);
|
|
|
|
Callable::CallError err;
|
|
Variant::construct(t, *dst, (const Variant **)argptrs, argc, err);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (err.error != Callable::CallError::CALL_OK) {
|
|
err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs);
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CONSTRUCT_VALIDATED) {
|
|
CHECK_SPACE(2 + instr_arg_count);
|
|
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
|
|
int constructor_idx = _code_ptr[ip + 2];
|
|
GD_ERR_BREAK(constructor_idx < 0 || constructor_idx >= _constructors_count);
|
|
Variant::ValidatedConstructor constructor = _constructors_ptr[constructor_idx];
|
|
|
|
Variant **argptrs = instruction_args;
|
|
|
|
GET_INSTRUCTION_ARG(dst, argc);
|
|
|
|
constructor(dst, (const Variant **)argptrs);
|
|
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CONSTRUCT_ARRAY) {
|
|
CHECK_SPACE(1 + instr_arg_count);
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
Array array;
|
|
array.resize(argc);
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
array[i] = *(instruction_args[i]);
|
|
}
|
|
|
|
GET_INSTRUCTION_ARG(dst, argc);
|
|
*dst = Variant(); // Clear potential previous typed array.
|
|
|
|
*dst = array;
|
|
|
|
ip += 2;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CONSTRUCT_TYPED_ARRAY) {
|
|
CHECK_SPACE(3 + instr_arg_count);
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
|
|
GET_INSTRUCTION_ARG(script_type, argc + 1);
|
|
Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 2];
|
|
int native_type_idx = _code_ptr[ip + 3];
|
|
GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count);
|
|
const StringName native_type = _global_names_ptr[native_type_idx];
|
|
|
|
Array array;
|
|
array.set_typed(builtin_type, native_type, script_type);
|
|
array.resize(argc);
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
array[i] = *(instruction_args[i]);
|
|
}
|
|
|
|
GET_INSTRUCTION_ARG(dst, argc);
|
|
*dst = Variant(); // Clear potential previous typed array.
|
|
|
|
*dst = array;
|
|
|
|
ip += 4;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CONSTRUCT_DICTIONARY) {
|
|
CHECK_SPACE(2 + instr_arg_count);
|
|
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
Dictionary dict;
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
GET_INSTRUCTION_ARG(k, i * 2 + 0);
|
|
GET_INSTRUCTION_ARG(v, i * 2 + 1);
|
|
dict[*k] = *v;
|
|
}
|
|
|
|
GET_INSTRUCTION_ARG(dst, argc * 2);
|
|
|
|
*dst = dict;
|
|
|
|
ip += 2;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CALL_ASYNC)
|
|
OPCODE(OPCODE_CALL_RETURN)
|
|
OPCODE(OPCODE_CALL) {
|
|
CHECK_SPACE(3 + instr_arg_count);
|
|
bool call_ret = (_code_ptr[ip] & INSTR_MASK) != OPCODE_CALL;
|
|
#ifdef DEBUG_ENABLED
|
|
bool call_async = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_ASYNC;
|
|
#endif
|
|
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
GD_ERR_BREAK(argc < 0);
|
|
|
|
int methodname_idx = _code_ptr[ip + 2];
|
|
GD_ERR_BREAK(methodname_idx < 0 || methodname_idx >= _global_names_count);
|
|
const StringName *methodname = &_global_names_ptr[methodname_idx];
|
|
|
|
GET_INSTRUCTION_ARG(base, argc);
|
|
Variant **argptrs = instruction_args;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
uint64_t call_time = 0;
|
|
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
call_time = OS::get_singleton()->get_ticks_usec();
|
|
}
|
|
|
|
#endif
|
|
Callable::CallError err;
|
|
if (call_ret) {
|
|
GET_INSTRUCTION_ARG(ret, argc + 1);
|
|
base->call(*methodname, (const Variant **)argptrs, argc, *ret, err);
|
|
#ifdef DEBUG_ENABLED
|
|
if (!call_async && ret->get_type() == Variant::OBJECT) {
|
|
// Check if getting a function state without await.
|
|
bool was_freed = false;
|
|
Object *obj = ret->get_validated_object_with_check(was_freed);
|
|
|
|
if (was_freed) {
|
|
err_text = "Got a freed object as a result of the call.";
|
|
OPCODE_BREAK;
|
|
}
|
|
if (obj && obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) {
|
|
err_text = R"(Trying to call an async function without "await".)";
|
|
OPCODE_BREAK;
|
|
}
|
|
}
|
|
#endif
|
|
} else {
|
|
Variant ret;
|
|
base->call(*methodname, (const Variant **)argptrs, argc, ret, err);
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
|
|
}
|
|
|
|
if (err.error != Callable::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 == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
|
|
err.argument += 1;
|
|
}
|
|
}
|
|
} else if (methodstr == "free") {
|
|
if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
|
|
if (base->is_ref()) {
|
|
err_text = "Attempted to free a reference.";
|
|
OPCODE_BREAK;
|
|
} else if (base->get_type() == Variant::OBJECT) {
|
|
err_text = "Attempted to free a locked object (calling or emitting).";
|
|
OPCODE_BREAK;
|
|
}
|
|
}
|
|
} else if (methodstr == "call_recursive" && basestr == "TreeItem") {
|
|
if (argc >= 1) {
|
|
methodstr = String(*argptrs[0]) + " (via TreeItem.call_recursive)";
|
|
if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
|
|
err.argument += 1;
|
|
}
|
|
}
|
|
}
|
|
err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs);
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CALL_METHOD_BIND)
|
|
OPCODE(OPCODE_CALL_METHOD_BIND_RET) {
|
|
CHECK_SPACE(3 + instr_arg_count);
|
|
bool call_ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_METHOD_BIND_RET;
|
|
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
GD_ERR_BREAK(argc < 0);
|
|
GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count);
|
|
MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];
|
|
|
|
GET_INSTRUCTION_ARG(base, argc);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
bool freed = false;
|
|
Object *base_obj = base->get_validated_object_with_check(freed);
|
|
if (freed) {
|
|
err_text = "Trying to call a function on a previously freed instance.";
|
|
OPCODE_BREAK;
|
|
} else if (!base_obj) {
|
|
err_text = "Trying to call a function on a null value.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#else
|
|
Object *base_obj = base->operator Object *();
|
|
#endif
|
|
Variant **argptrs = instruction_args;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
uint64_t call_time = 0;
|
|
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
call_time = OS::get_singleton()->get_ticks_usec();
|
|
}
|
|
#endif
|
|
|
|
Callable::CallError err;
|
|
if (call_ret) {
|
|
GET_INSTRUCTION_ARG(ret, argc + 1);
|
|
*ret = method->call(base_obj, (const Variant **)argptrs, argc, err);
|
|
} else {
|
|
method->call(base_obj, (const Variant **)argptrs, argc, err);
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
|
|
}
|
|
|
|
if (err.error != Callable::CallError::CALL_OK) {
|
|
String methodstr = method->get_name();
|
|
String basestr = _get_var_type(base);
|
|
|
|
if (methodstr == "call") {
|
|
if (argc >= 1) {
|
|
methodstr = String(*argptrs[0]) + " (via call)";
|
|
if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
|
|
err.argument += 1;
|
|
}
|
|
}
|
|
} else if (methodstr == "free") {
|
|
if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
|
|
if (base->is_ref()) {
|
|
err_text = "Attempted to free a reference.";
|
|
OPCODE_BREAK;
|
|
} else if (base->get_type() == Variant::OBJECT) {
|
|
err_text = "Attempted to free a locked object (calling or emitting).";
|
|
OPCODE_BREAK;
|
|
}
|
|
}
|
|
}
|
|
err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs);
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
#define OPCODE_CALL_PTR(m_type) \
|
|
OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \
|
|
CHECK_SPACE(3 + instr_arg_count); \
|
|
ip += instr_arg_count; \
|
|
int argc = _code_ptr[ip + 1]; \
|
|
GD_ERR_BREAK(argc < 0); \
|
|
GET_INSTRUCTION_ARG(base, argc); \
|
|
GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count); \
|
|
MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \
|
|
bool freed = false; \
|
|
Object *base_obj = base->get_validated_object_with_check(freed); \
|
|
if (freed) { \
|
|
err_text = "Trying to call a function on a previously freed instance."; \
|
|
OPCODE_BREAK; \
|
|
} else if (!base_obj) { \
|
|
err_text = "Trying to call a function on a null value."; \
|
|
OPCODE_BREAK; \
|
|
} \
|
|
const void **argptrs = call_args_ptr; \
|
|
for (int i = 0; i < argc; i++) { \
|
|
GET_INSTRUCTION_ARG(v, i); \
|
|
argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \
|
|
} \
|
|
uint64_t call_time = 0; \
|
|
if (GDScriptLanguage::get_singleton()->profiling) { \
|
|
call_time = OS::get_singleton()->get_ticks_usec(); \
|
|
} \
|
|
GET_INSTRUCTION_ARG(ret, argc + 1); \
|
|
VariantInternal::initialize(ret, Variant::m_type); \
|
|
void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \
|
|
method->ptrcall(base_obj, argptrs, ret_opaque); \
|
|
if (GDScriptLanguage::get_singleton()->profiling) { \
|
|
function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; \
|
|
} \
|
|
ip += 3; \
|
|
} \
|
|
DISPATCH_OPCODE
|
|
#else
|
|
#define OPCODE_CALL_PTR(m_type) \
|
|
OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \
|
|
CHECK_SPACE(3 + instr_arg_count); \
|
|
int argc = _code_ptr[ip + 1]; \
|
|
GET_INSTRUCTION_ARG(base, argc); \
|
|
MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \
|
|
Object *base_obj = *VariantInternal::get_object(base); \
|
|
const void **argptrs = call_args_ptr; \
|
|
for (int i = 0; i < argc; i++) { \
|
|
GET_INSTRUCTION_ARG(v, i); \
|
|
argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \
|
|
} \
|
|
GET_INSTRUCTION_ARG(ret, argc + 1); \
|
|
VariantInternal::initialize(ret, Variant::m_type); \
|
|
void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \
|
|
method->ptrcall(base_obj, argptrs, ret_opaque); \
|
|
ip += 3; \
|
|
} \
|
|
DISPATCH_OPCODE
|
|
#endif
|
|
|
|
OPCODE_CALL_PTR(BOOL);
|
|
OPCODE_CALL_PTR(INT);
|
|
OPCODE_CALL_PTR(FLOAT);
|
|
OPCODE_CALL_PTR(STRING);
|
|
OPCODE_CALL_PTR(VECTOR2);
|
|
OPCODE_CALL_PTR(VECTOR2I);
|
|
OPCODE_CALL_PTR(RECT2);
|
|
OPCODE_CALL_PTR(RECT2I);
|
|
OPCODE_CALL_PTR(VECTOR3);
|
|
OPCODE_CALL_PTR(VECTOR3I);
|
|
OPCODE_CALL_PTR(TRANSFORM2D);
|
|
OPCODE_CALL_PTR(PLANE);
|
|
OPCODE_CALL_PTR(QUAT);
|
|
OPCODE_CALL_PTR(AABB);
|
|
OPCODE_CALL_PTR(BASIS);
|
|
OPCODE_CALL_PTR(TRANSFORM);
|
|
OPCODE_CALL_PTR(COLOR);
|
|
OPCODE_CALL_PTR(STRING_NAME);
|
|
OPCODE_CALL_PTR(NODE_PATH);
|
|
OPCODE_CALL_PTR(RID);
|
|
OPCODE_CALL_PTR(CALLABLE);
|
|
OPCODE_CALL_PTR(SIGNAL);
|
|
OPCODE_CALL_PTR(DICTIONARY);
|
|
OPCODE_CALL_PTR(ARRAY);
|
|
OPCODE_CALL_PTR(PACKED_BYTE_ARRAY);
|
|
OPCODE_CALL_PTR(PACKED_INT32_ARRAY);
|
|
OPCODE_CALL_PTR(PACKED_INT64_ARRAY);
|
|
OPCODE_CALL_PTR(PACKED_FLOAT32_ARRAY);
|
|
OPCODE_CALL_PTR(PACKED_FLOAT64_ARRAY);
|
|
OPCODE_CALL_PTR(PACKED_STRING_ARRAY);
|
|
OPCODE_CALL_PTR(PACKED_VECTOR2_ARRAY);
|
|
OPCODE_CALL_PTR(PACKED_VECTOR3_ARRAY);
|
|
OPCODE_CALL_PTR(PACKED_COLOR_ARRAY);
|
|
OPCODE(OPCODE_CALL_PTRCALL_OBJECT) {
|
|
CHECK_SPACE(3 + instr_arg_count);
|
|
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
GD_ERR_BREAK(argc < 0);
|
|
|
|
GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count);
|
|
MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];
|
|
|
|
GET_INSTRUCTION_ARG(base, argc);
|
|
#ifdef DEBUG_ENABLED
|
|
bool freed = false;
|
|
Object *base_obj = base->get_validated_object_with_check(freed);
|
|
if (freed) {
|
|
err_text = "Trying to call a function on a previously freed instance.";
|
|
OPCODE_BREAK;
|
|
} else if (!base_obj) {
|
|
err_text = "Trying to call a function on a null value.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#else
|
|
Object *base_obj = *VariantInternal::get_object(base);
|
|
#endif
|
|
|
|
const void **argptrs = call_args_ptr;
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
GET_INSTRUCTION_ARG(v, i);
|
|
argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
uint64_t call_time = 0;
|
|
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
call_time = OS::get_singleton()->get_ticks_usec();
|
|
}
|
|
#endif
|
|
|
|
GET_INSTRUCTION_ARG(ret, argc + 1);
|
|
VariantInternal::initialize(ret, Variant::OBJECT);
|
|
Object **ret_opaque = VariantInternal::get_object(ret);
|
|
method->ptrcall(base_obj, argptrs, ret_opaque);
|
|
VariantInternal::object_assign(ret, *ret_opaque); // Set so ID is correct too.
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
|
|
}
|
|
#endif
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
OPCODE(OPCODE_CALL_PTRCALL_NO_RETURN) {
|
|
CHECK_SPACE(3 + instr_arg_count);
|
|
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
GD_ERR_BREAK(argc < 0);
|
|
|
|
GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count);
|
|
MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];
|
|
|
|
GET_INSTRUCTION_ARG(base, argc);
|
|
#ifdef DEBUG_ENABLED
|
|
bool freed = false;
|
|
Object *base_obj = base->get_validated_object_with_check(freed);
|
|
if (freed) {
|
|
err_text = "Trying to call a function on a previously freed instance.";
|
|
OPCODE_BREAK;
|
|
} else if (!base_obj) {
|
|
err_text = "Trying to call a function on a null value.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#else
|
|
Object *base_obj = *VariantInternal::get_object(base);
|
|
#endif
|
|
const void **argptrs = call_args_ptr;
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
GET_INSTRUCTION_ARG(v, i);
|
|
argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
uint64_t call_time = 0;
|
|
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
call_time = OS::get_singleton()->get_ticks_usec();
|
|
}
|
|
#endif
|
|
|
|
GET_INSTRUCTION_ARG(ret, argc + 1);
|
|
VariantInternal::initialize(ret, Variant::NIL);
|
|
method->ptrcall(base_obj, argptrs, nullptr);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
|
|
}
|
|
#endif
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CALL_BUILTIN_TYPE_VALIDATED) {
|
|
CHECK_SPACE(3 + instr_arg_count);
|
|
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
GD_ERR_BREAK(argc < 0);
|
|
|
|
GET_INSTRUCTION_ARG(base, argc);
|
|
|
|
GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _builtin_methods_count);
|
|
Variant::ValidatedBuiltInMethod method = _builtin_methods_ptr[_code_ptr[ip + 2]];
|
|
Variant **argptrs = instruction_args;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
uint64_t call_time = 0;
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
call_time = OS::get_singleton()->get_ticks_usec();
|
|
}
|
|
#endif
|
|
|
|
GET_INSTRUCTION_ARG(ret, argc + 1);
|
|
method(base, (const Variant **)argptrs, argc, ret);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
|
function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
|
|
}
|
|
#endif
|
|
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CALL_UTILITY) {
|
|
CHECK_SPACE(3 + instr_arg_count);
|
|
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
GD_ERR_BREAK(argc < 0);
|
|
|
|
GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _global_names_count);
|
|
StringName function = _global_names_ptr[_code_ptr[ip + 2]];
|
|
|
|
Variant **argptrs = instruction_args;
|
|
|
|
GET_INSTRUCTION_ARG(dst, argc);
|
|
|
|
Callable::CallError err;
|
|
Variant::call_utility_function(function, dst, (const Variant **)argptrs, argc, err);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (err.error != Callable::CallError::CALL_OK) {
|
|
String methodstr = function;
|
|
if (dst->get_type() == Variant::STRING) {
|
|
// Call provided error string.
|
|
err_text = "Error calling utility function '" + methodstr + "': " + String(*dst);
|
|
} else {
|
|
err_text = _get_call_error(err, "utility function '" + methodstr + "'", (const Variant **)argptrs);
|
|
}
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CALL_UTILITY_VALIDATED) {
|
|
CHECK_SPACE(3 + instr_arg_count);
|
|
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
GD_ERR_BREAK(argc < 0);
|
|
|
|
GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _utilities_count);
|
|
Variant::ValidatedUtilityFunction function = _utilities_ptr[_code_ptr[ip + 2]];
|
|
|
|
Variant **argptrs = instruction_args;
|
|
|
|
GET_INSTRUCTION_ARG(dst, argc);
|
|
|
|
function(dst, (const Variant **)argptrs, argc);
|
|
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CALL_GDSCRIPT_UTILITY) {
|
|
CHECK_SPACE(3 + instr_arg_count);
|
|
|
|
ip += instr_arg_count;
|
|
|
|
int argc = _code_ptr[ip + 1];
|
|
GD_ERR_BREAK(argc < 0);
|
|
|
|
GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _gds_utilities_count);
|
|
GDScriptUtilityFunctions::FunctionPtr function = _gds_utilities_ptr[_code_ptr[ip + 2]];
|
|
|
|
Variant **argptrs = instruction_args;
|
|
|
|
GET_INSTRUCTION_ARG(dst, argc);
|
|
|
|
Callable::CallError err;
|
|
function(dst, (const Variant **)argptrs, argc, err);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (err.error != Callable::CallError::CALL_OK) {
|
|
// TODO: Add this information in debug.
|
|
String methodstr = "<unkown function>";
|
|
if (dst->get_type() == Variant::STRING) {
|
|
// Call provided error string.
|
|
err_text = "Error calling GDScript utility function '" + methodstr + "': " + String(*dst);
|
|
} else {
|
|
err_text = _get_call_error(err, "GDScript utility function '" + methodstr + "'", (const Variant **)argptrs);
|
|
}
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_CALL_SELF_BASE) {
|
|
CHECK_SPACE(3 + instr_arg_count);
|
|
|
|
ip += instr_arg_count;
|
|
|
|
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";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
const StringName *methodname = &_global_names_ptr[self_fun];
|
|
|
|
int argc = _code_ptr[ip + 2];
|
|
GD_ERR_BREAK(argc < 0);
|
|
|
|
Variant **argptrs = instruction_args;
|
|
|
|
GET_INSTRUCTION_ARG(dst, argc);
|
|
|
|
const GDScript *gds = _script;
|
|
|
|
const Map<StringName, GDScriptFunction *>::Element *E = nullptr;
|
|
while (gds->base.ptr()) {
|
|
gds = gds->base.ptr();
|
|
E = gds->member_functions.find(*methodname);
|
|
if (E) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Callable::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 = Callable::CallError::CALL_ERROR_INVALID_METHOD;
|
|
} else {
|
|
*dst = mb->call(p_instance->owner, (const Variant **)argptrs, argc, err);
|
|
}
|
|
} else {
|
|
err.error = Callable::CallError::CALL_OK;
|
|
}
|
|
} else {
|
|
if (*methodname != GDScriptLanguage::get_singleton()->strings._init) {
|
|
err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
|
|
} else {
|
|
err.error = Callable::CallError::CALL_OK;
|
|
}
|
|
}
|
|
|
|
if (err.error != Callable::CallError::CALL_OK) {
|
|
String methodstr = *methodname;
|
|
err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs);
|
|
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_AWAIT) {
|
|
CHECK_SPACE(2);
|
|
|
|
// Do the oneshot connect.
|
|
GET_INSTRUCTION_ARG(argobj, 0);
|
|
|
|
Signal sig;
|
|
bool is_signal = true;
|
|
|
|
{
|
|
Variant result = *argobj;
|
|
|
|
if (argobj->get_type() == Variant::OBJECT) {
|
|
bool was_freed = false;
|
|
Object *obj = argobj->get_validated_object_with_check(was_freed);
|
|
|
|
if (was_freed) {
|
|
err_text = "Trying to await on a freed object.";
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
// Is this even possible to be null at this point?
|
|
if (obj) {
|
|
if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) {
|
|
static StringName completed = _scs_create("completed");
|
|
result = Signal(obj, completed);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result.get_type() != Variant::SIGNAL) {
|
|
ip += 4; // Skip OPCODE_AWAIT_RESUME and its data.
|
|
// The stack pointer should be the same, so we don't need to set a return value.
|
|
is_signal = false;
|
|
} else {
|
|
sig = result;
|
|
}
|
|
}
|
|
|
|
if (is_signal) {
|
|
Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState);
|
|
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.write[sizeof(Variant) * i], Variant(stack[i]));
|
|
}
|
|
gdfs->state.stack_size = _stack_size;
|
|
gdfs->state.alloca_size = alloca_size;
|
|
gdfs->state.ip = ip + 2;
|
|
gdfs->state.line = line;
|
|
gdfs->state.script = _script;
|
|
{
|
|
MutexLock lock(GDScriptLanguage::get_singleton()->lock);
|
|
_script->pending_func_states.add(&gdfs->scripts_list);
|
|
if (p_instance) {
|
|
gdfs->state.instance = p_instance;
|
|
p_instance->pending_func_states.add(&gdfs->instances_list);
|
|
} else {
|
|
gdfs->state.instance = nullptr;
|
|
}
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
gdfs->state.function_name = name;
|
|
gdfs->state.script_path = _script->get_path();
|
|
#endif
|
|
gdfs->state.defarg = defarg;
|
|
gdfs->function = this;
|
|
|
|
retvalue = gdfs;
|
|
|
|
Error err = sig.connect(Callable(gdfs.ptr(), "_signal_callback"), varray(gdfs), Object::CONNECT_ONESHOT);
|
|
if (err != OK) {
|
|
err_text = "Error connecting to signal: " + sig.get_name() + " during await.";
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
exit_ok = true;
|
|
awaited = true;
|
|
#endif
|
|
OPCODE_BREAK;
|
|
}
|
|
}
|
|
DISPATCH_OPCODE; // Needed for synchronous calls (when result is immediately available).
|
|
|
|
OPCODE(OPCODE_AWAIT_RESUME) {
|
|
CHECK_SPACE(2);
|
|
#ifdef DEBUG_ENABLED
|
|
if (!p_state) {
|
|
err_text = ("Invalid Resume (bug?)");
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
GET_INSTRUCTION_ARG(result, 0);
|
|
*result = p_state->result;
|
|
ip += 2;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_JUMP) {
|
|
CHECK_SPACE(2);
|
|
int to = _code_ptr[ip + 1];
|
|
|
|
GD_ERR_BREAK(to < 0 || to > _code_size);
|
|
ip = to;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_JUMP_IF) {
|
|
CHECK_SPACE(3);
|
|
|
|
GET_INSTRUCTION_ARG(test, 0);
|
|
|
|
bool result = test->booleanize();
|
|
|
|
if (result) {
|
|
int to = _code_ptr[ip + 2];
|
|
GD_ERR_BREAK(to < 0 || to > _code_size);
|
|
ip = to;
|
|
} else {
|
|
ip += 3;
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_JUMP_IF_NOT) {
|
|
CHECK_SPACE(3);
|
|
|
|
GET_INSTRUCTION_ARG(test, 0);
|
|
|
|
bool result = test->booleanize();
|
|
|
|
if (!result) {
|
|
int to = _code_ptr[ip + 2];
|
|
GD_ERR_BREAK(to < 0 || to > _code_size);
|
|
ip = to;
|
|
} else {
|
|
ip += 3;
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_JUMP_TO_DEF_ARGUMENT) {
|
|
CHECK_SPACE(2);
|
|
ip = _default_arg_ptr[defarg];
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_RETURN) {
|
|
CHECK_SPACE(2);
|
|
GET_INSTRUCTION_ARG(r, 0);
|
|
retvalue = *r;
|
|
#ifdef DEBUG_ENABLED
|
|
exit_ok = true;
|
|
#endif
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
OPCODE(OPCODE_RETURN_TYPED_BUILTIN) {
|
|
CHECK_SPACE(3);
|
|
GET_INSTRUCTION_ARG(r, 0);
|
|
|
|
Variant::Type ret_type = (Variant::Type)_code_ptr[ip + 2];
|
|
GD_ERR_BREAK(ret_type < 0 || ret_type >= Variant::VARIANT_MAX);
|
|
|
|
if (r->get_type() != ret_type) {
|
|
if (Variant::can_convert_strict(r->get_type(), ret_type)) {
|
|
Callable::CallError ce;
|
|
Variant::construct(ret_type, retvalue, const_cast<const Variant **>(&r), 1, ce);
|
|
} else {
|
|
#ifdef DEBUG_ENABLED
|
|
err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)",
|
|
Variant::get_type_name(r->get_type()), Variant::get_type_name(ret_type));
|
|
#endif // DEBUG_ENABLED
|
|
|
|
// Construct a base type anyway so type constraints are met.
|
|
Callable::CallError ce;
|
|
Variant::construct(ret_type, retvalue, nullptr, 0, ce);
|
|
OPCODE_BREAK;
|
|
}
|
|
} else {
|
|
retvalue = *r;
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
exit_ok = true;
|
|
#endif // DEBUG_ENABLED
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
OPCODE(OPCODE_RETURN_TYPED_ARRAY) {
|
|
CHECK_SPACE(5);
|
|
GET_INSTRUCTION_ARG(r, 0);
|
|
|
|
GET_INSTRUCTION_ARG(script_type, 1);
|
|
Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 3];
|
|
int native_type_idx = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count);
|
|
const StringName native_type = _global_names_ptr[native_type_idx];
|
|
|
|
if (r->get_type() != Variant::ARRAY) {
|
|
#ifdef DEBUG_ENABLED
|
|
err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "Array[%s]".)",
|
|
Variant::get_type_name(r->get_type()), Variant::get_type_name(builtin_type));
|
|
#endif
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
Array array;
|
|
array.set_typed(builtin_type, native_type, script_type);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
bool valid = array.typed_assign(*VariantInternal::get_array(r));
|
|
#else
|
|
array.typed_assign(*VariantInternal::get_array(r));
|
|
#endif // DEBUG_ENABLED
|
|
|
|
// Assign the return value anyway since we want it to be the valid type.
|
|
retvalue = array;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
err_text = "Trying to return a typed array with an array of different type.'";
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
exit_ok = true;
|
|
#endif // DEBUG_ENABLED
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
OPCODE(OPCODE_RETURN_TYPED_NATIVE) {
|
|
CHECK_SPACE(3);
|
|
GET_INSTRUCTION_ARG(r, 0);
|
|
|
|
GET_INSTRUCTION_ARG(type, 1);
|
|
GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *());
|
|
GD_ERR_BREAK(!nc);
|
|
|
|
if (r->get_type() != Variant::OBJECT && r->get_type() != Variant::NIL) {
|
|
err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)",
|
|
Variant::get_type_name(r->get_type()), nc->get_name());
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
bool freed = false;
|
|
Object *ret_obj = r->get_validated_object_with_check(freed);
|
|
|
|
if (freed) {
|
|
err_text = "Trying to return a previously freed instance.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#else
|
|
Object *ret_obj = r->operator Object *();
|
|
#endif // DEBUG_ENABLED
|
|
if (ret_obj && !ClassDB::is_parent_class(ret_obj->get_class_name(), nc->get_name())) {
|
|
#ifdef DEBUG_ENABLED
|
|
err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)",
|
|
ret_obj->get_class_name(), nc->get_name());
|
|
#endif // DEBUG_ENABLED
|
|
OPCODE_BREAK;
|
|
}
|
|
retvalue = *r;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
exit_ok = true;
|
|
#endif // DEBUG_ENABLED
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
OPCODE(OPCODE_RETURN_TYPED_SCRIPT) {
|
|
CHECK_SPACE(3);
|
|
GET_INSTRUCTION_ARG(r, 0);
|
|
|
|
GET_INSTRUCTION_ARG(type, 1);
|
|
Script *base_type = Object::cast_to<Script>(type->operator Object *());
|
|
GD_ERR_BREAK(!base_type);
|
|
|
|
if (r->get_type() != Variant::OBJECT && r->get_type() != Variant::NIL) {
|
|
#ifdef DEBUG_ENABLED
|
|
err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)",
|
|
Variant::get_type_name(r->get_type()), _get_script_name(Ref<Script>(base_type)));
|
|
#endif // DEBUG_ENABLED
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
bool freed = false;
|
|
Object *ret_obj = r->get_validated_object_with_check(freed);
|
|
|
|
if (freed) {
|
|
err_text = "Trying to return a previously freed instance.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#else
|
|
Object *ret_obj = r->operator Object *();
|
|
#endif // DEBUG_ENABLED
|
|
|
|
if (ret_obj) {
|
|
ScriptInstance *ret_inst = ret_obj->get_script_instance();
|
|
if (!ret_inst) {
|
|
#ifdef DEBUG_ENABLED
|
|
err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)",
|
|
ret_obj->get_class_name(), _get_script_name(Ref<GDScript>(base_type)));
|
|
#endif // DEBUG_ENABLED
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
Script *ret_type = ret_obj->get_script_instance()->get_script().ptr();
|
|
bool valid = false;
|
|
|
|
while (ret_type) {
|
|
if (ret_type == base_type) {
|
|
valid = true;
|
|
break;
|
|
}
|
|
ret_type = ret_type->get_base_script().ptr();
|
|
}
|
|
|
|
if (!valid) {
|
|
#ifdef DEBUG_ENABLED
|
|
err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)",
|
|
_get_script_name(ret_obj->get_script_instance()->get_script()), _get_script_name(Ref<GDScript>(base_type)));
|
|
#endif // DEBUG_ENABLED
|
|
OPCODE_BREAK;
|
|
}
|
|
}
|
|
retvalue = *r;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
exit_ok = true;
|
|
#endif // DEBUG_ENABLED
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
OPCODE(OPCODE_ITERATE_BEGIN) {
|
|
CHECK_SPACE(8); // Space for this and a regular iterate.
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
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()) + "'.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
|
|
*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()) + "'.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
ip += 5; // Skip regular iterate which is always next.
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_BEGIN_INT) {
|
|
CHECK_SPACE(8); // Check space for iterate instruction too.
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
int64_t size = *VariantInternal::get_int(container);
|
|
|
|
VariantInternal::initialize(counter, Variant::INT);
|
|
*VariantInternal::get_int(counter) = 0;
|
|
|
|
if (size > 0) {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
VariantInternal::initialize(iterator, Variant::INT);
|
|
*VariantInternal::get_int(iterator) = 0;
|
|
|
|
// Skip regular iterate.
|
|
ip += 5;
|
|
} else {
|
|
// Jump to end of loop.
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_BEGIN_FLOAT) {
|
|
CHECK_SPACE(8); // Check space for iterate instruction too.
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
double size = *VariantInternal::get_float(container);
|
|
|
|
VariantInternal::initialize(counter, Variant::FLOAT);
|
|
*VariantInternal::get_float(counter) = 0.0;
|
|
|
|
if (size > 0) {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
VariantInternal::initialize(iterator, Variant::FLOAT);
|
|
*VariantInternal::get_float(iterator) = 0;
|
|
|
|
// Skip regular iterate.
|
|
ip += 5;
|
|
} else {
|
|
// Jump to end of loop.
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_BEGIN_VECTOR2) {
|
|
CHECK_SPACE(8); // Check space for iterate instruction too.
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
Vector2 *bounds = VariantInternal::get_vector2(container);
|
|
|
|
VariantInternal::initialize(counter, Variant::FLOAT);
|
|
*VariantInternal::get_float(counter) = bounds->x;
|
|
|
|
if (bounds->x < bounds->y) {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
VariantInternal::initialize(iterator, Variant::FLOAT);
|
|
*VariantInternal::get_float(iterator) = bounds->x;
|
|
|
|
// Skip regular iterate.
|
|
ip += 5;
|
|
} else {
|
|
// Jump to end of loop.
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_BEGIN_VECTOR2I) {
|
|
CHECK_SPACE(8); // Check space for iterate instruction too.
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
Vector2i *bounds = VariantInternal::get_vector2i(container);
|
|
|
|
VariantInternal::initialize(counter, Variant::FLOAT);
|
|
*VariantInternal::get_int(counter) = bounds->x;
|
|
|
|
if (bounds->x < bounds->y) {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
VariantInternal::initialize(iterator, Variant::INT);
|
|
*VariantInternal::get_int(iterator) = bounds->x;
|
|
|
|
// Skip regular iterate.
|
|
ip += 5;
|
|
} else {
|
|
// Jump to end of loop.
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_BEGIN_VECTOR3) {
|
|
CHECK_SPACE(8); // Check space for iterate instruction too.
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
Vector3 *bounds = VariantInternal::get_vector3(container);
|
|
double from = bounds->x;
|
|
double to = bounds->y;
|
|
double step = bounds->z;
|
|
|
|
VariantInternal::initialize(counter, Variant::FLOAT);
|
|
*VariantInternal::get_float(counter) = from;
|
|
|
|
bool do_continue = from == to ? false : (from < to ? step > 0 : step < 0);
|
|
|
|
if (do_continue) {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
VariantInternal::initialize(iterator, Variant::FLOAT);
|
|
*VariantInternal::get_float(iterator) = from;
|
|
|
|
// Skip regular iterate.
|
|
ip += 5;
|
|
} else {
|
|
// Jump to end of loop.
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_BEGIN_VECTOR3I) {
|
|
CHECK_SPACE(8); // Check space for iterate instruction too.
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
Vector3i *bounds = VariantInternal::get_vector3i(container);
|
|
int64_t from = bounds->x;
|
|
int64_t to = bounds->y;
|
|
int64_t step = bounds->z;
|
|
|
|
VariantInternal::initialize(counter, Variant::INT);
|
|
*VariantInternal::get_int(counter) = from;
|
|
|
|
bool do_continue = from == to ? false : (from < to ? step > 0 : step < 0);
|
|
|
|
if (do_continue) {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
VariantInternal::initialize(iterator, Variant::INT);
|
|
*VariantInternal::get_int(iterator) = from;
|
|
|
|
// Skip regular iterate.
|
|
ip += 5;
|
|
} else {
|
|
// Jump to end of loop.
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_BEGIN_STRING) {
|
|
CHECK_SPACE(8); // Check space for iterate instruction too.
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
String *str = VariantInternal::get_string(container);
|
|
|
|
VariantInternal::initialize(counter, Variant::INT);
|
|
*VariantInternal::get_int(counter) = 0;
|
|
|
|
if (!str->is_empty()) {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
VariantInternal::initialize(iterator, Variant::STRING);
|
|
*VariantInternal::get_string(iterator) = str->substr(0, 1);
|
|
|
|
// Skip regular iterate.
|
|
ip += 5;
|
|
} else {
|
|
// Jump to end of loop.
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_BEGIN_DICTIONARY) {
|
|
CHECK_SPACE(8); // Check space for iterate instruction too.
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
Dictionary *dict = VariantInternal::get_dictionary(container);
|
|
const Variant *next = dict->next(nullptr);
|
|
|
|
if (!dict->is_empty()) {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*counter = *next;
|
|
*iterator = *next;
|
|
|
|
// Skip regular iterate.
|
|
ip += 5;
|
|
} else {
|
|
// Jump to end of loop.
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_BEGIN_ARRAY) {
|
|
CHECK_SPACE(8); // Check space for iterate instruction too.
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
Array *array = VariantInternal::get_array(container);
|
|
|
|
VariantInternal::initialize(counter, Variant::INT);
|
|
*VariantInternal::get_int(counter) = 0;
|
|
|
|
if (!array->is_empty()) {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*iterator = array->get(0);
|
|
|
|
// Skip regular iterate.
|
|
ip += 5;
|
|
} else {
|
|
// Jump to end of loop.
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
#define OPCODE_ITERATE_BEGIN_PACKED_ARRAY(m_var_type, m_elem_type, m_get_func, m_var_ret_type, m_ret_type, m_ret_get_func) \
|
|
OPCODE(OPCODE_ITERATE_BEGIN_PACKED_##m_var_type##_ARRAY) { \
|
|
CHECK_SPACE(8); \
|
|
GET_INSTRUCTION_ARG(counter, 0); \
|
|
GET_INSTRUCTION_ARG(container, 1); \
|
|
Vector<m_elem_type> *array = VariantInternal::m_get_func(container); \
|
|
VariantInternal::initialize(counter, Variant::INT); \
|
|
*VariantInternal::get_int(counter) = 0; \
|
|
if (!array->is_empty()) { \
|
|
GET_INSTRUCTION_ARG(iterator, 2); \
|
|
VariantInternal::initialize(iterator, Variant::m_var_ret_type); \
|
|
m_ret_type *it = VariantInternal::m_ret_get_func(iterator); \
|
|
*it = array->get(0); \
|
|
ip += 5; \
|
|
} else { \
|
|
int jumpto = _code_ptr[ip + 4]; \
|
|
GD_ERR_BREAK(jumpto<0 || jumpto> _code_size); \
|
|
ip = jumpto; \
|
|
} \
|
|
} \
|
|
DISPATCH_OPCODE
|
|
|
|
OPCODE_ITERATE_BEGIN_PACKED_ARRAY(BYTE, uint8_t, get_byte_array, INT, int64_t, get_int);
|
|
OPCODE_ITERATE_BEGIN_PACKED_ARRAY(INT32, int32_t, get_int32_array, INT, int64_t, get_int);
|
|
OPCODE_ITERATE_BEGIN_PACKED_ARRAY(INT64, int64_t, get_int64_array, INT, int64_t, get_int);
|
|
OPCODE_ITERATE_BEGIN_PACKED_ARRAY(FLOAT32, float, get_float32_array, FLOAT, double, get_float);
|
|
OPCODE_ITERATE_BEGIN_PACKED_ARRAY(FLOAT64, double, get_float64_array, FLOAT, double, get_float);
|
|
OPCODE_ITERATE_BEGIN_PACKED_ARRAY(STRING, String, get_string_array, STRING, String, get_string);
|
|
OPCODE_ITERATE_BEGIN_PACKED_ARRAY(VECTOR2, Vector2, get_vector2_array, VECTOR2, Vector2, get_vector2);
|
|
OPCODE_ITERATE_BEGIN_PACKED_ARRAY(VECTOR3, Vector3, get_vector3_array, VECTOR3, Vector3, get_vector3);
|
|
OPCODE_ITERATE_BEGIN_PACKED_ARRAY(COLOR, Color, get_color_array, COLOR, Color, get_color);
|
|
|
|
OPCODE(OPCODE_ITERATE_BEGIN_OBJECT) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
bool freed = false;
|
|
Object *obj = container->get_validated_object_with_check(freed);
|
|
if (freed) {
|
|
err_text = "Trying to iterate on a previously freed object.";
|
|
OPCODE_BREAK;
|
|
} else if (!obj) {
|
|
err_text = "Trying to iterate on a null value.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#else
|
|
Object *obj = *VariantInternal::get_object(container);
|
|
#endif
|
|
Array ref;
|
|
ref.push_back(*counter);
|
|
Variant vref;
|
|
VariantInternal::initialize(&vref, Variant::ARRAY);
|
|
*VariantInternal::get_array(&vref) = ref;
|
|
|
|
Variant **args = instruction_args; // Overriding an instruction argument, but we don't need access to that anymore.
|
|
args[0] = &vref;
|
|
|
|
Callable::CallError ce;
|
|
Variant has_next = obj->call(CoreStringNames::get_singleton()->_iter_init, (const Variant **)args, 1, ce);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (ce.error != Callable::CallError::CALL_OK) {
|
|
err_text = vformat(R"(There was an error calling "_iter_next" on iterator object of type %s.)", *container);
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
if (!has_next.booleanize()) {
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*iterator = obj->call(CoreStringNames::get_singleton()->_iter_get, (const Variant **)args, 1, ce);
|
|
#ifdef DEBUG_ENABLED
|
|
if (ce.error != Callable::CallError::CALL_OK) {
|
|
err_text = vformat(R"(There was an error calling "_iter_get" on iterator object of type %s.)", *container);
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
|
|
ip += 5; // Loop again.
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
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?).";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
|
|
*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?).";
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
ip += 5; //loop again
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_INT) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
int64_t size = *VariantInternal::get_int(container);
|
|
int64_t *count = VariantInternal::get_int(counter);
|
|
|
|
(*count)++;
|
|
|
|
if (*count >= size) {
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*VariantInternal::get_int(iterator) = *count;
|
|
|
|
ip += 5; // Loop again.
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_FLOAT) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
double size = *VariantInternal::get_float(container);
|
|
double *count = VariantInternal::get_float(counter);
|
|
|
|
(*count)++;
|
|
|
|
if (*count >= size) {
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*VariantInternal::get_float(iterator) = *count;
|
|
|
|
ip += 5; // Loop again.
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_VECTOR2) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
const Vector2 *bounds = VariantInternal::get_vector2((const Variant *)container);
|
|
double *count = VariantInternal::get_float(counter);
|
|
|
|
(*count)++;
|
|
|
|
if (*count >= bounds->y) {
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*VariantInternal::get_float(iterator) = *count;
|
|
|
|
ip += 5; // Loop again.
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_VECTOR2I) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
const Vector2i *bounds = VariantInternal::get_vector2i((const Variant *)container);
|
|
int64_t *count = VariantInternal::get_int(counter);
|
|
|
|
(*count)++;
|
|
|
|
if (*count >= bounds->y) {
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*VariantInternal::get_int(iterator) = *count;
|
|
|
|
ip += 5; // Loop again.
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_VECTOR3) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
const Vector3 *bounds = VariantInternal::get_vector3((const Variant *)container);
|
|
double *count = VariantInternal::get_float(counter);
|
|
|
|
*count += bounds->z;
|
|
|
|
if ((bounds->z < 0 && *count <= bounds->y) || (bounds->z > 0 && *count >= bounds->y)) {
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*VariantInternal::get_float(iterator) = *count;
|
|
|
|
ip += 5; // Loop again.
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_VECTOR3I) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
const Vector3i *bounds = VariantInternal::get_vector3i((const Variant *)container);
|
|
int64_t *count = VariantInternal::get_int(counter);
|
|
|
|
*count += bounds->z;
|
|
|
|
if ((bounds->z < 0 && *count <= bounds->y) || (bounds->z > 0 && *count >= bounds->y)) {
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*VariantInternal::get_int(iterator) = *count;
|
|
|
|
ip += 5; // Loop again.
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_STRING) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
const String *str = VariantInternal::get_string((const Variant *)container);
|
|
int64_t *idx = VariantInternal::get_int(counter);
|
|
(*idx)++;
|
|
|
|
if (*idx >= str->length()) {
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*VariantInternal::get_string(iterator) = str->substr(*idx, 1);
|
|
|
|
ip += 5; // Loop again.
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_DICTIONARY) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
const Dictionary *dict = VariantInternal::get_dictionary((const Variant *)container);
|
|
const Variant *next = dict->next(counter);
|
|
|
|
if (!next) {
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*counter = *next;
|
|
*iterator = *next;
|
|
|
|
ip += 5; // Loop again.
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ITERATE_ARRAY) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
const Array *array = VariantInternal::get_array((const Variant *)container);
|
|
int64_t *idx = VariantInternal::get_int(counter);
|
|
(*idx)++;
|
|
|
|
if (*idx >= array->size()) {
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*iterator = array->get(*idx);
|
|
|
|
ip += 5; // Loop again.
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
#define OPCODE_ITERATE_PACKED_ARRAY(m_var_type, m_elem_type, m_get_func, m_ret_get_func) \
|
|
OPCODE(OPCODE_ITERATE_PACKED_##m_var_type##_ARRAY) { \
|
|
CHECK_SPACE(4); \
|
|
GET_INSTRUCTION_ARG(counter, 0); \
|
|
GET_INSTRUCTION_ARG(container, 1); \
|
|
const Vector<m_elem_type> *array = VariantInternal::m_get_func((const Variant *)container); \
|
|
int64_t *idx = VariantInternal::get_int(counter); \
|
|
(*idx)++; \
|
|
if (*idx >= array->size()) { \
|
|
int jumpto = _code_ptr[ip + 4]; \
|
|
GD_ERR_BREAK(jumpto<0 || jumpto> _code_size); \
|
|
ip = jumpto; \
|
|
} else { \
|
|
GET_INSTRUCTION_ARG(iterator, 2); \
|
|
*VariantInternal::m_ret_get_func(iterator) = array->get(*idx); \
|
|
ip += 5; \
|
|
} \
|
|
} \
|
|
DISPATCH_OPCODE
|
|
|
|
OPCODE_ITERATE_PACKED_ARRAY(BYTE, uint8_t, get_byte_array, get_int);
|
|
OPCODE_ITERATE_PACKED_ARRAY(INT32, int32_t, get_int32_array, get_int);
|
|
OPCODE_ITERATE_PACKED_ARRAY(INT64, int64_t, get_int64_array, get_int);
|
|
OPCODE_ITERATE_PACKED_ARRAY(FLOAT32, float, get_float32_array, get_float);
|
|
OPCODE_ITERATE_PACKED_ARRAY(FLOAT64, double, get_float64_array, get_float);
|
|
OPCODE_ITERATE_PACKED_ARRAY(STRING, String, get_string_array, get_string);
|
|
OPCODE_ITERATE_PACKED_ARRAY(VECTOR2, Vector2, get_vector2_array, get_vector2);
|
|
OPCODE_ITERATE_PACKED_ARRAY(VECTOR3, Vector3, get_vector3_array, get_vector3);
|
|
OPCODE_ITERATE_PACKED_ARRAY(COLOR, Color, get_color_array, get_color);
|
|
|
|
OPCODE(OPCODE_ITERATE_OBJECT) {
|
|
CHECK_SPACE(4);
|
|
|
|
GET_INSTRUCTION_ARG(counter, 0);
|
|
GET_INSTRUCTION_ARG(container, 1);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
bool freed = false;
|
|
Object *obj = container->get_validated_object_with_check(freed);
|
|
if (freed) {
|
|
err_text = "Trying to iterate on a previously freed object.";
|
|
OPCODE_BREAK;
|
|
} else if (!obj) {
|
|
err_text = "Trying to iterate on a null value.";
|
|
OPCODE_BREAK;
|
|
}
|
|
#else
|
|
Object *obj = *VariantInternal::get_object(container);
|
|
#endif
|
|
Array ref;
|
|
ref.push_back(*counter);
|
|
Variant vref;
|
|
VariantInternal::initialize(&vref, Variant::ARRAY);
|
|
*VariantInternal::get_array(&vref) = ref;
|
|
|
|
Variant **args = instruction_args; // Overriding an instruction argument, but we don't need access to that anymore.
|
|
args[0] = &vref;
|
|
|
|
Callable::CallError ce;
|
|
Variant has_next = obj->call(CoreStringNames::get_singleton()->_iter_next, (const Variant **)args, 1, ce);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (ce.error != Callable::CallError::CALL_OK) {
|
|
err_text = vformat(R"(There was an error calling "_iter_next" on iterator object of type %s.)", *container);
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
if (!has_next.booleanize()) {
|
|
int jumpto = _code_ptr[ip + 4];
|
|
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
|
|
ip = jumpto;
|
|
} else {
|
|
GET_INSTRUCTION_ARG(iterator, 2);
|
|
*iterator = obj->call(CoreStringNames::get_singleton()->_iter_get, (const Variant **)args, 1, ce);
|
|
#ifdef DEBUG_ENABLED
|
|
if (ce.error != Callable::CallError::CALL_OK) {
|
|
err_text = vformat(R"(There was an error calling "_iter_get" on iterator object of type %s.)", *container);
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
|
|
ip += 5; // Loop again.
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_STORE_NAMED_GLOBAL) {
|
|
CHECK_SPACE(3);
|
|
int globalname_idx = _code_ptr[ip + 2];
|
|
GD_ERR_BREAK(globalname_idx < 0 || globalname_idx >= _global_names_count);
|
|
const StringName *globalname = &_global_names_ptr[globalname_idx];
|
|
|
|
GET_INSTRUCTION_ARG(dst, 0);
|
|
*dst = GDScriptLanguage::get_singleton()->get_named_globals_map()[*globalname];
|
|
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_ASSERT) {
|
|
CHECK_SPACE(3);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
GET_INSTRUCTION_ARG(test, 0);
|
|
bool result = test->booleanize();
|
|
|
|
if (!result) {
|
|
String message_str;
|
|
if (_code_ptr[ip + 2] != 0) {
|
|
GET_INSTRUCTION_ARG(message, 1);
|
|
message_str = *message;
|
|
}
|
|
if (message_str.is_empty()) {
|
|
err_text = "Assertion failed.";
|
|
} else {
|
|
err_text = "Assertion failed: " + message_str;
|
|
}
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
#endif
|
|
ip += 3;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_BREAKPOINT) {
|
|
#ifdef DEBUG_ENABLED
|
|
if (EngineDebugger::is_active()) {
|
|
GDScriptLanguage::get_singleton()->debug_break("Breakpoint Statement", true);
|
|
}
|
|
#endif
|
|
ip += 1;
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_LINE) {
|
|
CHECK_SPACE(2);
|
|
|
|
line = _code_ptr[ip + 1];
|
|
ip += 2;
|
|
|
|
if (EngineDebugger::is_active()) {
|
|
// line
|
|
bool do_break = false;
|
|
|
|
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) {
|
|
if (EngineDebugger::get_script_debugger()->get_depth() <= 0) {
|
|
EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1);
|
|
}
|
|
if (EngineDebugger::get_script_debugger()->get_lines_left() <= 0) {
|
|
do_break = true;
|
|
}
|
|
}
|
|
|
|
if (EngineDebugger::get_script_debugger()->is_breakpoint(line, source)) {
|
|
do_break = true;
|
|
}
|
|
|
|
if (do_break) {
|
|
GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true);
|
|
}
|
|
|
|
EngineDebugger::get_singleton()->line_poll();
|
|
}
|
|
}
|
|
DISPATCH_OPCODE;
|
|
|
|
OPCODE(OPCODE_END) {
|
|
#ifdef DEBUG_ENABLED
|
|
exit_ok = true;
|
|
#endif
|
|
OPCODE_BREAK;
|
|
}
|
|
|
|
#if 0 // Enable for debugging.
|
|
default: {
|
|
err_text = "Illegal opcode " + itos(_code_ptr[ip]) + " at address " + itos(ip);
|
|
OPCODE_BREAK;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
OPCODES_END
|
|
#ifdef DEBUG_ENABLED
|
|
if (exit_ok) {
|
|
OPCODE_OUT;
|
|
}
|
|
//error
|
|
// function, file, line, error, explanation
|
|
String err_file;
|
|
if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->path != "") {
|
|
err_file = p_instance->script->path;
|
|
} else if (script) {
|
|
err_file = script->path;
|
|
}
|
|
if (err_file == "") {
|
|
err_file = "<built-in>";
|
|
}
|
|
String err_func = name;
|
|
if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && 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
|
|
OPCODE_OUT;
|
|
}
|
|
|
|
OPCODES_OUT
|
|
#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;
|
|
}
|
|
|
|
// Check if this is the last time the function is resuming from await
|
|
// Will be true if never awaited as well
|
|
// When it's the last resume it will postpone the exit from stack,
|
|
// so the debugger knows which function triggered the resume of the next function (if any)
|
|
if (!p_state || awaited) {
|
|
if (EngineDebugger::is_active()) {
|
|
GDScriptLanguage::get_singleton()->exit_function();
|
|
}
|
|
#endif
|
|
|
|
if (_stack_size) {
|
|
//free stack
|
|
for (int i = 0; i < _stack_size; i++) {
|
|
stack[i].~Variant();
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
}
|
|
#endif
|
|
|
|
return retvalue;
|
|
}
|