mirror of
https://github.com/godotengine/godot
synced 2024-09-05 06:38:25 +00:00
![Juan Linietsky](/assets/img/avatar_default.png)
This push changes the binary and XML formats and bumps the major version to 2.0. As such, files saved in this version WILL NO LONGER WORK IN PREVIOUS VERSIONS. This compatibility breakage with older versions was required in order to properly provide project refactoring tools. If I were you, unless you are brave, I would wait a week or two before pulling, in case of bugs :) Summary of Changes -New Filesystem dock, with filesystem & tree view modes. -New refactoring tools, to change or fix dependencies. -Quick search dialog, to quickly search any file
2811 lines
62 KiB
C++
2811 lines
62 KiB
C++
/*************************************************************************/
|
|
/* gd_script.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* http://www.godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2015 Juan Linietsky, Ariel Manzur. */
|
|
/* */
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
/* a copy of this software and associated documentation files (the */
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
/* the following conditions: */
|
|
/* */
|
|
/* The above copyright notice and this permission notice shall be */
|
|
/* included in all copies or substantial portions of the Software. */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
/*************************************************************************/
|
|
#include "gd_script.h"
|
|
#include "globals.h"
|
|
#include "global_constants.h"
|
|
#include "gd_compiler.h"
|
|
#include "os/file_access.h"
|
|
#include "io/file_access_encrypted.h"
|
|
|
|
/* TODO:
|
|
|
|
*populate globals
|
|
*do checks as close to debugger as possible (but don't do debugger)
|
|
*const check plz
|
|
*check arguments and default arguments in GDFunction
|
|
-get property list in instance?
|
|
*missing opcodes
|
|
-const checks
|
|
-make thread safe
|
|
*/
|
|
|
|
|
|
|
|
Variant *GDFunction::_get_variant(int p_address,GDInstance *p_instance,GDScript *p_script,Variant &self, Variant *p_stack,String& r_error) const{
|
|
|
|
int address = p_address&ADDR_MASK;
|
|
|
|
//sequential table (jump table generated by compiler)
|
|
switch((p_address&ADDR_TYPE_MASK)>>ADDR_BITS) {
|
|
|
|
case ADDR_TYPE_SELF: {
|
|
|
|
if (!p_instance) {
|
|
r_error="Cannot access self without instance.";
|
|
return NULL;
|
|
}
|
|
return &self;
|
|
} break;
|
|
case ADDR_TYPE_CLASS: {
|
|
|
|
return &p_script->_static_ref;
|
|
} break;
|
|
case ADDR_TYPE_MEMBER: {
|
|
//member indexing is O(1)
|
|
if (!p_instance) {
|
|
r_error="Cannot access member without instance.";
|
|
return NULL;
|
|
}
|
|
return &p_instance->members[address];
|
|
} break;
|
|
case ADDR_TYPE_CLASS_CONSTANT: {
|
|
|
|
//todo change to index!
|
|
GDScript *o=p_script;
|
|
ERR_FAIL_INDEX_V(address,_global_names_count,NULL);
|
|
const StringName *sn = &_global_names_ptr[address];
|
|
|
|
while(o) {
|
|
GDScript *s=o;
|
|
while(s) {
|
|
|
|
Map<StringName,Variant>::Element *E=s->constants.find(*sn);
|
|
if (E) {
|
|
return &E->get();
|
|
}
|
|
s=s->_base;
|
|
}
|
|
o=o->_owner;
|
|
}
|
|
|
|
|
|
ERR_EXPLAIN("GDCompiler bug..");
|
|
ERR_FAIL_V(NULL);
|
|
} break;
|
|
case ADDR_TYPE_LOCAL_CONSTANT: {
|
|
ERR_FAIL_INDEX_V(address,_constant_count,NULL);
|
|
return &_constants_ptr[address];
|
|
} break;
|
|
case ADDR_TYPE_STACK:
|
|
case ADDR_TYPE_STACK_VARIABLE: {
|
|
ERR_FAIL_INDEX_V(address,_stack_size,NULL);
|
|
return &p_stack[address];
|
|
} break;
|
|
case ADDR_TYPE_GLOBAL: {
|
|
|
|
|
|
ERR_FAIL_INDEX_V(address,GDScriptLanguage::get_singleton()->get_global_array_size(),NULL);
|
|
|
|
|
|
return &GDScriptLanguage::get_singleton()->get_global_array()[address];
|
|
} break;
|
|
case ADDR_TYPE_NIL: {
|
|
return &nil;
|
|
} break;
|
|
}
|
|
|
|
ERR_EXPLAIN("Bad Code! (Addressing Mode)");
|
|
ERR_FAIL_V(NULL);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
String GDFunction::_get_call_error(const Variant::CallError& p_err, const String& p_where,const Variant**argptrs) const {
|
|
|
|
|
|
|
|
String err_text;
|
|
|
|
if (p_err.error==Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) {
|
|
int errorarg=p_err.argument;
|
|
err_text="Invalid type in "+p_where+". Cannot convert argument "+itos(errorarg+1)+" from "+Variant::get_type_name(argptrs[errorarg]->get_type())+" to "+Variant::get_type_name(p_err.expected)+".";
|
|
} else if (p_err.error==Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) {
|
|
err_text="Invalid call to "+p_where+". Expected "+itos(p_err.argument)+" arguments.";
|
|
} else if (p_err.error==Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) {
|
|
err_text="Invalid call to "+p_where+". Expected "+itos(p_err.argument)+" arguments.";
|
|
} else if (p_err.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) {
|
|
err_text="Invalid call. Nonexistent "+p_where+".";
|
|
} else if (p_err.error==Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL) {
|
|
err_text="Attempt to call "+p_where+" on a null instance.";
|
|
} else {
|
|
err_text="Bug, call error: #"+itos(p_err.error);
|
|
}
|
|
|
|
return err_text;
|
|
|
|
}
|
|
|
|
static String _get_var_type(const Variant* p_type) {
|
|
|
|
String basestr;
|
|
|
|
if (p_type->get_type()==Variant::OBJECT) {
|
|
Object *bobj = *p_type;
|
|
if (!bobj) {
|
|
basestr = "null instance";
|
|
} else {
|
|
#ifdef DEBUG_ENABLED
|
|
if (ObjectDB::instance_validate(bobj)) {
|
|
if (bobj->get_script_instance())
|
|
basestr= bobj->get_type()+" ("+bobj->get_script_instance()->get_script()->get_path().get_file()+")";
|
|
else
|
|
basestr = bobj->get_type();
|
|
} else {
|
|
basestr="previously freed instance";
|
|
}
|
|
|
|
#else
|
|
basestr="Object";
|
|
#endif
|
|
}
|
|
|
|
} else {
|
|
basestr = Variant::get_type_name(p_type->get_type());
|
|
}
|
|
|
|
return basestr;
|
|
|
|
}
|
|
|
|
Variant GDFunction::call(GDInstance *p_instance, const Variant **p_args, int p_argcount, Variant::CallError& r_err, CallState *p_state) {
|
|
|
|
|
|
if (!_code_ptr) {
|
|
|
|
return Variant();
|
|
}
|
|
|
|
r_err.error=Variant::CallError::CALL_OK;
|
|
|
|
Variant self;
|
|
Variant retvalue;
|
|
Variant *stack = NULL;
|
|
Variant **call_args;
|
|
int defarg=0;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
//GDScriptLanguage::get_singleton()->calls++;
|
|
|
|
#endif
|
|
|
|
uint32_t alloca_size=0;
|
|
GDScript *_class;
|
|
int ip=0;
|
|
int line=_initial_line;
|
|
|
|
if (p_state) {
|
|
//use existing (supplied) state (yielded)
|
|
stack=(Variant*)p_state->stack.ptr();
|
|
call_args=(Variant**)&p_state->stack[sizeof(Variant)*p_state->stack_size];
|
|
line=p_state->line;
|
|
ip=p_state->ip;
|
|
alloca_size=p_state->stack.size();
|
|
_class=p_state->_class;
|
|
p_instance=p_state->instance;
|
|
defarg=p_state->defarg;
|
|
self=p_state->self;
|
|
//stack[p_state->result_pos]=p_state->result; //assign stack with result
|
|
|
|
} else {
|
|
|
|
if (p_argcount!=_argument_count) {
|
|
|
|
if (p_argcount>_argument_count) {
|
|
|
|
r_err.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
|
|
r_err.argument=_argument_count;
|
|
|
|
return Variant();
|
|
} else if (p_argcount < _argument_count - _default_arg_count) {
|
|
|
|
r_err.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
|
|
r_err.argument=_argument_count - _default_arg_count;
|
|
return Variant();
|
|
} else {
|
|
|
|
defarg=_argument_count-p_argcount;
|
|
}
|
|
}
|
|
|
|
alloca_size = sizeof(Variant*)*_call_size + sizeof(Variant)*_stack_size;
|
|
|
|
if (alloca_size) {
|
|
|
|
uint8_t *aptr = (uint8_t*)alloca(alloca_size);
|
|
|
|
if (_stack_size) {
|
|
|
|
stack=(Variant*)aptr;
|
|
for(int i=0;i<p_argcount;i++)
|
|
memnew_placement(&stack[i],Variant(*p_args[i]));
|
|
for(int i=p_argcount;i<_stack_size;i++)
|
|
memnew_placement(&stack[i],Variant);
|
|
} else {
|
|
stack=NULL;
|
|
}
|
|
|
|
if (_call_size) {
|
|
|
|
call_args = (Variant**)&aptr[sizeof(Variant)*_stack_size];
|
|
} else {
|
|
|
|
call_args=NULL;
|
|
}
|
|
|
|
|
|
} else {
|
|
stack=NULL;
|
|
call_args=NULL;
|
|
}
|
|
|
|
if (p_instance) {
|
|
if (p_instance->base_ref && static_cast<Reference*>(p_instance->owner)->is_referenced()) {
|
|
|
|
self=REF(static_cast<Reference*>(p_instance->owner));
|
|
} else {
|
|
self=p_instance->owner;
|
|
}
|
|
_class=p_instance->script.ptr();
|
|
} else {
|
|
_class=_script;
|
|
}
|
|
}
|
|
|
|
String err_text;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
if (ScriptDebugger::get_singleton())
|
|
GDScriptLanguage::get_singleton()->enter_function(p_instance,this,stack,&ip,&line);
|
|
|
|
#define CHECK_SPACE(m_space)\
|
|
ERR_BREAK((ip+m_space)>_code_size)
|
|
|
|
#define GET_VARIANT_PTR(m_v,m_code_ofs) \
|
|
Variant *m_v; \
|
|
m_v = _get_variant(_code_ptr[ip+m_code_ofs],p_instance,_class,self,stack,err_text);\
|
|
if (!m_v)\
|
|
break;
|
|
|
|
|
|
#else
|
|
#define CHECK_SPACE(m_space)
|
|
#define GET_VARIANT_PTR(m_v,m_code_ofs) \
|
|
Variant *m_v; \
|
|
m_v = _get_variant(_code_ptr[ip+m_code_ofs],p_instance,_class,self,stack,err_text);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
bool exit_ok=false;
|
|
|
|
while(ip<_code_size) {
|
|
|
|
|
|
int last_opcode=_code_ptr[ip];
|
|
switch(_code_ptr[ip]) {
|
|
|
|
case OPCODE_OPERATOR: {
|
|
|
|
CHECK_SPACE(5);
|
|
|
|
bool valid;
|
|
Variant::Operator op = (Variant::Operator)_code_ptr[ip+1];
|
|
ERR_BREAK(op>=Variant::OP_MAX);
|
|
|
|
GET_VARIANT_PTR(a,2);
|
|
GET_VARIANT_PTR(b,3);
|
|
GET_VARIANT_PTR(dst,4);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
Variant ret;
|
|
Variant::evaluate(op,*a,*b,ret,valid);
|
|
#else
|
|
Variant::evaluate(op,*a,*b,*dst,valid);
|
|
#endif
|
|
|
|
if (!valid) {
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
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)+"'.";
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
*dst=ret;
|
|
#endif
|
|
|
|
ip+=5;
|
|
|
|
} continue;
|
|
case OPCODE_EXTENDS_TEST: {
|
|
|
|
CHECK_SPACE(4);
|
|
|
|
GET_VARIANT_PTR(a,1);
|
|
GET_VARIANT_PTR(b,2);
|
|
GET_VARIANT_PTR(dst,3);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
if (a->get_type()!=Variant::OBJECT || a->operator Object*()==NULL) {
|
|
|
|
err_text="Left operand of 'extends' is not an instance of anything.";
|
|
break;
|
|
|
|
}
|
|
if (b->get_type()!=Variant::OBJECT || b->operator Object*()==NULL) {
|
|
|
|
err_text="Right operand of 'extends' is not a class.";
|
|
break;
|
|
|
|
}
|
|
#endif
|
|
|
|
|
|
Object *obj_A = *a;
|
|
Object *obj_B = *b;
|
|
|
|
|
|
GDScript *scr_B = obj_B->cast_to<GDScript>();
|
|
|
|
bool extends_ok=false;
|
|
|
|
if (scr_B) {
|
|
//if B is a script, the only valid condition is that A has an instance which inherits from the script
|
|
//in other situation, this shoul return false.
|
|
|
|
if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language()==GDScriptLanguage::get_singleton()) {
|
|
|
|
GDInstance *ins = static_cast<GDInstance*>(obj_A->get_script_instance());
|
|
GDScript *cmp = ins->script.ptr();
|
|
//bool found=false;
|
|
while(cmp) {
|
|
|
|
if (cmp==scr_B) {
|
|
//inherits from script, all ok
|
|
extends_ok=true;
|
|
break;
|
|
|
|
}
|
|
|
|
cmp=cmp->_base;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
GDNativeClass *nc= obj_B->cast_to<GDNativeClass>();
|
|
|
|
if (!nc) {
|
|
|
|
err_text="Right operand of 'extends' is not a class (type: '"+obj_B->get_type()+"').";
|
|
break;
|
|
}
|
|
|
|
extends_ok=ObjectTypeDB::is_type(obj_A->get_type_name(),nc->get_name());
|
|
}
|
|
|
|
*dst=extends_ok;
|
|
ip+=4;
|
|
|
|
} continue;
|
|
case OPCODE_SET: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(dst,1);
|
|
GET_VARIANT_PTR(index,2);
|
|
GET_VARIANT_PTR(value,3);
|
|
|
|
bool valid;
|
|
dst->set(*index,*value,&valid);
|
|
|
|
if (!valid) {
|
|
String v = index->operator String();
|
|
if (v!="") {
|
|
v="'"+v+"'";
|
|
} else {
|
|
v="of type '"+_get_var_type(index)+"'";
|
|
}
|
|
err_text="Invalid set index "+v+" (on base: '"+_get_var_type(dst)+"').";
|
|
break;
|
|
}
|
|
|
|
ip+=4;
|
|
} continue;
|
|
case OPCODE_GET: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(src,1);
|
|
GET_VARIANT_PTR(index,2);
|
|
GET_VARIANT_PTR(dst,3);
|
|
|
|
bool valid;
|
|
#ifdef DEBUG_ENABLED
|
|
//allow better error message in cases where src and dst are the same stack position
|
|
Variant ret = src->get(*index,&valid);
|
|
#else
|
|
*dst = src->get(*index,&valid);
|
|
|
|
#endif
|
|
if (!valid) {
|
|
String v = index->operator String();
|
|
if (v!="") {
|
|
v="'"+v+"'";
|
|
} else {
|
|
v="of type '"+_get_var_type(index)+"'";
|
|
}
|
|
err_text="Invalid get index "+v+" (on base: '"+_get_var_type(src)+"').";
|
|
break;
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
*dst=ret;
|
|
#endif
|
|
ip+=4;
|
|
} continue;
|
|
case OPCODE_SET_NAMED: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(dst,1);
|
|
GET_VARIANT_PTR(value,3);
|
|
|
|
int indexname = _code_ptr[ip+2];
|
|
|
|
ERR_BREAK(indexname<0 || indexname>=_global_names_count);
|
|
const StringName *index = &_global_names_ptr[indexname];
|
|
|
|
bool valid;
|
|
dst->set_named(*index,*value,&valid);
|
|
|
|
if (!valid) {
|
|
String err_type;
|
|
err_text="Invalid set index '"+String(*index)+"' (on base: '"+_get_var_type(dst)+"').";
|
|
break;
|
|
}
|
|
|
|
ip+=4;
|
|
} continue;
|
|
case OPCODE_GET_NAMED: {
|
|
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(src,1);
|
|
GET_VARIANT_PTR(dst,3);
|
|
|
|
int indexname = _code_ptr[ip+2];
|
|
|
|
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
|
|
|
|
if (!valid) {
|
|
if (src->has_method(*index)) {
|
|
err_text="Invalid get index '"+index->operator String()+"' (on base: '"+_get_var_type(src)+"'). Did you mean '."+index->operator String()+"()' ?";
|
|
} else {
|
|
err_text="Invalid get index '"+index->operator String()+"' (on base: '"+_get_var_type(src)+"').";
|
|
}
|
|
break;
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
*dst=ret;
|
|
#endif
|
|
ip+=4;
|
|
} continue;
|
|
case OPCODE_ASSIGN: {
|
|
|
|
CHECK_SPACE(3);
|
|
GET_VARIANT_PTR(dst,1);
|
|
GET_VARIANT_PTR(src,2);
|
|
|
|
*dst = *src;
|
|
|
|
ip+=3;
|
|
|
|
} continue;
|
|
case OPCODE_ASSIGN_TRUE: {
|
|
|
|
CHECK_SPACE(2);
|
|
GET_VARIANT_PTR(dst,1);
|
|
|
|
*dst = true;
|
|
|
|
ip+=2;
|
|
} continue;
|
|
case OPCODE_ASSIGN_FALSE: {
|
|
|
|
CHECK_SPACE(2);
|
|
GET_VARIANT_PTR(dst,1);
|
|
|
|
*dst = false;
|
|
|
|
ip+=2;
|
|
} continue;
|
|
case OPCODE_CONSTRUCT: {
|
|
|
|
CHECK_SPACE(2);
|
|
Variant::Type t=Variant::Type(_code_ptr[ip+1]);
|
|
int argc=_code_ptr[ip+2];
|
|
CHECK_SPACE(argc+2);
|
|
Variant **argptrs = call_args;
|
|
for(int i=0;i<argc;i++) {
|
|
GET_VARIANT_PTR(v,3+i);
|
|
argptrs[i]=v;
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst,3+argc);
|
|
Variant::CallError err;
|
|
*dst = Variant::construct(t,(const Variant**)argptrs,argc,err);
|
|
|
|
if (err.error!=Variant::CallError::CALL_OK) {
|
|
|
|
err_text=_get_call_error(err,"'"+Variant::get_type_name(t)+"' constructor",(const Variant**)argptrs);
|
|
break;
|
|
}
|
|
|
|
ip+=4+argc;
|
|
//construct a basic type
|
|
} continue;
|
|
case OPCODE_CONSTRUCT_ARRAY: {
|
|
|
|
CHECK_SPACE(1);
|
|
int argc=_code_ptr[ip+1];
|
|
Array array(true); //arrays are always shared
|
|
array.resize(argc);
|
|
CHECK_SPACE(argc+2);
|
|
|
|
for(int i=0;i<argc;i++) {
|
|
GET_VARIANT_PTR(v,2+i);
|
|
array[i]=*v;
|
|
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst,2+argc);
|
|
|
|
*dst=array;
|
|
|
|
ip+=3+argc;
|
|
|
|
} continue;
|
|
case OPCODE_CONSTRUCT_DICTIONARY: {
|
|
|
|
CHECK_SPACE(1);
|
|
int argc=_code_ptr[ip+1];
|
|
Dictionary dict(true); //arrays are always shared
|
|
|
|
CHECK_SPACE(argc*2+2);
|
|
|
|
for(int i=0;i<argc;i++) {
|
|
|
|
GET_VARIANT_PTR(k,2+i*2+0);
|
|
GET_VARIANT_PTR(v,2+i*2+1);
|
|
dict[*k]=*v;
|
|
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst,2+argc*2);
|
|
|
|
*dst=dict;
|
|
|
|
ip+=3+argc*2;
|
|
|
|
} continue;
|
|
case OPCODE_CALL_RETURN:
|
|
case OPCODE_CALL: {
|
|
|
|
|
|
CHECK_SPACE(4);
|
|
bool call_ret = _code_ptr[ip]==OPCODE_CALL_RETURN;
|
|
|
|
int argc=_code_ptr[ip+1];
|
|
GET_VARIANT_PTR(base,2);
|
|
int nameg=_code_ptr[ip+3];
|
|
|
|
ERR_BREAK(nameg<0 || nameg>=_global_names_count);
|
|
const StringName *methodname = &_global_names_ptr[nameg];
|
|
|
|
ERR_BREAK(argc<0);
|
|
ip+=4;
|
|
CHECK_SPACE(argc+1);
|
|
Variant **argptrs = call_args;
|
|
|
|
for(int i=0;i<argc;i++) {
|
|
GET_VARIANT_PTR(v,i);
|
|
argptrs[i]=v;
|
|
}
|
|
|
|
Variant::CallError err;
|
|
if (call_ret) {
|
|
|
|
GET_VARIANT_PTR(ret,argc);
|
|
*ret = base->call(*methodname,(const Variant**)argptrs,argc,err);
|
|
} else {
|
|
|
|
base->call(*methodname,(const Variant**)argptrs,argc,err);
|
|
}
|
|
|
|
if (err.error!=Variant::CallError::CALL_OK) {
|
|
|
|
|
|
String methodstr = *methodname;
|
|
String basestr = _get_var_type(base);
|
|
|
|
if (methodstr=="call") {
|
|
if (argc>=1) {
|
|
methodstr=String(*argptrs[0])+" (via call)";
|
|
if (err.error==Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) {
|
|
err.argument-=1;
|
|
}
|
|
}
|
|
} if (methodstr=="free") {
|
|
|
|
if (err.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) {
|
|
|
|
if (base->is_ref()) {
|
|
err_text="Attempted to free a reference.";
|
|
break;
|
|
} else if (base->get_type()==Variant::OBJECT) {
|
|
|
|
err_text="Attempted to free a locked object (calling or emitting).";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
err_text=_get_call_error(err,"function '"+methodstr+"' in base '"+basestr+"'",(const Variant**)argptrs);
|
|
break;
|
|
}
|
|
|
|
//_call_func(NULL,base,*methodname,ip,argc,p_instance,stack);
|
|
ip+=argc+1;
|
|
|
|
} continue;
|
|
case OPCODE_CALL_BUILT_IN: {
|
|
|
|
CHECK_SPACE(4);
|
|
|
|
GDFunctions::Function func = GDFunctions::Function(_code_ptr[ip+1]);
|
|
int argc=_code_ptr[ip+2];
|
|
ERR_BREAK(argc<0);
|
|
|
|
ip+=3;
|
|
CHECK_SPACE(argc+1);
|
|
Variant **argptrs = call_args;
|
|
|
|
for(int i=0;i<argc;i++) {
|
|
GET_VARIANT_PTR(v,i);
|
|
argptrs[i]=v;
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst,argc);
|
|
|
|
Variant::CallError err;
|
|
|
|
GDFunctions::call(func,(const Variant**)argptrs,argc,*dst,err);
|
|
|
|
if (err.error!=Variant::CallError::CALL_OK) {
|
|
|
|
|
|
String methodstr = GDFunctions::get_func_name(func);
|
|
err_text=_get_call_error(err,"built-in function '"+methodstr+"'",(const Variant**)argptrs);
|
|
break;
|
|
}
|
|
ip+=argc+1;
|
|
|
|
} continue;
|
|
case OPCODE_CALL_SELF: {
|
|
|
|
|
|
} break;
|
|
case OPCODE_CALL_SELF_BASE: {
|
|
|
|
CHECK_SPACE(2);
|
|
int self_fun = _code_ptr[ip+1];
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
if (self_fun<0 || self_fun>=_global_names_count) {
|
|
|
|
err_text="compiler bug, function name not found";
|
|
break;
|
|
}
|
|
#endif
|
|
const StringName *methodname = &_global_names_ptr[self_fun];
|
|
|
|
int argc=_code_ptr[ip+2];
|
|
|
|
CHECK_SPACE(2+argc+1);
|
|
|
|
Variant **argptrs = call_args;
|
|
|
|
for(int i=0;i<argc;i++) {
|
|
GET_VARIANT_PTR(v,i+3);
|
|
argptrs[i]=v;
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst,argc+3);
|
|
|
|
const GDScript *gds = _script;
|
|
|
|
|
|
const Map<StringName,GDFunction>::Element *E=NULL;
|
|
while (gds->base.ptr()) {
|
|
gds=gds->base.ptr();
|
|
E=gds->member_functions.find(*methodname);
|
|
if (E)
|
|
break;
|
|
}
|
|
|
|
Variant::CallError err;
|
|
|
|
if (E) {
|
|
|
|
*dst=((GDFunction*)&E->get())->call(p_instance,(const Variant**)argptrs,argc,err);
|
|
} else if (gds->native.ptr()) {
|
|
|
|
if (*methodname!=GDScriptLanguage::get_singleton()->strings._init) {
|
|
|
|
MethodBind *mb = ObjectTypeDB::get_method(gds->native->get_name(),*methodname);
|
|
if (!mb) {
|
|
err.error=Variant::CallError::CALL_ERROR_INVALID_METHOD;
|
|
} else {
|
|
*dst=mb->call(p_instance->owner,(const Variant**)argptrs,argc,err);
|
|
}
|
|
} else {
|
|
err.error=Variant::CallError::CALL_OK;
|
|
}
|
|
} else {
|
|
|
|
if (*methodname!=GDScriptLanguage::get_singleton()->strings._init) {
|
|
err.error=Variant::CallError::CALL_ERROR_INVALID_METHOD;
|
|
} else {
|
|
err.error=Variant::CallError::CALL_OK;
|
|
}
|
|
}
|
|
|
|
|
|
if (err.error!=Variant::CallError::CALL_OK) {
|
|
|
|
|
|
String methodstr = *methodname;
|
|
err_text=_get_call_error(err,"function '"+methodstr+"'",(const Variant**)argptrs);
|
|
|
|
break;
|
|
}
|
|
|
|
ip+=4+argc;
|
|
|
|
} continue;
|
|
case OPCODE_YIELD:
|
|
case OPCODE_YIELD_SIGNAL: {
|
|
|
|
int ipofs=1;
|
|
if (_code_ptr[ip]==OPCODE_YIELD_SIGNAL) {
|
|
CHECK_SPACE(4);
|
|
ipofs+=2;
|
|
} else {
|
|
CHECK_SPACE(2);
|
|
|
|
}
|
|
|
|
Ref<GDFunctionState> gdfs = memnew( GDFunctionState );
|
|
gdfs->function=this;
|
|
|
|
gdfs->state.stack.resize(alloca_size);
|
|
//copy variant stack
|
|
for(int i=0;i<_stack_size;i++) {
|
|
memnew_placement(&gdfs->state.stack[sizeof(Variant)*i],Variant(stack[i]));
|
|
}
|
|
gdfs->state.stack_size=_stack_size;
|
|
gdfs->state.self=self;
|
|
gdfs->state.alloca_size=alloca_size;
|
|
gdfs->state._class=_class;
|
|
gdfs->state.ip=ip+ipofs;
|
|
gdfs->state.line=line;
|
|
//gdfs->state.result_pos=ip+ipofs-1;
|
|
gdfs->state.defarg=defarg;
|
|
gdfs->state.instance=p_instance;
|
|
gdfs->function=this;
|
|
|
|
retvalue=gdfs;
|
|
|
|
if (_code_ptr[ip]==OPCODE_YIELD_SIGNAL) {
|
|
GET_VARIANT_PTR(argobj,1);
|
|
GET_VARIANT_PTR(argname,2);
|
|
//do the oneshot connect
|
|
|
|
if (argobj->get_type()!=Variant::OBJECT) {
|
|
err_text="First argument of yield() not of type object.";
|
|
break;
|
|
}
|
|
if (argname->get_type()!=Variant::STRING) {
|
|
err_text="Second argument of yield() not a string (for signal name).";
|
|
break;
|
|
}
|
|
Object *obj=argobj->operator Object *();
|
|
String signal = argname->operator String();
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
if (!obj) {
|
|
err_text="First argument of yield() is null.";
|
|
break;
|
|
}
|
|
if (ScriptDebugger::get_singleton()) {
|
|
if (!ObjectDB::instance_validate(obj)) {
|
|
err_text="First argument of yield() is a previously freed instance.";
|
|
break;
|
|
}
|
|
}
|
|
if (signal.length()==0) {
|
|
|
|
err_text="Second argument of yield() is an empty string (for signal name).";
|
|
break;
|
|
}
|
|
|
|
#endif
|
|
Error err = obj->connect(signal,gdfs.ptr(),"_signal_callback",varray(gdfs),Object::CONNECT_ONESHOT);
|
|
if (err!=OK) {
|
|
err_text="Error connecting to signal: "+signal+" during yield().";
|
|
break;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
exit_ok=true;
|
|
|
|
} break;
|
|
case OPCODE_YIELD_RESUME: {
|
|
|
|
CHECK_SPACE(2);
|
|
if (!p_state) {
|
|
err_text=("Invalid Resume (bug?)");
|
|
break;
|
|
}
|
|
GET_VARIANT_PTR(result,1);
|
|
*result=p_state->result;
|
|
ip+=2;
|
|
|
|
} continue;
|
|
case OPCODE_JUMP: {
|
|
|
|
CHECK_SPACE(2);
|
|
int to = _code_ptr[ip+1];
|
|
|
|
ERR_BREAK(to<0 || to>_code_size);
|
|
ip=to;
|
|
|
|
} continue;
|
|
case OPCODE_JUMP_IF: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(test,1);
|
|
|
|
bool valid;
|
|
bool result = test->booleanize(valid);
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
|
|
err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type());
|
|
break;
|
|
}
|
|
#endif
|
|
if (result) {
|
|
int to = _code_ptr[ip+2];
|
|
ERR_BREAK(to<0 || to>_code_size);
|
|
ip=to;
|
|
continue;
|
|
}
|
|
ip+=3;
|
|
} continue;
|
|
case OPCODE_JUMP_IF_NOT: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(test,1);
|
|
|
|
bool valid;
|
|
bool result = test->booleanize(valid);
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
|
|
err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type());
|
|
break;
|
|
}
|
|
#endif
|
|
if (!result) {
|
|
int to = _code_ptr[ip+2];
|
|
ERR_BREAK(to<0 || to>_code_size);
|
|
ip=to;
|
|
continue;
|
|
}
|
|
ip+=3;
|
|
} continue;
|
|
case OPCODE_JUMP_TO_DEF_ARGUMENT: {
|
|
|
|
CHECK_SPACE(2);
|
|
ip=_default_arg_ptr[defarg];
|
|
|
|
} continue;
|
|
case OPCODE_RETURN: {
|
|
|
|
CHECK_SPACE(2);
|
|
GET_VARIANT_PTR(r,1);
|
|
retvalue=*r;
|
|
exit_ok=true;
|
|
|
|
} break;
|
|
case OPCODE_ITERATE_BEGIN: {
|
|
|
|
CHECK_SPACE(8); //space for this an regular iterate
|
|
|
|
GET_VARIANT_PTR(counter,1);
|
|
GET_VARIANT_PTR(container,2);
|
|
|
|
bool valid;
|
|
if (!container->iter_init(*counter,valid)) {
|
|
if (!valid) {
|
|
err_text="Unable to iterate on object of type "+Variant::get_type_name(container->get_type())+"'.";
|
|
break;
|
|
}
|
|
int jumpto=_code_ptr[ip+3];
|
|
ERR_BREAK(jumpto<0 || jumpto>_code_size);
|
|
ip=jumpto;
|
|
continue;
|
|
}
|
|
GET_VARIANT_PTR(iterator,4);
|
|
|
|
|
|
*iterator=container->iter_get(*counter,valid);
|
|
if (!valid) {
|
|
err_text="Unable to obtain iterator object of type "+Variant::get_type_name(container->get_type())+"'.";
|
|
break;
|
|
}
|
|
|
|
|
|
ip+=5; //skip regular iterate which is always next
|
|
|
|
} continue;
|
|
case OPCODE_ITERATE: {
|
|
|
|
CHECK_SPACE(4);
|
|
|
|
GET_VARIANT_PTR(counter,1);
|
|
GET_VARIANT_PTR(container,2);
|
|
|
|
bool valid;
|
|
if (!container->iter_next(*counter,valid)) {
|
|
if (!valid) {
|
|
err_text="Unable to iterate on object of type "+Variant::get_type_name(container->get_type())+"' (type changed since first iteration?).";
|
|
break;
|
|
}
|
|
int jumpto=_code_ptr[ip+3];
|
|
ERR_BREAK(jumpto<0 || jumpto>_code_size);
|
|
ip=jumpto;
|
|
continue;
|
|
}
|
|
GET_VARIANT_PTR(iterator,4);
|
|
|
|
*iterator=container->iter_get(*counter,valid);
|
|
if (!valid) {
|
|
err_text="Unable to obtain iterator object of type "+Variant::get_type_name(container->get_type())+"' (but was obtained on first iteration?).";
|
|
break;
|
|
}
|
|
|
|
ip+=5; //loop again
|
|
} continue;
|
|
case OPCODE_ASSERT: {
|
|
CHECK_SPACE(2);
|
|
GET_VARIANT_PTR(test,1);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
bool valid;
|
|
bool result = test->booleanize(valid);
|
|
|
|
|
|
if (!valid) {
|
|
|
|
err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type());
|
|
break;
|
|
}
|
|
|
|
|
|
if (!result) {
|
|
|
|
err_text="Assertion failed.";
|
|
break;
|
|
}
|
|
|
|
#endif
|
|
|
|
ip+=2;
|
|
} continue;
|
|
case OPCODE_LINE: {
|
|
CHECK_SPACE(2);
|
|
|
|
line=_code_ptr[ip+1];
|
|
ip+=2;
|
|
|
|
if (ScriptDebugger::get_singleton()) {
|
|
// line
|
|
bool do_break=false;
|
|
|
|
if (ScriptDebugger::get_singleton()->get_lines_left()>0) {
|
|
|
|
if (ScriptDebugger::get_singleton()->get_depth()<=0)
|
|
ScriptDebugger::get_singleton()->set_lines_left( ScriptDebugger::get_singleton()->get_lines_left() -1 );
|
|
if (ScriptDebugger::get_singleton()->get_lines_left()<=0)
|
|
do_break=true;
|
|
}
|
|
|
|
if (ScriptDebugger::get_singleton()->is_breakpoint(line,source))
|
|
do_break=true;
|
|
|
|
if (do_break) {
|
|
GDScriptLanguage::get_singleton()->debug_break("Breakpoint",true);
|
|
}
|
|
|
|
ScriptDebugger::get_singleton()->line_poll();
|
|
|
|
}
|
|
} continue;
|
|
case OPCODE_END: {
|
|
|
|
exit_ok=true;
|
|
break;
|
|
|
|
} break;
|
|
default: {
|
|
|
|
err_text="Illegal opcode "+itos(_code_ptr[ip])+" at address "+itos(ip);
|
|
} break;
|
|
|
|
}
|
|
|
|
if (exit_ok)
|
|
break;
|
|
//error
|
|
// function, file, line, error, explanation
|
|
String err_file;
|
|
if (p_instance)
|
|
err_file=p_instance->script->path;
|
|
else if (_class)
|
|
err_file=_class->path;
|
|
if (err_file=="")
|
|
err_file="<built-in>";
|
|
String err_func = name;
|
|
if (p_instance && p_instance->script->name!="")
|
|
err_func=p_instance->script->name+"."+err_func;
|
|
int err_line=line;
|
|
if (err_text=="") {
|
|
err_text="Internal Script Error! - opcode #"+itos(last_opcode)+" (report please).";
|
|
}
|
|
|
|
if (!GDScriptLanguage::get_singleton()->debug_break(err_text,false)) {
|
|
// debugger break did not happen
|
|
|
|
_err_print_error(err_func.utf8().get_data(),err_file.utf8().get_data(),err_line,err_text.utf8().get_data());
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
if (ScriptDebugger::get_singleton())
|
|
GDScriptLanguage::get_singleton()->exit_function();
|
|
|
|
|
|
if (_stack_size) {
|
|
//free stack
|
|
for(int i=0;i<_stack_size;i++)
|
|
stack[i].~Variant();
|
|
}
|
|
|
|
return retvalue;
|
|
|
|
}
|
|
|
|
const int* GDFunction::get_code() const {
|
|
|
|
return _code_ptr;
|
|
}
|
|
int GDFunction::get_code_size() const{
|
|
|
|
return _code_size;
|
|
}
|
|
|
|
Variant GDFunction::get_constant(int p_idx) const {
|
|
|
|
ERR_FAIL_INDEX_V(p_idx,constants.size(),"<errconst>");
|
|
return constants[p_idx];
|
|
}
|
|
|
|
StringName GDFunction::get_global_name(int p_idx) const {
|
|
|
|
ERR_FAIL_INDEX_V(p_idx,global_names.size(),"<errgname>");
|
|
return global_names[p_idx];
|
|
}
|
|
|
|
int GDFunction::get_default_argument_count() const {
|
|
|
|
return default_arguments.size();
|
|
}
|
|
int GDFunction::get_default_argument_addr(int p_arg) const{
|
|
|
|
ERR_FAIL_INDEX_V(p_arg,default_arguments.size(),-1);
|
|
return default_arguments[p_arg];
|
|
}
|
|
|
|
|
|
StringName GDFunction::get_name() const {
|
|
|
|
return name;
|
|
}
|
|
|
|
int GDFunction::get_max_stack_size() const {
|
|
|
|
return _stack_size;
|
|
}
|
|
|
|
struct _GDFKC {
|
|
|
|
int order;
|
|
List<int> pos;
|
|
};
|
|
|
|
struct _GDFKCS {
|
|
|
|
int order;
|
|
StringName id;
|
|
int pos;
|
|
|
|
bool operator<(const _GDFKCS &p_r) const {
|
|
|
|
return order<p_r.order;
|
|
}
|
|
};
|
|
|
|
void GDFunction::debug_get_stack_member_state(int p_line,List<Pair<StringName,int> > *r_stackvars) const {
|
|
|
|
|
|
int oc=0;
|
|
Map<StringName,_GDFKC> sdmap;
|
|
for( const List<StackDebug>::Element *E=stack_debug.front();E;E=E->next()) {
|
|
|
|
const StackDebug &sd=E->get();
|
|
if (sd.line>p_line)
|
|
break;
|
|
|
|
if (sd.added) {
|
|
|
|
if (!sdmap.has(sd.identifier)) {
|
|
_GDFKC d;
|
|
d.order=oc++;
|
|
d.pos.push_back(sd.pos);
|
|
sdmap[sd.identifier]=d;
|
|
|
|
} else {
|
|
sdmap[sd.identifier].pos.push_back(sd.pos);
|
|
}
|
|
} else {
|
|
|
|
|
|
ERR_CONTINUE(!sdmap.has(sd.identifier));
|
|
|
|
sdmap[sd.identifier].pos.pop_back();
|
|
if (sdmap[sd.identifier].pos.empty())
|
|
sdmap.erase(sd.identifier);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
List<_GDFKCS> stackpositions;
|
|
for(Map<StringName,_GDFKC>::Element *E=sdmap.front();E;E=E->next() ) {
|
|
|
|
_GDFKCS spp;
|
|
spp.id=E->key();
|
|
spp.order=E->get().order;
|
|
spp.pos=E->get().pos.back()->get();
|
|
stackpositions.push_back(spp);
|
|
}
|
|
|
|
stackpositions.sort();
|
|
|
|
for(List<_GDFKCS>::Element *E=stackpositions.front();E;E=E->next()) {
|
|
|
|
Pair<StringName,int> p;
|
|
p.first=E->get().id;
|
|
p.second=E->get().pos;
|
|
r_stackvars->push_back(p);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
#if 0
|
|
void GDFunction::clear() {
|
|
|
|
name=StringName();
|
|
constants.clear();
|
|
_stack_size=0;
|
|
code.clear();
|
|
_constants_ptr=NULL;
|
|
_constant_count=0;
|
|
_global_names_ptr=NULL;
|
|
_global_names_count=0;
|
|
_code_ptr=NULL;
|
|
_code_size=0;
|
|
|
|
}
|
|
#endif
|
|
GDFunction::GDFunction() {
|
|
|
|
_stack_size=0;
|
|
_call_size=0;
|
|
name="<anonymous>";
|
|
#ifdef DEBUG_ENABLED
|
|
_func_cname=NULL;
|
|
#endif
|
|
|
|
}
|
|
|
|
/////////////////////
|
|
|
|
|
|
Variant GDFunctionState::_signal_callback(const Variant** p_args, int p_argcount, Variant::CallError& r_error) {
|
|
|
|
Variant arg;
|
|
r_error.error=Variant::CallError::CALL_OK;
|
|
|
|
ERR_FAIL_COND_V(!function,Variant());
|
|
|
|
if (p_argcount==0) {
|
|
r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
|
|
r_error.argument=1;
|
|
return Variant();
|
|
} else if (p_argcount==1) {
|
|
//noooneee
|
|
} else if (p_argcount==2) {
|
|
arg=*p_args[0];
|
|
} else {
|
|
Array extra_args;
|
|
for(int i=0;i<p_argcount-1;i++) {
|
|
extra_args.push_back(*p_args[i]);
|
|
}
|
|
arg=extra_args;
|
|
}
|
|
|
|
Ref<GDFunctionState> self = *p_args[p_argcount-1];
|
|
|
|
if (self.is_null()) {
|
|
r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
|
r_error.argument=p_argcount-1;
|
|
r_error.expected=Variant::OBJECT;
|
|
return Variant();
|
|
}
|
|
|
|
state.result=arg;
|
|
Variant ret = function->call(NULL,NULL,0,r_error,&state);
|
|
function=NULL; //cleaned up;
|
|
state.result=Variant();
|
|
return ret;
|
|
}
|
|
|
|
|
|
bool GDFunctionState::is_valid() const {
|
|
|
|
return function!=NULL;
|
|
}
|
|
|
|
Variant GDFunctionState::resume(const Variant& p_arg) {
|
|
|
|
ERR_FAIL_COND_V(!function,Variant());
|
|
|
|
state.result=p_arg;
|
|
Variant::CallError err;
|
|
Variant ret = function->call(NULL,NULL,0,err,&state);
|
|
function=NULL; //cleaned up;
|
|
state.result=Variant();
|
|
return ret;
|
|
}
|
|
|
|
|
|
void GDFunctionState::_bind_methods() {
|
|
|
|
ObjectTypeDB::bind_method(_MD("resume:var","arg"),&GDFunctionState::resume,DEFVAL(Variant()));
|
|
ObjectTypeDB::bind_method(_MD("is_valid"),&GDFunctionState::is_valid);
|
|
ObjectTypeDB::bind_native_method(METHOD_FLAGS_DEFAULT,"_signal_callback",&GDFunctionState::_signal_callback,MethodInfo("_signal_callback"));
|
|
|
|
}
|
|
|
|
GDFunctionState::GDFunctionState() {
|
|
|
|
function=NULL;
|
|
}
|
|
|
|
GDFunctionState::~GDFunctionState() {
|
|
|
|
if (function!=NULL) {
|
|
//never called, deinitialize stack
|
|
for(int i=0;i<state.stack_size;i++) {
|
|
Variant *v=(Variant*)&state.stack[sizeof(Variant)*i];
|
|
v->~Variant();
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////
|
|
|
|
GDNativeClass::GDNativeClass(const StringName& p_name) {
|
|
|
|
name=p_name;
|
|
}
|
|
|
|
/*void GDNativeClass::call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount){
|
|
|
|
|
|
}*/
|
|
|
|
|
|
bool GDNativeClass::_get(const StringName& p_name,Variant &r_ret) const {
|
|
|
|
bool ok;
|
|
int v = ObjectTypeDB::get_integer_constant(name, p_name, &ok);
|
|
|
|
if (ok) {
|
|
r_ret=v;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
void GDNativeClass::_bind_methods() {
|
|
|
|
ObjectTypeDB::bind_method(_MD("new"),&GDNativeClass::_new);
|
|
|
|
}
|
|
|
|
Variant GDNativeClass::_new() {
|
|
|
|
Object *o = instance();
|
|
if (!o) {
|
|
ERR_EXPLAIN("Class type: '"+String(name)+"' is not instantiable.");
|
|
ERR_FAIL_COND_V(!o,Variant());
|
|
}
|
|
|
|
Reference *ref = o->cast_to<Reference>();
|
|
if (ref) {
|
|
return REF(ref);
|
|
} else {
|
|
return o;
|
|
}
|
|
|
|
}
|
|
|
|
Object *GDNativeClass::instance() {
|
|
|
|
return ObjectTypeDB::instance(name);
|
|
}
|
|
|
|
|
|
|
|
GDInstance* GDScript::_create_instance(const Variant** p_args,int p_argcount,Object *p_owner,bool p_isref) {
|
|
|
|
|
|
/* STEP 1, CREATE */
|
|
|
|
GDInstance* instance = memnew( GDInstance );
|
|
instance->base_ref=p_isref;
|
|
instance->members.resize(member_indices.size());
|
|
instance->script=Ref<GDScript>(this);
|
|
instance->owner=p_owner;
|
|
instance->owner->set_script_instance(instance);
|
|
|
|
/* STEP 2, INITIALIZE AND CONSRTUCT */
|
|
|
|
instances.insert(instance->owner);
|
|
|
|
Variant::CallError err;
|
|
initializer->call(instance,p_args,p_argcount,err);
|
|
|
|
if (err.error!=Variant::CallError::CALL_OK) {
|
|
instance->script=Ref<GDScript>();
|
|
instance->owner->set_script_instance(NULL);
|
|
instances.erase(p_owner);
|
|
ERR_FAIL_COND_V(err.error!=Variant::CallError::CALL_OK, NULL); //error consrtucting
|
|
}
|
|
|
|
//@TODO make thread safe
|
|
return instance;
|
|
|
|
}
|
|
|
|
Variant GDScript::_new(const Variant** p_args,int p_argcount,Variant::CallError& r_error) {
|
|
|
|
/* STEP 1, CREATE */
|
|
|
|
r_error.error=Variant::CallError::CALL_OK;
|
|
REF ref;
|
|
Object *owner=NULL;
|
|
|
|
GDScript *_baseptr=this;
|
|
while (_baseptr->_base) {
|
|
_baseptr=_baseptr->_base;
|
|
}
|
|
|
|
if (_baseptr->native.ptr()) {
|
|
owner=_baseptr->native->instance();
|
|
} else {
|
|
owner=memnew( Reference ); //by default, no base means use reference
|
|
}
|
|
|
|
Reference *r=owner->cast_to<Reference>();
|
|
if (r) {
|
|
ref=REF(r);
|
|
}
|
|
|
|
|
|
GDInstance* instance = _create_instance(p_args,p_argcount,owner,r!=NULL);
|
|
if (!instance) {
|
|
if (ref.is_null()) {
|
|
memdelete(owner); //no owner, sorry
|
|
}
|
|
return Variant();
|
|
}
|
|
|
|
if (ref.is_valid()) {
|
|
return ref;
|
|
} else {
|
|
return owner;
|
|
}
|
|
}
|
|
|
|
bool GDScript::can_instance() const {
|
|
|
|
//return valid; //any script in GDscript can instance
|
|
return valid || (!tool && !ScriptServer::is_scripting_enabled());
|
|
|
|
}
|
|
|
|
StringName GDScript::get_instance_base_type() const {
|
|
|
|
if (native.is_valid())
|
|
return native->get_name();
|
|
if (base.is_valid())
|
|
return base->get_instance_base_type();
|
|
return StringName();
|
|
}
|
|
|
|
struct _GDScriptMemberSort {
|
|
|
|
int index;
|
|
StringName name;
|
|
_FORCE_INLINE_ bool operator<(const _GDScriptMemberSort& p_member) const { return index < p_member.index; }
|
|
|
|
};
|
|
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
|
|
void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) {
|
|
|
|
placeholders.erase(p_placeholder);
|
|
}
|
|
|
|
/*
|
|
void GDScript::_update_placeholder(PlaceHolderScriptInstance *p_placeholder) {
|
|
|
|
|
|
List<PropertyInfo> plist;
|
|
GDScript *scr=this;
|
|
|
|
Map<StringName,Variant> default_values;
|
|
while(scr) {
|
|
|
|
Vector<_GDScriptMemberSort> msort;
|
|
for(Map<StringName,PropertyInfo>::Element *E=scr->member_info.front();E;E=E->next()) {
|
|
|
|
_GDScriptMemberSort ms;
|
|
ERR_CONTINUE(!scr->member_indices.has(E->key()));
|
|
ms.index=scr->member_indices[E->key()].index;
|
|
ms.name=E->key();
|
|
|
|
msort.push_back(ms);
|
|
|
|
}
|
|
|
|
msort.sort();
|
|
msort.invert();
|
|
for(int i=0;i<msort.size();i++) {
|
|
|
|
plist.push_front(scr->member_info[msort[i].name]);
|
|
if (scr->member_default_values.has(msort[i].name))
|
|
default_values[msort[i].name]=scr->member_default_values[msort[i].name];
|
|
else {
|
|
Variant::CallError err;
|
|
default_values[msort[i].name]=Variant::construct(scr->member_info[msort[i].name].type,NULL,0,err);
|
|
}
|
|
}
|
|
|
|
scr=scr->_base;
|
|
}
|
|
|
|
|
|
p_placeholder->update(plist,default_values);
|
|
|
|
}*/
|
|
#endif
|
|
ScriptInstance* GDScript::instance_create(Object *p_this) {
|
|
|
|
|
|
if (!tool && !ScriptServer::is_scripting_enabled()) {
|
|
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
//instance a fake script for editing the values
|
|
//plist.invert();
|
|
|
|
/*print_line("CREATING PLACEHOLDER");
|
|
for(List<PropertyInfo>::Element *E=plist.front();E;E=E->next()) {
|
|
print_line(E->get().name);
|
|
}*/
|
|
PlaceHolderScriptInstance *si = memnew( PlaceHolderScriptInstance(GDScriptLanguage::get_singleton(),Ref<Script>(this),p_this) );
|
|
placeholders.insert(si);
|
|
//_update_placeholder(si);
|
|
_update_exports();
|
|
return si;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
GDScript *top=this;
|
|
while(top->_base)
|
|
top=top->_base;
|
|
|
|
if (top->native.is_valid()) {
|
|
if (!ObjectTypeDB::is_type(p_this->get_type_name(),top->native->get_name())) {
|
|
|
|
if (ScriptDebugger::get_singleton()) {
|
|
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(),0,"Script inherits from native type '"+String(top->native->get_name())+"', so it can't be instanced in object of type: '"+p_this->get_type()+"'");
|
|
}
|
|
ERR_EXPLAIN("Script inherits from native type '"+String(top->native->get_name())+"', so it can't be instanced in object of type: '"+p_this->get_type()+"'");
|
|
ERR_FAIL_V(NULL);
|
|
|
|
}
|
|
}
|
|
|
|
return _create_instance(NULL,0,p_this,p_this->cast_to<Reference>());
|
|
|
|
}
|
|
bool GDScript::instance_has(const Object *p_this) const {
|
|
|
|
return instances.has((Object*)p_this);
|
|
}
|
|
|
|
bool GDScript::has_source_code() const {
|
|
|
|
return source!="";
|
|
}
|
|
String GDScript::get_source_code() const {
|
|
|
|
return source;
|
|
}
|
|
void GDScript::set_source_code(const String& p_code) {
|
|
|
|
if (source==p_code)
|
|
return;
|
|
source=p_code;
|
|
#ifdef TOOLS_ENABLED
|
|
source_changed_cache=true;
|
|
//print_line("SC CHANGED "+get_path());
|
|
#endif
|
|
|
|
}
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
void GDScript::_update_exports_values(Map<StringName,Variant>& values, List<PropertyInfo> &propnames) {
|
|
|
|
if (base_cache.is_valid()) {
|
|
base_cache->_update_exports_values(values,propnames);
|
|
}
|
|
|
|
for(Map<StringName,Variant>::Element *E=member_default_values_cache.front();E;E=E->next()) {
|
|
values[E->key()]=E->get();
|
|
}
|
|
|
|
for (List<PropertyInfo>::Element *E=members_cache.front();E;E=E->next()) {
|
|
propnames.push_back(E->get());
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
bool GDScript::_update_exports() {
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
bool changed=false;
|
|
|
|
if (source_changed_cache) {
|
|
//print_line("updating source for "+get_path());
|
|
source_changed_cache=false;
|
|
changed=true;
|
|
|
|
String basedir=path;
|
|
|
|
if (basedir=="")
|
|
basedir=get_path();
|
|
|
|
if (basedir!="")
|
|
basedir=basedir.get_base_dir();
|
|
|
|
GDParser parser;
|
|
Error err = parser.parse(source,basedir,true,path);
|
|
|
|
if (err==OK) {
|
|
|
|
const GDParser::Node* root = parser.get_parse_tree();
|
|
ERR_FAIL_COND_V(root->type!=GDParser::Node::TYPE_CLASS,false);
|
|
|
|
const GDParser::ClassNode *c = static_cast<const GDParser::ClassNode*>(root);
|
|
|
|
if (base_cache.is_valid()) {
|
|
base_cache->inheriters_cache.erase(get_instance_ID());
|
|
base_cache=Ref<GDScript>();
|
|
}
|
|
|
|
|
|
if (c->extends_used && String(c->extends_file)!="" && String(c->extends_file) != get_path()) {
|
|
|
|
String path = c->extends_file;
|
|
if (path.is_rel_path()) {
|
|
|
|
String base = get_path();
|
|
if (base=="" || base.is_rel_path()) {
|
|
|
|
ERR_PRINT(("Could not resolve relative path for parent class: "+path).utf8().get_data());
|
|
} else {
|
|
path=base.get_base_dir().plus_file(path);
|
|
}
|
|
}
|
|
|
|
Ref<GDScript> bf = ResourceLoader::load(path);
|
|
|
|
if (bf.is_valid()) {
|
|
|
|
//print_line("parent is: "+bf->get_path());
|
|
base_cache=bf;
|
|
bf->inheriters_cache.insert(get_instance_ID());
|
|
|
|
//bf->_update_exports(p_instances,true,false);
|
|
|
|
}
|
|
}
|
|
|
|
members_cache.clear();;
|
|
member_default_values_cache.clear();
|
|
|
|
for(int i=0;i<c->variables.size();i++) {
|
|
if (c->variables[i]._export.type==Variant::NIL)
|
|
continue;
|
|
|
|
members_cache.push_back(c->variables[i]._export);
|
|
//print_line("found "+c->variables[i]._export.name);
|
|
member_default_values_cache[c->variables[i].identifier]=c->variables[i].default_value;
|
|
}
|
|
|
|
_signals.clear();
|
|
|
|
for(int i=0;i<c->_signals.size();i++) {
|
|
_signals[c->_signals[i].name]=c->_signals[i].arguments;
|
|
}
|
|
}
|
|
} else {
|
|
//print_line("unchaged is "+get_path());
|
|
|
|
}
|
|
|
|
if (base_cache.is_valid()) {
|
|
if (base_cache->_update_exports()) {
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (/*changed &&*/ placeholders.size()) { //hm :(
|
|
|
|
//print_line("updating placeholders for "+get_path());
|
|
|
|
//update placeholders if any
|
|
Map<StringName,Variant> values;
|
|
List<PropertyInfo> propnames;
|
|
_update_exports_values(values,propnames);
|
|
|
|
for (Set<PlaceHolderScriptInstance*>::Element *E=placeholders.front();E;E=E->next()) {
|
|
|
|
E->get()->update(propnames,values);
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void GDScript::update_exports() {
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
_update_exports();
|
|
|
|
Set<ObjectID> copy=inheriters_cache; //might get modified
|
|
|
|
//print_line("update exports for "+get_path()+" ic: "+itos(copy.size()));
|
|
for(Set<ObjectID>::Element *E=copy.front();E;E=E->next()) {
|
|
Object *id=ObjectDB::get_instance(E->get());
|
|
if (!id)
|
|
continue;
|
|
GDScript *s=id->cast_to<GDScript>();
|
|
if (!s)
|
|
continue;
|
|
s->update_exports();
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
void GDScript::_set_subclass_path(Ref<GDScript>& p_sc,const String& p_path) {
|
|
|
|
p_sc->path=p_path;
|
|
for(Map<StringName,Ref<GDScript> >::Element *E=p_sc->subclasses.front();E;E=E->next()) {
|
|
|
|
_set_subclass_path(E->get(),p_path);
|
|
}
|
|
}
|
|
|
|
Error GDScript::reload() {
|
|
|
|
|
|
ERR_FAIL_COND_V(instances.size(),ERR_ALREADY_IN_USE);
|
|
|
|
String basedir=path;
|
|
|
|
if (basedir=="")
|
|
basedir=get_path();
|
|
|
|
if (basedir!="")
|
|
basedir=basedir.get_base_dir();
|
|
|
|
|
|
|
|
|
|
valid=false;
|
|
GDParser parser;
|
|
Error err = parser.parse(source,basedir,false,path);
|
|
if (err) {
|
|
if (ScriptDebugger::get_singleton()) {
|
|
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(),parser.get_error_line(),"Parser Error: "+parser.get_error());
|
|
}
|
|
_err_print_error("GDScript::reload",path.empty()?"built-in":(const char*)path.utf8().get_data(),parser.get_error_line(),("Parse Error: "+parser.get_error()).utf8().get_data());
|
|
ERR_FAIL_V(ERR_PARSE_ERROR);
|
|
}
|
|
|
|
GDCompiler compiler;
|
|
err = compiler.compile(&parser,this);
|
|
|
|
if (err) {
|
|
if (ScriptDebugger::get_singleton()) {
|
|
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(),compiler.get_error_line(),"Parser Error: "+compiler.get_error());
|
|
}
|
|
_err_print_error("GDScript::reload",path.empty()?"built-in":(const char*)path.utf8().get_data(),compiler.get_error_line(),("Compile Error: "+compiler.get_error()).utf8().get_data());
|
|
ERR_FAIL_V(ERR_COMPILATION_FAILED);
|
|
}
|
|
|
|
valid=true;
|
|
|
|
for(Map<StringName,Ref<GDScript> >::Element *E=subclasses.front();E;E=E->next()) {
|
|
|
|
_set_subclass_path(E->get(),path);
|
|
}
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
/*for (Set<PlaceHolderScriptInstance*>::Element *E=placeholders.front();E;E=E->next()) {
|
|
|
|
_update_placeholder(E->get());
|
|
}*/
|
|
#endif
|
|
return OK;
|
|
}
|
|
|
|
String GDScript::get_node_type() const {
|
|
|
|
return ""; // ?
|
|
}
|
|
|
|
ScriptLanguage *GDScript::get_language() const {
|
|
|
|
return GDScriptLanguage::get_singleton();
|
|
}
|
|
|
|
|
|
Variant GDScript::call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error) {
|
|
|
|
|
|
GDScript *top=this;
|
|
while(top) {
|
|
|
|
Map<StringName,GDFunction>::Element *E=top->member_functions.find(p_method);
|
|
if (E) {
|
|
|
|
if (!E->get().is_static()) {
|
|
WARN_PRINT(String("Can't call non-static function: '"+String(p_method)+"' in script.").utf8().get_data());
|
|
}
|
|
|
|
return E->get().call(NULL,p_args,p_argcount,r_error);
|
|
}
|
|
top=top->_base;
|
|
}
|
|
|
|
//none found, regular
|
|
|
|
return Script::call(p_method,p_args,p_argcount,r_error);
|
|
|
|
}
|
|
|
|
bool GDScript::_get(const StringName& p_name,Variant &r_ret) const {
|
|
|
|
{
|
|
|
|
|
|
const GDScript *top=this;
|
|
while(top) {
|
|
|
|
{
|
|
const Map<StringName,Variant>::Element *E=top->constants.find(p_name);
|
|
if (E) {
|
|
|
|
r_ret= E->get();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
{
|
|
const Map<StringName,Ref<GDScript> >::Element *E=subclasses.find(p_name);
|
|
if (E) {
|
|
|
|
r_ret=E->get();
|
|
return true;
|
|
}
|
|
}
|
|
top=top->_base;
|
|
}
|
|
|
|
if (p_name==GDScriptLanguage::get_singleton()->strings._script_source) {
|
|
|
|
r_ret=get_source_code();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
bool GDScript::_set(const StringName& p_name, const Variant& p_value) {
|
|
|
|
if (p_name==GDScriptLanguage::get_singleton()->strings._script_source) {
|
|
|
|
set_source_code(p_value);
|
|
reload();
|
|
} else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const {
|
|
|
|
p_properties->push_back( PropertyInfo(Variant::STRING,"script/source",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR) );
|
|
}
|
|
|
|
|
|
void GDScript::_bind_methods() {
|
|
|
|
ObjectTypeDB::bind_native_method(METHOD_FLAGS_DEFAULT,"new",&GDScript::_new,MethodInfo("new"));
|
|
|
|
ObjectTypeDB::bind_method(_MD("get_as_byte_code"),&GDScript::get_as_byte_code);
|
|
|
|
}
|
|
|
|
|
|
Vector<uint8_t> GDScript::get_as_byte_code() const {
|
|
|
|
GDTokenizerBuffer tokenizer;
|
|
return tokenizer.parse_code_string(source);
|
|
};
|
|
|
|
|
|
Error GDScript::load_byte_code(const String& p_path) {
|
|
|
|
Vector<uint8_t> bytecode;
|
|
|
|
if (p_path.ends_with("gde")) {
|
|
|
|
FileAccess *fa = FileAccess::open(p_path,FileAccess::READ);
|
|
ERR_FAIL_COND_V(!fa,ERR_CANT_OPEN);
|
|
FileAccessEncrypted *fae = memnew( FileAccessEncrypted );
|
|
ERR_FAIL_COND_V(!fae,ERR_CANT_OPEN);
|
|
Vector<uint8_t> key;
|
|
key.resize(32);
|
|
for(int i=0;i<key.size();i++) {
|
|
key[i]=script_encryption_key[i];
|
|
}
|
|
Error err = fae->open_and_parse(fa,key,FileAccessEncrypted::MODE_READ);
|
|
ERR_FAIL_COND_V(err,err);
|
|
bytecode.resize(fae->get_len());
|
|
fae->get_buffer(bytecode.ptr(),bytecode.size());
|
|
memdelete(fae);
|
|
} else {
|
|
|
|
bytecode = FileAccess::get_file_as_array(p_path);
|
|
}
|
|
ERR_FAIL_COND_V(bytecode.size()==0,ERR_PARSE_ERROR);
|
|
path=p_path;
|
|
|
|
String basedir=path;
|
|
|
|
if (basedir=="")
|
|
basedir=get_path();
|
|
|
|
if (basedir!="")
|
|
basedir=basedir.get_base_dir();
|
|
|
|
valid=false;
|
|
GDParser parser;
|
|
Error err = parser.parse_bytecode(bytecode,basedir,get_path());
|
|
if (err) {
|
|
_err_print_error("GDScript::load_byte_code",path.empty()?"built-in":(const char*)path.utf8().get_data(),parser.get_error_line(),("Parse Error: "+parser.get_error()).utf8().get_data());
|
|
ERR_FAIL_V(ERR_PARSE_ERROR);
|
|
}
|
|
|
|
GDCompiler compiler;
|
|
err = compiler.compile(&parser,this);
|
|
|
|
if (err) {
|
|
_err_print_error("GDScript::load_byte_code",path.empty()?"built-in":(const char*)path.utf8().get_data(),compiler.get_error_line(),("Compile Error: "+compiler.get_error()).utf8().get_data());
|
|
ERR_FAIL_V(ERR_COMPILATION_FAILED);
|
|
}
|
|
|
|
valid=true;
|
|
|
|
for(Map<StringName,Ref<GDScript> >::Element *E=subclasses.front();E;E=E->next()) {
|
|
|
|
_set_subclass_path(E->get(),path);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
Error GDScript::load_source_code(const String& p_path) {
|
|
|
|
|
|
DVector<uint8_t> sourcef;
|
|
Error err;
|
|
FileAccess *f=FileAccess::open(p_path,FileAccess::READ,&err);
|
|
if (err) {
|
|
|
|
ERR_FAIL_COND_V(err,err);
|
|
}
|
|
|
|
int len = f->get_len();
|
|
sourcef.resize(len+1);
|
|
DVector<uint8_t>::Write w = sourcef.write();
|
|
int r = f->get_buffer(w.ptr(),len);
|
|
f->close();
|
|
memdelete(f);
|
|
ERR_FAIL_COND_V(r!=len,ERR_CANT_OPEN);
|
|
w[len]=0;
|
|
|
|
String s;
|
|
if (s.parse_utf8((const char*)w.ptr())) {
|
|
|
|
ERR_EXPLAIN("Script '"+p_path+"' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode.");
|
|
ERR_FAIL_V(ERR_INVALID_DATA);
|
|
}
|
|
|
|
source=s;
|
|
#ifdef TOOLS_ENABLED
|
|
source_changed_cache=true;
|
|
#endif
|
|
//print_line("LSC :"+get_path());
|
|
path=p_path;
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
const Map<StringName,GDFunction>& GDScript::debug_get_member_functions() const {
|
|
|
|
return member_functions;
|
|
}
|
|
|
|
|
|
|
|
StringName GDScript::debug_get_member_by_index(int p_idx) const {
|
|
|
|
|
|
for(const Map<StringName,MemberInfo>::Element *E=member_indices.front();E;E=E->next()) {
|
|
|
|
if (E->get().index==p_idx)
|
|
return E->key();
|
|
}
|
|
|
|
return "<error>";
|
|
}
|
|
|
|
|
|
Ref<GDScript> GDScript::get_base() const {
|
|
|
|
return base;
|
|
}
|
|
|
|
bool GDScript::has_script_signal(const StringName& p_signal) const {
|
|
if (_signals.has(p_signal))
|
|
return true;
|
|
if (base.is_valid()) {
|
|
return base->has_script_signal(p_signal);
|
|
}
|
|
#ifdef TOOLS_ENABLED
|
|
else if (base_cache.is_valid()){
|
|
return base_cache->has_script_signal(p_signal);
|
|
}
|
|
|
|
#endif
|
|
return false;
|
|
}
|
|
void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
|
|
|
|
for(const Map<StringName,Vector<StringName> >::Element *E=_signals.front();E;E=E->next()) {
|
|
|
|
MethodInfo mi;
|
|
mi.name=E->key();
|
|
for(int i=0;i<E->get().size();i++) {
|
|
PropertyInfo arg;
|
|
arg.name=E->get()[i];
|
|
mi.arguments.push_back(arg);
|
|
}
|
|
r_signals->push_back(mi);
|
|
}
|
|
|
|
if (base.is_valid()) {
|
|
base->get_script_signal_list(r_signals);
|
|
}
|
|
#ifdef TOOLS_ENABLED
|
|
else if (base_cache.is_valid()){
|
|
base_cache->get_script_signal_list(r_signals);
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
GDScript::GDScript() {
|
|
|
|
|
|
_static_ref=this;
|
|
valid=false;
|
|
subclass_count=0;
|
|
initializer=NULL;
|
|
_base=NULL;
|
|
_owner=NULL;
|
|
tool=false;
|
|
#ifdef TOOLS_ENABLED
|
|
source_changed_cache=false;
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
// INSTANCE //
|
|
//////////////////////////////
|
|
|
|
|
|
bool GDInstance::set(const StringName& p_name, const Variant& p_value) {
|
|
|
|
//member
|
|
{
|
|
const Map<StringName,GDScript::MemberInfo>::Element *E = script->member_indices.find(p_name);
|
|
if (E) {
|
|
if (E->get().setter) {
|
|
const Variant *val=&p_value;
|
|
Variant::CallError err;
|
|
call(E->get().setter,&val,1,err);
|
|
if (err.error==Variant::CallError::CALL_OK) {
|
|
return true; //function exists, call was successful
|
|
}
|
|
}
|
|
else
|
|
members[E->get().index] = p_value;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
GDScript *sptr=script.ptr();
|
|
while(sptr) {
|
|
|
|
|
|
Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set);
|
|
if (E) {
|
|
|
|
Variant name=p_name;
|
|
const Variant *args[2]={&name,&p_value};
|
|
|
|
Variant::CallError err;
|
|
Variant ret = E->get().call(this,(const Variant**)args,2,err);
|
|
if (err.error==Variant::CallError::CALL_OK && ret.get_type()==Variant::BOOL && ret.operator bool())
|
|
return true;
|
|
}
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GDInstance::get(const StringName& p_name, Variant &r_ret) const {
|
|
|
|
|
|
|
|
const GDScript *sptr=script.ptr();
|
|
while(sptr) {
|
|
|
|
{
|
|
const Map<StringName,GDScript::MemberInfo>::Element *E = script->member_indices.find(p_name);
|
|
if (E) {
|
|
if (E->get().getter) {
|
|
Variant::CallError err;
|
|
r_ret=const_cast<GDInstance*>(this)->call(E->get().getter,NULL,0,err);
|
|
if (err.error==Variant::CallError::CALL_OK) {
|
|
return true;
|
|
}
|
|
}
|
|
r_ret=members[E->get().index];
|
|
return true; //index found
|
|
|
|
}
|
|
}
|
|
|
|
{
|
|
const Map<StringName,Variant>::Element *E = script->constants.find(p_name);
|
|
if (E) {
|
|
r_ret=E->get();
|
|
return true; //index found
|
|
|
|
}
|
|
}
|
|
|
|
{
|
|
const Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get);
|
|
if (E) {
|
|
|
|
Variant name=p_name;
|
|
const Variant *args[1]={&name};
|
|
|
|
Variant::CallError err;
|
|
Variant ret = const_cast<GDFunction*>(&E->get())->call(const_cast<GDInstance*>(this),(const Variant**)args,1,err);
|
|
if (err.error==Variant::CallError::CALL_OK && ret.get_type()!=Variant::NIL) {
|
|
r_ret=ret;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
void GDInstance::get_property_list(List<PropertyInfo> *p_properties) const {
|
|
// exported members, not doen yet!
|
|
|
|
const GDScript *sptr=script.ptr();
|
|
List<PropertyInfo> props;
|
|
|
|
while(sptr) {
|
|
|
|
|
|
const Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list);
|
|
if (E) {
|
|
|
|
|
|
Variant::CallError err;
|
|
Variant ret = const_cast<GDFunction*>(&E->get())->call(const_cast<GDInstance*>(this),NULL,0,err);
|
|
if (err.error==Variant::CallError::CALL_OK) {
|
|
|
|
if (ret.get_type()!=Variant::ARRAY) {
|
|
|
|
ERR_EXPLAIN("Wrong type for _get_property list, must be an array of dictionaries.");
|
|
ERR_FAIL();
|
|
}
|
|
Array arr = ret;
|
|
for(int i=0;i<arr.size();i++) {
|
|
|
|
Dictionary d = arr[i];
|
|
ERR_CONTINUE(!d.has("name"));
|
|
ERR_CONTINUE(!d.has("type"));
|
|
PropertyInfo pinfo;
|
|
pinfo.type = Variant::Type( d["type"].operator int());
|
|
ERR_CONTINUE(pinfo.type<0 || pinfo.type>=Variant::VARIANT_MAX );
|
|
pinfo.name = d["name"];
|
|
ERR_CONTINUE(pinfo.name=="");
|
|
if (d.has("hint"))
|
|
pinfo.hint=PropertyHint(d["hint"].operator int());
|
|
if (d.has("hint_string"))
|
|
pinfo.hint_string=d["hint_string"];
|
|
if (d.has("usage"))
|
|
pinfo.usage=d["usage"];
|
|
|
|
props.push_back(pinfo);
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//instance a fake script for editing the values
|
|
|
|
Vector<_GDScriptMemberSort> msort;
|
|
for(Map<StringName,PropertyInfo>::Element *E=sptr->member_info.front();E;E=E->next()) {
|
|
|
|
_GDScriptMemberSort ms;
|
|
ERR_CONTINUE(!sptr->member_indices.has(E->key()));
|
|
ms.index=sptr->member_indices[E->key()].index;
|
|
ms.name=E->key();
|
|
msort.push_back(ms);
|
|
|
|
}
|
|
|
|
msort.sort();
|
|
msort.invert();
|
|
for(int i=0;i<msort.size();i++) {
|
|
|
|
props.push_front(sptr->member_info[msort[i].name]);
|
|
|
|
}
|
|
#if 0
|
|
if (sptr->member_functions.has("_get_property_list")) {
|
|
|
|
Variant::CallError err;
|
|
GDFunction *f = const_cast<GDFunction*>(&sptr->member_functions["_get_property_list"]);
|
|
Variant plv = f->call(const_cast<GDInstance*>(this),NULL,0,err);
|
|
|
|
if (plv.get_type()!=Variant::ARRAY) {
|
|
|
|
ERR_PRINT("_get_property_list: expected array returned");
|
|
} else {
|
|
|
|
Array pl=plv;
|
|
|
|
for(int i=0;i<pl.size();i++) {
|
|
|
|
Dictionary p = pl[i];
|
|
PropertyInfo pinfo;
|
|
if (!p.has("name")) {
|
|
ERR_PRINT("_get_property_list: expected 'name' key of type string.")
|
|
continue;
|
|
}
|
|
if (!p.has("type")) {
|
|
ERR_PRINT("_get_property_list: expected 'type' key of type integer.")
|
|
continue;
|
|
}
|
|
pinfo.name=p["name"];
|
|
pinfo.type=Variant::Type(int(p["type"]));
|
|
if (p.has("hint"))
|
|
pinfo.hint=PropertyHint(int(p["hint"]));
|
|
if (p.has("hint_string"))
|
|
pinfo.hint_string=p["hint_string"];
|
|
if (p.has("usage"))
|
|
pinfo.usage=p["usage"];
|
|
|
|
|
|
props.push_back(pinfo);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
//props.invert();
|
|
|
|
for (List<PropertyInfo>::Element *E=props.front();E;E=E->next()) {
|
|
|
|
p_properties->push_back(E->get());
|
|
}
|
|
}
|
|
|
|
void GDInstance::get_method_list(List<MethodInfo> *p_list) const {
|
|
|
|
const GDScript *sptr=script.ptr();
|
|
while(sptr) {
|
|
|
|
for (Map<StringName,GDFunction>::Element *E = sptr->member_functions.front();E;E=E->next()) {
|
|
|
|
MethodInfo mi;
|
|
mi.name=E->key();
|
|
for(int i=0;i<E->get().get_argument_count();i++)
|
|
mi.arguments.push_back(PropertyInfo(Variant::NIL,"arg"+itos(i)));
|
|
p_list->push_back(mi);
|
|
}
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
}
|
|
|
|
bool GDInstance::has_method(const StringName& p_method) const {
|
|
|
|
const GDScript *sptr=script.ptr();
|
|
while(sptr) {
|
|
const Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(p_method);
|
|
if (E)
|
|
return true;
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
Variant GDInstance::call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error) {
|
|
|
|
//printf("calling %ls:%i method %ls\n", script->get_path().c_str(), -1, String(p_method).c_str());
|
|
|
|
GDScript *sptr=script.ptr();
|
|
while(sptr) {
|
|
Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(p_method);
|
|
if (E) {
|
|
return E->get().call(this,p_args,p_argcount,r_error);
|
|
}
|
|
sptr = sptr->_base;
|
|
}
|
|
r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD;
|
|
return Variant();
|
|
}
|
|
|
|
void GDInstance::call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount) {
|
|
|
|
GDScript *sptr=script.ptr();
|
|
Variant::CallError ce;
|
|
|
|
while(sptr) {
|
|
Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(p_method);
|
|
if (E) {
|
|
E->get().call(this,p_args,p_argcount,ce);
|
|
}
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void GDInstance::_ml_call_reversed(GDScript *sptr,const StringName& p_method,const Variant** p_args,int p_argcount) {
|
|
|
|
if (sptr->_base)
|
|
_ml_call_reversed(sptr->_base,p_method,p_args,p_argcount);
|
|
|
|
Variant::CallError ce;
|
|
|
|
Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(p_method);
|
|
if (E) {
|
|
E->get().call(this,p_args,p_argcount,ce);
|
|
}
|
|
|
|
}
|
|
|
|
void GDInstance::call_multilevel_reversed(const StringName& p_method,const Variant** p_args,int p_argcount) {
|
|
|
|
if (script.ptr()) {
|
|
_ml_call_reversed(script.ptr(),p_method,p_args,p_argcount);
|
|
}
|
|
}
|
|
|
|
void GDInstance::notification(int p_notification) {
|
|
|
|
//notification is not virutal, it gets called at ALL levels just like in C.
|
|
Variant value=p_notification;
|
|
const Variant *args[1]={&value };
|
|
|
|
GDScript *sptr=script.ptr();
|
|
while(sptr) {
|
|
Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._notification);
|
|
if (E) {
|
|
Variant::CallError err;
|
|
E->get().call(this,args,1,err);
|
|
if (err.error!=Variant::CallError::CALL_OK) {
|
|
//print error about notification call
|
|
|
|
}
|
|
}
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
}
|
|
|
|
Ref<Script> GDInstance::get_script() const {
|
|
|
|
return script;
|
|
}
|
|
|
|
ScriptLanguage *GDInstance::get_language() {
|
|
|
|
return GDScriptLanguage::get_singleton();
|
|
}
|
|
|
|
|
|
GDInstance::GDInstance() {
|
|
owner=NULL;
|
|
base_ref=false;
|
|
}
|
|
|
|
GDInstance::~GDInstance() {
|
|
if (script.is_valid() && owner) {
|
|
script->instances.erase(owner);
|
|
}
|
|
}
|
|
|
|
/************* SCRIPT LANGUAGE **************/
|
|
/************* SCRIPT LANGUAGE **************/
|
|
/************* SCRIPT LANGUAGE **************/
|
|
/************* SCRIPT LANGUAGE **************/
|
|
/************* SCRIPT LANGUAGE **************/
|
|
|
|
GDScriptLanguage *GDScriptLanguage::singleton=NULL;
|
|
|
|
|
|
String GDScriptLanguage::get_name() const {
|
|
|
|
return "GDScript";
|
|
}
|
|
|
|
/* LANGUAGE FUNCTIONS */
|
|
|
|
void GDScriptLanguage::_add_global(const StringName& p_name,const Variant& p_value) {
|
|
|
|
|
|
if (globals.has(p_name)) {
|
|
//overwrite existing
|
|
global_array[globals[p_name]]=p_value;
|
|
return;
|
|
}
|
|
globals[p_name]=global_array.size();
|
|
global_array.push_back(p_value);
|
|
_global_array=global_array.ptr();
|
|
}
|
|
|
|
void GDScriptLanguage::init() {
|
|
|
|
|
|
//populate global constants
|
|
int gcc=GlobalConstants::get_global_constant_count();
|
|
for(int i=0;i<gcc;i++) {
|
|
|
|
_add_global(StaticCString::create(GlobalConstants::get_global_constant_name(i)),GlobalConstants::get_global_constant_value(i));
|
|
}
|
|
|
|
_add_global(StaticCString::create("PI"),Math_PI);
|
|
|
|
//populate native classes
|
|
|
|
List<StringName> class_list;
|
|
ObjectTypeDB::get_type_list(&class_list);
|
|
for(List<StringName>::Element *E=class_list.front();E;E=E->next()) {
|
|
|
|
StringName n = E->get();
|
|
String s = String(n);
|
|
if (s.begins_with("_"))
|
|
n=s.substr(1,s.length());
|
|
|
|
if (globals.has(n))
|
|
continue;
|
|
Ref<GDNativeClass> nc = memnew( GDNativeClass(E->get()) );
|
|
_add_global(n,nc);
|
|
}
|
|
|
|
//populate singletons
|
|
|
|
List<Globals::Singleton> singletons;
|
|
Globals::get_singleton()->get_singletons(&singletons);
|
|
for(List<Globals::Singleton>::Element *E=singletons.front();E;E=E->next()) {
|
|
|
|
_add_global(E->get().name,E->get().ptr);
|
|
}
|
|
}
|
|
|
|
String GDScriptLanguage::get_type() const {
|
|
|
|
return "GDScript";
|
|
}
|
|
String GDScriptLanguage::get_extension() const {
|
|
|
|
return "gd";
|
|
}
|
|
Error GDScriptLanguage::execute_file(const String& p_path) {
|
|
|
|
// ??
|
|
return OK;
|
|
}
|
|
void GDScriptLanguage::finish() {
|
|
|
|
|
|
}
|
|
|
|
|
|
void GDScriptLanguage::frame() {
|
|
|
|
// print_line("calls: "+itos(calls));
|
|
calls=0;
|
|
}
|
|
|
|
/* EDITOR FUNCTIONS */
|
|
void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
|
|
|
|
static const char *_reserved_words[]={
|
|
"break",
|
|
"class",
|
|
"continue",
|
|
"const",
|
|
"else",
|
|
"elif",
|
|
"enum",
|
|
"extends" ,
|
|
"for" ,
|
|
"func" ,
|
|
"if" ,
|
|
"in" ,
|
|
"null" ,
|
|
"not" ,
|
|
"return" ,
|
|
"self" ,
|
|
"while" ,
|
|
"true" ,
|
|
"false" ,
|
|
"tool",
|
|
"var",
|
|
"setget",
|
|
"pass",
|
|
"and",
|
|
"or",
|
|
"export",
|
|
"assert",
|
|
"yield",
|
|
"static",
|
|
"float",
|
|
"int",
|
|
"signal",
|
|
0};
|
|
|
|
|
|
const char **w=_reserved_words;
|
|
|
|
|
|
while (*w) {
|
|
|
|
p_words->push_back(*w);
|
|
w++;
|
|
}
|
|
|
|
for(int i=0;i<GDFunctions::FUNC_MAX;i++) {
|
|
p_words->push_back(GDFunctions::get_func_name(GDFunctions::Function(i)));
|
|
}
|
|
|
|
}
|
|
|
|
GDScriptLanguage::GDScriptLanguage() {
|
|
|
|
calls=0;
|
|
ERR_FAIL_COND(singleton);
|
|
singleton=this;
|
|
strings._init = StaticCString::create("_init");
|
|
strings._notification = StaticCString::create("_notification");
|
|
strings._set= StaticCString::create("_set");
|
|
strings._get= StaticCString::create("_get");
|
|
strings._get_property_list= StaticCString::create("_get_property_list");
|
|
strings._script_source=StaticCString::create("script/source");
|
|
_debug_parse_err_line=-1;
|
|
_debug_parse_err_file="";
|
|
|
|
_debug_call_stack_pos=0;
|
|
int dmcs=GLOBAL_DEF("debug/script_max_call_stack",1024);
|
|
if (ScriptDebugger::get_singleton()) {
|
|
//debugging enabled!
|
|
|
|
_debug_max_call_stack = dmcs;
|
|
if (_debug_max_call_stack<1024)
|
|
_debug_max_call_stack=1024;
|
|
_call_stack = memnew_arr( CallLevel, _debug_max_call_stack+1 );
|
|
|
|
} else {
|
|
_debug_max_call_stack=0;
|
|
_call_stack=NULL;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
GDScriptLanguage::~GDScriptLanguage() {
|
|
|
|
if (_call_stack) {
|
|
memdelete_arr(_call_stack);
|
|
}
|
|
singleton=NULL;
|
|
}
|
|
|
|
/*************** RESOURCE ***************/
|
|
|
|
RES ResourceFormatLoaderGDScript::load(const String &p_path, const String& p_original_path, Error *r_error) {
|
|
|
|
if (r_error)
|
|
*r_error=ERR_FILE_CANT_OPEN;
|
|
|
|
GDScript *script = memnew( GDScript );
|
|
|
|
Ref<GDScript> scriptres(script);
|
|
|
|
if (p_path.ends_with(".gde") || p_path.ends_with(".gdc")) {
|
|
|
|
script->set_script_path(p_original_path); // script needs this.
|
|
script->set_path(p_original_path);
|
|
Error err = script->load_byte_code(p_path);
|
|
|
|
|
|
if (err!=OK) {
|
|
|
|
ERR_FAIL_COND_V(err!=OK, RES());
|
|
}
|
|
|
|
} else {
|
|
Error err = script->load_source_code(p_path);
|
|
|
|
if (err!=OK) {
|
|
|
|
ERR_FAIL_COND_V(err!=OK, RES());
|
|
}
|
|
|
|
script->set_script_path(p_original_path); // script needs this.
|
|
script->set_path(p_original_path);
|
|
//script->set_name(p_path.get_file());
|
|
|
|
script->reload();
|
|
}
|
|
if (r_error)
|
|
*r_error=OK;
|
|
|
|
return scriptres;
|
|
}
|
|
void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const {
|
|
|
|
p_extensions->push_back("gd");
|
|
p_extensions->push_back("gdc");
|
|
p_extensions->push_back("gde");
|
|
}
|
|
|
|
bool ResourceFormatLoaderGDScript::handles_type(const String& p_type) const {
|
|
|
|
return (p_type=="Script" || p_type=="GDScript");
|
|
}
|
|
|
|
String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const {
|
|
|
|
String el = p_path.extension().to_lower();
|
|
if (el=="gd" || el=="gdc" || el=="gde")
|
|
return "GDScript";
|
|
return "";
|
|
}
|
|
|
|
|
|
Error ResourceFormatSaverGDScript::save(const String &p_path,const RES& p_resource,uint32_t p_flags) {
|
|
|
|
Ref<GDScript> sqscr = p_resource;
|
|
ERR_FAIL_COND_V(sqscr.is_null(),ERR_INVALID_PARAMETER);
|
|
|
|
String source = sqscr->get_source_code();
|
|
|
|
Error err;
|
|
FileAccess *file = FileAccess::open(p_path,FileAccess::WRITE,&err);
|
|
|
|
|
|
if (err) {
|
|
|
|
ERR_FAIL_COND_V(err,err);
|
|
}
|
|
|
|
file->store_string(source);
|
|
if (file->get_error()!=OK && file->get_error()!=ERR_FILE_EOF) {
|
|
memdelete(file);
|
|
return ERR_CANT_CREATE;
|
|
}
|
|
file->close();
|
|
memdelete(file);
|
|
return OK;
|
|
}
|
|
|
|
void ResourceFormatSaverGDScript::get_recognized_extensions(const RES& p_resource,List<String> *p_extensions) const {
|
|
|
|
if (p_resource->cast_to<GDScript>()) {
|
|
p_extensions->push_back("gd");
|
|
}
|
|
|
|
}
|
|
bool ResourceFormatSaverGDScript::recognize(const RES& p_resource) const {
|
|
|
|
return p_resource->cast_to<GDScript>()!=NULL;
|
|
}
|