diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/gdnative/gdnative.cpp | 3 | ||||
| -rw-r--r-- | modules/gdnative/godot/gdnative.cpp | 5 | ||||
| -rw-r--r-- | modules/gdnative/godot/gdnative.h | 5 | ||||
| -rw-r--r-- | modules/gdnative/register_types.cpp | 3 | ||||
| -rw-r--r-- | modules/gdscript/gd_editor.cpp | 4 | ||||
| -rw-r--r-- | modules/gdscript/gd_parser.cpp | 86 | ||||
| -rw-r--r-- | modules/gdscript/gd_tokenizer.cpp | 318 | ||||
| -rw-r--r-- | modules/gdscript/gd_tokenizer.h | 3 | ||||
| -rw-r--r-- | modules/nativescript/nativescript.cpp | 271 | ||||
| -rw-r--r-- | modules/nativescript/nativescript.h | 36 | ||||
| -rw-r--r-- | modules/nativescript/register_types.cpp | 33 | ||||
| -rw-r--r-- | modules/stb_vorbis/audio_stream_ogg_vorbis.cpp | 2 | ||||
| -rw-r--r-- | modules/visual_script/visual_script_editor.cpp | 10 |
13 files changed, 585 insertions, 194 deletions
diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index e810c33f1c..158f7fd94d 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -185,6 +185,8 @@ void GDNative::_bind_methods() { } void GDNative::set_library(Ref<GDNativeLibrary> p_library) { + ERR_EXPLAIN("Tried to change library of GDNative when it is already set"); + ERR_FAIL_COND(library.is_valid()); library = p_library; } @@ -229,6 +231,7 @@ bool GDNative::initialize() { options.core_api_hash = ClassDB::get_api_hash(ClassDB::API_CORE); options.editor_api_hash = ClassDB::get_api_hash(ClassDB::API_EDITOR); options.no_api_hash = ClassDB::get_api_hash(ClassDB::API_NONE); + options.gd_native_library = (godot_object *)(get_library().ptr()); library_init_fpointer(&options); diff --git a/modules/gdnative/godot/gdnative.cpp b/modules/gdnative/godot/gdnative.cpp index 7b94b75a52..29b499ebab 100644 --- a/modules/gdnative/godot/gdnative.cpp +++ b/modules/gdnative/godot/gdnative.cpp @@ -33,6 +33,7 @@ #include "error_macros.h" #include "gdnative.h" #include "global_constants.h" +#include "os/os.h" #include "project_settings.h" #include "variant.h" @@ -89,6 +90,10 @@ godot_object GDAPI *godot_global_get_singleton(char *p_name) { return (godot_object *)ProjectSettings::get_singleton()->get_singleton_object(String(p_name)); } // result shouldn't be freed +void GDAPI *godot_get_stack_bottom() { + return OS::get_singleton()->get_stack_bottom(); +} + // MethodBind API godot_method_bind GDAPI *godot_method_bind_get_method(const char *p_classname, const char *p_methodname) { diff --git a/modules/gdnative/godot/gdnative.h b/modules/gdnative/godot/gdnative.h index 4b79706b52..b0343272ef 100644 --- a/modules/gdnative/godot/gdnative.h +++ b/modules/gdnative/godot/gdnative.h @@ -245,6 +245,10 @@ void GDAPI godot_object_destroy(godot_object *p_o); godot_object GDAPI *godot_global_get_singleton(char *p_name); // result shouldn't be freed +////// OS API + +void GDAPI *godot_get_stack_bottom(); // returns stack bottom of the main thread + ////// MethodBind API typedef struct { @@ -261,6 +265,7 @@ typedef struct { uint64_t core_api_hash; uint64_t editor_api_hash; uint64_t no_api_hash; + godot_object *gd_native_library; // pointer to GDNativeLibrary that is being initialized } godot_gdnative_init_options; typedef struct { diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index 20ac1ecc0c..d180d5aada 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -47,7 +47,8 @@ godot_variant cb_standard_varcall(void *handle, godot_string *p_procedure, godot Error err = OS::get_singleton()->get_dynamic_library_symbol_handle( handle, *(String *)p_procedure, - library_proc); + library_proc, + true); // we roll our own message if (err != OK) { ERR_PRINT((String("GDNative procedure \"" + *(String *)p_procedure) + "\" does not exists and can't be called").utf8().get_data()); godot_variant ret; diff --git a/modules/gdscript/gd_editor.cpp b/modules/gdscript/gd_editor.cpp index 1e1327d72f..c88889767c 100644 --- a/modules/gdscript/gd_editor.cpp +++ b/modules/gdscript/gd_editor.cpp @@ -1334,8 +1334,8 @@ static void _find_identifiers(GDCompletionContext &context, int p_line, bool p_o static const char *_type_names[Variant::VARIANT_MAX] = { "null", "bool", "int", "float", "String", "Vector2", "Rect2", "Vector3", "Transform2D", "Plane", "Quat", "AABB", "Basis", "Transform", - "Color", "NodePath", "RID", "Object", "Dictionary", "Array", "RawArray", "IntArray", "FloatArray", "StringArray", - "Vector2Array", "Vector3Array", "ColorArray" + "Color", "NodePath", "RID", "Object", "Dictionary", "Array", "PoolByteArray", "PoolIntArray", "PoolRealArray", "PoolStringArray", + "PoolVector2Array", "PoolVector3Array", "PoolColorArray" }; for (int i = 0; i < Variant::VARIANT_MAX; i++) { diff --git a/modules/gdscript/gd_parser.cpp b/modules/gdscript/gd_parser.cpp index 75029a020b..36aa249398 100644 --- a/modules/gdscript/gd_parser.cpp +++ b/modules/gdscript/gd_parser.cpp @@ -185,8 +185,8 @@ void GDParser::_make_completable_call(int p_arg) { bool GDParser::_get_completable_identifier(CompletionType p_type, StringName &identifier) { identifier = StringName(); - if (tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER) { - identifier = tokenizer->get_token_identifier(); + if (tokenizer->is_token_literal()) { + identifier = tokenizer->get_token_literal(); tokenizer->advance(); } if (tokenizer->get_token() == GDTokenizer::TK_CURSOR) { @@ -201,8 +201,8 @@ bool GDParser::_get_completable_identifier(CompletionType p_type, StringName &id completion_ident_is_call = false; tokenizer->advance(); - if (tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER) { - identifier = identifier.operator String() + tokenizer->get_token_identifier().operator String(); + if (tokenizer->is_token_literal()) { + identifier = identifier.operator String() + tokenizer->get_token_literal().operator String(); tokenizer->advance(); } @@ -296,17 +296,6 @@ GDParser::Node *GDParser::_parse_expression(Node *p_parent, bool p_static, bool need_identifier = false; } break; - case GDTokenizer::TK_IDENTIFIER: { - if (!need_identifier) { - done = true; - break; - } - - path += String(tokenizer->get_token_identifier()); - tokenizer->advance(); - need_identifier = false; - - } break; case GDTokenizer::TK_OP_DIV: { if (need_identifier) { @@ -320,7 +309,15 @@ GDParser::Node *GDParser::_parse_expression(Node *p_parent, bool p_static, bool } break; default: { - done = true; + // Instead of checking for TK_IDENTIFIER, we check with is_token_literal, as this allows us to use match/sync/etc. as a name + if (need_identifier && tokenizer->is_token_literal()) { + path += String(tokenizer->get_token_literal()); + tokenizer->advance(); + need_identifier = false; + } else { + done = true; + } + break; } } @@ -585,7 +582,8 @@ GDParser::Node *GDParser::_parse_expression(Node *p_parent, bool p_static, bool cn->value = Variant::get_numeric_constant_value(bi_type, identifier); expr = cn; - } else if (tokenizer->get_token(1) == GDTokenizer::TK_PARENTHESIS_OPEN && (tokenizer->get_token() == GDTokenizer::TK_BUILT_IN_TYPE || tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER || tokenizer->get_token() == GDTokenizer::TK_BUILT_IN_FUNC)) { + } else if (tokenizer->get_token(1) == GDTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) { + // We check with is_token_literal, as this allows us to use match/sync/etc. as a name //function or constructor OperatorNode *op = alloc_node<OperatorNode>(); @@ -627,7 +625,8 @@ GDParser::Node *GDParser::_parse_expression(Node *p_parent, bool p_static, bool expr = op; - } else if (tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER) { + } else if (tokenizer->is_token_literal(0, true)) { + // We check with is_token_literal, as this allows us to use match/sync/etc. as a name //identifier (reference) const ClassNode *cln = current_class; @@ -827,10 +826,11 @@ GDParser::Node *GDParser::_parse_expression(Node *p_parent, bool p_static, bool if (expecting == DICT_EXPECT_KEY) { - if (tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER && tokenizer->get_token(1) == GDTokenizer::TK_OP_ASSIGN) { + if (tokenizer->is_token_literal() && tokenizer->get_token(1) == GDTokenizer::TK_OP_ASSIGN) { + // We check with is_token_literal, as this allows us to use match/sync/etc. as a name //lua style identifier, easier to write ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = tokenizer->get_token_identifier(); + cn->value = tokenizer->get_token_literal(); key = cn; tokenizer->advance(2); expecting = DICT_EXPECT_VALUE; @@ -870,7 +870,8 @@ GDParser::Node *GDParser::_parse_expression(Node *p_parent, bool p_static, bool expr = dict; - } else if (tokenizer->get_token() == GDTokenizer::TK_PERIOD && (tokenizer->get_token(1) == GDTokenizer::TK_IDENTIFIER || tokenizer->get_token(1) == GDTokenizer::TK_CURSOR) && tokenizer->get_token(2) == GDTokenizer::TK_PARENTHESIS_OPEN) { + } else if (tokenizer->get_token() == GDTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDTokenizer::TK_CURSOR) && tokenizer->get_token(2) == GDTokenizer::TK_PARENTHESIS_OPEN) { + // We check with is_token_literal, as this allows us to use match/sync/etc. as a name // parent call tokenizer->advance(); //goto identifier @@ -922,7 +923,8 @@ GDParser::Node *GDParser::_parse_expression(Node *p_parent, bool p_static, bool //indexing using "." - if (tokenizer->get_token(1) != GDTokenizer::TK_CURSOR && tokenizer->get_token(1) != GDTokenizer::TK_IDENTIFIER && tokenizer->get_token(1) != GDTokenizer::TK_BUILT_IN_FUNC) { + if (tokenizer->get_token(1) != GDTokenizer::TK_CURSOR && !tokenizer->is_token_literal(1)) { + // We check with is_token_literal, as this allows us to use match/sync/etc. as a name _set_error("Expected identifier as member"); return NULL; } else if (tokenizer->get_token(2) == GDTokenizer::TK_PARENTHESIS_OPEN) { @@ -2341,12 +2343,12 @@ void GDParser::_parse_block(BlockNode *p_block, bool p_static) { //variale declaration and (eventual) initialization tokenizer->advance(); - if (tokenizer->get_token() != GDTokenizer::TK_IDENTIFIER) { + if (!tokenizer->is_token_literal(0, true)) { _set_error("Expected identifier for local variable name."); return; } - StringName n = tokenizer->get_token_identifier(); + StringName n = tokenizer->get_token_literal(); tokenizer->advance(); if (current_function) { for (int i = 0; i < current_function->arguments.size(); i++) { @@ -2571,7 +2573,7 @@ void GDParser::_parse_block(BlockNode *p_block, bool p_static) { tokenizer->advance(); - if (tokenizer->get_token() != GDTokenizer::TK_IDENTIFIER) { + if (!tokenizer->is_token_literal(0, true)) { _set_error("identifier expected after 'for'"); } @@ -3108,7 +3110,7 @@ void GDParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); //var before the identifier is allowed } - if (tokenizer->get_token() != GDTokenizer::TK_IDENTIFIER) { + if (!tokenizer->is_token_literal(0, true)) { _set_error("Expected identifier for argument."); return; @@ -3260,7 +3262,7 @@ void GDParser::_parse_class(ClassNode *p_class) { case GDTokenizer::TK_PR_SIGNAL: { tokenizer->advance(); - if (tokenizer->get_token() != GDTokenizer::TK_IDENTIFIER) { + if (!tokenizer->is_token_literal()) { _set_error("Expected identifier after 'signal'."); return; } @@ -3282,7 +3284,7 @@ void GDParser::_parse_class(ClassNode *p_class) { break; } - if (tokenizer->get_token() != GDTokenizer::TK_IDENTIFIER) { + if (!tokenizer->is_token_literal(0, true)) { _set_error("Expected identifier in signal argument."); return; } @@ -3847,13 +3849,13 @@ void GDParser::_parse_class(ClassNode *p_class) { bool onready = tokenizer->get_token(-1) == GDTokenizer::TK_PR_ONREADY; tokenizer->advance(); - if (tokenizer->get_token() != GDTokenizer::TK_IDENTIFIER) { + if (!tokenizer->is_token_literal(0, true)) { _set_error("Expected identifier for member variable name."); return; } - member.identifier = tokenizer->get_token_identifier(); + member.identifier = tokenizer->get_token_literal(); member.expression = NULL; member._export.name = member.identifier; member.line = tokenizer->get_token_line(); @@ -3979,11 +3981,11 @@ void GDParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() != GDTokenizer::TK_COMMA) { //just comma means using only getter - if (tokenizer->get_token() != GDTokenizer::TK_IDENTIFIER) { - _set_error("Expected identifier for setter function after 'notify'."); + if (!tokenizer->is_token_literal()) { + _set_error("Expected identifier for setter function after 'setget'."); } - member.setter = tokenizer->get_token_identifier(); + member.setter = tokenizer->get_token_literal(); tokenizer->advance(); } @@ -3992,11 +3994,11 @@ void GDParser::_parse_class(ClassNode *p_class) { //there is a getter tokenizer->advance(); - if (tokenizer->get_token() != GDTokenizer::TK_IDENTIFIER) { + if (!tokenizer->is_token_literal()) { _set_error("Expected identifier for getter function after ','."); } - member.getter = tokenizer->get_token_identifier(); + member.getter = tokenizer->get_token_literal(); tokenizer->advance(); } } @@ -4014,13 +4016,13 @@ void GDParser::_parse_class(ClassNode *p_class) { ClassNode::Constant constant; tokenizer->advance(); - if (tokenizer->get_token() != GDTokenizer::TK_IDENTIFIER) { + if (!tokenizer->is_token_literal(0, true)) { _set_error("Expected name (identifier) for constant."); return; } - constant.identifier = tokenizer->get_token_identifier(); + constant.identifier = tokenizer->get_token_literal(); tokenizer->advance(); if (tokenizer->get_token() != GDTokenizer::TK_OP_ASSIGN) { @@ -4061,8 +4063,8 @@ void GDParser::_parse_class(ClassNode *p_class) { Dictionary enum_dict; tokenizer->advance(); - if (tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER) { - enum_name = tokenizer->get_token_identifier(); + if (tokenizer->is_token_literal(0, true)) { + enum_name = tokenizer->get_token_literal(); tokenizer->advance(); } if (tokenizer->get_token() != GDTokenizer::TK_CURLY_BRACKET_OPEN) { @@ -4079,7 +4081,7 @@ void GDParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); break; // End of enum - } else if (tokenizer->get_token() != GDTokenizer::TK_IDENTIFIER) { + } else if (!tokenizer->is_token_literal(0, true)) { if (tokenizer->get_token() == GDTokenizer::TK_EOF) { _set_error("Unexpected end of file."); @@ -4088,10 +4090,10 @@ void GDParser::_parse_class(ClassNode *p_class) { } return; - } else { // tokenizer->get_token()==GDTokenizer::TK_IDENTIFIER + } else { // tokenizer->is_token_literal(0, true) ClassNode::Constant constant; - constant.identifier = tokenizer->get_token_identifier(); + constant.identifier = tokenizer->get_token_literal(); tokenizer->advance(); diff --git a/modules/gdscript/gd_tokenizer.cpp b/modules/gdscript/gd_tokenizer.cpp index f4e0cc8e29..5803046185 100644 --- a/modules/gdscript/gd_tokenizer.cpp +++ b/modules/gdscript/gd_tokenizer.cpp @@ -130,12 +130,222 @@ const char *GDTokenizer::token_names[TK_MAX] = { "Cursor" }; +struct _bit { + Variant::Type type; + const char *text; +}; +//built in types + +static const _bit _type_list[] = { + //types + { Variant::BOOL, "bool" }, + { Variant::INT, "int" }, + { Variant::REAL, "float" }, + { Variant::STRING, "String" }, + { Variant::VECTOR2, "Vector2" }, + { Variant::RECT2, "Rect2" }, + { Variant::TRANSFORM2D, "Transform2D" }, + { Variant::VECTOR3, "Vector3" }, + { Variant::RECT3, "Rect3" }, + { Variant::PLANE, "Plane" }, + { Variant::QUAT, "Quat" }, + { Variant::BASIS, "Basis" }, + { Variant::TRANSFORM, "Transform" }, + { Variant::COLOR, "Color" }, + { Variant::_RID, "RID" }, + { Variant::OBJECT, "Object" }, + { Variant::NODE_PATH, "NodePath" }, + { Variant::DICTIONARY, "Dictionary" }, + { Variant::ARRAY, "Array" }, + { Variant::POOL_BYTE_ARRAY, "PoolByteArray" }, + { Variant::POOL_INT_ARRAY, "PoolIntArray" }, + { Variant::POOL_REAL_ARRAY, "PoolRealArray" }, + { Variant::POOL_STRING_ARRAY, "PoolStringArray" }, + { Variant::POOL_VECTOR2_ARRAY, "PoolVector2Array" }, + { Variant::POOL_VECTOR3_ARRAY, "PoolVector3Array" }, + { Variant::POOL_COLOR_ARRAY, "PoolColorArray" }, + { Variant::VARIANT_MAX, NULL }, +}; + +struct _kws { + GDTokenizer::Token token; + const char *text; +}; + +static const _kws _keyword_list[] = { + //ops + { GDTokenizer::TK_OP_IN, "in" }, + { GDTokenizer::TK_OP_NOT, "not" }, + { GDTokenizer::TK_OP_OR, "or" }, + { GDTokenizer::TK_OP_AND, "and" }, + //func + { GDTokenizer::TK_PR_FUNCTION, "func" }, + { GDTokenizer::TK_PR_CLASS, "class" }, + { GDTokenizer::TK_PR_EXTENDS, "extends" }, + { GDTokenizer::TK_PR_IS, "is" }, + { GDTokenizer::TK_PR_ONREADY, "onready" }, + { GDTokenizer::TK_PR_TOOL, "tool" }, + { GDTokenizer::TK_PR_STATIC, "static" }, + { GDTokenizer::TK_PR_EXPORT, "export" }, + { GDTokenizer::TK_PR_SETGET, "setget" }, + { GDTokenizer::TK_PR_VAR, "var" }, + { GDTokenizer::TK_PR_PRELOAD, "preload" }, + { GDTokenizer::TK_PR_ASSERT, "assert" }, + { GDTokenizer::TK_PR_YIELD, "yield" }, + { GDTokenizer::TK_PR_SIGNAL, "signal" }, + { GDTokenizer::TK_PR_BREAKPOINT, "breakpoint" }, + { GDTokenizer::TK_PR_REMOTE, "remote" }, + { GDTokenizer::TK_PR_MASTER, "master" }, + { GDTokenizer::TK_PR_SLAVE, "slave" }, + { GDTokenizer::TK_PR_SYNC, "sync" }, + { GDTokenizer::TK_PR_CONST, "const" }, + { GDTokenizer::TK_PR_ENUM, "enum" }, + //controlflow + { GDTokenizer::TK_CF_IF, "if" }, + { GDTokenizer::TK_CF_ELIF, "elif" }, + { GDTokenizer::TK_CF_ELSE, "else" }, + { GDTokenizer::TK_CF_FOR, "for" }, + { GDTokenizer::TK_CF_WHILE, "while" }, + { GDTokenizer::TK_CF_DO, "do" }, + { GDTokenizer::TK_CF_SWITCH, "switch" }, + { GDTokenizer::TK_CF_CASE, "case" }, + { GDTokenizer::TK_CF_BREAK, "break" }, + { GDTokenizer::TK_CF_CONTINUE, "continue" }, + { GDTokenizer::TK_CF_RETURN, "return" }, + { GDTokenizer::TK_CF_MATCH, "match" }, + { GDTokenizer::TK_CF_PASS, "pass" }, + { GDTokenizer::TK_SELF, "self" }, + { GDTokenizer::TK_CONST_PI, "PI" }, + { GDTokenizer::TK_WILDCARD, "_" }, + { GDTokenizer::TK_CONST_INF, "INF" }, + { GDTokenizer::TK_CONST_NAN, "NAN" }, + { GDTokenizer::TK_ERROR, NULL } +}; + const char *GDTokenizer::get_token_name(Token p_token) { ERR_FAIL_INDEX_V(p_token, TK_MAX, "<error>"); return token_names[p_token]; } +bool GDTokenizer::is_token_literal(int p_offset, bool variable_safe) const { + switch (get_token(p_offset)) { + // Can always be literal: + case TK_IDENTIFIER: + + case TK_PR_ONREADY: + case TK_PR_TOOL: + case TK_PR_STATIC: + case TK_PR_EXPORT: + case TK_PR_SETGET: + case TK_PR_SIGNAL: + case TK_PR_REMOTE: + case TK_PR_MASTER: + case TK_PR_SLAVE: + case TK_PR_SYNC: + return true; + + // Literal for non-variables only: + case TK_BUILT_IN_TYPE: + case TK_BUILT_IN_FUNC: + + case TK_OP_IN: + //case TK_OP_NOT: + //case TK_OP_OR: + //case TK_OP_AND: + + case TK_PR_CLASS: + case TK_PR_CONST: + case TK_PR_ENUM: + case TK_PR_PRELOAD: + case TK_PR_FUNCTION: + case TK_PR_EXTENDS: + case TK_PR_ASSERT: + case TK_PR_YIELD: + case TK_PR_VAR: + + case TK_CF_IF: + case TK_CF_ELIF: + case TK_CF_ELSE: + case TK_CF_FOR: + case TK_CF_WHILE: + case TK_CF_DO: + case TK_CF_SWITCH: + case TK_CF_CASE: + case TK_CF_BREAK: + case TK_CF_CONTINUE: + case TK_CF_RETURN: + case TK_CF_MATCH: + case TK_CF_PASS: + case TK_SELF: + case TK_CONST_PI: + case TK_WILDCARD: + case TK_CONST_INF: + case TK_CONST_NAN: + case TK_ERROR: + return !variable_safe; + + case TK_CONSTANT: { + switch (get_token_constant(p_offset).get_type()) { + case Variant::NIL: + case Variant::BOOL: + return true; + default: + return false; + } + } + default: + return false; + } +} + +StringName GDTokenizer::get_token_literal(int p_offset) const { + Token token = get_token(p_offset); + switch (token) { + case TK_IDENTIFIER: + return get_token_identifier(p_offset); + case TK_BUILT_IN_TYPE: { + Variant::Type type = get_token_type(p_offset); + int idx = 0; + + while (_type_list[idx].text) { + if (type == _type_list[idx].type) { + return _type_list[idx].text; + } + idx++; + } + } break; // Shouldn't get here, stuff happens + case TK_BUILT_IN_FUNC: + return GDFunctions::get_func_name(get_token_built_in_func(p_offset)); + case TK_CONSTANT: { + const Variant value = get_token_constant(p_offset); + + switch (value.get_type()) { + case Variant::NIL: + return "null"; + case Variant::BOOL: + return value ? "true" : "false"; + default: {} + } + } + case TK_OP_AND: + case TK_OP_OR: + break; // Don't get into default, since they can be non-literal + default: { + int idx = 0; + + while (_keyword_list[idx].text) { + if (token == _keyword_list[idx].token) { + return _keyword_list[idx].text; + } + idx++; + } + } + } + ERR_EXPLAIN("Failed to get token literal"); + ERR_FAIL_V(""); +} + static bool _is_text_char(CharType c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; @@ -779,51 +989,14 @@ void GDTokenizerText::_advance() { bool found = false; - struct _bit { - Variant::Type type; - const char *text; - }; - //built in types - - static const _bit type_list[] = { - //types - { Variant::BOOL, "bool" }, - { Variant::INT, "int" }, - { Variant::REAL, "float" }, - { Variant::STRING, "String" }, - { Variant::VECTOR2, "Vector2" }, - { Variant::RECT2, "Rect2" }, - { Variant::TRANSFORM2D, "Transform2D" }, - { Variant::VECTOR3, "Vector3" }, - { Variant::RECT3, "Rect3" }, - { Variant::PLANE, "Plane" }, - { Variant::QUAT, "Quat" }, - { Variant::BASIS, "Basis" }, - { Variant::TRANSFORM, "Transform" }, - { Variant::COLOR, "Color" }, - { Variant::_RID, "RID" }, - { Variant::OBJECT, "Object" }, - { Variant::NODE_PATH, "NodePath" }, - { Variant::DICTIONARY, "Dictionary" }, - { Variant::ARRAY, "Array" }, - { Variant::POOL_BYTE_ARRAY, "PoolByteArray" }, - { Variant::POOL_INT_ARRAY, "PoolIntArray" }, - { Variant::POOL_REAL_ARRAY, "PoolFloatArray" }, - { Variant::POOL_STRING_ARRAY, "PoolStringArray" }, - { Variant::POOL_VECTOR2_ARRAY, "PoolVector2Array" }, - { Variant::POOL_VECTOR3_ARRAY, "PoolVector3Array" }, - { Variant::POOL_COLOR_ARRAY, "PoolColorArray" }, - { Variant::VARIANT_MAX, NULL }, - }; - { int idx = 0; - while (type_list[idx].text) { + while (_type_list[idx].text) { - if (str == type_list[idx].text) { - _make_type(type_list[idx].type); + if (str == _type_list[idx].text) { + _make_type(_type_list[idx].type); found = true; break; } @@ -844,74 +1017,18 @@ void GDTokenizerText::_advance() { break; } } - - //keywor } if (!found) { - - struct _kws { - Token token; - const char *text; - }; - - static const _kws keyword_list[] = { - //ops - { TK_OP_IN, "in" }, - { TK_OP_NOT, "not" }, - { TK_OP_OR, "or" }, - { TK_OP_AND, "and" }, - //func - { TK_PR_FUNCTION, "func" }, - { TK_PR_CLASS, "class" }, - { TK_PR_EXTENDS, "extends" }, - { TK_PR_IS, "is" }, - { TK_PR_ONREADY, "onready" }, - { TK_PR_TOOL, "tool" }, - { TK_PR_STATIC, "static" }, - { TK_PR_EXPORT, "export" }, - { TK_PR_SETGET, "setget" }, - { TK_PR_VAR, "var" }, - { TK_PR_PRELOAD, "preload" }, - { TK_PR_ASSERT, "assert" }, - { TK_PR_YIELD, "yield" }, - { TK_PR_SIGNAL, "signal" }, - { TK_PR_BREAKPOINT, "breakpoint" }, - { TK_PR_REMOTE, "remote" }, - { TK_PR_MASTER, "master" }, - { TK_PR_SLAVE, "slave" }, - { TK_PR_SYNC, "sync" }, - { TK_PR_CONST, "const" }, - { TK_PR_ENUM, "enum" }, - //controlflow - { TK_CF_IF, "if" }, - { TK_CF_ELIF, "elif" }, - { TK_CF_ELSE, "else" }, - { TK_CF_FOR, "for" }, - { TK_CF_WHILE, "while" }, - { TK_CF_DO, "do" }, - { TK_CF_SWITCH, "switch" }, - { TK_CF_CASE, "case" }, - { TK_CF_BREAK, "break" }, - { TK_CF_CONTINUE, "continue" }, - { TK_CF_RETURN, "return" }, - { TK_CF_MATCH, "match" }, - { TK_CF_PASS, "pass" }, - { TK_SELF, "self" }, - { TK_CONST_PI, "PI" }, - { TK_WILDCARD, "_" }, - { TK_CONST_INF, "INF" }, - { TK_CONST_NAN, "NAN" }, - { TK_ERROR, NULL } - }; + //keyword int idx = 0; found = false; - while (keyword_list[idx].text) { + while (_keyword_list[idx].text) { - if (str == keyword_list[idx].text) { - _make_token(keyword_list[idx].token); + if (str == _keyword_list[idx].text) { + _make_token(_keyword_list[idx].token); found = true; break; } @@ -992,6 +1109,7 @@ const Variant &GDTokenizerText::get_token_constant(int p_offset) const { ERR_FAIL_COND_V(tk_rb[ofs].type != TK_CONSTANT, tk_rb[0].constant); return tk_rb[ofs].constant; } + StringName GDTokenizerText::get_token_identifier(int p_offset) const { ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, StringName()); diff --git a/modules/gdscript/gd_tokenizer.h b/modules/gdscript/gd_tokenizer.h index c051176097..4e868301a3 100644 --- a/modules/gdscript/gd_tokenizer.h +++ b/modules/gdscript/gd_tokenizer.h @@ -149,6 +149,9 @@ protected: public: static const char *get_token_name(Token p_token); + bool is_token_literal(int p_offset = 0, bool variable_safe = false) const; + StringName get_token_literal(int p_offset = 0) const; + virtual const Variant &get_token_constant(int p_offset = 0) const = 0; virtual Token get_token(int p_offset = 0) const = 0; virtual StringName get_token_identifier(int p_offset = 0) const = 0; diff --git a/modules/nativescript/nativescript.cpp b/modules/nativescript/nativescript.cpp index 952e8e349c..e7445e6da9 100644 --- a/modules/nativescript/nativescript.cpp +++ b/modules/nativescript/nativescript.cpp @@ -40,6 +40,10 @@ #include "scene/main/scene_tree.h" #include "scene/resources/scene_format_text.h" +#ifndef NO_THREADS +#include "os/thread.h" +#endif + #if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED) #include "api_generator.h" #endif @@ -59,6 +63,8 @@ void NativeScript::_bind_methods() { ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "class_name"), "set_class_name", "get_class_name"); ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library"); + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &NativeScript::_new, MethodInfo(Variant::OBJECT, "new")); } #define NSL NativeScriptLanguage::get_singleton() @@ -104,42 +110,16 @@ void NativeScript::set_library(Ref<GDNativeLibrary> p_library) { return; } library = p_library; - - // See if this library was "registered" already. - lib_path = library->get_active_library_path(); - Map<String, Ref<GDNative> >::Element *E = NSL->library_gdnatives.find(lib_path); - - if (!E) { - Ref<GDNative> gdn; - gdn.instance(); - gdn->set_library(library); - - // TODO(karroffel): check the return value? - gdn->initialize(); - - NSL->library_gdnatives.insert(lib_path, gdn); - - NSL->library_classes.insert(lib_path, Map<StringName, NativeScriptDesc>()); - - if (!NSL->library_script_users.has(lib_path)) - NSL->library_script_users.insert(lib_path, Set<NativeScript *>()); - - NSL->library_script_users[lib_path].insert(this); - void *args[1] = { - (void *)&lib_path - }; - - // here the library registers all the classes and stuff. - gdn->call_native_raw(NSL->_init_call_type, - NSL->_init_call_name, - NULL, - 1, - args, - NULL); - } else { - // already initialized. Nice. +#ifndef NO_THREADS + if (Thread::get_caller_ID() != Thread::get_main_ID()) { + NSL->defer_init_library(p_library, this); + } else +#endif + { + NSL->init_library(p_library); + NSL->register_script(this); } } @@ -223,7 +203,21 @@ ScriptInstance *NativeScript::instance_create(Object *p_this) { nsi->userdata = script_data->create_func.create_func((godot_object *)p_this, script_data->create_func.method_data); #endif +#ifndef NO_THREADS + owners_lock->lock(); +#endif + instance_owners.insert(p_this); + +#ifndef NO_THREADS + owners_lock->unlock(); +#endif + + // try to call _init + // we don't care if it doesn't exist, so we ignore errors. + Variant::CallError err; + call("_init", NULL, 0, err); + return nsi; } @@ -294,9 +288,13 @@ ScriptLanguage *NativeScript::get_language() const { bool NativeScript::has_script_signal(const StringName &p_signal) const { NativeScriptDesc *script_data = get_script_desc(); - if (!script_data) - return false; - return script_data->signals_.has(p_signal); + + while (script_data) { + if (script_data->signals_.has(p_signal)) + return true; + script_data = script_data->base_data; + } + return false; } void NativeScript::get_script_signal_list(List<MethodInfo> *r_signals) const { @@ -413,9 +411,6 @@ Variant NativeScript::_new(const Variant **p_args, int p_argcount, Variant::Call ref = REF(r); } - // GDScript does it like this: _create_instance(p_args, p_argcount, owner, r != NULL, r_error); - // TODO(karroffel): support varargs for constructors. - NativeScriptInstance *instance = (NativeScriptInstance *)instance_create(owner); owner->set_script_instance(instance); @@ -439,11 +434,18 @@ NativeScript::NativeScript() { library = Ref<GDNative>(); lib_path = ""; class_name = ""; +#ifndef NO_THREADS + owners_lock = Mutex::create(); +#endif } // TODO(karroffel): implement this NativeScript::~NativeScript() { - NSL->library_script_users[lib_path].erase(this); + NSL->unregister_script(this); + +#ifndef NO_THREADS + memdelete(owners_lock); +#endif } ////// ScriptInstance stuff @@ -500,7 +502,7 @@ bool NativeScriptInstance::get(const StringName &p_name, Variant &r_ret) const { if (P) { godot_variant value; value = P->get().getter.get_func((godot_object *)owner, - P->get().setter.method_data, + P->get().getter.method_data, userdata); r_ret = *(Variant *)&value; godot_variant_destroy(&value); @@ -648,6 +650,28 @@ void NativeScriptInstance::notification(int p_notification) { call_multilevel("_notification", args, 1); } +void NativeScriptInstance::refcount_incremented() { + Variant::CallError err; + call("_refcount_incremented", NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK && err.error != Variant::CallError::CALL_ERROR_INVALID_METHOD) { + ERR_PRINT("Failed to invoke _refcount_incremented - should not happen"); + } +} + +bool NativeScriptInstance::refcount_decremented() { + Variant::CallError err; + Variant ret = call("_refcount_decremented", NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK && err.error != Variant::CallError::CALL_ERROR_INVALID_METHOD) { + ERR_PRINT("Failed to invoke _refcount_decremented - should not happen"); + return true; // assume we can destroy the object + } + if (err.error == Variant::CallError::CALL_ERROR_INVALID_METHOD) { + // the method does not exist, default is true + return true; + } + return ret; +} + Ref<Script> NativeScriptInstance::get_script() const { return script; } @@ -752,7 +776,16 @@ NativeScriptInstance::~NativeScriptInstance() { script_data->destroy_func.destroy_func((godot_object *)owner, script_data->destroy_func.method_data, userdata); if (owner) { + +#ifndef NO_THREADS + script->owners_lock->lock(); +#endif + script->instance_owners.erase(owner); + +#ifndef NO_THREADS + script->owners_lock->unlock(); +#endif } } @@ -796,6 +829,9 @@ void NativeScriptLanguage::_unload_stuff() { NativeScriptLanguage::NativeScriptLanguage() { NativeScriptLanguage::singleton = this; +#ifndef NO_THREADS + mutex = Mutex::create(); +#endif } // TODO(karroffel): implement this @@ -809,6 +845,10 @@ NativeScriptLanguage::~NativeScriptLanguage() { NSL->library_gdnatives.clear(); NSL->library_script_users.clear(); } + +#ifndef NO_THREADS + memdelete(mutex); +#endif } String NativeScriptLanguage::get_name() const { @@ -946,6 +986,134 @@ int NativeScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, in return -1; } +#ifndef NO_THREADS +void NativeScriptLanguage::defer_init_library(Ref<GDNativeLibrary> lib, NativeScript *script) { + MutexLock lock(mutex); + libs_to_init.insert(lib); + scripts_to_register.insert(script); + has_objects_to_register = true; +} +#endif + +void NativeScriptLanguage::init_library(const Ref<GDNativeLibrary> &lib) { +#ifndef NO_THREADS + MutexLock lock(mutex); +#endif + // See if this library was "registered" already. + const String &lib_path = lib->get_active_library_path(); + Map<String, Ref<GDNative> >::Element *E = library_gdnatives.find(lib_path); + + if (!E) { + Ref<GDNative> gdn; + gdn.instance(); + gdn->set_library(lib); + + // TODO(karroffel): check the return value? + gdn->initialize(); + + library_gdnatives.insert(lib_path, gdn); + + library_classes.insert(lib_path, Map<StringName, NativeScriptDesc>()); + + if (!library_script_users.has(lib_path)) + library_script_users.insert(lib_path, Set<NativeScript *>()); + + void *args[1] = { + (void *)&lib_path + }; + + // here the library registers all the classes and stuff. + gdn->call_native_raw(_init_call_type, + _init_call_name, + NULL, + 1, + args, + NULL); + } else { + // already initialized. Nice. + } +} + +void NativeScriptLanguage::register_script(NativeScript *script) { +#ifndef NO_THREADS + MutexLock lock(mutex); +#endif + library_script_users[script->lib_path].insert(script); +} + +void NativeScriptLanguage::unregister_script(NativeScript *script) { +#ifndef NO_THREADS + MutexLock lock(mutex); +#endif + Map<String, Set<NativeScript *> >::Element *S = library_script_users.find(script->lib_path); + if (S) { + S->get().erase(script); + if (S->get().size() == 0) { + library_script_users.erase(S); + } + } +#ifndef NO_THREADS + scripts_to_register.erase(script); +#endif +} + +#ifndef NO_THREADS + +void NativeScriptLanguage::frame() { + if (has_objects_to_register) { + MutexLock lock(mutex); + for (Set<Ref<GDNativeLibrary> >::Element *L = libs_to_init.front(); L; L = L->next()) { + init_library(L->get()); + } + libs_to_init.clear(); + for (Set<NativeScript *>::Element *S = scripts_to_register.front(); S; S = S->next()) { + register_script(S->get()); + } + scripts_to_register.clear(); + has_objects_to_register = false; + } +} + +void NativeScriptLanguage::thread_enter() { + Vector<Ref<GDNative> > libs; + { + MutexLock lock(mutex); + for (Map<String, Ref<GDNative> >::Element *L = library_gdnatives.front(); L; L = L->next()) { + libs.push_back(L->get()); + } + } + for (int i = 0; i < libs.size(); ++i) { + libs[i]->call_native_raw( + _thread_cb_call_type, + _thread_enter_call_name, + NULL, + 0, + NULL, + NULL); + } +} + +void NativeScriptLanguage::thread_exit() { + Vector<Ref<GDNative> > libs; + { + MutexLock lock(mutex); + for (Map<String, Ref<GDNative> >::Element *L = library_gdnatives.front(); L; L = L->next()) { + libs.push_back(L->get()); + } + } + for (int i = 0; i < libs.size(); ++i) { + libs[i]->call_native_raw( + _thread_cb_call_type, + _thread_exit_call_name, + NULL, + 0, + NULL, + NULL); + } +} + +#endif // NO_THREADS + void NativeReloadNode::_bind_methods() { ClassDB::bind_method(D_METHOD("_notification"), &NativeReloadNode::_notification); } @@ -956,8 +1124,11 @@ void NativeReloadNode::_notification(int p_what) { switch (p_what) { case MainLoop::NOTIFICATION_WM_FOCUS_OUT: { - print_line("unload"); - + if (unloaded) + break; +#ifndef NO_THREADS + MutexLock lock(NSL->mutex); +#endif NSL->_unload_stuff(); for (Map<String, Ref<GDNative> >::Element *L = NSL->library_gdnatives.front(); L; L = L->next()) { @@ -965,14 +1136,18 @@ void NativeReloadNode::_notification(int p_what) { NSL->library_classes.erase(L->key()); } + unloaded = true; + } break; case MainLoop::NOTIFICATION_WM_FOCUS_IN: { - print_line("load"); - + if (!unloaded) + break; +#ifndef NO_THREADS + MutexLock lock(NSL->mutex); +#endif Set<StringName> libs_to_remove; - for (Map<String, Ref<GDNative> >::Element *L = NSL->library_gdnatives.front(); L; L = L->next()) { if (!L->get()->initialize()) { @@ -1008,6 +1183,8 @@ void NativeReloadNode::_notification(int p_what) { } } + unloaded = false; + for (Set<StringName>::Element *R = libs_to_remove.front(); R; R = R->next()) { NSL->library_gdnatives.erase(R->get()); } diff --git a/modules/nativescript/nativescript.h b/modules/nativescript/nativescript.h index 5702ba2e8d..95b4954171 100644 --- a/modules/nativescript/nativescript.h +++ b/modules/nativescript/nativescript.h @@ -41,6 +41,10 @@ #include "godot_nativescript.h" #include "modules/gdnative/gdnative.h" +#ifndef NO_THREADS +#include "os/mutex.h" +#endif + struct NativeScriptDesc { struct Method { @@ -102,6 +106,9 @@ class NativeScript : public Script { String class_name; +#ifndef NO_THREADS + Mutex *owners_lock; +#endif Set<Object *> instance_owners; protected: @@ -181,6 +188,9 @@ public: virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount); virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount); + virtual void refcount_incremented(); + virtual bool refcount_decremented(); + ~NativeScriptInstance(); }; @@ -197,6 +207,19 @@ private: void _unload_stuff(); +#ifndef NO_THREADS + Mutex *mutex; + + Set<Ref<GDNativeLibrary> > libs_to_init; + Set<NativeScript *> scripts_to_register; + volatile bool has_objects_to_register; // so that we don't lock mutex every frame - it's rarely needed + void defer_init_library(Ref<GDNativeLibrary> lib, NativeScript *script); +#endif + + void init_library(const Ref<GDNativeLibrary> &lib); + void register_script(NativeScript *script); + void unregister_script(NativeScript *script); + public: Map<String, Map<StringName, NativeScriptDesc> > library_classes; Map<String, Ref<GDNative> > library_gdnatives; @@ -206,6 +229,10 @@ public: const StringName _init_call_type = "nativescript_init"; const StringName _init_call_name = "godot_nativescript_init"; + const StringName _thread_cb_call_type = "godot_nativescript_thread_cb"; + const StringName _thread_enter_call_name = "godot_nativescript_thread_enter"; + const StringName _thread_exit_call_name = "godot_nativescript_thread_exit"; + NativeScriptLanguage(); ~NativeScriptLanguage(); @@ -215,6 +242,13 @@ public: void _hacky_api_anchor(); +#ifndef NO_THREADS + virtual void thread_enter(); + virtual void thread_exit(); + + virtual void frame(); +#endif + virtual String get_name() const; virtual void init(); virtual String get_type() const; @@ -259,6 +293,8 @@ inline NativeScriptDesc *NativeScript::get_script_desc() const { class NativeReloadNode : public Node { GDCLASS(NativeReloadNode, Node) + bool unloaded = false; + public: static void _bind_methods(); void _notification(int p_what); diff --git a/modules/nativescript/register_types.cpp b/modules/nativescript/register_types.cpp index 6c88b04a56..dfa16d8a2a 100644 --- a/modules/nativescript/register_types.cpp +++ b/modules/nativescript/register_types.cpp @@ -50,7 +50,8 @@ void init_call_cb(void *p_handle, godot_string *p_proc_name, void *p_data, int p Error err = OS::get_singleton()->get_dynamic_library_symbol_handle( p_handle, *(String *)p_proc_name, - library_proc); + library_proc, + true); // we print our own message if (err != OK) { ERR_PRINT((String("GDNative procedure \"" + *(String *)p_proc_name) + "\" does not exists and can't be called").utf8().get_data()); return; @@ -61,6 +62,33 @@ void init_call_cb(void *p_handle, godot_string *p_proc_name, void *p_data, int p fn(args[0]); } +#ifndef NO_THREADS + +typedef void (*native_script_empty_callback)(); + +void thread_call_cb(void *p_handle, godot_string *p_proc_name, void *p_data, int p_num_args, void **args, void *r_ret) { + if (p_handle == NULL) { + ERR_PRINT("No valid library handle, can't call nativescript thread enter/exit callback"); + return; + } + + void *library_proc; + Error err = OS::get_singleton()->get_dynamic_library_symbol_handle( + p_handle, + *(String *)p_proc_name, + library_proc, + true); + if (err != OK) { + // it's fine if thread callbacks are not present in the library. + return; + } + + native_script_empty_callback fn = (native_script_empty_callback)library_proc; + fn(); +} + +#endif // NO_THREADS + ResourceFormatLoaderNativeScript *resource_loader_gdns = NULL; ResourceFormatSaverNativeScript *resource_saver_gdns = NULL; @@ -72,6 +100,9 @@ void register_nativescript_types() { ScriptServer::register_language(native_script_language); GDNativeCallRegistry::singleton->register_native_raw_call_type(native_script_language->_init_call_type, init_call_cb); +#ifndef NO_THREADS + GDNativeCallRegistry::singleton->register_native_raw_call_type(native_script_language->_thread_cb_call_type, thread_call_cb); +#endif resource_saver_gdns = memnew(ResourceFormatSaverNativeScript); ResourceSaver::add_resource_format_saver(resource_saver_gdns); diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp index c645a55703..7b8b2abebb 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp @@ -39,7 +39,7 @@ void AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_fra int todo = p_frames; - while (todo) { + while (todo && active) { int mixed = stb_vorbis_get_samples_float_interleaved(ogg_stream, 2, (float *)p_buffer, todo * 2); todo -= mixed; diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index ba3463445d..35358d5a1f 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -2328,6 +2328,16 @@ void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot, undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, p_from.to_int(), from_port, p_to.to_int()); undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, p_from.to_int(), from_port, p_to.to_int()); } else { + + // disconect current, and connect the new one + if (script->is_input_value_port_connected(edited_func, p_to.to_int(), to_port)) { + int conn_from; + int conn_port; + script->get_input_value_port_connection_source(edited_func, p_to.to_int(), to_port, &conn_from, &conn_port); + undo_redo->add_do_method(script.ptr(), "data_disconnect", edited_func, conn_from, conn_port, p_to.to_int(), to_port); + undo_redo->add_undo_method(script.ptr(), "data_connect", edited_func, conn_from, conn_port, p_to.to_int(), to_port); + } + undo_redo->add_do_method(script.ptr(), "data_connect", edited_func, p_from.to_int(), from_port, p_to.to_int(), to_port); undo_redo->add_undo_method(script.ptr(), "data_disconnect", edited_func, p_from.to_int(), from_port, p_to.to_int(), to_port); //update nodes in sgraph |