mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2025-01-09 12:29:35 +01:00
Removed yield from cscript.
This commit is contained in:
parent
c4edec424d
commit
c8579ef63f
@ -77,17 +77,6 @@ Object *CScriptNativeClass::instance() {
|
||||
return ClassDB::instance(name);
|
||||
}
|
||||
|
||||
void CScript::_clear_pending_func_states() {
|
||||
CScriptLanguage::get_singleton()->lock.lock();
|
||||
while (SelfList<CScriptFunctionState> *E = pending_func_states.first()) {
|
||||
// Order matters since clearing the stack may already cause
|
||||
// the CSCriptFunctionState to be destroyed and thus removed from the list.
|
||||
pending_func_states.remove(E);
|
||||
E->self()->_clear_stack();
|
||||
}
|
||||
CScriptLanguage::get_singleton()->lock.unlock();
|
||||
}
|
||||
|
||||
CScriptInstance *CScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error) {
|
||||
/* STEP 1, CREATE */
|
||||
|
||||
@ -628,7 +617,6 @@ Error CScript::reload(bool p_keep_state) {
|
||||
for (Map<StringName, Ref<CScript>>::Element *E = subclasses.front(); E; E = E->next()) {
|
||||
_set_subclass_path(E->get(), path);
|
||||
}
|
||||
_clear_pending_func_states();
|
||||
|
||||
return OK;
|
||||
}
|
||||
@ -955,8 +943,6 @@ void CScript::_save_orphaned_subclasses() {
|
||||
}
|
||||
|
||||
CScript::~CScript() {
|
||||
_clear_pending_func_states();
|
||||
|
||||
for (Map<StringName, CScriptFunction *>::Element *E = member_functions.front(); E; E = E->next()) {
|
||||
memdelete(E->get());
|
||||
}
|
||||
@ -1328,13 +1314,6 @@ CScriptInstance::CScriptInstance() {
|
||||
CScriptInstance::~CScriptInstance() {
|
||||
CScriptLanguage::singleton->lock.lock();
|
||||
|
||||
while (SelfList<CScriptFunctionState> *E = pending_func_states.first()) {
|
||||
// Order matters since clearing the stack may already cause
|
||||
// the CSCriptFunctionState to be destroyed and thus removed from the list.
|
||||
pending_func_states.remove(E);
|
||||
E->self()->_clear_stack();
|
||||
}
|
||||
|
||||
if (script.is_valid() && owner) {
|
||||
script->instances.erase(owner);
|
||||
}
|
||||
@ -1742,7 +1721,6 @@ void CScriptLanguage::get_reserved_words(List<String> *p_words) const {
|
||||
"setget",
|
||||
"signal",
|
||||
"tool",
|
||||
"yield",
|
||||
// var
|
||||
"const",
|
||||
"enum",
|
||||
@ -1930,10 +1908,6 @@ String CScriptWarning::get_message() const {
|
||||
case NARROWING_CONVERSION: {
|
||||
return "Narrowing conversion (float is converted to int and loses precision).";
|
||||
} break;
|
||||
case FUNCTION_MAY_YIELD: {
|
||||
CHECK_SYMBOLS(1);
|
||||
return "Assigned variable is typed but the function '" + symbols[0] + "()' may yield and return a CScriptFunctionState instead.";
|
||||
} break;
|
||||
case VARIABLE_CONFLICTS_FUNCTION: {
|
||||
CHECK_SYMBOLS(1);
|
||||
return "Variable declaration of '" + symbols[0] + "' conflicts with a function of the same name.";
|
||||
@ -2025,7 +1999,6 @@ String CScriptWarning::get_name_from_code(Code p_code) {
|
||||
"STANDALONE_EXPRESSION",
|
||||
"VOID_ASSIGNMENT",
|
||||
"NARROWING_CONVERSION",
|
||||
"FUNCTION_MAY_YIELD",
|
||||
"VARIABLE_CONFLICTS_FUNCTION",
|
||||
"FUNCTION_CONFLICTS_VARIABLE",
|
||||
"FUNCTION_CONFLICTS_CONSTANT",
|
||||
|
@ -109,9 +109,6 @@ class CScript : public Script {
|
||||
String fully_qualified_name;
|
||||
SelfList<CScript> script_list;
|
||||
|
||||
SelfList<CScriptFunctionState>::List pending_func_states;
|
||||
void _clear_pending_func_states();
|
||||
|
||||
CScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error);
|
||||
|
||||
void _set_subclass_path(Ref<CScript> &p_sc, const String &p_path);
|
||||
@ -258,8 +255,6 @@ class CScriptInstance : public ScriptInstance {
|
||||
Vector<Variant> members;
|
||||
bool base_ref;
|
||||
|
||||
SelfList<CScriptFunctionState>::List pending_func_states;
|
||||
|
||||
void _ml_call_reversed(CScript *sptr, const StringName &p_method, const Variant **p_args, int p_argcount);
|
||||
|
||||
public:
|
||||
@ -311,7 +306,6 @@ struct CScriptWarning {
|
||||
STANDALONE_EXPRESSION, // Expression not assigned to a variable
|
||||
VOID_ASSIGNMENT, // Function returns void but it's assigned to a variable
|
||||
NARROWING_CONVERSION, // Float value into an integer slot, precision is lost
|
||||
FUNCTION_MAY_YIELD, // Typed assign of function call that yields (it may return a function state)
|
||||
VARIABLE_CONFLICTS_FUNCTION, // Variable has the same name of a function
|
||||
FUNCTION_CONFLICTS_VARIABLE, // Function has the same name of a variable
|
||||
FUNCTION_CONFLICTS_CONSTANT, // Function has the same name of a constant
|
||||
@ -346,8 +340,6 @@ struct CScriptWarning {
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
class CScriptLanguage : public ScriptLanguage {
|
||||
friend class CScriptFunctionState;
|
||||
|
||||
static CScriptLanguage *singleton;
|
||||
|
||||
Variant *_global_array;
|
||||
|
@ -676,32 +676,6 @@ int CScriptCompiler::_parse_expression(CodeGen &codegen, const CScriptParser::No
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case CScriptParser::OperatorNode::OP_YIELD: {
|
||||
ERR_FAIL_COND_V(on->arguments.size() && on->arguments.size() != 2, -1);
|
||||
|
||||
Vector<int> arguments;
|
||||
int slevel = p_stack_level;
|
||||
for (int i = 0; i < on->arguments.size(); i++) {
|
||||
int ret = _parse_expression(codegen, on->arguments[i], slevel);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
if ((ret >> CScriptFunction::ADDR_BITS & CScriptFunction::ADDR_TYPE_STACK) == CScriptFunction::ADDR_TYPE_STACK) {
|
||||
slevel++;
|
||||
codegen.alloc_stack(slevel);
|
||||
}
|
||||
arguments.push_back(ret);
|
||||
}
|
||||
|
||||
//push call bytecode
|
||||
codegen.opcodes.push_back(arguments.size() == 0 ? CScriptFunction::OPCODE_YIELD : CScriptFunction::OPCODE_YIELD_SIGNAL); // basic type constructor
|
||||
for (int i = 0; i < arguments.size(); i++) {
|
||||
codegen.opcodes.push_back(arguments[i]); //arguments
|
||||
}
|
||||
codegen.opcodes.push_back(CScriptFunction::OPCODE_YIELD_RESUME);
|
||||
//next will be where to place the result :)
|
||||
|
||||
} break;
|
||||
|
||||
//indexing operator
|
||||
case CScriptParser::OperatorNode::OP_INDEX:
|
||||
|
@ -391,16 +391,6 @@ void CScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const
|
||||
mi.return_val = PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, "Resource");
|
||||
p_functions->push_back(mi);
|
||||
}
|
||||
{
|
||||
MethodInfo mi;
|
||||
mi.name = "yield";
|
||||
mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "object"));
|
||||
mi.arguments.push_back(PropertyInfo(Variant::STRING, "signal"));
|
||||
mi.default_arguments.push_back(Variant());
|
||||
mi.default_arguments.push_back(String());
|
||||
mi.return_val = PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, "CScriptFunctionState");
|
||||
p_functions->push_back(mi);
|
||||
}
|
||||
{
|
||||
MethodInfo mi;
|
||||
mi.name = "assert";
|
||||
@ -2193,7 +2183,7 @@ static void _find_identifiers(const CScriptCompletionContext &p_context, bool p_
|
||||
|
||||
static const char *_keywords[] = {
|
||||
"and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert",
|
||||
"breakpoint", "class", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield",
|
||||
"breakpoint", "class", "extends", "is", "func", "preload", "setget", "signal", "tool",
|
||||
"const", "enum", "export", "onready", "static", "var", "break", "continue", "if", "elif",
|
||||
"else", "for", "pass", "return", "match", "while",
|
||||
nullptr
|
||||
@ -2771,72 +2761,6 @@ Error CScriptLanguage::complete_code(const String &p_code, const String &p_path,
|
||||
options.insert(option.display, option);
|
||||
}
|
||||
} break;
|
||||
case CScriptParser::COMPLETION_YIELD: {
|
||||
const CScriptParser::Node *node = parser.get_completion_node();
|
||||
|
||||
CScriptCompletionContext c = context;
|
||||
c.line = node->line;
|
||||
CScriptCompletionIdentifier type;
|
||||
if (!_guess_expression_type(c, node, type)) {
|
||||
break;
|
||||
}
|
||||
|
||||
CScriptParser::DataType base_type = type.type;
|
||||
while (base_type.has_type) {
|
||||
switch (base_type.kind) {
|
||||
case CScriptParser::DataType::CLASS: {
|
||||
for (int i = 0; i < base_type.class_type->_signals.size(); i++) {
|
||||
ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL);
|
||||
option.insert_text = quote_style + option.display + quote_style;
|
||||
options.insert(option.display, option);
|
||||
}
|
||||
base_type = base_type.class_type->base_type;
|
||||
} break;
|
||||
case CScriptParser::DataType::SCRIPT:
|
||||
case CScriptParser::DataType::CSCRIPT: {
|
||||
Ref<Script> scr = base_type.script_type;
|
||||
if (scr.is_valid()) {
|
||||
List<MethodInfo> signals;
|
||||
scr->get_script_signal_list(&signals);
|
||||
for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
|
||||
ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL);
|
||||
options.insert(option.display, option);
|
||||
}
|
||||
Ref<Script> base_script = scr->get_base_script();
|
||||
if (base_script.is_valid()) {
|
||||
base_type.script_type = base_script;
|
||||
} else {
|
||||
base_type.kind = CScriptParser::DataType::NATIVE;
|
||||
base_type.native_type = scr->get_instance_base_type();
|
||||
}
|
||||
} else {
|
||||
base_type.has_type = false;
|
||||
}
|
||||
} break;
|
||||
case CScriptParser::DataType::NATIVE: {
|
||||
base_type.has_type = false;
|
||||
|
||||
StringName class_name = base_type.native_type;
|
||||
if (!ClassDB::class_exists(class_name)) {
|
||||
class_name = String("_") + class_name;
|
||||
if (!ClassDB::class_exists(class_name)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<MethodInfo> signals;
|
||||
ClassDB::get_signal_list(class_name, &signals);
|
||||
for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
|
||||
ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL);
|
||||
options.insert(option.display, option);
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
base_type.has_type = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case CScriptParser::COMPLETION_RESOURCE_PATH: {
|
||||
if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) {
|
||||
_get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options);
|
||||
|
@ -212,9 +212,6 @@ String CScriptFunction::_get_call_error(const Variant::CallError &p_err, const S
|
||||
&&OPCODE_CALL_BUILT_IN, \
|
||||
&&OPCODE_CALL_SELF, \
|
||||
&&OPCODE_CALL_SELF_BASE, \
|
||||
&&OPCODE_YIELD, \
|
||||
&&OPCODE_YIELD_SIGNAL, \
|
||||
&&OPCODE_YIELD_RESUME, \
|
||||
&&OPCODE_JUMP, \
|
||||
&&OPCODE_JUMP_IF, \
|
||||
&&OPCODE_JUMP_IF_NOT, \
|
||||
@ -251,7 +248,7 @@ String CScriptFunction::_get_call_error(const Variant::CallError &p_err, const S
|
||||
#define OPCODE_OUT break
|
||||
#endif
|
||||
|
||||
Variant CScriptFunction::call(CScriptInstance *p_instance, const Variant **p_args, int p_argcount, Variant::CallError &r_err, CallState *p_state) {
|
||||
Variant CScriptFunction::call(CScriptInstance *p_instance, const Variant **p_args, int p_argcount, Variant::CallError &r_err) {
|
||||
OPCODES_TABLE;
|
||||
|
||||
if (!_code_ptr) {
|
||||
@ -278,90 +275,75 @@ Variant CScriptFunction::call(CScriptInstance *p_instance, const Variant **p_arg
|
||||
int ip = 0;
|
||||
int line = _initial_line;
|
||||
|
||||
if (p_state) {
|
||||
//use existing (supplied) state (yielded)
|
||||
stack = (Variant *)p_state->stack.ptr();
|
||||
call_args = (Variant **)&p_state->stack.ptr()[sizeof(Variant) * p_state->stack_size]; //ptr() to avoid bounds check
|
||||
line = p_state->line;
|
||||
ip = p_state->ip;
|
||||
alloca_size = p_state->stack.size();
|
||||
script = p_state->script;
|
||||
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
|
||||
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;
|
||||
|
||||
} 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;
|
||||
}
|
||||
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;
|
||||
alloca_size = sizeof(Variant *) * _call_size + sizeof(Variant) * _stack_size;
|
||||
|
||||
if (alloca_size) {
|
||||
uint8_t *aptr = (uint8_t *)alloca(alloca_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++) {
|
||||
if (!argument_types[i].has_type) {
|
||||
memnew_placement(&stack[i], Variant(*p_args[i]));
|
||||
if (_stack_size) {
|
||||
stack = (Variant *)aptr;
|
||||
for (int i = 0; i < p_argcount; i++) {
|
||||
if (!argument_types[i].has_type) {
|
||||
memnew_placement(&stack[i], Variant(*p_args[i]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!argument_types[i].is_type(*p_args[i], true)) {
|
||||
if (argument_types[i].is_type(Variant(), true)) {
|
||||
memnew_placement(&stack[i], Variant);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!argument_types[i].is_type(*p_args[i], true)) {
|
||||
if (argument_types[i].is_type(Variant(), true)) {
|
||||
memnew_placement(&stack[i], Variant);
|
||||
continue;
|
||||
} else {
|
||||
r_err.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_err.argument = i;
|
||||
r_err.expected = argument_types[i].kind == CScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT;
|
||||
return Variant();
|
||||
}
|
||||
}
|
||||
if (argument_types[i].kind == CScriptDataType::BUILTIN) {
|
||||
Variant arg = Variant::construct(argument_types[i].builtin_type, &p_args[i], 1, r_err);
|
||||
memnew_placement(&stack[i], Variant(arg));
|
||||
} else {
|
||||
memnew_placement(&stack[i], Variant(*p_args[i]));
|
||||
r_err.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_err.argument = i;
|
||||
r_err.expected = argument_types[i].kind == CScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT;
|
||||
return Variant();
|
||||
}
|
||||
}
|
||||
for (int i = p_argcount; i < _stack_size; i++) {
|
||||
memnew_placement(&stack[i], Variant);
|
||||
if (argument_types[i].kind == CScriptDataType::BUILTIN) {
|
||||
Variant arg = Variant::construct(argument_types[i].builtin_type, &p_args[i], 1, r_err);
|
||||
memnew_placement(&stack[i], Variant(arg));
|
||||
} else {
|
||||
memnew_placement(&stack[i], Variant(*p_args[i]));
|
||||
}
|
||||
} else {
|
||||
stack = nullptr;
|
||||
}
|
||||
|
||||
if (_call_size) {
|
||||
call_args = (Variant **)&aptr[sizeof(Variant) * _stack_size];
|
||||
} else {
|
||||
call_args = nullptr;
|
||||
for (int i = p_argcount; i < _stack_size; i++) {
|
||||
memnew_placement(&stack[i], Variant);
|
||||
}
|
||||
|
||||
} else {
|
||||
stack = nullptr;
|
||||
}
|
||||
|
||||
if (_call_size) {
|
||||
call_args = (Variant **)&aptr[sizeof(Variant) * _stack_size];
|
||||
} else {
|
||||
call_args = nullptr;
|
||||
}
|
||||
|
||||
if (p_instance) {
|
||||
self = p_instance->owner;
|
||||
script = p_instance->script.ptr();
|
||||
} else {
|
||||
script = _script;
|
||||
}
|
||||
} else {
|
||||
stack = nullptr;
|
||||
call_args = nullptr;
|
||||
}
|
||||
|
||||
if (p_instance) {
|
||||
self = p_instance->owner;
|
||||
script = p_instance->script.ptr();
|
||||
} else {
|
||||
script = _script;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
@ -415,7 +397,6 @@ Variant CScriptFunction::call(CScriptInstance *p_instance, const Variant **p_arg
|
||||
profile.frame_call_count++;
|
||||
}
|
||||
bool exit_ok = false;
|
||||
bool yielded = false;
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
@ -1206,114 +1187,6 @@ Variant CScriptFunction::call(CScriptInstance *p_instance, const Variant **p_arg
|
||||
}
|
||||
DISPATCH_OPCODE;
|
||||
|
||||
OPCODE(OPCODE_YIELD)
|
||||
OPCODE(OPCODE_YIELD_SIGNAL) {
|
||||
int ipofs = 1;
|
||||
if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) {
|
||||
CHECK_SPACE(4);
|
||||
ipofs += 2;
|
||||
} else {
|
||||
CHECK_SPACE(2);
|
||||
}
|
||||
|
||||
Ref<CScriptFunctionState> gdfs = memnew(CScriptFunctionState);
|
||||
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.self = self;
|
||||
gdfs->state.alloca_size = alloca_size;
|
||||
gdfs->state.ip = ip + ipofs;
|
||||
gdfs->state.line = line;
|
||||
gdfs->state.script = _script;
|
||||
CScriptLanguage::singleton->lock.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;
|
||||
}
|
||||
CScriptLanguage::singleton->lock.unlock();
|
||||
#ifdef DEBUG_ENABLED
|
||||
gdfs->state.function_name = name;
|
||||
gdfs->state.script_path = _script->get_path();
|
||||
#endif
|
||||
//gdfs->state.result_pos=ip+ipofs-1;
|
||||
gdfs->state.defarg = defarg;
|
||||
gdfs->function = this;
|
||||
|
||||
retvalue = gdfs;
|
||||
|
||||
if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) {
|
||||
//do the oneshot connect
|
||||
GET_VARIANT_PTR(argobj, 1);
|
||||
GET_VARIANT_PTR(argname, 2);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (argobj->get_type() != Variant::OBJECT) {
|
||||
err_text = "First argument of yield() not of type object.";
|
||||
OPCODE_BREAK;
|
||||
}
|
||||
if (argname->get_type() != Variant::STRING) {
|
||||
err_text = "Second argument of yield() not a string (for signal name).";
|
||||
OPCODE_BREAK;
|
||||
}
|
||||
#endif
|
||||
|
||||
Object *obj = argobj->operator Object *();
|
||||
String signal = argname->operator String();
|
||||
|
||||
if (argobj->is_invalid_object()) {
|
||||
err_text = "First argument of yield() is a previously freed instance.";
|
||||
OPCODE_BREAK;
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (!obj) {
|
||||
err_text = "First argument of yield() is null.";
|
||||
OPCODE_BREAK;
|
||||
}
|
||||
if (signal.length() == 0) {
|
||||
err_text = "Second argument of yield() is an empty string (for signal name).";
|
||||
OPCODE_BREAK;
|
||||
}
|
||||
|
||||
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().";
|
||||
OPCODE_BREAK;
|
||||
}
|
||||
#else
|
||||
obj->connect(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
exit_ok = true;
|
||||
yielded = true;
|
||||
#endif
|
||||
OPCODE_BREAK;
|
||||
}
|
||||
|
||||
OPCODE(OPCODE_YIELD_RESUME) {
|
||||
CHECK_SPACE(2);
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (!p_state) {
|
||||
err_text = ("Invalid Resume (bug?)");
|
||||
OPCODE_BREAK;
|
||||
}
|
||||
#endif
|
||||
GET_VARIANT_PTR(result, 1);
|
||||
*result = p_state->result;
|
||||
ip += 2;
|
||||
}
|
||||
DISPATCH_OPCODE;
|
||||
|
||||
OPCODE(OPCODE_JUMP) {
|
||||
CHECK_SPACE(2);
|
||||
int to = _code_ptr[ip + 1];
|
||||
@ -1570,23 +1443,18 @@ Variant CScriptFunction::call(CScriptInstance *p_instance, const Variant **p_arg
|
||||
// Will be true if never yielded 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 || yielded) {
|
||||
if (ScriptDebugger::get_singleton()) {
|
||||
CScriptLanguage::get_singleton()->exit_function();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_stack_size) {
|
||||
//free stack
|
||||
for (int i = 0; i < _stack_size; i++) {
|
||||
stack[i].~Variant();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (ScriptDebugger::get_singleton()) {
|
||||
CScriptLanguage::get_singleton()->exit_function();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_stack_size) {
|
||||
//free stack
|
||||
for (int i = 0; i < _stack_size; i++) {
|
||||
stack[i].~Variant();
|
||||
}
|
||||
}
|
||||
|
||||
return retvalue;
|
||||
}
|
||||
|
||||
@ -1727,152 +1595,3 @@ CScriptFunction::~CScriptFunction() {
|
||||
CScriptLanguage::get_singleton()->lock.unlock();
|
||||
#endif
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
|
||||
Variant CScriptFunctionState::_signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
|
||||
Variant arg;
|
||||
r_error.error = Variant::CallError::CALL_OK;
|
||||
|
||||
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<CScriptFunctionState> 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();
|
||||
}
|
||||
|
||||
return resume(arg);
|
||||
}
|
||||
|
||||
bool CScriptFunctionState::is_valid(bool p_extended_check) const {
|
||||
if (function == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_extended_check) {
|
||||
#ifndef NO_THREADS
|
||||
MutexLock lock(CScriptLanguage::get_singleton()->lock);
|
||||
#endif
|
||||
// Script gone?
|
||||
if (!scripts_list.in_list()) {
|
||||
return false;
|
||||
}
|
||||
// Class instance gone? (if not static function)
|
||||
if (state.instance && !instances_list.in_list()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Variant CScriptFunctionState::resume(const Variant &p_arg) {
|
||||
ERR_FAIL_COND_V(!function, Variant());
|
||||
{
|
||||
#ifndef NO_THREADS
|
||||
MutexLock lock(CScriptLanguage::singleton->lock);
|
||||
#endif
|
||||
if (!scripts_list.in_list()) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after yield, but script is gone. At script: " + state.script_path + ":" + itos(state.line));
|
||||
#else
|
||||
return Variant();
|
||||
#endif
|
||||
}
|
||||
if (state.instance && !instances_list.in_list()) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after yield, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line));
|
||||
#else
|
||||
return Variant();
|
||||
#endif
|
||||
}
|
||||
// Do these now to avoid locking again after the call
|
||||
scripts_list.remove_from_list();
|
||||
instances_list.remove_from_list();
|
||||
}
|
||||
|
||||
state.result = p_arg;
|
||||
Variant::CallError err;
|
||||
Variant ret = function->call(nullptr, nullptr, 0, err, &state);
|
||||
|
||||
bool completed = true;
|
||||
|
||||
// If the return value is a CScriptFunctionState reference,
|
||||
// then the function did yield again after resuming.
|
||||
if (ret.is_ref()) {
|
||||
CScriptFunctionState *gdfs = Object::cast_to<CScriptFunctionState>(ret);
|
||||
if (gdfs && gdfs->function == function) {
|
||||
completed = false;
|
||||
gdfs->first_state = first_state.is_valid() ? first_state : Ref<CScriptFunctionState>(this);
|
||||
}
|
||||
}
|
||||
|
||||
function = nullptr; //cleaned up;
|
||||
state.result = Variant();
|
||||
|
||||
if (completed) {
|
||||
if (first_state.is_valid()) {
|
||||
first_state->emit_signal("completed", ret);
|
||||
} else {
|
||||
emit_signal("completed", ret);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (ScriptDebugger::get_singleton()) {
|
||||
CScriptLanguage::get_singleton()->exit_function();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CScriptFunctionState::_clear_stack() {
|
||||
if (state.stack_size) {
|
||||
Variant *stack = (Variant *)state.stack.ptr();
|
||||
for (int i = 0; i < state.stack_size; i++) {
|
||||
stack[i].~Variant();
|
||||
}
|
||||
state.stack_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CScriptFunctionState::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("resume", "arg"), &CScriptFunctionState::resume, DEFVAL(Variant()));
|
||||
ClassDB::bind_method(D_METHOD("is_valid", "extended_check"), &CScriptFunctionState::is_valid, DEFVAL(false));
|
||||
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "_signal_callback", &CScriptFunctionState::_signal_callback, MethodInfo("_signal_callback"));
|
||||
|
||||
ADD_SIGNAL(MethodInfo("completed", PropertyInfo(Variant::NIL, "result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
|
||||
}
|
||||
|
||||
CScriptFunctionState::CScriptFunctionState() :
|
||||
scripts_list(this),
|
||||
instances_list(this) {
|
||||
function = nullptr;
|
||||
}
|
||||
|
||||
CScriptFunctionState::~CScriptFunctionState() {
|
||||
_clear_stack();
|
||||
CScriptLanguage::singleton->lock.lock();
|
||||
scripts_list.remove_from_list();
|
||||
instances_list.remove_from_list();
|
||||
CScriptLanguage::singleton->lock.unlock();
|
||||
}
|
||||
|
@ -184,9 +184,6 @@ public:
|
||||
OPCODE_CALL_BUILT_IN,
|
||||
OPCODE_CALL_SELF,
|
||||
OPCODE_CALL_SELF_BASE,
|
||||
OPCODE_YIELD,
|
||||
OPCODE_YIELD_SIGNAL,
|
||||
OPCODE_YIELD_RESUME,
|
||||
OPCODE_JUMP,
|
||||
OPCODE_JUMP_IF,
|
||||
OPCODE_JUMP_IF_NOT,
|
||||
@ -352,34 +349,10 @@ public:
|
||||
return default_arguments[p_idx];
|
||||
}
|
||||
|
||||
Variant call(CScriptInstance *p_instance, const Variant **p_args, int p_argcount, Variant::CallError &r_err, CallState *p_state = nullptr);
|
||||
Variant call(CScriptInstance *p_instance, const Variant **p_args, int p_argcount, Variant::CallError &r_err);
|
||||
|
||||
CScriptFunction();
|
||||
~CScriptFunction();
|
||||
};
|
||||
|
||||
class CScriptFunctionState : public Reference {
|
||||
GDCLASS(CScriptFunctionState, Reference);
|
||||
friend class CScriptFunction;
|
||||
CScriptFunction *function;
|
||||
CScriptFunction::CallState state;
|
||||
Variant _signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error);
|
||||
Ref<CScriptFunctionState> first_state;
|
||||
|
||||
SelfList<CScriptFunctionState> scripts_list;
|
||||
SelfList<CScriptFunctionState> instances_list;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
bool is_valid(bool p_extended_check = false) const;
|
||||
Variant resume(const Variant &p_arg = Variant());
|
||||
|
||||
void _clear_stack();
|
||||
|
||||
CScriptFunctionState();
|
||||
~CScriptFunctionState();
|
||||
};
|
||||
|
||||
#endif // CSCRIPT_FUNCTION_H
|
||||
|
@ -518,79 +518,6 @@ CScriptParser::Node *CScriptParser::_parse_expression(Node *p_parent, bool p_sta
|
||||
constant->datatype = _type_from_variant(constant->value);
|
||||
|
||||
expr = constant;
|
||||
} else if (tokenizer->get_token() == CScriptTokenizer::TK_PR_YIELD) {
|
||||
if (!current_function) {
|
||||
_set_error("\"yield()\" can only be used inside function blocks.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
current_function->has_yield = true;
|
||||
|
||||
tokenizer->advance();
|
||||
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_OPEN) {
|
||||
_set_error("Expected \"(\" after \"yield\".");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
tokenizer->advance();
|
||||
|
||||
OperatorNode *yield = alloc_node<OperatorNode>();
|
||||
yield->op = OperatorNode::OP_YIELD;
|
||||
|
||||
while (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE) {
|
||||
tokenizer->advance();
|
||||
}
|
||||
|
||||
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
||||
expr = yield;
|
||||
tokenizer->advance();
|
||||
} else {
|
||||
parenthesis++;
|
||||
|
||||
Node *object = _parse_and_reduce_expression(p_parent, p_static);
|
||||
if (!object) {
|
||||
return nullptr;
|
||||
}
|
||||
yield->arguments.push_back(object);
|
||||
|
||||
if (tokenizer->get_token() != CScriptTokenizer::TK_COMMA) {
|
||||
_set_error("Expected \",\" after the first argument of \"yield\".");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
tokenizer->advance();
|
||||
|
||||
if (tokenizer->get_token() == CScriptTokenizer::TK_CURSOR) {
|
||||
completion_cursor = StringName();
|
||||
completion_node = object;
|
||||
completion_type = COMPLETION_YIELD;
|
||||
completion_class = current_class;
|
||||
completion_function = current_function;
|
||||
completion_line = tokenizer->get_token_line();
|
||||
completion_argument = 0;
|
||||
completion_block = current_block;
|
||||
completion_found = true;
|
||||
tokenizer->advance();
|
||||
}
|
||||
|
||||
Node *signal = _parse_and_reduce_expression(p_parent, p_static);
|
||||
if (!signal) {
|
||||
return nullptr;
|
||||
}
|
||||
yield->arguments.push_back(signal);
|
||||
|
||||
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
||||
_set_error("Expected \")\" after the second argument of \"yield\".");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
parenthesis--;
|
||||
|
||||
tokenizer->advance();
|
||||
|
||||
expr = yield;
|
||||
}
|
||||
|
||||
} else if (tokenizer->get_token() == CScriptTokenizer::TK_SELF) {
|
||||
if (p_static) {
|
||||
_set_error("\"self\" isn't allowed in a static function or constant expression.");
|
||||
@ -1874,9 +1801,6 @@ CScriptParser::Node *CScriptParser::_reduce_expression(Node *p_node, bool p_to_c
|
||||
|
||||
return op; //don't reduce yet
|
||||
|
||||
} else if (op->op == OperatorNode::OP_YIELD) {
|
||||
return op;
|
||||
|
||||
} else if (op->op == OperatorNode::OP_INDEX) {
|
||||
//can reduce indices into constant arrays or dictionaries
|
||||
|
||||
@ -6355,23 +6279,6 @@ CScriptParser::DataType CScriptParser::_reduce_node_type(Node *p_node) {
|
||||
case OperatorNode::OP_PARENT_CALL: {
|
||||
node_type = _reduce_function_call_type(op);
|
||||
} break;
|
||||
case OperatorNode::OP_YIELD: {
|
||||
if (op->arguments.size() == 2) {
|
||||
DataType base_type = _reduce_node_type(op->arguments[0]);
|
||||
DataType signal_type = _reduce_node_type(op->arguments[1]);
|
||||
// TODO: Check if signal exists when it's a constant
|
||||
if (base_type.has_type && base_type.kind == DataType::BUILTIN && base_type.builtin_type != Variant::NIL && base_type.builtin_type != Variant::OBJECT) {
|
||||
_set_error("The first argument of \"yield()\" must be an object.", op->line);
|
||||
return DataType();
|
||||
}
|
||||
if (signal_type.has_type && (signal_type.kind != DataType::BUILTIN || signal_type.builtin_type != Variant::STRING)) {
|
||||
_set_error("The second argument of \"yield()\" must be a string.", op->line);
|
||||
return DataType();
|
||||
}
|
||||
}
|
||||
// yield can return anything
|
||||
node_type.has_type = false;
|
||||
} break;
|
||||
case OperatorNode::OP_IS:
|
||||
case OperatorNode::OP_IS_BUILTIN: {
|
||||
if (op->arguments.size() != 2) {
|
||||
@ -7029,9 +6936,6 @@ CScriptParser::DataType CScriptParser::_reduce_function_call_type(const Operator
|
||||
if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_types[i].kind == DataType::BUILTIN && par_types[i].builtin_type == Variant::REAL) {
|
||||
_add_warning(CScriptWarning::NARROWING_CONVERSION, p_call->line, Variant::get_type_name(tn->vtype));
|
||||
}
|
||||
if (par_types[i].may_yield && p_call->arguments[i + 1]->type == Node::TYPE_OPERATOR) {
|
||||
_add_warning(CScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i + 1])));
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
}
|
||||
@ -7269,9 +7173,6 @@ CScriptParser::DataType CScriptParser::_reduce_function_call_type(const Operator
|
||||
|
||||
if (!par_type.has_type) {
|
||||
_mark_line_as_unsafe(p_call->line);
|
||||
if (par_type.may_yield && p_call->arguments[i]->type == Node::TYPE_OPERATOR) {
|
||||
_add_warning(CScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i])));
|
||||
}
|
||||
} else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) {
|
||||
// Supertypes are acceptable for dynamic compliance
|
||||
if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) {
|
||||
@ -8057,12 +7958,6 @@ void CScriptParser::_check_function_types(FunctionNode *p_function) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_function->has_yield) {
|
||||
// yield() will make the function return a CScriptFunctionState, so the type is ambiguous
|
||||
p_function->return_type.has_type = false;
|
||||
p_function->return_type.may_yield = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CScriptParser::_check_class_blocks_types(ClassNode *p_class) {
|
||||
@ -8171,9 +8066,6 @@ void CScriptParser::_check_block_types(BlockNode *p_block) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lv->datatype.has_type && assign_type.may_yield && lv->assign->type == Node::TYPE_OPERATOR) {
|
||||
_add_warning(CScriptWarning::FUNCTION_MAY_YIELD, lv->line, _find_function_name(static_cast<OperatorNode *>(lv->assign)));
|
||||
}
|
||||
for (int i = 0; i < current_class->variables.size(); i++) {
|
||||
if (current_class->variables[i].identifier == lv->name) {
|
||||
_add_warning(CScriptWarning::SHADOWED_VARIABLE, lv->line, lv->name, itos(current_class->variables[i].line));
|
||||
@ -8311,9 +8203,6 @@ void CScriptParser::_check_block_types(BlockNode *p_block) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lh_type.has_type && rh_type.may_yield && op->arguments[1]->type == Node::TYPE_OPERATOR) {
|
||||
_add_warning(CScriptWarning::FUNCTION_MAY_YIELD, op->line, _find_function_name(static_cast<OperatorNode *>(op->arguments[1])));
|
||||
}
|
||||
|
||||
#endif // DEBUG_ENABLED
|
||||
bool type_match = lh_type.has_type && rh_type.has_type;
|
||||
@ -8384,10 +8273,6 @@ void CScriptParser::_check_block_types(BlockNode *p_block) {
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
case OperatorNode::OP_YIELD: {
|
||||
_mark_line_as_safe(op->line);
|
||||
_reduce_node_type(op);
|
||||
} break;
|
||||
default: {
|
||||
_mark_line_as_safe(op->line);
|
||||
_reduce_node_type(op); // Test for safety anyway
|
||||
|
@ -57,7 +57,6 @@ public:
|
||||
bool is_constant;
|
||||
bool is_meta_type; // Whether the value can be used as a type
|
||||
bool infer_type;
|
||||
bool may_yield; // For function calls
|
||||
|
||||
Variant::Type builtin_type;
|
||||
StringName native_type;
|
||||
@ -99,7 +98,6 @@ public:
|
||||
is_constant(false),
|
||||
is_meta_type(false),
|
||||
infer_type(false),
|
||||
may_yield(false),
|
||||
builtin_type(Variant::NIL),
|
||||
class_type(nullptr) {}
|
||||
};
|
||||
@ -203,7 +201,6 @@ public:
|
||||
|
||||
struct FunctionNode : public Node {
|
||||
bool _static;
|
||||
bool has_yield;
|
||||
bool has_unreachable_code;
|
||||
StringName name;
|
||||
DataType return_type;
|
||||
@ -228,7 +225,6 @@ public:
|
||||
FunctionNode() {
|
||||
type = TYPE_FUNCTION;
|
||||
_static = false;
|
||||
has_yield = false;
|
||||
has_unreachable_code = false;
|
||||
}
|
||||
};
|
||||
@ -343,7 +339,6 @@ public:
|
||||
//call/constructor operator
|
||||
OP_CALL,
|
||||
OP_PARENT_CALL,
|
||||
OP_YIELD,
|
||||
OP_IS,
|
||||
OP_IS_BUILTIN,
|
||||
//indexing operator
|
||||
@ -510,7 +505,6 @@ public:
|
||||
COMPLETION_RESOURCE_PATH,
|
||||
COMPLETION_INDEX,
|
||||
COMPLETION_VIRTUAL_FUNC,
|
||||
COMPLETION_YIELD,
|
||||
COMPLETION_ASSIGN,
|
||||
COMPLETION_TYPE_HINT,
|
||||
COMPLETION_TYPE_HINT_INDEX,
|
||||
|
@ -103,7 +103,6 @@ const char *CScriptTokenizer::token_names[TK_MAX] = {
|
||||
"enum",
|
||||
"preload",
|
||||
"assert",
|
||||
"yield",
|
||||
"signal",
|
||||
"breakpoint",
|
||||
"'['",
|
||||
@ -200,7 +199,6 @@ static const _kws _keyword_list[] = {
|
||||
{ CScriptTokenizer::TK_PR_VOID, "void" },
|
||||
{ CScriptTokenizer::TK_PR_PRELOAD, "preload" },
|
||||
{ CScriptTokenizer::TK_PR_ASSERT, "assert" },
|
||||
{ CScriptTokenizer::TK_PR_YIELD, "yield" },
|
||||
{ CScriptTokenizer::TK_PR_SIGNAL, "signal" },
|
||||
{ CScriptTokenizer::TK_PR_BREAKPOINT, "breakpoint" },
|
||||
{ CScriptTokenizer::TK_PR_CONST, "const" },
|
||||
@ -259,7 +257,6 @@ bool CScriptTokenizer::is_token_literal(int p_offset, bool variable_safe) const
|
||||
case TK_PR_FUNCTION:
|
||||
case TK_PR_EXTENDS:
|
||||
case TK_PR_ASSERT:
|
||||
case TK_PR_YIELD:
|
||||
case TK_PR_VAR:
|
||||
|
||||
case TK_CF_IF:
|
||||
|
@ -108,7 +108,6 @@ public:
|
||||
TK_PR_ENUM,
|
||||
TK_PR_PRELOAD,
|
||||
TK_PR_ASSERT,
|
||||
TK_PR_YIELD,
|
||||
TK_PR_SIGNAL,
|
||||
TK_PR_BREAKPOINT,
|
||||
TK_BRACKET_OPEN,
|
||||
|
@ -138,7 +138,6 @@ static void _editor_init() {
|
||||
|
||||
void register_cscript_types() {
|
||||
ClassDB::register_class<CScript>();
|
||||
ClassDB::register_virtual_class<CScriptFunctionState>();
|
||||
|
||||
script_language_cscript = memnew(CScriptLanguage);
|
||||
ScriptServer::register_language(script_language_cscript);
|
||||
|
Loading…
Reference in New Issue
Block a user