diff options
Diffstat (limited to 'modules')
97 files changed, 2368 insertions, 604 deletions
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index d1ef08dc83..4eb798de11 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -521,6 +521,11 @@ CSGBrush *CSGMesh::_build_brush() { Array arrays = mesh->surface_get_arrays(i); + if (arrays.size() == 0) { + _make_dirty(); + ERR_FAIL_COND_V(arrays.size() == 0, NULL); + } + PoolVector<Vector3> avertices = arrays[Mesh::ARRAY_VERTEX]; if (avertices.size() == 0) continue; @@ -593,8 +598,8 @@ CSGBrush *CSGMesh::_build_brush() { mw[j / 3] = mat; } } else { - int is = vertices.size(); - int as = avertices.size(); + int as = vertices.size(); + int is = avertices.size(); vertices.resize(as + is); smooth.resize((as + is) / 3); diff --git a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml index 6d990f6f6f..e057a435ac 100644 --- a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml +++ b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml @@ -7,7 +7,7 @@ A PacketPeer implementation that should be passed to [method SceneTree.set_network_peer] after being initialized as either a client or server. Events can then be handled by connecting to [SceneTree] signals. </description> <tutorials> - <link>http://docs.godotengine.org/en/3.0/tutorials/networking/high_level_multiplayer.html</link> + <link>https://docs.godotengine.org/en/latest/tutorials/networking/high_level_multiplayer.html</link> <link>http://enet.bespin.org/usergroup0.html</link> </tutorials> <demos> diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub index 7ca40c1b8b..3e2068b8db 100644 --- a/modules/freetype/SCsub +++ b/modules/freetype/SCsub @@ -59,6 +59,8 @@ if env['builtin_freetype']: if env['platform'] == 'uwp': # Include header for UWP to fix build issues env_freetype.Append(CCFLAGS=['/FI', '"modules/freetype/uwpdef.h"']) + # Globally too, as freetype is used in scene (see bottom) + env.Append(CCFLAGS=['/FI', '"modules/freetype/uwpdef.h"']) sfnt = thirdparty_dir + 'src/sfnt/sfnt.c' if env['platform'] == 'javascript': diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index ecde73ae1c..f07fdef488 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -243,12 +243,12 @@ void GDNativeLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("set_symbol_prefix", "symbol_prefix"), &GDNativeLibrary::set_symbol_prefix); ClassDB::bind_method(D_METHOD("set_reloadable", "reloadable"), &GDNativeLibrary::set_reloadable); - ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "config_file", PROPERTY_HINT_RESOURCE_TYPE, "ConfigFile"), "set_config_file", "get_config_file"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "config_file", PROPERTY_HINT_RESOURCE_TYPE, "ConfigFile"), "set_config_file", "get_config_file"); - ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "load_once"), "set_load_once", "should_load_once"); - ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "singleton"), "set_singleton", "is_singleton"); - ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "symbol_prefix"), "set_symbol_prefix", "get_symbol_prefix"); - ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "reloadable"), "set_reloadable", "is_reloadable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "load_once"), "set_load_once", "should_load_once"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "singleton"), "set_singleton", "is_singleton"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "symbol_prefix"), "set_symbol_prefix", "get_symbol_prefix"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reloadable"), "set_reloadable", "is_reloadable"); } GDNative::GDNative() { @@ -268,7 +268,7 @@ void GDNative::_bind_methods() { ClassDB::bind_method(D_METHOD("call_native", "calling_type", "procedure_name", "arguments"), &GDNative::call_native); - ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library"); } void GDNative::set_library(Ref<GDNativeLibrary> p_library) { @@ -306,6 +306,13 @@ bool GDNative::initialize() { #elif defined(UWP_ENABLED) // On UWP we use a relative path from the app String path = lib_path.replace("res://", ""); +#elif defined(OSX_ENABLED) + // On OSX the exported libraries are located under the Frameworks directory. + // So we need to replace the library path. + String path = ProjectSettings::get_singleton()->globalize_path(lib_path); + if (!FileAccess::exists(path)) { + path = OS::get_singleton()->get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(lib_path.get_file()); + } #else String path = ProjectSettings::get_singleton()->globalize_path(lib_path); #endif diff --git a/modules/gdnative/gdnative/array.cpp b/modules/gdnative/gdnative/array.cpp index 1fb0ff0500..a30cc09bf6 100644 --- a/modules/gdnative/gdnative/array.cpp +++ b/modules/gdnative/gdnative/array.cpp @@ -318,6 +318,38 @@ void GDAPI godot_array_destroy(godot_array *p_self) { ((Array *)p_self)->~Array(); } +godot_array GDAPI godot_array_duplicate(const godot_array *p_self, const godot_bool p_deep) { + const Array *self = (const Array *)p_self; + godot_array res; + Array *val = (Array *)&res; + memnew_placement(val, Array); + *val = self->duplicate(p_deep); + return res; +} + +godot_variant GDAPI godot_array_max(const godot_array *p_self) { + const Array *self = (const Array *)p_self; + godot_variant v; + Variant *val = (Variant *)&v; + memnew_placement(val, Variant); + *val = self->max(); + return v; +} + +godot_variant GDAPI godot_array_min(const godot_array *p_self) { + const Array *self = (const Array *)p_self; + godot_variant v; + Variant *val = (Variant *)&v; + memnew_placement(val, Variant); + *val = self->min(); + return v; +} + +void GDAPI godot_array_shuffle(godot_array *p_self) { + Array *self = (Array *)p_self; + self->shuffle(); +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative/basis.cpp b/modules/gdnative/gdnative/basis.cpp index 70d2814577..d88499ade1 100644 --- a/modules/gdnative/gdnative/basis.cpp +++ b/modules/gdnative/gdnative/basis.cpp @@ -282,6 +282,15 @@ godot_basis GDAPI godot_basis_operator_multiply_scalar(const godot_basis *p_self return raw_dest; } +godot_basis GDAPI godot_basis_slerp(const godot_basis *p_self, const godot_basis *p_b, const godot_real p_t) { + godot_basis raw_dest; + Basis *dest = (Basis *)&raw_dest; + const Basis *self = (const Basis *)p_self; + const Basis *b = (const Basis *)p_b; + *dest = self->slerp(*b, p_t); + return raw_dest; +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative/color.cpp b/modules/gdnative/gdnative/color.cpp index 4089f4458a..79f0c71b5e 100644 --- a/modules/gdnative/gdnative/color.cpp +++ b/modules/gdnative/gdnative/color.cpp @@ -116,6 +116,26 @@ godot_int GDAPI godot_color_to_rgba32(const godot_color *p_self) { return self->to_rgba32(); } +godot_int GDAPI godot_color_to_abgr32(const godot_color *p_self) { + const Color *self = (const Color *)p_self; + return self->to_abgr32(); +} + +godot_int GDAPI godot_color_to_abgr64(const godot_color *p_self) { + const Color *self = (const Color *)p_self; + return self->to_abgr64(); +} + +godot_int GDAPI godot_color_to_argb64(const godot_color *p_self) { + const Color *self = (const Color *)p_self; + return self->to_argb64(); +} + +godot_int GDAPI godot_color_to_rgba64(const godot_color *p_self) { + const Color *self = (const Color *)p_self; + return self->to_rgba64(); +} + godot_int GDAPI godot_color_to_argb32(const godot_color *p_self) { const Color *self = (const Color *)p_self; return self->to_argb32(); @@ -156,6 +176,27 @@ godot_color GDAPI godot_color_blend(const godot_color *p_self, const godot_color return dest; } +godot_color GDAPI godot_color_darkened(const godot_color *p_self, const godot_real p_amount) { + godot_color dest; + const Color *self = (const Color *)p_self; + *((Color *)&dest) = self->darkened(p_amount); + return dest; +} + +godot_color GDAPI godot_color_from_hsv(const godot_color *p_self, const godot_real p_h, const godot_real p_s, const godot_real p_v, const godot_real p_a) { + godot_color dest; + const Color *self = (const Color *)p_self; + *((Color *)&dest) = self->from_hsv(p_h, p_s, p_v, p_a); + return dest; +} + +godot_color GDAPI godot_color_lightened(const godot_color *p_self, const godot_real p_amount) { + godot_color dest; + const Color *self = (const Color *)p_self; + *((Color *)&dest) = self->lightened(p_amount); + return dest; +} + godot_string GDAPI godot_color_to_html(const godot_color *p_self, const godot_bool p_with_alpha) { godot_string dest; const Color *self = (const Color *)p_self; diff --git a/modules/gdnative/gdnative/node_path.cpp b/modules/gdnative/gdnative/node_path.cpp index f24facaae8..cf82940e09 100644 --- a/modules/gdnative/gdnative/node_path.cpp +++ b/modules/gdnative/gdnative/node_path.cpp @@ -110,6 +110,15 @@ godot_bool GDAPI godot_node_path_operator_equal(const godot_node_path *p_self, c return *self == *b; } +godot_node_path godot_node_path_get_as_property_path(const godot_node_path *p_self) { + const NodePath *self = (const NodePath *)p_self; + godot_node_path res; + NodePath *val = (NodePath *)&res; + memnew_placement(val, NodePath); + *val = self->get_as_property_path(); + return res; +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative/quat.cpp b/modules/gdnative/gdnative/quat.cpp index ddec77edcd..2594759508 100644 --- a/modules/gdnative/gdnative/quat.cpp +++ b/modules/gdnative/gdnative/quat.cpp @@ -225,6 +225,12 @@ godot_quat GDAPI godot_quat_operator_neg(const godot_quat *p_self) { return raw_dest; } +void GDAPI godot_quat_set_axis_angle(godot_quat *p_self, const godot_vector3 *p_axis, const godot_real p_angle) { + Quat *self = (Quat *)p_self; + const Vector3 *axis = (const Vector3 *)p_axis; + self->set_axis_angle(*axis, p_angle); +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative/rect2.cpp b/modules/gdnative/gdnative/rect2.cpp index 54b98fc4e5..5cbc2712c3 100644 --- a/modules/gdnative/gdnative/rect2.cpp +++ b/modules/gdnative/gdnative/rect2.cpp @@ -109,6 +109,27 @@ godot_rect2 GDAPI godot_rect2_grow(const godot_rect2 *p_self, const godot_real p return dest; } +godot_rect2 GDAPI godot_rect2_grow_individual(const godot_rect2 *p_self, const godot_real p_left, const godot_real p_top, const godot_real p_right, const godot_real p_bottom) { + godot_rect2 dest; + const Rect2 *self = (const Rect2 *)p_self; + *((Rect2 *)&dest) = self->grow_individual(p_left, p_top, p_right, p_bottom); + return dest; +} + +godot_rect2 GDAPI godot_rect2_grow_margin(const godot_rect2 *p_self, const godot_int p_margin, const godot_real p_by) { + godot_rect2 dest; + const Rect2 *self = (const Rect2 *)p_self; + *((Rect2 *)&dest) = self->grow_margin((Margin)p_margin, p_by); + return dest; +} + +godot_rect2 GDAPI godot_rect2_abs(const godot_rect2 *p_self) { + godot_rect2 dest; + const Rect2 *self = (const Rect2 *)p_self; + *((Rect2 *)&dest) = self->abs(); + return dest; +} + godot_rect2 GDAPI godot_rect2_expand(const godot_rect2 *p_self, const godot_vector2 *p_to) { godot_rect2 dest; const Rect2 *self = (const Rect2 *)p_self; diff --git a/modules/gdnative/gdnative/string.cpp b/modules/gdnative/gdnative/string.cpp index 8ca57392a3..0996633b70 100644 --- a/modules/gdnative/gdnative/string.cpp +++ b/modules/gdnative/gdnative/string.cpp @@ -1285,6 +1285,64 @@ godot_bool GDAPI godot_string_is_valid_ip_address(const godot_string *p_self) { return self->is_valid_ip_address(); } +godot_string GDAPI godot_string_dedent(const godot_string *p_self) { + const String *self = (const String *)p_self; + godot_string result; + String return_value = self->dedent(); + memnew_placement(&result, String(return_value)); + + return result; +} + +godot_string GDAPI godot_string_trim_prefix(const godot_string *p_self, const godot_string *p_prefix) { + const String *self = (const String *)p_self; + String *prefix = (String *)p_prefix; + godot_string result; + String return_value = self->trim_prefix(*prefix); + memnew_placement(&result, String(return_value)); + + return result; +} + +godot_string GDAPI godot_string_trim_suffix(const godot_string *p_self, const godot_string *p_suffix) { + const String *self = (const String *)p_self; + String *suffix = (String *)p_suffix; + godot_string result; + String return_value = self->trim_suffix(*suffix); + memnew_placement(&result, String(return_value)); + + return result; +} + +godot_string GDAPI godot_string_rstrip(const godot_string *p_self, const godot_string *p_chars) { + const String *self = (const String *)p_self; + String *chars = (String *)p_chars; + godot_string result; + String return_value = self->rstrip(*chars); + memnew_placement(&result, String(return_value)); + + return result; +} + +godot_pool_string_array GDAPI godot_string_rsplit(const godot_string *p_self, const godot_string *p_divisor, + const godot_bool p_allow_empty, const godot_int p_maxsplit) { + const String *self = (const String *)p_self; + String *divisor = (String *)p_divisor; + + godot_pool_string_array result; + memnew_placement(&result, PoolStringArray); + PoolStringArray *proxy = (PoolStringArray *)&result; + PoolStringArray::Write proxy_writer = proxy->write(); + Vector<String> tmp_result = self->rsplit(*divisor, p_allow_empty, p_maxsplit); + proxy->resize(tmp_result.size()); + + for (int i = 0; i < tmp_result.size(); i++) { + proxy_writer[i] = tmp_result[i]; + } + + return result; +} + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 16a34a9a33..c5a1fa139e 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -14,6 +14,183 @@ "next": null, "api": [ { + "name": "godot_color_to_abgr32", + "return_type": "godot_int", + "arguments": [ + ["const godot_color *", "p_self"] + ] + }, + { + "name": "godot_color_to_abgr64", + "return_type": "godot_int", + "arguments": [ + ["const godot_color *", "p_self"] + ] + }, + { + "name": "godot_color_to_argb64", + "return_type": "godot_int", + "arguments": [ + ["const godot_color *", "p_self"] + ] + }, + { + "name": "godot_color_to_rgba64", + "return_type": "godot_int", + "arguments": [ + ["const godot_color *", "p_self"] + ] + }, + { + "name": "godot_color_darkened", + "return_type": "godot_color", + "arguments": [ + ["const godot_color *", "p_self"], + ["const godot_real", "p_amount"] + ] + }, + { + "name": "godot_color_from_hsv", + "return_type": "godot_color", + "arguments": [ + ["const godot_color *", "p_self"], + ["const godot_real", "p_h"], + ["const godot_real", "p_s"], + ["const godot_real", "p_v"], + ["const godot_real", "p_a"] + ] + }, + { + "name": "godot_color_lightened", + "return_type": "godot_color", + "arguments": [ + ["const godot_color *", "p_self"], + ["const godot_real", "p_amount"] + ] + }, + { + "name": "godot_array_duplicate", + "return_type": "godot_array", + "arguments": [ + ["const godot_array *", "p_self"], + ["const godot_bool", "p_deep"] + ] + }, + { + "name": "godot_array_max", + "return_type": "godot_variant", + "arguments": [ + ["const godot_array *", "p_self"] + ] + }, + { + "name": "godot_array_min", + "return_type": "godot_variant", + "arguments": [ + ["const godot_array *", "p_self"] + ] + }, + { + "name": "godot_array_shuffle", + "return_type": "void", + "arguments": [ + ["godot_array *", "p_self"] + ] + }, + { + "name": "godot_basis_slerp", + "return_type": "godot_basis", + "arguments": [ + ["const godot_basis *", "p_self"], + ["const godot_basis *", "p_b"], + ["const godot_real", "p_t"] + ] + }, + { + "name": "godot_node_path_get_as_property_path", + "return_type": "godot_node_path", + "arguments": [ + ["const godot_node_path *", "p_self"] + ] + }, + { + "name": "godot_quat_set_axis_angle", + "return_type": "void", + "arguments": [ + ["godot_quat *", "p_self"], + ["const godot_vector3 *", "p_axis"], + ["const godot_real", "p_angle"] + ] + }, + { + "name": "godot_rect2_grow_individual", + "return_type": "godot_rect2", + "arguments": [ + ["const godot_rect2 *", "p_self"], + ["const godot_real", "p_left"], + ["const godot_real", "p_top"], + ["const godot_real", "p_right"], + ["const godot_real", "p_bottom"] + ] + }, + { + "name": "godot_rect2_grow_margin", + "return_type": "godot_rect2", + "arguments": [ + ["const godot_rect2 *", "p_self"], + ["const godot_int", "p_margin"], + ["const godot_real", "p_by"] + ] + }, + { + "name": "godot_rect2_abs", + "return_type": "godot_rect2", + "arguments": [ + ["const godot_rect2 *", "p_self"] + ] + }, + { + "name": "godot_string_dedent", + "return_type": "godot_string", + "arguments": [ + ["const godot_string *", "p_self"] + ] + }, + { + "name": "godot_string_trim_prefix", + "return_type": "godot_string", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_string *", "p_prefix"] + ] + }, + { + "name": "godot_string_trim_suffix", + "return_type": "godot_string", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_string *", "p_suffix"] + ] + }, + { + "name": "godot_string_rstrip", + "return_type": "godot_string", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_string *", "p_chars"] + ] + }, + { + "name": "godot_string_rsplit", + "return_type": "godot_pool_string_array", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_string *", "p_divisor"], + ["const godot_bool", "p_allow_empty"], + ["const godot_int", "p_maxsplit"] + ] + }, + { "name": "godot_basis_get_quat", "return_type": "godot_quat", "arguments": [ diff --git a/modules/gdnative/include/gdnative/array.h b/modules/gdnative/include/gdnative/array.h index 1e66d133b9..876b8f8e8f 100644 --- a/modules/gdnative/include/gdnative/array.h +++ b/modules/gdnative/include/gdnative/array.h @@ -130,6 +130,14 @@ godot_int GDAPI godot_array_bsearch_custom(godot_array *p_self, const godot_vari void GDAPI godot_array_destroy(godot_array *p_self); +godot_array GDAPI godot_array_duplicate(const godot_array *p_self, const godot_bool p_deep); + +godot_variant GDAPI godot_array_max(const godot_array *p_self); + +godot_variant GDAPI godot_array_min(const godot_array *p_self); + +void GDAPI godot_array_shuffle(godot_array *p_self); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/include/gdnative/basis.h b/modules/gdnative/include/gdnative/basis.h index ebe2b1125b..6128bf3ac3 100644 --- a/modules/gdnative/include/gdnative/basis.h +++ b/modules/gdnative/include/gdnative/basis.h @@ -127,6 +127,8 @@ godot_basis GDAPI godot_basis_operator_multiply_vector(const godot_basis *p_self godot_basis GDAPI godot_basis_operator_multiply_scalar(const godot_basis *p_self, const godot_real p_b); +godot_basis GDAPI godot_basis_slerp(const godot_basis *p_self, const godot_basis *p_b, const godot_real p_t); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/include/gdnative/color.h b/modules/gdnative/include/gdnative/color.h index 1f0ac8354d..3007dbc6e3 100644 --- a/modules/gdnative/include/gdnative/color.h +++ b/modules/gdnative/include/gdnative/color.h @@ -81,6 +81,14 @@ godot_string GDAPI godot_color_as_string(const godot_color *p_self); godot_int GDAPI godot_color_to_rgba32(const godot_color *p_self); +godot_int GDAPI godot_color_to_abgr32(const godot_color *p_self); + +godot_int GDAPI godot_color_to_abgr64(const godot_color *p_self); + +godot_int GDAPI godot_color_to_argb64(const godot_color *p_self); + +godot_int GDAPI godot_color_to_rgba64(const godot_color *p_self); + godot_int GDAPI godot_color_to_argb32(const godot_color *p_self); godot_real GDAPI godot_color_gray(const godot_color *p_self); @@ -93,6 +101,12 @@ godot_color GDAPI godot_color_linear_interpolate(const godot_color *p_self, cons godot_color GDAPI godot_color_blend(const godot_color *p_self, const godot_color *p_over); +godot_color GDAPI godot_color_darkened(const godot_color *p_self, const godot_real p_amount); + +godot_color GDAPI godot_color_from_hsv(const godot_color *p_self, const godot_real p_h, const godot_real p_s, const godot_real p_v, const godot_real p_a); + +godot_color GDAPI godot_color_lightened(const godot_color *p_self, const godot_real p_amount); + godot_string GDAPI godot_color_to_html(const godot_color *p_self, const godot_bool p_with_alpha); godot_bool GDAPI godot_color_operator_equal(const godot_color *p_self, const godot_color *p_b); diff --git a/modules/gdnative/include/gdnative/node_path.h b/modules/gdnative/include/gdnative/node_path.h index 2b55e01d13..48fe5b4d3d 100644 --- a/modules/gdnative/include/gdnative/node_path.h +++ b/modules/gdnative/include/gdnative/node_path.h @@ -80,6 +80,8 @@ godot_bool GDAPI godot_node_path_is_empty(const godot_node_path *p_self); godot_bool GDAPI godot_node_path_operator_equal(const godot_node_path *p_self, const godot_node_path *p_b); +godot_node_path godot_node_path_get_as_property_path(const godot_node_path *p_self); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/include/gdnative/quat.h b/modules/gdnative/include/gdnative/quat.h index b1290f745c..634f486e66 100644 --- a/modules/gdnative/include/gdnative/quat.h +++ b/modules/gdnative/include/gdnative/quat.h @@ -109,6 +109,8 @@ godot_bool GDAPI godot_quat_operator_equal(const godot_quat *p_self, const godot godot_quat GDAPI godot_quat_operator_neg(const godot_quat *p_self); +void GDAPI godot_quat_set_axis_angle(godot_quat *p_self, const godot_vector3 *p_axis, const godot_real p_angle); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/include/gdnative/rect2.h b/modules/gdnative/include/gdnative/rect2.h index 4adcb73e3d..47c15c80bd 100644 --- a/modules/gdnative/include/gdnative/rect2.h +++ b/modules/gdnative/include/gdnative/rect2.h @@ -77,6 +77,12 @@ godot_bool GDAPI godot_rect2_has_point(const godot_rect2 *p_self, const godot_ve godot_rect2 GDAPI godot_rect2_grow(const godot_rect2 *p_self, const godot_real p_by); +godot_rect2 GDAPI godot_rect2_grow_individual(const godot_rect2 *p_self, const godot_real p_left, const godot_real p_top, const godot_real p_right, const godot_real p_bottom); + +godot_rect2 GDAPI godot_rect2_grow_margin(const godot_rect2 *p_self, const godot_int p_margin, const godot_real p_by); + +godot_rect2 GDAPI godot_rect2_abs(const godot_rect2 *p_self); + godot_rect2 GDAPI godot_rect2_expand(const godot_rect2 *p_self, const godot_vector2 *p_to); godot_bool GDAPI godot_rect2_operator_equal(const godot_rect2 *p_self, const godot_rect2 *p_b); diff --git a/modules/gdnative/include/gdnative/string.h b/modules/gdnative/include/gdnative/string.h index 73245160c1..95ae42a9ec 100644 --- a/modules/gdnative/include/gdnative/string.h +++ b/modules/gdnative/include/gdnative/string.h @@ -246,6 +246,12 @@ godot_bool GDAPI godot_string_is_valid_identifier(const godot_string *p_self); godot_bool GDAPI godot_string_is_valid_integer(const godot_string *p_self); godot_bool GDAPI godot_string_is_valid_ip_address(const godot_string *p_self); +godot_string GDAPI godot_string_dedent(const godot_string *p_self); +godot_string GDAPI godot_string_trim_prefix(const godot_string *p_self, const godot_string *p_prefix); +godot_string GDAPI godot_string_trim_suffix(const godot_string *p_self, const godot_string *p_suffix); +godot_string GDAPI godot_string_rstrip(const godot_string *p_self, const godot_string *p_chars); +godot_pool_string_array GDAPI godot_string_rsplit(const godot_string *p_self, const godot_string *p_divisor, const godot_bool p_allow_empty, const godot_int p_maxsplit); + void GDAPI godot_string_destroy(godot_string *p_self); #ifdef __cplusplus diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index 3e56275396..eb133502c4 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -72,11 +72,11 @@ void NativeScript::_bind_methods() { ClassDB::bind_method(D_METHOD("get_signal_documentation", "signal_name"), &NativeScript::get_signal_documentation); ClassDB::bind_method(D_METHOD("get_property_documentation", "path"), &NativeScript::get_property_documentation); - 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"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "class_name"), "set_class_name", "get_class_name"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library"); ADD_GROUP("Script Class", "script_class_"); - ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "script_class_name"), "set_script_class_name", "get_script_class_name"); - ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "script_class_icon_path", PROPERTY_HINT_FILE), "set_script_class_icon_path", "get_script_class_icon_path"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "script_class_name"), "set_script_class_name", "get_script_class_name"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "script_class_icon_path", PROPERTY_HINT_FILE), "set_script_class_icon_path", "get_script_class_icon_path"); ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &NativeScript::_new, MethodInfo(Variant::OBJECT, "new")); } diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 632970f8c0..4cefdbd7cb 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -8,7 +8,7 @@ [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. </description> <tutorials> - <link>http://docs.godotengine.org/en/3.0/getting_started/scripting/gdscript/index.html</link> + <link>https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link> </tutorials> <demos> </demos> diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 48c1760662..159085df34 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -469,8 +469,15 @@ bool GDScript::_update_exports() { for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { E->get()->set_build_failed(true); } + return false; } } else { + if (!valid) { + for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->set_build_failed(true); + } + return false; + } } if (base_cache.is_valid()) { @@ -2065,12 +2072,12 @@ GDScriptLanguage::GDScriptLanguage() { _debug_call_stack_pos = 0; int dmcs = GLOBAL_DEF("debug/settings/gdscript/max_call_stack", 1024); + ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/gdscript/max_call_stack", PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater")); //minimum is 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 { @@ -2081,6 +2088,7 @@ GDScriptLanguage::GDScriptLanguage() { #ifdef DEBUG_ENABLED GLOBAL_DEF("debug/gdscript/warnings/enable", true); GLOBAL_DEF("debug/gdscript/warnings/treat_warnings_as_errors", false); + GLOBAL_DEF("debug/gdscript/completion/autocomplete_setters_and_getters", false); for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); GLOBAL_DEF("debug/gdscript/warnings/" + warning, !warning.begins_with("unsafe_")); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 310c4e21f2..45319c59e7 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1603,13 +1603,13 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo return OK; } -Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready) { +Error GDScriptCompiler::_parse_function(Ref<GDScript> p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready) { Vector<int> bytecode; CodeGen codegen; codegen.class_node = p_class; - codegen.script = p_script; + codegen.script = p_script.ptr(); codegen.function_node = p_func; codegen.stack_max = 0; codegen.current_line = 0; @@ -1853,7 +1853,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser return OK; } -Error GDScriptCompiler::_parse_class_level(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { +Error GDScriptCompiler::_parse_class_level(Ref<GDScript> p_script, Ref<GDScript> p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { if (p_class->owner && p_class->owner->owner) { // Owner is not root @@ -1887,7 +1887,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, GDScript *p_owner p_script->initializer = NULL; p_script->subclasses.clear(); - p_script->_owner = p_owner; + p_script->_owner = p_owner.ptr(); p_script->tool = p_class->tool; p_script->name = p_class->name; @@ -1994,7 +1994,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, GDScript *p_owner StringName name = p_class->_signals[i].name; - GDScript *c = p_script; + GDScript *c = p_script.ptr(); while (c) { @@ -2054,7 +2054,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, GDScript *p_owner return OK; } -Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { +Error GDScriptCompiler::_parse_class_blocks(Ref<GDScript> p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { //parse methods bool has_initializer = false; @@ -2159,7 +2159,7 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa return OK; } -void GDScriptCompiler::_make_scripts(const GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { +void GDScriptCompiler::_make_scripts(const Ref<GDScript> p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { Map<StringName, Ref<GDScript> > old_subclasses; @@ -2178,20 +2178,20 @@ void GDScriptCompiler::_make_scripts(const GDScript *p_script, const GDScriptPar subclass.instance(); } - subclass->_owner = const_cast<GDScript *>(p_script); + subclass->_owner = const_cast<GDScript *>(p_script.ptr()); class_map.insert(name, subclass); _make_scripts(subclass.ptr(), p_class->subclasses[i], p_keep_state); } } -Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state) { +Error GDScriptCompiler::compile(const GDScriptParser *p_parser, Ref<GDScript> p_script, bool p_keep_state) { err_line = -1; err_column = -1; error = ""; parser = p_parser; - main_script = p_script; + main_script = p_script.ptr(); const GDScriptParser::Node *root = parser->get_parse_tree(); ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, ERR_INVALID_DATA); diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 55f5e2b48e..23c6450aa7 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -148,17 +148,17 @@ class GDScriptCompiler { int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level); int _parse_expression(CodeGen &codegen, const GDScriptParser::Node *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false); Error _parse_block(CodeGen &codegen, const GDScriptParser::BlockNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1); - Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false); - Error _parse_class_level(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state); - Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); - void _make_scripts(const GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _parse_function(Ref<GDScript> p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false); + Error _parse_class_level(Ref<GDScript> p_script, Ref<GDScript> p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _parse_class_blocks(Ref<GDScript> p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + void _make_scripts(const Ref<GDScript> p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); int err_line; int err_column; StringName source; String error; public: - Error compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state = false); + Error compile(const GDScriptParser *p_parser, Ref<GDScript> p_script, bool p_keep_state = false); String get_error() const; int get_error_line() const; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index ddd9e6b01c..068e8d2d92 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1308,37 +1308,38 @@ static bool _guess_identifier_type(const GDScriptCompletionContext &p_context, c return false; } - // Check ClassDB - if (ClassDB::class_exists(p_identifier)) { - r_type.type.has_type = true; - r_type.type.kind = GDScriptParser::DataType::NATIVE; - r_type.type.native_type = p_identifier; - if (Engine::get_singleton()->has_singleton(p_identifier)) { - r_type.type.is_meta_type = false; - r_type.value = Engine::get_singleton()->get_singleton_object(p_identifier); - } else { - r_type.type.is_meta_type = true; - int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier]; - r_type.value = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + for (int i = 0; i < 2; i++) { + StringName target_id; + switch (i) { + case 0: + // Check ClassDB + target_id = p_identifier; + break; + case 1: + // ClassDB again for underscore-prefixed classes + target_id = String("_") + p_identifier; + break; } - return true; - } - // ClassDB again for underscore-prefixed classes - StringName under_id = String("_") + p_identifier; - if (ClassDB::class_exists(under_id)) { - r_type.type.has_type = true; - r_type.type.kind = GDScriptParser::DataType::NATIVE; - r_type.type.native_type = p_identifier; - if (Engine::get_singleton()->has_singleton(p_identifier)) { - r_type.type.is_meta_type = false; - r_type.value = Engine::get_singleton()->get_singleton_object(p_identifier); - } else { - r_type.type.is_meta_type = true; - int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier]; - r_type.value = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + if (ClassDB::class_exists(target_id)) { + r_type.type.has_type = true; + r_type.type.kind = GDScriptParser::DataType::NATIVE; + r_type.type.native_type = target_id; + if (Engine::get_singleton()->has_singleton(target_id)) { + r_type.type.is_meta_type = false; + r_type.value = Engine::get_singleton()->get_singleton_object(target_id); + } else { + r_type.type.is_meta_type = true; + const Map<StringName, int>::Element *target_elem = GDScriptLanguage::get_singleton()->get_global_map().find(target_id); + // Check because classes like EditorNode are in ClassDB by now, but unknown to GDScript + if (!target_elem) { + return false; + } + int idx = target_elem->get(); + r_type.value = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + } + return true; } - return true; } // Check autoload singletons @@ -1999,7 +2000,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context if (!_static) { List<MethodInfo> methods; - ClassDB::get_method_list(type, &methods, false, true); + bool is_autocompleting_getters = GLOBAL_GET("debug/gdscript/completion/autocomplete_setters_and_getters").booleanize(); + ClassDB::get_method_list(type, &methods, false, !is_autocompleting_getters); for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { if (E->get().name.begins_with("_")) { continue; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index f09ff224e8..31115a4bd9 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -275,7 +275,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif uint32_t alloca_size = 0; - GDScript *_class; + GDScript *script; int ip = 0; int line = _initial_line; @@ -286,7 +286,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a line = p_state->line; ip = p_state->ip; alloca_size = p_state->stack.size(); - _class = p_state->_class; + script = p_state->script.ptr(); p_instance = p_state->instance; defarg = p_state->defarg; self = p_state->self; @@ -368,9 +368,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else { self = p_instance->owner; } - _class = p_instance->script.ptr(); + script = p_instance->script.ptr(); } else { - _class = _script; + script = this->_script.ptr(); } } @@ -395,7 +395,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #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); \ + m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, stack, err_text); \ if (unlikely(!m_v)) \ OPCODE_BREAK; @@ -404,7 +404,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #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); + m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, stack, err_text); #endif @@ -1185,7 +1185,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_VARIANT_PTR(dst, argc + 3); - const GDScript *gds = _script; + const GDScript *gds = _script.ptr(); const Map<StringName, GDScriptFunction *>::Element *E = NULL; while (gds->base.ptr()) { @@ -1256,11 +1256,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a gdfs->state.stack_size = _stack_size; gdfs->state.self = self; gdfs->state.alloca_size = alloca_size; - gdfs->state._class = _class; + gdfs->state.script = _script; gdfs->state.ip = ip + ipofs; gdfs->state.line = line; gdfs->state.instance_id = (p_instance && p_instance->get_owner()) ? p_instance->get_owner()->get_instance_id() : 0; - gdfs->state.script_id = _class->get_instance_id(); //gdfs->state.result_pos=ip+ipofs-1; gdfs->state.defarg = defarg; gdfs->state.instance = p_instance; @@ -1549,8 +1548,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a String err_file; if (p_instance) err_file = p_instance->script->path; - else if (_class) - err_file = _class->path; + else if (script) + err_file = script->path; if (err_file == "") err_file = "<built-in>"; String err_func = name; @@ -1765,15 +1764,20 @@ GDScriptFunction::~GDScriptFunction() { Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error) { #ifdef DEBUG_ENABLED - if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { - ERR_EXPLAIN("Resumed after yield, but class instance is gone"); + // Checking this first since it's faster + if (!state.script.is_valid()) { + ERR_EXPLAIN("Resumed after yield, but script is gone"); ERR_FAIL_V(Variant()); } - if (state.script_id && !ObjectDB::get_instance(state.script_id)) { - ERR_EXPLAIN("Resumed after yield, but script is gone"); + if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { + ERR_EXPLAIN("Resumed after yield, but class instance is gone"); ERR_FAIL_V(Variant()); } +#else + if (!is_valid()) { + return Variant(); + } #endif Variant arg; @@ -1841,12 +1845,12 @@ bool GDScriptFunctionState::is_valid(bool p_extended_check) const { return false; if (p_extended_check) { + //script gone? (checking this first since it's faster) + if (!state.script.is_valid()) + return false; //class instance gone? if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) return false; - //script gone? - if (state.script_id && !ObjectDB::get_instance(state.script_id)) - return false; } return true; @@ -1856,13 +1860,14 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) { ERR_FAIL_COND_V(!function, Variant()); #ifdef DEBUG_ENABLED - if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { - ERR_EXPLAIN("Resumed after yield, but class instance is gone"); + // Checking this first since it's faster + if (!state.script.is_valid()) { + ERR_EXPLAIN("Resumed after yield, but script is gone"); ERR_FAIL_V(Variant()); } - if (state.script_id && !ObjectDB::get_instance(state.script_id)) { - ERR_EXPLAIN("Resumed after yield, but script is gone"); + if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { + ERR_EXPLAIN("Resumed after yield, but class instance is gone"); ERR_FAIL_V(Variant()); } #endif diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index bfb6d673f1..a47070de4f 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -225,7 +225,7 @@ private: bool _static; MultiplayerAPI::RPCMode rpc_mode; - GDScript *_script; + Ref<GDScript> _script; StringName name; Vector<Variant> constants; @@ -272,15 +272,13 @@ private: public: struct CallState { - ObjectID instance_id; //by debug only - ObjectID script_id; - + ObjectID instance_id; GDScriptInstance *instance; Vector<uint8_t> stack; int stack_size; Variant self; uint32_t alloca_size; - GDScript *_class; + Ref<GDScript> script; int ip; int line; int defarg; @@ -299,7 +297,7 @@ public: int get_default_argument_addr(int p_idx) const; GDScriptDataType get_return_type() const; GDScriptDataType get_argument_type(int p_idx) const; - GDScript *get_script() const { return _script; } + GDScript *get_script() const { return const_cast<GDScript *>(_script.ptr()); } StringName get_source() const { return source; } void debug_get_stack_member_state(int p_line, List<Pair<StringName, int> > *r_stackvars) const; diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp index 5af9bbc05f..2f31d59c46 100644 --- a/modules/gdscript/gdscript_functions.cpp +++ b/modules/gdscript/gdscript_functions.cpp @@ -106,6 +106,8 @@ const char *GDScriptFunctions::get_func_name(Function p_func) { "printerr", "printraw", "print_debug", + "push_error", + "push_warning", "var2str", "str2var", "var2bytes", @@ -707,13 +709,40 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ ScriptLanguage *script = GDScriptLanguage::get_singleton(); if (script->debug_get_stack_level_count() > 0) { - str += "\n\t"; - str += "At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)); // + " in function '" + script->debug_get_stack_level_function(0) + "'"; + str += "\n At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()"; } print_line(str); r_ret = Variant(); } break; + case PUSH_ERROR: { + VALIDATE_ARG_COUNT(1); + if (p_args[0]->get_type() != Variant::STRING) { + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + r_ret = Variant(); + break; + } + + String message = *p_args[0]; + ERR_PRINTS(message); + r_ret = Variant(); + } break; + case PUSH_WARNING: { + VALIDATE_ARG_COUNT(1); + if (p_args[0]->get_type() != Variant::STRING) { + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + r_ret = Variant(); + break; + } + + String message = *p_args[0]; + WARN_PRINTS(message); + r_ret = Variant(); + } break; case VAR_TO_STR: { VALIDATE_ARG_COUNT(1); String vars; @@ -1754,11 +1783,25 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; + case PUSH_ERROR: { + + MethodInfo mi(Variant::NIL, "push_error", PropertyInfo(Variant::STRING, "message")); + mi.return_val.type = Variant::NIL; + return mi; + + } break; + case PUSH_WARNING: { + + MethodInfo mi(Variant::NIL, "push_warning", PropertyInfo(Variant::STRING, "message")); + mi.return_val.type = Variant::NIL; + return mi; + + } break; case VAR_TO_STR: { + MethodInfo mi("var2str", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::STRING; return mi; - } break; case STR_TO_VAR: { @@ -1768,10 +1811,10 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; case VAR_TO_BYTES: { + MethodInfo mi("var2bytes", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::POOL_BYTE_ARRAY; return mi; - } break; case BYTES_TO_VAR: { diff --git a/modules/gdscript/gdscript_functions.h b/modules/gdscript/gdscript_functions.h index e920dd4ece..33d5f27230 100644 --- a/modules/gdscript/gdscript_functions.h +++ b/modules/gdscript/gdscript_functions.h @@ -97,6 +97,8 @@ public: TEXT_PRINTERR, TEXT_PRINTRAW, TEXT_PRINT_DEBUG, + PUSH_ERROR, + PUSH_WARNING, VAR_TO_STR, STR_TO_VAR, VAR_TO_BYTES, @@ -117,7 +119,6 @@ public: LEN, IS_INSTANCE_VALID, FUNC_MAX - }; static const char *get_func_name(Function p_func); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 5f2655eb92..09b3a5631f 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -4353,6 +4353,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { current_export.type = Variant::INT; current_export.hint = is_flags ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM; + current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; Dictionary enum_values = constant; List<Variant> keys; @@ -4839,6 +4840,21 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->is_token_literal(0, true)) { enum_name = tokenizer->get_token_literal(); + + if (current_class->constant_expressions.has(enum_name)) { + _set_error("A constant named '" + String(enum_name) + "' already exists in this class (at line: " + + itos(current_class->constant_expressions[enum_name].expression->line) + ")."); + return; + } + + for (int i = 0; i < current_class->variables.size(); i++) { + if (current_class->variables[i].identifier == enum_name) { + _set_error("A variable named '" + String(enum_name) + "' already exists in this class (at line: " + + itos(current_class->variables[i].line) + ")."); + return; + } + } + tokenizer->advance(); } if (tokenizer->get_token() != GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) { @@ -4865,26 +4881,12 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } else { // tokenizer->is_token_literal(0, true) - ClassNode::Constant constant; - StringName const_id = tokenizer->get_token_literal(); - if (current_class->constant_expressions.has(const_id)) { - _set_error("A constant named '" + String(const_id) + "' already exists in this class (at line: " + - itos(current_class->constant_expressions[const_id].expression->line) + ")."); - return; - } - - for (int i = 0; i < current_class->variables.size(); i++) { - if (current_class->variables[i].identifier == const_id) { - _set_error("A variable named '" + String(const_id) + "' already exists in this class (at line: " + - itos(current_class->variables[i].line) + ")."); - return; - } - } - tokenizer->advance(); + ConstantNode *enum_value_expr; + if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { tokenizer->advance(); @@ -4901,23 +4903,20 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } - ConstantNode *subexpr_const = static_cast<ConstantNode *>(subexpr); + enum_value_expr = static_cast<ConstantNode *>(subexpr); - if (subexpr_const->value.get_type() != Variant::INT) { + if (enum_value_expr->value.get_type() != Variant::INT) { _set_error("Expected an int value for enum"); return; } - last_assign = subexpr_const->value; - - constant.expression = subexpr_const; + last_assign = enum_value_expr->value; } else { last_assign = last_assign + 1; - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = last_assign; - cn->datatype = _type_from_variant(cn->value); - constant.expression = cn; + enum_value_expr = alloc_node<ConstantNode>(); + enum_value_expr->value = last_assign; + enum_value_expr->datatype = _type_from_variant(enum_value_expr->value); } if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { @@ -4925,14 +4924,29 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } if (enum_name != "") { - const ConstantNode *cn = static_cast<const ConstantNode *>(constant.expression); - enum_dict[const_id] = cn->value; - } + enum_dict[const_id] = enum_value_expr->value; + } else { + if (current_class->constant_expressions.has(const_id)) { + _set_error("A constant named '" + String(const_id) + "' already exists in this class (at line: " + + itos(current_class->constant_expressions[const_id].expression->line) + ")."); + return; + } - constant.type.has_type = true; - constant.type.kind = DataType::BUILTIN; - constant.type.builtin_type = Variant::INT; - p_class->constant_expressions.insert(const_id, constant); + for (int i = 0; i < current_class->variables.size(); i++) { + if (current_class->variables[i].identifier == const_id) { + _set_error("A variable named '" + String(const_id) + "' already exists in this class (at line: " + + itos(current_class->variables[i].line) + ")."); + return; + } + } + + ClassNode::Constant constant; + constant.type.has_type = true; + constant.type.kind = DataType::BUILTIN; + constant.type.builtin_type = Variant::INT; + constant.expression = enum_value_expr; + p_class->constant_expressions.insert(const_id, constant); + } } } @@ -6709,9 +6723,15 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat } } + bool rets = false; return_type.has_type = true; return_type.kind = DataType::BUILTIN; - return_type.builtin_type = Variant::get_method_return_type(base_type.builtin_type, callee_name); + return_type.builtin_type = Variant::get_method_return_type(base_type.builtin_type, callee_name, &rets); + // If the method returns, but it might return any type, (Variant::NIL), pretend we don't know the type. + // At least make sure we know that it returns + if (rets && return_type.builtin_type == Variant::NIL) { + return_type.has_type = false; + } break; } @@ -7074,7 +7094,6 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType DataType member_type; for (int i = 0; i < current_class->variables.size(); i++) { - ClassNode::Member m = current_class->variables[i]; if (current_class->variables[i].identifier == p_identifier) { member_type = current_class->variables[i].data_type; current_class->variables.write[i].usages += 1; diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index f13479940d..9b9088dd82 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -10,7 +10,7 @@ A GridMap is split into a sparse collection of octants for efficient rendering and physics processing. Every octant has the same dimensions and can contain several cells. </description> <tutorials> - <link>http://docs.godotengine.org/en/3.0/tutorials/3d/using_gridmaps.html</link> + <link>https://docs.godotengine.org/en/latest/tutorials/3d/using_gridmaps.html</link> </tutorials> <demos> </demos> diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index a8fdf8cf1f..274a2f0249 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -892,7 +892,7 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("make_baked_meshes", "gen_lightmap_uv", "lightmap_uv_texel_size"), &GridMap::make_baked_meshes, DEFVAL(false), DEFVAL(0.1)); #ifndef DISABLE_DEPRECATED - ADD_PROPERTYNO(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "MeshLibrary", 0), "set_theme", "get_theme"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "MeshLibrary", 0), "set_theme", "get_theme"); #endif // DISABLE_DEPRECATED ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh_library", PROPERTY_HINT_RESOURCE_TYPE, "MeshLibrary"), "set_mesh_library", "get_mesh_library"); diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp index a49137ae73..24d40111cf 100644 --- a/modules/jpg/image_loader_jpegd.cpp +++ b/modules/jpg/image_loader_jpegd.cpp @@ -48,9 +48,9 @@ Error jpeg_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p const int image_width = decoder.get_width(); const int image_height = decoder.get_height(); - int comps = decoder.get_num_components(); - if (comps == 3) - comps = 4; //weird + const int comps = decoder.get_num_components(); + if (comps != 1 && comps != 3) + return ERR_FILE_CORRUPT; if (decoder.begin_decoding() != jpgd::JPGD_SUCCESS) return ERR_FILE_CORRUPT; @@ -73,7 +73,19 @@ Error jpeg_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p } jpgd::uint8 *pDst = pImage_data + y * dst_bpl; - memcpy(pDst, pScan_line, dst_bpl); + + if (comps == 1) { + memcpy(pDst, pScan_line, dst_bpl); + } else { + // For images with more than 1 channel pScan_line will always point to a buffer + // containing 32-bit RGBA pixels. Alpha is always 255 and we ignore it. + for (int x = 0; x < image_width; x++) { + pDst[0] = pScan_line[x * 4 + 0]; + pDst[1] = pScan_line[x * 4 + 1]; + pDst[2] = pScan_line[x * 4 + 2]; + pDst += 3; + } + } } //all good @@ -82,7 +94,7 @@ Error jpeg_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p if (comps == 1) fmt = Image::FORMAT_L8; else - fmt = Image::FORMAT_RGBA8; + fmt = Image::FORMAT_RGB8; dw = PoolVector<uint8_t>::Write(); p_image->create(image_width, image_height, 0, fmt, data); diff --git a/modules/mono/SCsub b/modules/mono/SCsub index 9a62b0fdc9..0e5dd9b4cf 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -44,7 +44,7 @@ def make_cs_files_header(src, dst, version_dst): if i > 0: header.write(', ') header.write(byte_to_str(buf[buf_idx])) - inserted_files += '\tr_files.insert("' + filepath_src_rel + '", ' \ + inserted_files += '\tr_files.insert("' + filepath_src_rel.replace('\\', '\\\\') + '", ' \ 'CompressedFile(_cs_' + name + '_compressed_size, ' \ '_cs_' + name + '_uncompressed_size, ' \ '_cs_' + name + '_compressed));\n' @@ -102,6 +102,75 @@ env_mono = conf.Finish() import os +def find_nuget_unix(): + import os.path + import sys + + hint_dirs = ['/opt/novell/mono/bin'] + if sys.platform == 'darwin': + hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin', '/usr/local/var/homebrew/linked/mono/bin'] + hint_dirs + + for hint_dir in hint_dirs: + hint_path = os.path.join(hint_dir, 'nuget') + if os.path.isfile(hint_path): + return hint_path + elif os.path.isfile(hint_path + '.exe'): + return hint_path + '.exe' + + for hint_dir in os.environ['PATH'].split(os.pathsep): + hint_dir = hint_dir.strip('"') + hint_path = os.path.join(hint_dir, 'nuget') + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK): + return hint_path + '.exe' + + return None + + +def find_nuget_windows(): + import mono_reg_utils as monoreg + + mono_root = '' + bits = env['bits'] + + if bits == '32': + if os.getenv('MONO32_PREFIX'): + mono_root = os.getenv('MONO32_PREFIX') + else: + mono_root = monoreg.find_mono_root_dir(bits) + else: + if os.getenv('MONO64_PREFIX'): + mono_root = os.getenv('MONO64_PREFIX') + else: + mono_root = monoreg.find_mono_root_dir(bits) + + if mono_root: + mono_bin_dir = os.path.join(mono_root, 'bin') + nuget_mono = os.path.join(mono_bin_dir, 'nuget.bat') + + if os.path.isfile(nuget_mono): + return nuget_mono + + # Standalone NuGet + + for hint_dir in os.environ['PATH'].split(os.pathsep): + hint_dir = hint_dir.strip('"') + hint_path = os.path.join(hint_dir, 'nuget.exe') + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + + if 'NUGET_PATH' in os.environ: + hint_path = os.environ['NUGET_PATH'] + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + hint_path = os.path.join(hint_path, 'nuget.exe') + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + + return None + + def find_msbuild_unix(filename): import os.path import sys @@ -176,14 +245,17 @@ def mono_build_solution(source, target, env): import mono_reg_utils as monoreg from shutil import copyfile - framework_path = '' + sln_path = os.path.abspath(str(source[0])) + target_path = os.path.abspath(str(target[0])) + framework_path = '' msbuild_env = os.environ.copy() # Needed when running from Developer Command Prompt for VS if 'PLATFORM' in msbuild_env: del msbuild_env['PLATFORM'] + # Find MSBuild if os.name == 'nt': msbuild_info = find_msbuild_windows() if msbuild_info is None: @@ -213,11 +285,27 @@ def mono_build_solution(source, target, env): print('MSBuild path: ' + msbuild_path) + # Find NuGet + nuget_path = find_nuget_windows() if os.name == 'nt' else find_nuget_unix() + if nuget_path is None: + raise RuntimeError('Cannot find NuGet executable') + + print('NuGet path: ' + nuget_path) + + # Do NuGet restore + + try: + subprocess.check_call([nuget_path, 'restore', sln_path]) + except subprocess.CalledProcessError: + raise RuntimeError('GodotSharpTools: NuGet restore failed') + + # Build solution + build_config = 'Release' msbuild_args = [ msbuild_path, - os.path.abspath(str(source[0])), + sln_path, '/p:Configuration=' + build_config, ] @@ -227,20 +315,24 @@ def mono_build_solution(source, target, env): try: subprocess.check_call(msbuild_args, env=msbuild_env) except subprocess.CalledProcessError: - raise RuntimeError('GodotSharpTools build failed') + raise RuntimeError('GodotSharpTools: Build failed') + + # Copy files - src_dir = os.path.abspath(os.path.join(str(source[0]), os.pardir, 'bin', build_config)) - dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir)) + src_dir = os.path.abspath(os.path.join(sln_path, os.pardir, 'bin', build_config)) + dst_dir = os.path.abspath(os.path.join(target_path, os.pardir)) + asm_file = 'GodotSharpTools.dll' if not os.path.isdir(dst_dir): if os.path.exists(dst_dir): raise RuntimeError('Target directory is a file') os.makedirs(dst_dir) - asm_file = 'GodotSharpTools.dll' - copyfile(os.path.join(src_dir, asm_file), os.path.join(dst_dir, asm_file)) + # Dependencies + copyfile(os.path.join(src_dir, "DotNet.Glob.dll"), os.path.join(dst_dir, "DotNet.Glob.dll")) + if env['tools']: output_dir = Dir('#bin').abspath editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools') diff --git a/modules/mono/config.py b/modules/mono/config.py index 8427103ee7..189699cca8 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -265,11 +265,7 @@ def make_template_dir(env, mono_root): template_dir_name = '' - if platform == 'windows': - template_dir_name = 'data.mono.%s.%s.%s' % (platform, env['bits'], target) - elif platform == 'osx': - template_dir_name = 'data.mono.%s.%s' % (platform, target) - elif platform == 'x11': + if platform in ['windows', 'osx', 'x11']: template_dir_name = 'data.mono.%s.%s.%s' % (platform, env['bits'], target) else: assert False diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 5160d42367..700e518cfc 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -32,6 +32,7 @@ #include <mono/metadata/threads.h> +#include "core/io/json.h" #include "core/os/file_access.h" #include "core/os/os.h" #include "core/os/thread.h" @@ -42,7 +43,6 @@ #include "editor/csharp_project.h" #include "editor/editor_node.h" #include "editor/godotsharp_editor.h" -#include "utils/string_utils.h" #endif #include "godotsharp_dirs.h" @@ -50,6 +50,7 @@ #include "mono_gd/gd_mono_marshal.h" #include "signal_awaiter_utils.h" #include "utils/macros.h" +#include "utils/string_utils.h" #include "utils/thread_local.h" #define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var) @@ -370,6 +371,7 @@ bool CSharpLanguage::supports_builtin_mode() const { return false; } +#ifdef TOOLS_ENABLED static String variant_type_to_managed_name(const String &p_var_type_name) { if (p_var_type_name.empty()) @@ -432,8 +434,7 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { return p_var_type_name; } -String CSharpLanguage::make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const { -#ifdef TOOLS_ENABLED +String CSharpLanguage::make_function(const String &, const String &p_name, const PoolStringArray &p_args) const { // FIXME // - Due to Godot's API limitation this just appends the function to the end of the file // - Use fully qualified name if there is ambiguity @@ -449,10 +450,12 @@ String CSharpLanguage::make_function(const String &p_class, const String &p_name s += ")\n{\n // Replace with function body.\n}\n"; return s; +} #else +String CSharpLanguage::make_function(const String &, const String &, const PoolStringArray &) const { return String(); -#endif } +#endif String CSharpLanguage::_get_indentation() const { #ifdef TOOLS_ENABLED @@ -822,6 +825,49 @@ void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) { } #endif +void CSharpLanguage::project_assembly_loaded() { + + scripts_metadata.clear(); + + String scripts_metadata_filename = "scripts_metadata."; + +#ifdef TOOLS_ENABLED + scripts_metadata_filename += Engine::get_singleton()->is_editor_hint() ? "editor" : "editor_player"; +#else +#ifdef DEBUG_ENABLED + scripts_metadata_filename += "debug"; +#else + scripts_metadata_filename += "release"; +#endif +#endif + + String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename); + + if (FileAccess::exists(scripts_metadata_path)) { + String old_json; + + Error ferr = read_all_file_utf8(scripts_metadata_path, old_json); + ERR_FAIL_COND(ferr != OK); + + Variant old_dict_var; + String err_str; + int err_line; + Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line); + if (json_err != OK) { + ERR_PRINTS("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ")"); + return; + } + + scripts_metadata = old_dict_var.operator Dictionary(); + + print_verbose("Successfully loaded scripts metadata"); + } else { + if (!Engine::get_singleton()->is_editor_hint()) { + ERR_PRINT("Missing scripts metadata file"); + } + } +} + void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("cs"); @@ -1407,6 +1453,8 @@ bool CSharpInstance::_unreference_owner_unsafe() { if (!unsafe_referenced) return false; // Already unreferenced + unsafe_referenced = false; + // Called from CSharpInstance::mono_object_disposed() or ~CSharpInstance() // Unsafe refcount decrement. The managed instance also counts as a reference. @@ -2208,10 +2256,27 @@ bool CSharpScript::can_instance() const { #endif #ifdef TOOLS_ENABLED - return valid && (tool || ScriptServer::is_scripting_enabled()); + bool extra_cond = tool || ScriptServer::is_scripting_enabled(); #else - return valid; + bool extra_cond = true; #endif + + // FIXME Need to think this through better. + // For tool scripts, this will never fire if the class is not found. That's because we + // don't know if it's a tool script if we can't find the class to access the attributes. + if (extra_cond && !script_class) { + if (GDMono::get_singleton()->get_project_assembly() == NULL) { + // The project assembly is not loaded + ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path()); + ERR_FAIL_V(NULL); + } else { + // The project assembly is loaded, but the class could not found + ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path()); + ERR_FAIL_V(NULL); + } + } + + return valid && extra_cond; } StringName CSharpScript::get_instance_base_type() const { @@ -2317,20 +2382,6 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { CRASH_COND(!valid); #endif - if (!script_class) { - if (GDMono::get_singleton()->get_project_assembly() == NULL) { - // The project assembly is not loaded - ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path()); - ERR_FAIL_V(NULL); - } else { - // The project assembly is loaded, but the class could not found - ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path()); - ERR_FAIL_V(NULL); - } - } - - ERR_FAIL_COND_V(!valid, NULL); - if (native) { String native_name = native->get_name(); if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { @@ -2418,7 +2469,23 @@ Error CSharpScript::reload(bool p_keep_state) { GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly(); if (project_assembly) { - script_class = project_assembly->get_object_derived_class(name); + const Variant *script_metadata_var = CSharpLanguage::get_singleton()->get_scripts_metadata().getptr(get_path()); + if (script_metadata_var) { + Dictionary script_metadata = script_metadata_var->operator Dictionary()["class"]; + const Variant *namespace_ = script_metadata.getptr("namespace"); + const Variant *class_name = script_metadata.getptr("class_name"); + ERR_FAIL_NULL_V(namespace_, ERR_BUG); + ERR_FAIL_NULL_V(class_name, ERR_BUG); + GDMonoClass *klass = project_assembly->get_class(namespace_->operator String(), class_name->operator String()); + if (klass) { + bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(klass); + ERR_FAIL_COND_V(!obj_type, ERR_BUG); + script_class = klass; + } + } else { + // Missing script metadata. Fallback to legacy method + script_class = project_assembly->get_object_derived_class(name); + } valid = script_class != NULL; @@ -2546,29 +2613,14 @@ int CSharpScript::get_member_line(const StringName &p_member) const { Error CSharpScript::load_source_code(const String &p_path) { - PoolVector<uint8_t> sourcef; - Error err; - FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err); - ERR_FAIL_COND_V(err != OK, err); - - int len = f->get_len(); - sourcef.resize(len + 1); - PoolVector<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); + Error ferr = read_all_file_utf8(p_path, source); + if (ferr != OK) { + if (ferr == ERR_INVALID_DATA) { + 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(ferr); } - source = s; - #ifdef TOOLS_ENABLED source_changed_cache = true; #endif @@ -2718,7 +2770,8 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r "Compile", ProjectSettings::get_singleton()->globalize_path(p_path)); } else { - ERR_PRINTS("Cannot add " + p_path + " to the C# project because it could not be created."); + ERR_PRINTS("Failed to create C# project"); + ERR_PRINTS("Cannot add " + p_path + " to the C# project"); } } #endif diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 8b0a095890..fc604df2a0 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -67,7 +67,7 @@ class CSharpScript : public Script { friend class CSharpInstance; friend class CSharpLanguage; - friend class CSharpScriptDepSort; + friend struct CSharpScriptDepSort; bool tool; bool valid; @@ -275,6 +275,8 @@ class CSharpLanguage : public ScriptLanguage { int lang_idx; + Dictionary scripts_metadata; + public: StringNameCache string_names; @@ -295,6 +297,10 @@ public: void reload_assemblies_if_needed(bool p_soft_reload); #endif + void project_assembly_loaded(); + + _FORCE_INLINE_ const Dictionary &get_scripts_metadata() { return scripts_metadata; } + virtual String get_name() const; /* LANGUAGE FUNCTIONS */ diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj b/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj index fceb732426..f9e9f41977 100644 --- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj +++ b/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj @@ -31,6 +31,9 @@ <Reference Include="System" /> <Reference Include="Microsoft.Build" /> <Reference Include="Microsoft.Build.Framework" /> + <Reference Include="DotNet.Glob, Version=2.1.1.0, Culture=neutral, PublicKeyToken=b68cc888b4f632d1, processorArchitecture=MSIL"> + <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + </Reference> </ItemGroup> <ItemGroup> <Compile Include="StringExtensions.cs" /> @@ -43,5 +46,8 @@ <Compile Include="Utils\OS.cs" /> <Compile Include="Editor\GodotSharpExport.cs" /> </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> </Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs index f00ec5a2ad..647d9ac81d 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs +++ b/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs @@ -1,4 +1,5 @@ using System; +using DotNet.Globbing; using Microsoft.Build.Construction; namespace GodotSharpTools.Project @@ -7,7 +8,10 @@ namespace GodotSharpTools.Project { public static bool HasItem(this ProjectRootElement root, string itemType, string include) { - string includeNormalized = include.NormalizePath(); + GlobOptions globOptions = new GlobOptions(); + globOptions.Evaluation.CaseInsensitive = false; + + string normalizedInclude = include.NormalizePath(); foreach (var itemGroup in root.ItemGroups) { @@ -16,10 +20,14 @@ namespace GodotSharpTools.Project foreach (var item in itemGroup.Items) { - if (item.ItemType == itemType) + if (item.ItemType != itemType) + continue; + + var glob = Glob.Parse(item.Include.NormalizePath(), globOptions); + + if (glob.IsMatch(normalizedInclude)) { - if (item.Include.NormalizePath() == includeNormalized) - return true; + return true; } } } diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs index 1d863e6f61..2ce7837a27 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs +++ b/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs @@ -6,18 +6,24 @@ namespace GodotSharpTools.Project { public static class ProjectGenerator { + public const string CoreApiProjectName = "GodotSharp"; + public const string EditorApiProjectName = "GodotSharpEditor"; + const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"; + const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}"; + public static string GenCoreApiProject(string dir, string[] compileItems) { - string path = Path.Combine(dir, CoreApiProject + ".csproj"); + string path = Path.Combine(dir, CoreApiProjectName + ".csproj"); ProjectPropertyGroupElement mainGroup; - var root = CreateLibraryProject(CoreApiProject, out mainGroup); + var root = CreateLibraryProject(CoreApiProjectName, out mainGroup); mainGroup.AddProperty("DocumentationFile", Path.Combine("$(OutputPath)", "$(AssemblyName).xml")); mainGroup.SetProperty("RootNamespace", "Godot"); + mainGroup.SetProperty("ProjectGuid", CoreApiProjectGuid); - GenAssemblyInfoFile(root, dir, CoreApiProject, - new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProject + "\")]" }, + GenAssemblyInfoFile(root, dir, CoreApiProjectName, + new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]" }, new string[] { "System.Runtime.CompilerServices" }); foreach (var item in compileItems) @@ -27,33 +33,33 @@ namespace GodotSharpTools.Project root.Save(path); - return root.GetGuid().ToString().ToUpper(); + return CoreApiProjectGuid; } - public static string GenEditorApiProject(string dir, string coreApiHintPath, string[] compileItems) + public static string GenEditorApiProject(string dir, string coreApiProjPath, string[] compileItems) { - string path = Path.Combine(dir, EditorApiProject + ".csproj"); + string path = Path.Combine(dir, EditorApiProjectName + ".csproj"); ProjectPropertyGroupElement mainGroup; - var root = CreateLibraryProject(EditorApiProject, out mainGroup); + var root = CreateLibraryProject(EditorApiProjectName, out mainGroup); mainGroup.AddProperty("DocumentationFile", Path.Combine("$(OutputPath)", "$(AssemblyName).xml")); mainGroup.SetProperty("RootNamespace", "Godot"); + mainGroup.SetProperty("ProjectGuid", EditorApiProjectGuid); - GenAssemblyInfoFile(root, dir, EditorApiProject); + GenAssemblyInfoFile(root, dir, EditorApiProjectName); foreach (var item in compileItems) { root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\")); } - var coreApiRef = root.AddItem("Reference", CoreApiProject); - coreApiRef.AddMetadata("HintPath", coreApiHintPath); + var coreApiRef = root.AddItem("ProjectReference", coreApiProjPath.Replace("/", "\\")); coreApiRef.AddMetadata("Private", "False"); root.Save(path); - return root.GetGuid().ToString().ToUpper(); + return EditorApiProjectGuid; } public static string GenGameProject(string dir, string name, string[] compileItems) @@ -77,13 +83,13 @@ namespace GodotSharpTools.Project toolsGroup.AddProperty("WarningLevel", "4"); toolsGroup.AddProperty("ConsolePause", "false"); - var coreApiRef = root.AddItem("Reference", CoreApiProject); - coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProject + ".dll")); + var coreApiRef = root.AddItem("Reference", CoreApiProjectName); + coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProjectName + ".dll")); coreApiRef.AddMetadata("Private", "False"); - var editorApiRef = root.AddItem("Reference", EditorApiProject); + var editorApiRef = root.AddItem("Reference", EditorApiProjectName); editorApiRef.Condition = " '$(Configuration)' == 'Tools' "; - editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProject + ".dll")); + editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProjectName + ".dll")); editorApiRef.AddMetadata("Private", "False"); GenAssemblyInfoFile(root, dir, name); @@ -182,9 +188,6 @@ namespace GodotSharpTools.Project } } - public const string CoreApiProject = "GodotSharp"; - public const string EditorApiProject = "GodotSharpEditor"; - private const string assemblyInfoTemplate = @"using System.Reflection;{0} diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs index 6889ea715f..a13f4fd6ef 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs +++ b/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs @@ -1,5 +1,6 @@ -using System; +using System.Collections.Generic; using System.IO; +using DotNet.Globbing; using Microsoft.Build.Construction; namespace GodotSharpTools.Project @@ -10,8 +11,61 @@ namespace GodotSharpTools.Project { var dir = Directory.GetParent(projectPath).FullName; var root = ProjectRootElement.Open(projectPath); - if (root.AddItemChecked(itemType, include.RelativeToPath(dir).Replace("/", "\\"))) + var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\"); + + if (root.AddItemChecked(itemType, normalizedInclude)) root.Save(); } + + private static string[] GetAllFilesRecursive(string rootDirectory, string mask) + { + string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories); + + // We want relative paths + for (int i = 0; i < files.Length; i++) { + files[i] = files[i].RelativeToPath(rootDirectory); + } + + return files; + } + + public static string[] GetIncludeFiles(string projectPath, string itemType) + { + var result = new List<string>(); + var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs"); + + GlobOptions globOptions = new GlobOptions(); + globOptions.Evaluation.CaseInsensitive = false; + + var root = ProjectRootElement.Open(projectPath); + + foreach (var itemGroup in root.ItemGroups) + { + if (itemGroup.Condition.Length != 0) + continue; + + foreach (var item in itemGroup.Items) + { + if (item.ItemType != itemType) + continue; + + string normalizedInclude = item.Include.NormalizePath(); + + var glob = Glob.Parse(normalizedInclude, globOptions); + + // TODO Check somehow if path has no blog to avoid the following loop... + + foreach (var existingFile in existingFiles) + { + if (glob.IsMatch(existingFile)) + { + result.Add(existingFile); + } + } + } + } + + return result.ToArray(); + } } } diff --git a/modules/mono/editor/GodotSharpTools/StringExtensions.cs b/modules/mono/editor/GodotSharpTools/StringExtensions.cs index b66c86f8ce..b0436d2f18 100644 --- a/modules/mono/editor/GodotSharpTools/StringExtensions.cs +++ b/modules/mono/editor/GodotSharpTools/StringExtensions.cs @@ -36,7 +36,9 @@ namespace GodotSharpTools public static bool IsAbsolutePath(this string path) { - return path.StartsWith("/") || path.StartsWith("\\") || path.StartsWith(driveRoot); + return path.StartsWith("/", StringComparison.Ordinal) || + path.StartsWith("\\", StringComparison.Ordinal) || + path.StartsWith(driveRoot, StringComparison.Ordinal); } public static string CsvEscape(this string value, char delimiter = ',') diff --git a/modules/mono/editor/GodotSharpTools/packages.config b/modules/mono/editor/GodotSharpTools/packages.config new file mode 100644 index 0000000000..2c7cb0bd4b --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="DotNet.Glob" version="2.1.1" targetFramework="net45" /> +</packages>
\ No newline at end of file diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 710682d3aa..166b3e1324 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -47,7 +47,6 @@ #include "../utils/path_utils.h" #include "../utils/string_utils.h" #include "csharp_project.h" -#include "net_solution.h" #define CS_INDENT " " // 4 whitespaces @@ -401,32 +400,29 @@ void BindingsGenerator::_generate_global_constants(List<String> &p_output) { p_output.push_back(CLOSE_BLOCK); // end of namespace } -Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bool p_verbose_output) { +Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output) { verbose_output = p_verbose_output; + String proj_dir = p_solution_dir.plus_file(CORE_API_ASSEMBLY_NAME); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - if (!DirAccess::exists(p_output_dir)) { - Error err = da->make_dir_recursive(p_output_dir); + if (!DirAccess::exists(proj_dir)) { + Error err = da->make_dir_recursive(proj_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - da->change_dir(p_output_dir); + da->change_dir(proj_dir); da->make_dir("Core"); da->make_dir("ObjectType"); - String core_dir = path_join(p_output_dir, "Core"); - String obj_type_dir = path_join(p_output_dir, "ObjectType"); + String core_dir = path_join(proj_dir, "Core"); + String obj_type_dir = path_join(proj_dir, "ObjectType"); Vector<String> compile_items; - NETSolution solution(API_ASSEMBLY_NAME); - - if (!solution.set_path(p_output_dir)) - return ERR_FILE_NOT_FOUND; - // Generate source file for global scope constants and enums { List<String> constants_source; @@ -530,15 +526,15 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo compile_items.push_back(internal_methods_file); - String guid = CSharpProject::generate_core_api_project(p_output_dir, compile_items); + String guid = CSharpProject::generate_core_api_project(proj_dir, compile_items); - solution.add_new_project(API_ASSEMBLY_NAME, guid); + DotNetSolution::ProjectInfo proj_info; + proj_info.guid = guid; + proj_info.relpath = String(CORE_API_ASSEMBLY_NAME).plus_file(CORE_API_ASSEMBLY_NAME ".csproj"); + proj_info.configs.push_back("Debug"); + proj_info.configs.push_back("Release"); - Error sln_error = solution.save(); - if (sln_error != OK) { - ERR_PRINT("Could not to save .NET solution."); - return sln_error; - } + r_solution.add_new_project(CORE_API_ASSEMBLY_NAME, proj_info); if (verbose_output) OS::get_singleton()->print("The solution and C# project for the Core API was generated successfully\n"); @@ -546,32 +542,29 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo return OK; } -Error BindingsGenerator::generate_cs_editor_project(const String &p_output_dir, const String &p_core_dll_path, bool p_verbose_output) { +Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output) { verbose_output = p_verbose_output; + String proj_dir = p_solution_dir.plus_file(EDITOR_API_ASSEMBLY_NAME); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - if (!DirAccess::exists(p_output_dir)) { - Error err = da->make_dir_recursive(p_output_dir); + if (!DirAccess::exists(proj_dir)) { + Error err = da->make_dir_recursive(proj_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - da->change_dir(p_output_dir); + da->change_dir(proj_dir); da->make_dir("Core"); da->make_dir("ObjectType"); - String core_dir = path_join(p_output_dir, "Core"); - String obj_type_dir = path_join(p_output_dir, "ObjectType"); + String core_dir = path_join(proj_dir, "Core"); + String obj_type_dir = path_join(proj_dir, "ObjectType"); Vector<String> compile_items; - NETSolution solution(EDITOR_API_ASSEMBLY_NAME); - - if (!solution.set_path(p_output_dir)) - return ERR_FILE_NOT_FOUND; - for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) { const TypeInterface &itype = E.get(); @@ -632,19 +625,57 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_output_dir, compile_items.push_back(internal_methods_file); - String guid = CSharpProject::generate_editor_api_project(p_output_dir, p_core_dll_path, compile_items); + String guid = CSharpProject::generate_editor_api_project(proj_dir, "../" CORE_API_ASSEMBLY_NAME "/" CORE_API_ASSEMBLY_NAME ".csproj", compile_items); + + DotNetSolution::ProjectInfo proj_info; + proj_info.guid = guid; + proj_info.relpath = String(EDITOR_API_ASSEMBLY_NAME).plus_file(EDITOR_API_ASSEMBLY_NAME ".csproj"); + proj_info.configs.push_back("Debug"); + proj_info.configs.push_back("Release"); + + r_solution.add_new_project(EDITOR_API_ASSEMBLY_NAME, proj_info); + + if (verbose_output) + OS::get_singleton()->print("The solution and C# project for the Editor API was generated successfully\n"); + + return OK; +} - solution.add_new_project(EDITOR_API_ASSEMBLY_NAME, guid); +Error BindingsGenerator::generate_cs_api(const String &p_output_dir, bool p_verbose_output) { + + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); + + if (!DirAccess::exists(p_output_dir)) { + Error err = da->make_dir_recursive(p_output_dir); + ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); + } + + DotNetSolution solution(API_SOLUTION_NAME); + + if (!solution.set_path(p_output_dir)) + return ERR_FILE_NOT_FOUND; + + Error proj_err; + + proj_err = generate_cs_core_project(p_output_dir, solution, p_verbose_output); + if (proj_err != OK) { + ERR_PRINT("Generation of the Core API C# project failed"); + return proj_err; + } + + proj_err = generate_cs_editor_project(p_output_dir, solution, p_verbose_output); + if (proj_err != OK) { + ERR_PRINT("Generation of the Editor API C# project failed"); + return proj_err; + } Error sln_error = solution.save(); if (sln_error != OK) { - ERR_PRINT("Could not to save .NET solution."); + ERR_PRINT("Failed to save API solution"); return sln_error; } - if (verbose_output) - OS::get_singleton()->print("The solution and C# project for the Editor API was generated successfully\n"); - return OK; } @@ -2368,12 +2399,11 @@ void BindingsGenerator::initialize() { void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) { - const int NUM_OPTIONS = 3; + const int NUM_OPTIONS = 2; int options_left = NUM_OPTIONS; String mono_glue_option = "--generate-mono-glue"; - String cs_core_api_option = "--generate-cs-core-api"; - String cs_editor_api_option = "--generate-cs-editor-api"; + String cs_api_option = "--generate-cs-api"; verbose_output = true; @@ -2387,42 +2417,24 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) if (path_elem) { if (get_singleton()->generate_glue(path_elem->get()) != OK) - ERR_PRINT("Mono glue generation failed"); + ERR_PRINTS(mono_glue_option + ": Failed to generate mono glue"); elem = elem->next(); } else { - ERR_PRINTS("--generate-mono-glue: No output directory specified"); + ERR_PRINTS(mono_glue_option + ": No output directory specified"); } --options_left; - } else if (elem->get() == cs_core_api_option) { + } else if (elem->get() == cs_api_option) { const List<String>::Element *path_elem = elem->next(); if (path_elem) { - if (get_singleton()->generate_cs_core_project(path_elem->get()) != OK) - ERR_PRINT("Generation of solution and C# project for the Core API failed"); + if (get_singleton()->generate_cs_api(path_elem->get()) != OK) + ERR_PRINTS(cs_api_option + ": Failed to generate the C# API"); elem = elem->next(); } else { - ERR_PRINTS(cs_core_api_option + ": No output directory specified"); - } - - --options_left; - - } else if (elem->get() == cs_editor_api_option) { - - const List<String>::Element *path_elem = elem->next(); - - if (path_elem) { - if (path_elem->next()) { - if (get_singleton()->generate_cs_editor_project(path_elem->get(), path_elem->next()->get()) != OK) - ERR_PRINT("Generation of solution and C# project for the Editor API failed"); - elem = path_elem->next(); - } else { - ERR_PRINTS(cs_editor_api_option + ": No hint path for the Core API dll specified"); - } - } else { - ERR_PRINTS(cs_editor_api_option + ": No output directory specified"); + ERR_PRINTS(cs_api_option + ": No output directory specified"); } --options_left; @@ -2434,7 +2446,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) verbose_output = false; if (options_left != NUM_OPTIONS) - exit(0); + ::exit(0); } #endif diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 38cf99c294..91c474c4f0 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -32,6 +32,7 @@ #define BINDINGS_GENERATOR_H #include "core/class_db.h" +#include "dotnet_solution.h" #include "editor/doc/doc_data.h" #include "editor/editor_help.h" @@ -556,8 +557,9 @@ class BindingsGenerator { static BindingsGenerator *singleton; public: - Error generate_cs_core_project(const String &p_output_dir, bool p_verbose_output = true); - Error generate_cs_editor_project(const String &p_output_dir, const String &p_core_dll_path, bool p_verbose_output = true); + Error generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output = true); + Error generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output = true); + Error generate_cs_api(const String &p_output_dir, bool p_verbose_output = true); Error generate_glue(const String &p_output_dir); static uint32_t get_version(); diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp index 6f223b75a3..416108603b 100644 --- a/modules/mono/editor/csharp_project.cpp +++ b/modules/mono/editor/csharp_project.cpp @@ -30,11 +30,17 @@ #include "csharp_project.h" +#include "core/io/json.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" #include "core/os/os.h" #include "core/project_settings.h" +#include "../csharp_script.h" #include "../mono_gd/gd_mono_class.h" #include "../mono_gd/gd_mono_marshal.h" +#include "../utils/string_utils.h" +#include "script_class_parser.h" namespace CSharpProject { @@ -58,16 +64,16 @@ String generate_core_api_project(const String &p_dir, const Vector<String> &p_fi return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String(); } -String generate_editor_api_project(const String &p_dir, const String &p_core_dll_path, const Vector<String> &p_files) { +String generate_editor_api_project(const String &p_dir, const String &p_core_proj_path, const Vector<String> &p_files) { _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); Variant dir = p_dir; - Variant core_dll_path = p_core_dll_path; + Variant core_proj_path = p_core_proj_path; Variant compile_items = p_files; - const Variant *args[3] = { &dir, &core_dll_path, &compile_items }; + const Variant *args[3] = { &dir, &core_proj_path, &compile_items }; MonoException *exc = NULL; MonoObject *ret = klass->get_method("GenEditorApiProject", 3)->invoke(NULL, args, &exc); @@ -102,6 +108,9 @@ String generate_game_project(const String &p_dir, const String &p_name, const Ve void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) { + if (!GLOBAL_DEF("mono/project/auto_update_project", true)) + return; + _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils"); @@ -118,4 +127,118 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str ERR_FAIL(); } } + +Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path) { + + _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + + if (FileAccess::exists(p_output_path)) { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + Error rm_err = da->remove(p_output_path); + + ERR_EXPLAIN("Failed to remove old scripts metadata file"); + ERR_FAIL_COND_V(rm_err != OK, rm_err); + } + + GDMonoClass *project_utils = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils"); + + void *args[2] = { + GDMonoMarshal::mono_string_from_godot(p_project_path), + GDMonoMarshal::mono_string_from_godot("Compile") + }; + + MonoException *exc = NULL; + MonoArray *ret = (MonoArray *)project_utils->get_method("GetIncludeFiles", 2)->invoke_raw(NULL, args, &exc); + + if (exc) { + GDMonoUtils::debug_print_unhandled_exception(exc); + ERR_FAIL_V(FAILED); + } + + PoolStringArray project_files = GDMonoMarshal::mono_array_to_PoolStringArray(ret); + PoolStringArray::Read r = project_files.read(); + + Dictionary old_dict = CSharpLanguage::get_singleton()->get_scripts_metadata(); + Dictionary new_dict; + + for (int i = 0; i < project_files.size(); i++) { + const String &project_file = ("res://" + r[i]).simplify_path(); + + uint64_t modified_time = FileAccess::get_modified_time(project_file); + + const Variant *old_file_var = old_dict.getptr(project_file); + if (old_file_var) { + Dictionary old_file_dict = old_file_var->operator Dictionary(); + + if (old_file_dict["modified_time"].operator uint64_t() == modified_time) { + // No changes so no need to parse again + new_dict[project_file] = old_file_dict; + continue; + } + } + + ScriptClassParser scp; + Error err = scp.parse_file(project_file); + if (err != OK) { + ERR_PRINTS("Parse error: " + scp.get_error()); + ERR_EXPLAIN("Failed to determine namespace and class for script: " + project_file); + ERR_FAIL_V(err); + } + + Vector<ScriptClassParser::ClassDecl> classes = scp.get_classes(); + + bool found = false; + Dictionary class_dict; + + String search_name = project_file.get_file().get_basename(); + + for (int j = 0; j < classes.size(); j++) { + const ScriptClassParser::ClassDecl &class_decl = classes[j]; + + if (class_decl.base.size() == 0) + continue; // Does not inherit nor implement anything, so it can't be a script class + + String class_cmp; + + if (class_decl.nested) { + class_cmp = class_decl.name.get_slice(".", class_decl.name.get_slice_count(".") - 1); + } else { + class_cmp = class_decl.name; + } + + if (class_cmp != search_name) + continue; + + class_dict["namespace"] = class_decl.namespace_; + class_dict["class_name"] = class_decl.name; + class_dict["nested"] = class_decl.nested; + + found = true; + break; + } + + if (found) { + Dictionary file_dict; + file_dict["modified_time"] = modified_time; + file_dict["class"] = class_dict; + new_dict[project_file] = file_dict; + } + } + + if (new_dict.size()) { + String json = JSON::print(new_dict, "", false); + + Error ferr; + FileAccess *f = FileAccess::open(p_output_path, FileAccess::WRITE, &ferr); + ERR_EXPLAIN("Cannot open file for writing: " + p_output_path); + ERR_FAIL_COND_V(ferr != OK, ferr); + f->store_string(json); + f->flush(); + f->close(); + memdelete(f); + } + + return OK; +} + } // namespace CSharpProject diff --git a/modules/mono/editor/csharp_project.h b/modules/mono/editor/csharp_project.h index d852139de0..aeeeff50f0 100644 --- a/modules/mono/editor/csharp_project.h +++ b/modules/mono/editor/csharp_project.h @@ -40,6 +40,9 @@ String generate_editor_api_project(const String &p_dir, const String &p_core_dll String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files = Vector<String>()); void add_item(const String &p_project_path, const String &p_item_type, const String &p_include); + +Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path); + } // namespace CSharpProject #endif // CSHARP_PROJECT_H diff --git a/modules/mono/editor/net_solution.cpp b/modules/mono/editor/dotnet_solution.cpp index 8bbd376c9a..ab92e2e378 100644 --- a/modules/mono/editor/net_solution.cpp +++ b/modules/mono/editor/dotnet_solution.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* net_solution.cpp */ +/* dotnet_solution.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "net_solution.h" +#include "dotnet_solution.h" #include "core/os/dir_access.h" #include "core/os/file_access.h" @@ -52,33 +52,32 @@ #define PROJECT_DECLARATION "Project(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"%0\", \"%1\", \"{%2}\"\nEndProject" -#define SOLUTION_PLATFORMS_CONFIG "\t\%0|Any CPU = %0|Any CPU" +#define SOLUTION_PLATFORMS_CONFIG "\t%0|Any CPU = %0|Any CPU" #define PROJECT_PLATFORMS_CONFIG \ "\t\t{%0}.%1|Any CPU.ActiveCfg = %1|Any CPU\n" \ "\t\t{%0}.%1|Any CPU.Build.0 = %1|Any CPU" -void NETSolution::add_new_project(const String &p_name, const String &p_guid, const Vector<String> &p_extra_configs) { - if (projects.has(p_name)) - WARN_PRINT("Overriding existing project."); - - ProjectInfo procinfo; - procinfo.guid = p_guid; +void DotNetSolution::add_new_project(const String &p_name, const ProjectInfo &p_project_info) { + projects[p_name] = p_project_info; +} - procinfo.configs.push_back("Debug"); - procinfo.configs.push_back("Release"); +bool DotNetSolution::has_project(const String &p_name) const { + return projects.find(p_name) != NULL; +} - for (int i = 0; i < p_extra_configs.size(); i++) { - procinfo.configs.push_back(p_extra_configs[i]); - } +const DotNetSolution::ProjectInfo &DotNetSolution::get_project_info(const String &p_name) const { + return projects[p_name]; +} - projects[p_name] = procinfo; +bool DotNetSolution::remove_project(const String &p_name) { + return projects.erase(p_name); } -Error NETSolution::save() { +Error DotNetSolution::save() { bool dir_exists = DirAccess::exists(path); ERR_EXPLAIN("The directory does not exist."); - ERR_FAIL_COND_V(!dir_exists, ERR_FILE_BAD_PATH); + ERR_FAIL_COND_V(!dir_exists, ERR_FILE_NOT_FOUND); String projs_decl; String sln_platform_cfg; @@ -86,34 +85,40 @@ Error NETSolution::save() { for (Map<String, ProjectInfo>::Element *E = projects.front(); E; E = E->next()) { const String &name = E->key(); - const ProjectInfo &procinfo = E->value(); + const ProjectInfo &proj_info = E->value(); - projs_decl += sformat(PROJECT_DECLARATION, name, name + ".csproj", procinfo.guid); + bool is_front = E == projects.front(); - for (int i = 0; i < procinfo.configs.size(); i++) { - const String &config = procinfo.configs[i]; + if (!is_front) + projs_decl += "\n"; - if (i != 0) { + projs_decl += sformat(PROJECT_DECLARATION, name, proj_info.relpath.replace("/", "\\"), proj_info.guid); + + for (int i = 0; i < proj_info.configs.size(); i++) { + const String &config = proj_info.configs[i]; + + if (i != 0 || !is_front) { sln_platform_cfg += "\n"; proj_platform_cfg += "\n"; } sln_platform_cfg += sformat(SOLUTION_PLATFORMS_CONFIG, config); - proj_platform_cfg += sformat(PROJECT_PLATFORMS_CONFIG, procinfo.guid, config); + proj_platform_cfg += sformat(PROJECT_PLATFORMS_CONFIG, proj_info.guid, config); } } String content = sformat(SOLUTION_TEMPLATE, projs_decl, sln_platform_cfg, proj_platform_cfg); - FileAccessRef file = FileAccess::open(path_join(path, name + ".sln"), FileAccess::WRITE); - ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE); + FileAccess *file = FileAccess::open(path_join(path, name + ".sln"), FileAccess::WRITE); + ERR_FAIL_NULL_V(file, ERR_FILE_CANT_WRITE); file->store_string(content); file->close(); + memdelete(file); return OK; } -bool NETSolution::set_path(const String &p_existing_path) { +bool DotNetSolution::set_path(const String &p_existing_path) { if (p_existing_path.is_abs_path()) { path = p_existing_path; } else { @@ -126,6 +131,10 @@ bool NETSolution::set_path(const String &p_existing_path) { return true; } -NETSolution::NETSolution(const String &p_name) { +String DotNetSolution::get_path() { + return path; +} + +DotNetSolution::DotNetSolution(const String &p_name) { name = p_name; } diff --git a/modules/mono/editor/net_solution.h b/modules/mono/editor/dotnet_solution.h index bdff24af0b..629605ad18 100644 --- a/modules/mono/editor/net_solution.h +++ b/modules/mono/editor/dotnet_solution.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* net_solution.h */ +/* dotnet_solution.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -34,23 +34,28 @@ #include "core/map.h" #include "core/ustring.h" -struct NETSolution { +struct DotNetSolution { String name; - void add_new_project(const String &p_name, const String &p_guid, const Vector<String> &p_extra_configs = Vector<String>()); + struct ProjectInfo { + String guid; + String relpath; // Must be relative to the solution directory + Vector<String> configs; + }; + + void add_new_project(const String &p_name, const ProjectInfo &p_project_info); + bool has_project(const String &p_name) const; + const ProjectInfo &get_project_info(const String &p_name) const; + bool remove_project(const String &p_name); Error save(); bool set_path(const String &p_existing_path); + String get_path(); - NETSolution(const String &p_name); + DotNetSolution(const String &p_name); private: - struct ProjectInfo { - String guid; - Vector<String> configs; - }; - String path; Map<String, ProjectInfo> projects; }; diff --git a/modules/mono/editor/godotsharp_builds.cpp b/modules/mono/editor/godotsharp_builds.cpp index b504cfe712..0f12fc5243 100644 --- a/modules/mono/editor/godotsharp_builds.cpp +++ b/modules/mono/editor/godotsharp_builds.cpp @@ -39,6 +39,7 @@ #include "../mono_gd/gd_mono_marshal.h" #include "../utils/path_utils.h" #include "bindings_generator.h" +#include "csharp_project.h" #include "godotsharp_editor.h" #define PROP_NAME_MSBUILD_MONO "MSBuild (Mono)" @@ -226,20 +227,24 @@ void GodotSharpBuilds::show_build_error_dialog(const String &p_message) { MonoBottomPanel::get_singleton()->show_build_tab(); } -bool GodotSharpBuilds::build_api_sln(const String &p_name, const String &p_api_sln_dir, const String &p_config) { +bool GodotSharpBuilds::build_api_sln(const String &p_api_sln_dir, const String &p_config) { - String api_sln_file = p_api_sln_dir.plus_file(p_name + ".sln"); - String api_assembly_dir = p_api_sln_dir.plus_file("bin").plus_file(p_config); - String api_assembly_file = api_assembly_dir.plus_file(p_name + ".dll"); + String api_sln_file = p_api_sln_dir.plus_file(API_SOLUTION_NAME ".sln"); - if (!FileAccess::exists(api_assembly_file)) { + String core_api_assembly_dir = p_api_sln_dir.plus_file(CORE_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config); + String core_api_assembly_file = core_api_assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + + String editor_api_assembly_dir = p_api_sln_dir.plus_file(EDITOR_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config); + String editor_api_assembly_file = editor_api_assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + + if (!FileAccess::exists(core_api_assembly_file) || !FileAccess::exists(editor_api_assembly_file)) { MonoBuildInfo api_build_info(api_sln_file, p_config); // TODO Replace this global NoWarn with '#pragma warning' directives on generated files, // once we start to actively document manually maintained C# classes api_build_info.custom_props.push_back("NoWarn=1591"); // Ignore missing documentation warnings if (!GodotSharpBuilds::get_singleton()->build(api_build_info)) { - show_build_error_dialog("Failed to build " + p_name + " solution."); + show_build_error_dialog("Failed to build " API_SOLUTION_NAME " solution."); return false; } } @@ -268,7 +273,7 @@ bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String & if (!FileAccess::exists(assembly_dst) || FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) || GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) { - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); String xml_file = p_assembly_name + ".xml"; if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK) @@ -280,8 +285,6 @@ bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String & Error err = da->copy(assembly_src, assembly_dst); - memdelete(da); - if (err != OK) { show_build_error_dialog("Failed to copy " + assembly_file); return false; @@ -303,9 +306,9 @@ String GodotSharpBuilds::_api_folder_name(APIAssembly::Type p_api_type) { "_" + String::num_uint64(CS_GLUE_VERSION); } -bool GodotSharpBuilds::make_api_sln(APIAssembly::Type p_api_type) { +bool GodotSharpBuilds::make_api_assembly(APIAssembly::Type p_api_type) { - String api_name = p_api_type == APIAssembly::API_CORE ? API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; + String api_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; String editor_prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir(); String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); @@ -318,55 +321,35 @@ bool GodotSharpBuilds::make_api_sln(APIAssembly::Type p_api_type) { String api_build_config = "Release"; - EditorProgress pr("mono_build_release_" + api_name, "Building " + api_name + " solution...", 3); + EditorProgress pr("mono_build_release_" API_SOLUTION_NAME, "Building " API_SOLUTION_NAME " solution...", 3); - pr.step("Generating " + api_name + " solution", 0); + pr.step("Generating " API_SOLUTION_NAME " solution", 0); - String core_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir() - .plus_file(_api_folder_name(APIAssembly::API_CORE)) - .plus_file(API_ASSEMBLY_NAME); - String editor_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir() - .plus_file(_api_folder_name(APIAssembly::API_EDITOR)) - .plus_file(EDITOR_API_ASSEMBLY_NAME); + String api_sln_dir = GodotSharpDirs::get_mono_solutions_dir() + .plus_file(_api_folder_name(APIAssembly::API_CORE)); - String api_sln_dir = p_api_type == APIAssembly::API_CORE ? core_api_sln_dir : editor_api_sln_dir; - String api_sln_file = api_sln_dir.plus_file(api_name + ".sln"); + String api_sln_file = api_sln_dir.plus_file(API_SOLUTION_NAME ".sln"); if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) { - String core_api_assembly; - - if (p_api_type == APIAssembly::API_EDITOR) { - core_api_assembly = core_api_sln_dir.plus_file("bin") - .plus_file(api_build_config) - .plus_file(API_ASSEMBLY_NAME ".dll"); - } - -#ifndef DEBUG_METHODS_ENABLED -#error "How am I supposed to generate the bindings?" -#endif - BindingsGenerator *gen = BindingsGenerator::get_singleton(); bool gen_verbose = OS::get_singleton()->is_stdout_verbose(); - Error err = p_api_type == APIAssembly::API_CORE ? - gen->generate_cs_core_project(api_sln_dir, gen_verbose) : - gen->generate_cs_editor_project(api_sln_dir, core_api_assembly, gen_verbose); - + Error err = gen->generate_cs_api(api_sln_dir, gen_verbose); if (err != OK) { - show_build_error_dialog("Failed to generate " + api_name + " solution. Error: " + itos(err)); + show_build_error_dialog("Failed to generate " API_SOLUTION_NAME " solution. Error: " + itos(err)); return false; } } - pr.step("Building " + api_name + " solution", 1); + pr.step("Building " API_SOLUTION_NAME " solution", 1); - if (!GodotSharpBuilds::build_api_sln(api_name, api_sln_dir, api_build_config)) + if (!GodotSharpBuilds::build_api_sln(api_sln_dir, api_build_config)) return false; pr.step("Copying " + api_name + " assembly", 2); // Copy the built assembly to the assemblies directory - String api_assembly_dir = api_sln_dir.plus_file("bin").plus_file(api_build_config); + String api_assembly_dir = api_sln_dir.plus_file(api_name).plus_file("bin").plus_file(api_build_config); if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name, p_api_type)) return false; @@ -378,10 +361,10 @@ bool GodotSharpBuilds::build_project_blocking(const String &p_config) { if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) return true; // No solution to build - if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_CORE)) + if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) return false; - if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_EDITOR)) + if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) return false; EditorProgress pr("mono_project_debug_build", "Building project solution...", 1); @@ -398,6 +381,20 @@ bool GodotSharpBuilds::build_project_blocking(const String &p_config) { bool GodotSharpBuilds::editor_build_callback() { + String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor"); + String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player"); + + Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor); + ERR_FAIL_COND_V(metadata_err != OK, false); + + if (FileAccess::exists(scripts_metadata_path_editor)) { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player); + + ERR_EXPLAIN("Failed to copy scripts metadata file"); + ERR_FAIL_COND_V(copy_err != OK, false); + } + return build_project_blocking("Tools"); } diff --git a/modules/mono/editor/godotsharp_builds.h b/modules/mono/editor/godotsharp_builds.h index c6dc6b6236..7f38b0aa49 100644 --- a/modules/mono/editor/godotsharp_builds.h +++ b/modules/mono/editor/godotsharp_builds.h @@ -84,10 +84,10 @@ public: bool build(const MonoBuildInfo &p_build_info); bool build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL); - static bool build_api_sln(const String &p_name, const String &p_api_sln_dir, const String &p_config); + static bool build_api_sln(const String &p_api_sln_dir, const String &p_config); static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type); - static bool make_api_sln(APIAssembly::Type p_api_type); + static bool make_api_assembly(APIAssembly::Type p_api_type); static bool build_project_blocking(const String &p_config); diff --git a/modules/mono/editor/godotsharp_editor.cpp b/modules/mono/editor/godotsharp_editor.cpp index 9df4e10266..f27511ad5e 100644 --- a/modules/mono/editor/godotsharp_editor.cpp +++ b/modules/mono/editor/godotsharp_editor.cpp @@ -42,8 +42,8 @@ #include "../utils/path_utils.h" #include "bindings_generator.h" #include "csharp_project.h" +#include "dotnet_solution.h" #include "godotsharp_export.h" -#include "net_solution.h" #ifdef OSX_ENABLED #include "../utils/osx_utils.h" @@ -71,17 +71,21 @@ bool GodotSharpEditor::_create_project_solution() { if (guid.length()) { - NETSolution solution(name); + DotNetSolution solution(name); if (!solution.set_path(path)) { show_error_dialog(TTR("Failed to create solution.")); return false; } - Vector<String> extra_configs; - extra_configs.push_back("Tools"); + DotNetSolution::ProjectInfo proj_info; + proj_info.guid = guid; + proj_info.relpath = name + ".csproj"; + proj_info.configs.push_back("Debug"); + proj_info.configs.push_back("Release"); + proj_info.configs.push_back("Tools"); - solution.add_new_project(name, guid, extra_configs); + solution.add_new_project(name, proj_info); Error sln_error = solution.save(); @@ -90,10 +94,10 @@ bool GodotSharpEditor::_create_project_solution() { return false; } - if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_CORE)) + if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) return false; - if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_EDITOR)) + if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) return false; pr.step(TTR("Done")); @@ -122,15 +126,15 @@ void GodotSharpEditor::_make_api_solutions_if_needed_impl() { // If the project has a solution and C# project make sure the API assemblies are present and up to date String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); - if (!FileAccess::exists(res_assemblies_dir.plus_file(API_ASSEMBLY_NAME ".dll")) || + if (!FileAccess::exists(res_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll")) || GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) { - if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_CORE)) + if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) return; } if (!FileAccess::exists(res_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll")) || GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) { - if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_EDITOR)) + if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) return; // Redundant? I don't think so } } diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index 933ede93dc..34c710320a 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -37,6 +37,7 @@ #include "../godotsharp_dirs.h" #include "../mono_gd/gd_mono_class.h" #include "../mono_gd/gd_mono_marshal.h" +#include "csharp_project.h" #include "godotsharp_builds.h" static MonoString *godot_icall_GodotSharpExport_GetTemplatesDir() { @@ -86,6 +87,12 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug String build_config = p_debug ? "Debug" : "Release"; + String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata." + String(p_debug ? "debug" : "release")); + Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path); + ERR_FAIL_COND(metadata_err != OK); + + ERR_FAIL_COND(!_add_file(scripts_metadata_path, scripts_metadata_path)); + ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config)); // Add dependency assemblies @@ -110,7 +117,7 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug GDMonoAssembly *scripts_assembly = NULL; bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name, - project_dll_src_dir, &scripts_assembly, /* refonly: */ true); + project_dll_src_path, &scripts_assembly, /* refonly: */ true); ERR_EXPLAIN("Cannot load refonly assembly: " + project_dll_name); ERR_FAIL_COND(!load_success); @@ -124,7 +131,7 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug for (Map<String, String>::Element *E = dependencies.front(); E; E = E->next()) { String depend_src_path = E->value(); String depend_dst_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(depend_src_path.get_file()); - ERR_FAIL_COND(!_add_assembly(depend_src_path, depend_dst_path)); + ERR_FAIL_COND(!_add_file(depend_src_path, depend_dst_path)); } // Mono specific export template extras (data dir) @@ -155,7 +162,7 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug } } -bool GodotSharpExport::_add_assembly(const String &p_src_path, const String &p_dst_path) { +bool GodotSharpExport::_add_file(const String &p_src_path, const String &p_dst_path, bool p_remap) { FileAccessRef f = FileAccess::open(p_src_path, FileAccess::READ); ERR_FAIL_COND_V(!f, false); @@ -164,7 +171,7 @@ bool GodotSharpExport::_add_assembly(const String &p_src_path, const String &p_d data.resize(f->get_len()); f->get_buffer(data.ptrw(), data.size()); - add_file(p_dst_path, data, false); + add_file(p_dst_path, data, p_remap); return true; } @@ -191,21 +198,21 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c if (has_extension) { path = search_dir.plus_file(ref_name); if (FileAccess::exists(path)) { - GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), search_dir, ref_aname, &ref_assembly, true); + GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), path, &ref_assembly, true); if (ref_assembly != NULL) break; } } else { path = search_dir.plus_file(ref_name + ".dll"); if (FileAccess::exists(path)) { - GDMono::get_singleton()->load_assembly_from(ref_name, search_dir, ref_aname, &ref_assembly, true); + GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true); if (ref_assembly != NULL) break; } path = search_dir.plus_file(ref_name + ".exe"); if (FileAccess::exists(path)) { - GDMono::get_singleton()->load_assembly_from(ref_name, search_dir, ref_aname, &ref_assembly, true); + GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true); if (ref_assembly != NULL) break; } diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h index f007016578..10d4375567 100644 --- a/modules/mono/editor/godotsharp_export.h +++ b/modules/mono/editor/godotsharp_export.h @@ -41,7 +41,7 @@ class GodotSharpExport : public EditorExportPlugin { MonoAssemblyName *aname_prealloc; - bool _add_assembly(const String &p_src_path, const String &p_dst_path); + bool _add_file(const String &p_src_path, const String &p_dst_path, bool p_remap = false); Error _get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies); diff --git a/modules/mono/editor/mono_bottom_panel.cpp b/modules/mono/editor/mono_bottom_panel.cpp index 0ac59a1be8..d7bfa54aba 100644 --- a/modules/mono/editor/mono_bottom_panel.cpp +++ b/modules/mono/editor/mono_bottom_panel.cpp @@ -31,6 +31,8 @@ #include "mono_bottom_panel.h" #include "../csharp_script.h" +#include "../godotsharp_dirs.h" +#include "csharp_project.h" #include "godotsharp_editor.h" MonoBottomPanel *MonoBottomPanel::singleton = NULL; @@ -148,6 +150,10 @@ void MonoBottomPanel::_errors_toggled(bool p_pressed) { void MonoBottomPanel::_build_project_pressed() { + String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor"); + Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path); + ERR_FAIL_COND(metadata_err != OK); + GodotSharpBuilds::get_singleton()->build_project_blocking("Tools"); MonoReloadNode::get_singleton()->restart_reload_timer(); diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp new file mode 100644 index 0000000000..bc8eed81cf --- /dev/null +++ b/modules/mono/editor/script_class_parser.cpp @@ -0,0 +1,542 @@ +#include "script_class_parser.h" + +#include "core/map.h" +#include "core/os/os.h" + +#include "../utils/string_utils.h" + +const char *ScriptClassParser::token_names[ScriptClassParser::TK_MAX] = { + "[", + "]", + "{", + "}", + ".", + ":", + ",", + "Symbol", + "Identifier", + "String", + "Number", + "<", + ">", + "EOF", + "Error" +}; + +String ScriptClassParser::get_token_name(ScriptClassParser::Token p_token) { + + ERR_FAIL_INDEX_V(p_token, TK_MAX, "<error>"); + return token_names[p_token]; +} + +ScriptClassParser::Token ScriptClassParser::get_token() { + + while (true) { + switch (code[idx]) { + case '\n': { + line++; + idx++; + break; + }; + case 0: { + return TK_EOF; + } break; + case '{': { + idx++; + return TK_CURLY_BRACKET_OPEN; + }; + case '}': { + idx++; + return TK_CURLY_BRACKET_CLOSE; + }; + case '[': { + idx++; + return TK_BRACKET_OPEN; + }; + case ']': { + idx++; + return TK_BRACKET_CLOSE; + }; + case '<': { + idx++; + return TK_OP_LESS; + }; + case '>': { + idx++; + return TK_OP_GREATER; + }; + case ':': { + idx++; + return TK_COLON; + }; + case ',': { + idx++; + return TK_COMMA; + }; + case '.': { + idx++; + return TK_PERIOD; + }; + case '#': { + //compiler directive + while (code[idx] != '\n' && code[idx] != 0) { + idx++; + } + continue; + } break; + case '/': { + switch (code[idx + 1]) { + case '*': { // block comment + idx += 2; + while (true) { + if (code[idx] == 0) { + error_str = "Unterminated comment"; + error = true; + return TK_ERROR; + } else if (code[idx] == '*' && code[idx + 1] == '/') { + idx += 2; + break; + } else if (code[idx] == '\n') { + line++; + } + + idx++; + } + + } break; + case '/': { // line comment skip + while (code[idx] != '\n' && code[idx] != 0) { + idx++; + } + + } break; + default: { + value = "/"; + idx++; + return TK_SYMBOL; + } + } + + continue; // a comment + } break; + case '\'': + case '"': { + bool verbatim = idx != 0 && code[idx - 1] == '@'; + + CharType begin_str = code[idx]; + idx++; + String tk_string = String(); + while (true) { + if (code[idx] == 0) { + error_str = "Unterminated String"; + error = true; + return TK_ERROR; + } else if (code[idx] == begin_str) { + if (verbatim && code[idx + 1] == '"') { // `""` is verbatim string's `\"` + idx += 2; // skip next `"` as well + continue; + } + + idx += 1; + break; + } else if (code[idx] == '\\' && !verbatim) { + //escaped characters... + idx++; + CharType next = code[idx]; + if (next == 0) { + error_str = "Unterminated String"; + error = true; + return TK_ERROR; + } + CharType res = 0; + + switch (next) { + case 'b': res = 8; break; + case 't': res = 9; break; + case 'n': res = 10; break; + case 'f': res = 12; break; + case 'r': + res = 13; + break; + case '\"': res = '\"'; break; + case '\\': + res = '\\'; + break; + default: { + res = next; + } break; + } + + tk_string += res; + + } else { + if (code[idx] == '\n') + line++; + tk_string += code[idx]; + } + idx++; + } + + value = tk_string; + + return TK_STRING; + } break; + default: { + if (code[idx] <= 32) { + idx++; + break; + } + + if ((code[idx] >= 33 && code[idx] <= 47) || (code[idx] >= 58 && code[idx] <= 63) || (code[idx] >= 91 && code[idx] <= 94) || code[idx] == 96 || (code[idx] >= 123 && code[idx] <= 127)) { + value = String::chr(code[idx]); + idx++; + return TK_SYMBOL; + } + + if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) { + //a number + const CharType *rptr; + double number = String::to_double(&code[idx], &rptr); + idx += (rptr - &code[idx]); + value = number; + return TK_NUMBER; + + } else if ((code[idx] == '@' && code[idx + 1] != '"') || code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) { + String id; + + id += code[idx]; + idx++; + + while (code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || (code[idx] >= '0' && code[idx] <= '9') || code[idx] > 127) { + id += code[idx]; + idx++; + } + + value = id; + return TK_IDENTIFIER; + } else if (code[idx] == '@' && code[idx + 1] == '"') { + // begin of verbatim string + idx++; + } else { + error_str = "Unexpected character."; + error = true; + return TK_ERROR; + } + } + } + } +} + +Error ScriptClassParser::_skip_generic_type_params() { + + Token tk; + + while (true) { + tk = get_token(); + + if (tk == TK_IDENTIFIER) { + tk = get_token(); + + if (tk == TK_PERIOD) { + while (true) { + tk = get_token(); + + if (tk != TK_IDENTIFIER) { + error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + + tk = get_token(); + + if (tk != TK_PERIOD) + break; + } + } + + if (tk == TK_OP_LESS) { + Error err = _skip_generic_type_params(); + if (err) + return err; + continue; + } else if (tk != TK_COMMA) { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + } else if (tk == TK_OP_LESS) { + error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found " + get_token_name(TK_OP_LESS); + error = true; + return ERR_PARSE_ERROR; + } else if (tk == TK_OP_GREATER) { + return OK; + } else { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + } +} + +Error ScriptClassParser::_parse_type_full_name(String &r_full_name) { + + Token tk = get_token(); + + if (tk != TK_IDENTIFIER) { + error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + + r_full_name += String(value); + + if (code[idx] != '.') // We only want to take the next token if it's a period + return OK; + + tk = get_token(); + + CRASH_COND(tk != TK_PERIOD); // Assertion + + r_full_name += "."; + + return _parse_type_full_name(r_full_name); +} + +Error ScriptClassParser::_parse_class_base(Vector<String> &r_base) { + + String name; + + Error err = _parse_type_full_name(name); + if (err) + return err; + + Token tk = get_token(); + + if (tk == TK_OP_LESS) { + // We don't add it to the base list if it's generic + Error err = _skip_generic_type_params(); + if (err) + return err; + } else if (tk == TK_COMMA) { + Error err = _parse_class_base(r_base); + if (err) + return err; + r_base.push_back(name); + } else if (tk == TK_CURLY_BRACKET_OPEN) { + r_base.push_back(name); + } else { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + + return OK; +} + +Error ScriptClassParser::_parse_namespace_name(String &r_name, int &r_curly_stack) { + + Token tk = get_token(); + + if (tk == TK_IDENTIFIER) { + r_name += String(value); + } else { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + + tk = get_token(); + + if (tk == TK_PERIOD) { + r_name += "."; + return _parse_namespace_name(r_name, r_curly_stack); + } else if (tk == TK_CURLY_BRACKET_OPEN) { + r_curly_stack++; + return OK; + } else { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } +} + +Error ScriptClassParser::parse(const String &p_code) { + + code = p_code; + idx = 0; + line = 0; + error_str = String(); + error = false; + value = Variant(); + classes.clear(); + + Token tk = get_token(); + + Map<int, NameDecl> name_stack; + int curly_stack = 0; + int type_curly_stack = 0; + + while (!error && tk != TK_EOF) { + if (tk == TK_IDENTIFIER && String(value) == "class") { + tk = get_token(); + + if (tk == TK_IDENTIFIER) { + String name = value; + int at_level = type_curly_stack; + + ClassDecl class_decl; + + for (Map<int, NameDecl>::Element *E = name_stack.front(); E; E = E->next()) { + const NameDecl &name_decl = E->value(); + + if (name_decl.type == NameDecl::NAMESPACE_DECL) { + if (E != name_stack.front()) + class_decl.namespace_ += "."; + class_decl.namespace_ += name_decl.name; + } else { + class_decl.name += name_decl.name + "."; + } + } + + class_decl.name += name; + class_decl.nested = type_curly_stack > 0; + + bool generic = false; + + while (true) { + tk = get_token(); + + if (tk == TK_COLON) { + Error err = _parse_class_base(class_decl.base); + if (err) + return err; + + curly_stack++; + type_curly_stack++; + + break; + } else if (tk == TK_CURLY_BRACKET_OPEN) { + curly_stack++; + type_curly_stack++; + break; + } else if (tk == TK_OP_LESS && !generic) { + generic = true; + + Error err = _skip_generic_type_params(); + if (err) + return err; + } else { + error_str = "Unexpected token: " + get_token_name(tk); + error = true; + return ERR_PARSE_ERROR; + } + } + + NameDecl name_decl; + name_decl.name = name; + name_decl.type = NameDecl::CLASS_DECL; + name_stack[at_level] = name_decl; + + if (!generic) { // no generics, thanks + classes.push_back(class_decl); + } else if (OS::get_singleton()->is_stdout_verbose()) { + String full_name = class_decl.namespace_; + if (full_name.length()) + full_name += "."; + full_name += class_decl.name; + OS::get_singleton()->print(String("Ignoring generic class declaration: " + class_decl.name).utf8()); + } + } + } else if (tk == TK_IDENTIFIER && String(value) == "struct") { + String name; + int at_level = type_curly_stack; + while (true) { + tk = get_token(); + if (tk == TK_IDENTIFIER && name.empty()) { + name = String(value); + } else if (tk == TK_CURLY_BRACKET_OPEN) { + if (name.empty()) { + error_str = "Expected " + get_token_name(TK_IDENTIFIER) + " after keyword `struct`, found " + get_token_name(TK_CURLY_BRACKET_OPEN); + error = true; + return ERR_PARSE_ERROR; + } + + curly_stack++; + type_curly_stack++; + break; + } else if (tk == TK_EOF) { + error_str = "Expected " + get_token_name(TK_CURLY_BRACKET_OPEN) + " after struct decl, found " + get_token_name(TK_EOF); + error = true; + return ERR_PARSE_ERROR; + } + } + + NameDecl name_decl; + name_decl.name = name; + name_decl.type = NameDecl::STRUCT_DECL; + name_stack[at_level] = name_decl; + } else if (tk == TK_IDENTIFIER && String(value) == "namespace") { + if (type_curly_stack > 0) { + error_str = "Found namespace nested inside type."; + error = true; + return ERR_PARSE_ERROR; + } + + String name; + int at_level = curly_stack; + + Error err = _parse_namespace_name(name, curly_stack); + if (err) + return err; + + NameDecl name_decl; + name_decl.name = name; + name_decl.type = NameDecl::NAMESPACE_DECL; + name_stack[at_level] = name_decl; + } else if (tk == TK_CURLY_BRACKET_OPEN) { + curly_stack++; + } else if (tk == TK_CURLY_BRACKET_CLOSE) { + curly_stack--; + if (name_stack.has(curly_stack)) { + if (name_stack[curly_stack].type != NameDecl::NAMESPACE_DECL) + type_curly_stack--; + name_stack.erase(curly_stack); + } + } + + tk = get_token(); + } + + if (!error && tk == TK_EOF && curly_stack > 0) { + error_str = "Reached EOF with missing close curly brackets."; + error = true; + } + + if (error) + return ERR_PARSE_ERROR; + + return OK; +} + +Error ScriptClassParser::parse_file(const String &p_filepath) { + + String source; + + Error ferr = read_all_file_utf8(p_filepath, source); + if (ferr != OK) { + if (ferr == ERR_INVALID_DATA) { + ERR_EXPLAIN("File '" + p_filepath + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); + } + ERR_FAIL_V(ferr); + } + + return parse(source); +} + +String ScriptClassParser::get_error() { + return error_str; +} + +Vector<ScriptClassParser::ClassDecl> ScriptClassParser::get_classes() { + return classes; +} diff --git a/modules/mono/editor/script_class_parser.h b/modules/mono/editor/script_class_parser.h new file mode 100644 index 0000000000..1e174c28a9 --- /dev/null +++ b/modules/mono/editor/script_class_parser.h @@ -0,0 +1,79 @@ +#ifndef SCRIPT_CLASS_PARSER_H +#define SCRIPT_CLASS_PARSER_H + +#include "core/ustring.h" +#include "core/variant.h" +#include "core/vector.h" + +class ScriptClassParser { + +public: + struct NameDecl { + enum Type { + NAMESPACE_DECL, + CLASS_DECL, + STRUCT_DECL + }; + + String name; + Type type; + }; + + struct ClassDecl { + String name; + String namespace_; + Vector<String> base; + bool nested; + bool has_script_attr; + }; + +private: + String code; + int idx; + int line; + String error_str; + bool error; + Variant value; + + Vector<ClassDecl> classes; + + enum Token { + TK_BRACKET_OPEN, + TK_BRACKET_CLOSE, + TK_CURLY_BRACKET_OPEN, + TK_CURLY_BRACKET_CLOSE, + TK_PERIOD, + TK_COLON, + TK_COMMA, + TK_SYMBOL, + TK_IDENTIFIER, + TK_STRING, + TK_NUMBER, + TK_OP_LESS, + TK_OP_GREATER, + TK_EOF, + TK_ERROR, + TK_MAX + }; + + static const char *token_names[TK_MAX]; + static String get_token_name(Token p_token); + + Token get_token(); + + Error _skip_generic_type_params(); + + Error _parse_type_full_name(String &r_full_name); + Error _parse_class_base(Vector<String> &r_base); + Error _parse_namespace_name(String &r_name, int &r_curly_stack); + +public: + Error parse(const String &p_code); + Error parse_file(const String &p_filepath); + + String get_error(); + + Vector<ClassDecl> get_classes(); +}; + +#endif // SCRIPT_CLASS_PARSER_H diff --git a/modules/mono/glue/Managed/Files/GD.cs b/modules/mono/glue/Managed/Files/GD.cs index e4818e186c..75a35a9eea 100644 --- a/modules/mono/glue/Managed/Files/GD.cs +++ b/modules/mono/glue/Managed/Files/GD.cs @@ -70,6 +70,16 @@ namespace Godot return ResourceLoader.Load<T>(path); } + public static void PushError(string message) + { + godot_icall_GD_pusherror(message); + } + + public static void PushWarning(string message) + { + godot_icall_GD_pushwarning(message); + } + public static void Print(params object[] what) { godot_icall_GD_print(what); @@ -238,5 +248,11 @@ namespace Godot [MethodImpl(MethodImplOptions.InternalCall)] internal extern static string godot_icall_GD_var2str(object var); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_GD_pusherror(string type); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_GD_pushwarning(string type); } } diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp index 051f42b966..9f5bcecdd9 100644 --- a/modules/mono/glue/gd_glue.cpp +++ b/modules/mono/glue/gd_glue.cpp @@ -157,6 +157,14 @@ bool godot_icall_GD_type_exists(MonoString *p_type) { return ClassDB::class_exists(GDMonoMarshal::mono_string_to_godot(p_type)); } +void godot_icall_GD_pusherror(MonoString *p_str) { + ERR_PRINTS(GDMonoMarshal::mono_string_to_godot(p_str)); +} + +void godot_icall_GD_pushwarning(MonoString *p_str) { + WARN_PRINTS(GDMonoMarshal::mono_string_to_godot(p_str)); +} + MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var) { Variant var = GDMonoMarshal::mono_object_to_variant(p_var); @@ -186,6 +194,8 @@ void godot_register_gd_icalls() { mono_add_internal_call("Godot.GD::godot_icall_GD_convert", (void *)godot_icall_GD_convert); mono_add_internal_call("Godot.GD::godot_icall_GD_hash", (void *)godot_icall_GD_hash); mono_add_internal_call("Godot.GD::godot_icall_GD_instance_from_id", (void *)godot_icall_GD_instance_from_id); + mono_add_internal_call("Godot.GD::godot_icall_GD_pusherror", (void *)godot_icall_GD_pusherror); + mono_add_internal_call("Godot.GD::godot_icall_GD_pushwarning", (void *)godot_icall_GD_pushwarning); mono_add_internal_call("Godot.GD::godot_icall_GD_print", (void *)godot_icall_GD_print); mono_add_internal_call("Godot.GD::godot_icall_GD_printerr", (void *)godot_icall_GD_printerr); mono_add_internal_call("Godot.GD::godot_icall_GD_printraw", (void *)godot_icall_GD_printraw); diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h index 39d608de9f..5a6a2d1742 100644 --- a/modules/mono/godotsharp_defs.h +++ b/modules/mono/godotsharp_defs.h @@ -36,7 +36,8 @@ #define BINDINGS_GLOBAL_SCOPE_CLASS "GD" #define BINDINGS_PTR_FIELD "ptr" #define BINDINGS_NATIVE_NAME_FIELD "nativeName" -#define API_ASSEMBLY_NAME "GodotSharp" +#define API_SOLUTION_NAME "GodotSharp" +#define CORE_API_ASSEMBLY_NAME "GodotSharp" #define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor" #define EDITOR_TOOLS_ASSEMBLY_NAME "GodotSharpTools" diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 4d88cca6f1..a80155bd89 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -377,34 +377,24 @@ GDMonoAssembly **GDMono::get_loaded_assembly(const String &p_name) { bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) { - return load_assembly_from(p_name, String(), r_assembly, p_refonly); -} - -bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) { - - return load_assembly_from(p_name, String(), p_aname, r_assembly, p_refonly); -} - -bool GDMono::load_assembly_from(const String &p_name, const String &p_basedir, GDMonoAssembly **r_assembly, bool p_refonly) { - CRASH_COND(!r_assembly); MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8()); - bool result = load_assembly_from(p_name, p_basedir, aname, r_assembly, p_refonly); + bool result = load_assembly(p_name, aname, r_assembly, p_refonly); mono_assembly_name_free(aname); mono_free(aname); return result; } -bool GDMono::load_assembly_from(const String &p_name, const String &p_basedir, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) { +bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) { CRASH_COND(!r_assembly); print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "..."); MonoImageOpenStatus status = MONO_IMAGE_OK; - MonoAssembly *assembly = mono_assembly_load_full(p_aname, p_basedir.length() ? p_basedir.utf8().get_data() : NULL, &status, p_refonly); + MonoAssembly *assembly = mono_assembly_load_full(p_aname, NULL, &status, p_refonly); if (!assembly) return false; @@ -425,6 +415,32 @@ bool GDMono::load_assembly_from(const String &p_name, const String &p_basedir, M return true; } +bool GDMono::load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly) { + + CRASH_COND(!r_assembly); + + print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "..."); + + GDMonoAssembly *assembly = GDMonoAssembly::load_from(p_name, p_path, p_refonly); + + if (!assembly) + return false; + +#ifdef DEBUG_ENABLED + uint32_t domain_id = mono_domain_get_id(mono_domain_get()); + GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name); + + ERR_FAIL_COND_V(stored_assembly == NULL, false); + ERR_FAIL_COND_V(*stored_assembly != assembly, false); +#endif + + *r_assembly = assembly; + + print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path()); + + return true; +} + APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, APIAssembly::Type p_api_type) { APIAssembly::Version api_assembly_version; @@ -480,7 +496,14 @@ bool GDMono::_load_core_api_assembly() { } #endif - bool success = load_assembly(API_ASSEMBLY_NAME, &core_api_assembly); + String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + + if (!FileAccess::exists(assembly_path)) + return false; + + bool success = load_assembly_from(CORE_API_ASSEMBLY_NAME, + assembly_path, + &core_api_assembly); if (success) { #ifdef MONO_GLUE_ENABLED @@ -510,7 +533,14 @@ bool GDMono::_load_editor_api_assembly() { return false; } - bool success = load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly); + String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + + if (!FileAccess::exists(assembly_path)) + return false; + + bool success = load_assembly_from(EDITOR_API_ASSEMBLY_NAME, + assembly_path, + &editor_api_assembly); if (success) { #ifdef MONO_GLUE_ENABLED @@ -551,6 +581,8 @@ bool GDMono::_load_project_assembly() { if (success) { mono_assembly_set_main(project_assembly->get_assembly()); + + CSharpLanguage::get_singleton()->project_assembly_loaded(); } else { if (OS::get_singleton()->is_stdout_verbose()) print_error("Mono: Failed to load project assembly"); @@ -603,7 +635,7 @@ void GDMono::metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, String assembly_path = GodotSharpDirs::get_res_assemblies_dir() .plus_file(p_api_type == APIAssembly::API_CORE ? - API_ASSEMBLY_NAME ".dll" : + CORE_API_ASSEMBLY_NAME ".dll" : EDITOR_API_ASSEMBLY_NAME ".dll"); ERR_FAIL_COND(!FileAccess::exists(assembly_path)); @@ -634,7 +666,7 @@ bool GDMono::metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type) String assembly_path = GodotSharpDirs::get_res_assemblies_dir() .plus_file(p_api_type == APIAssembly::API_CORE ? - API_ASSEMBLY_NAME ".dll" : + CORE_API_ASSEMBLY_NAME ".dll" : EDITOR_API_ASSEMBLY_NAME ".dll"); if (!FileAccess::exists(assembly_path)) diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 745dcc34eb..fd9551b6de 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -199,8 +199,7 @@ public: bool load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly = false); bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false); - bool load_assembly_from(const String &p_name, const String &p_basedir, GDMonoAssembly **r_assembly, bool p_refonly = false); - bool load_assembly_from(const String &p_name, const String &p_basedir, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false); + bool load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly = false); Error finalize_and_unload_domain(MonoDomain *p_domain); diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 1067c11e0e..72b0204672 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -457,6 +457,20 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) return match; } +GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) { + + GDMonoAssembly **loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name); + if (loaded_asm) + return *loaded_asm; +#ifdef DEBUG_ENABLED + CRASH_COND(!FileAccess::exists(p_path)); +#endif + no_search = true; + GDMonoAssembly *res = _load_assembly_from(p_name, p_path, p_refonly); + no_search = false; + return res; +} + GDMonoAssembly::GDMonoAssembly(const String &p_name, const String &p_path) { loaded = false; diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h index 4c9b1cb10d..2af40ec901 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.h +++ b/modules/mono/mono_gd/gd_mono_assembly.h @@ -128,6 +128,8 @@ public: static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String()); + static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly); + GDMonoAssembly(const String &p_name, const String &p_path = String()); ~GDMonoAssembly(); }; diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp index fe41722af0..5d9b9213e7 100644 --- a/modules/mono/mono_gd/gd_mono_field.cpp +++ b/modules/mono/mono_gd/gd_mono_field.cpp @@ -223,6 +223,8 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ ERR_FAIL(); } } + + break; } ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + tclass->get_name()); diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index b97a24b09c..fe2c09799c 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -631,36 +631,36 @@ void set_pending_exception(MonoException *p_exc) { _THREAD_LOCAL_(int) current_invoke_count = 0; -MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **p_exc) { +MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **r_exc) { GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoObject *ret = mono_runtime_invoke(p_method, p_obj, p_params, (MonoObject **)p_exc); + MonoObject *ret = mono_runtime_invoke(p_method, p_obj, p_params, (MonoObject **)r_exc); GD_MONO_END_RUNTIME_INVOKE; return ret; } -MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **p_exc) { +MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **r_exc) { GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoObject *ret = mono_runtime_invoke_array(p_method, p_obj, p_params, (MonoObject **)p_exc); + MonoObject *ret = mono_runtime_invoke_array(p_method, p_obj, p_params, (MonoObject **)r_exc); GD_MONO_END_RUNTIME_INVOKE; return ret; } -MonoString *object_to_string(MonoObject *p_obj, MonoException **p_exc) { +MonoString *object_to_string(MonoObject *p_obj, MonoException **r_exc) { GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoString *ret = mono_object_to_string(p_obj, (MonoObject **)p_exc); + MonoString *ret = mono_object_to_string(p_obj, (MonoObject **)r_exc); GD_MONO_END_RUNTIME_INVOKE; return ret; } -void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **p_exc) { +void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc) { GD_MONO_BEGIN_RUNTIME_INVOKE; - mono_property_set_value(p_prop, p_obj, p_params, (MonoObject **)p_exc); + mono_property_set_value(p_prop, p_obj, p_params, (MonoObject **)r_exc); GD_MONO_END_RUNTIME_INVOKE; } -MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **p_exc) { +MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc) { GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoObject *ret = mono_property_get_value(p_prop, p_obj, p_params, (MonoObject **)p_exc); + MonoObject *ret = mono_property_get_value(p_prop, p_obj, p_params, (MonoObject **)r_exc); GD_MONO_END_RUNTIME_INVOKE; return ret; } diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index ec3a57eb46..f00680ff03 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -233,13 +233,13 @@ _FORCE_INLINE_ int &get_runtime_invoke_count_ref() { return current_invoke_count; } -MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **p_exc); -MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **p_exc); +MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **r_exc); +MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **r_exc); -MonoString *object_to_string(MonoObject *p_obj, MonoException **p_exc); +MonoString *object_to_string(MonoObject *p_obj, MonoException **r_exc); -void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **p_exc); -MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **p_exc); +void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc); +MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc); uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &r_error); diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp index b4c78df538..fa1fbebb16 100644 --- a/modules/mono/signal_awaiter_utils.cpp +++ b/modules/mono/signal_awaiter_utils.cpp @@ -141,7 +141,7 @@ SignalAwaiterHandle::~SignalAwaiterHandle() { if (exc) { GDMonoUtils::set_pending_exception(exc); - ERR_FAIL_V(); + ERR_FAIL(); } } } diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index ea942a9a8e..e663ee3c4a 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -88,7 +88,7 @@ void fix_path(const String &p_path, String &r_out) { bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path) { #ifdef WINDOWS_ENABLED CharType ret[_MAX_PATH]; - if (_wfullpath(ret, p_existing_path.c_str(), _MAX_PATH)) { + if (::_wfullpath(ret, p_existing_path.c_str(), _MAX_PATH)) { String abspath = String(ret).replace("\\", "/"); int pos = abspath.find(":/"); if (pos != -1) { @@ -99,10 +99,12 @@ bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path) { return true; } #else - char ret[PATH_MAX]; - if (realpath(p_existing_path.utf8().get_data(), ret)) { + char *resolved_path = ::realpath(p_existing_path.utf8().get_data(), NULL); + if (resolved_path) { String retstr; - if (!retstr.parse_utf8(ret)) { + bool success = !retstr.parse_utf8(resolved_path); + ::free(resolved_path); + if (success) { r_abs_path = retstr; return true; } diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index 8691932f9a..6900866725 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -30,6 +30,8 @@ #include "string_utils.h" +#include "core/os/file_access.h" + namespace { int sfind(const String &p_text, int p_from) { @@ -128,6 +130,7 @@ String sformat(const String &p_text, const Variant &p1, const Variant &p2, const return new_string; } +#ifdef TOOLS_ENABLED bool is_csharp_keyword(const String &p_name) { // Reserved keywords @@ -156,3 +159,28 @@ bool is_csharp_keyword(const String &p_name) { String escape_csharp_keyword(const String &p_name) { return is_csharp_keyword(p_name) ? "@" + p_name : p_name; } +#endif + +Error read_all_file_utf8(const String &p_path, String &r_content) { + PoolVector<uint8_t> sourcef; + Error err; + FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err); + ERR_FAIL_COND_V(err != OK, err); + + int len = f->get_len(); + sourcef.resize(len + 1); + PoolVector<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 source; + if (source.parse_utf8((const char *)w.ptr())) { + ERR_FAIL_V(ERR_INVALID_DATA); + } + + r_content = source; + return OK; +} diff --git a/modules/mono/utils/string_utils.h b/modules/mono/utils/string_utils.h index f2df2340ae..ee803bd720 100644 --- a/modules/mono/utils/string_utils.h +++ b/modules/mono/utils/string_utils.h @@ -42,4 +42,6 @@ bool is_csharp_keyword(const String &p_name); String escape_csharp_keyword(const String &p_name); #endif +Error read_all_file_utf8(const String &p_path, String &r_content); + #endif // STRING_FORMAT_H diff --git a/modules/opensimplex/doc_classes/NoiseTexture.xml b/modules/opensimplex/doc_classes/NoiseTexture.xml index d88abb3180..ba54160a90 100644 --- a/modules/opensimplex/doc_classes/NoiseTexture.xml +++ b/modules/opensimplex/doc_classes/NoiseTexture.xml @@ -17,6 +17,9 @@ <member name="as_normalmap" type="bool" setter="set_as_normalmap" getter="is_normalmap"> If true, the resulting texture contains a normal map created from the original noise interpreted as a bump map. </member> + <member name="height" type="int" setter="set_height" getter="get_height"> + Height of the generated texture. + </member> <member name="noise" type="OpenSimplexNoise" setter="set_noise" getter="get_noise"> The [OpenSimplexNoise] instance used to generate the noise. </member> @@ -26,9 +29,6 @@ <member name="width" type="int" setter="set_width" getter="get_width"> Width of the generated texture. </member> - <member name="height" type="int" setter="set_height" getter="get_height"> - Height of the generated texture. - </member> </members> <constants> </constants> diff --git a/modules/recast/navigation_mesh_editor_plugin.cpp b/modules/recast/navigation_mesh_editor_plugin.cpp index 98351fbaee..d121a82d9e 100644 --- a/modules/recast/navigation_mesh_editor_plugin.cpp +++ b/modules/recast/navigation_mesh_editor_plugin.cpp @@ -103,24 +103,25 @@ void NavigationMeshEditor::_bind_methods() { NavigationMeshEditor::NavigationMeshEditor() { bake_hbox = memnew(HBoxContainer); + button_bake = memnew(ToolButton); - button_bake->set_text(TTR("Bake!")); + bake_hbox->add_child(button_bake); button_bake->set_toggle_mode(true); - button_reset = memnew(Button); - button_bake->set_tooltip(TTR("Bake the navigation mesh.") + "\n"); + button_bake->set_text(TTR("Bake NavMesh")); + button_bake->connect("pressed", this, "_bake_pressed"); - bake_info = memnew(Label); - bake_hbox->add_child(button_bake); + button_reset = memnew(ToolButton); bake_hbox->add_child(button_reset); + // No button text, we only use a revert icon which is set when entering the tree. + button_reset->set_tooltip(TTR("Clear the navigation mesh.")); + button_reset->connect("pressed", this, "_clear_pressed"); + + bake_info = memnew(Label); bake_hbox->add_child(bake_info); err_dialog = memnew(AcceptDialog); add_child(err_dialog); node = NULL; - - button_bake->connect("pressed", this, "_bake_pressed"); - button_reset->connect("pressed", this, "_clear_pressed"); - button_reset->set_tooltip(TTR("Clear the navigation mesh.")); } NavigationMeshEditor::~NavigationMeshEditor() { diff --git a/modules/recast/navigation_mesh_editor_plugin.h b/modules/recast/navigation_mesh_editor_plugin.h index 4f3e222002..914d9ab8b9 100644 --- a/modules/recast/navigation_mesh_editor_plugin.h +++ b/modules/recast/navigation_mesh_editor_plugin.h @@ -43,8 +43,8 @@ class NavigationMeshEditor : public Control { AcceptDialog *err_dialog; HBoxContainer *bake_hbox; - Button *button_bake; - Button *button_reset; + ToolButton *button_bake; + ToolButton *button_reset; Label *bake_info; NavigationMeshInstance *node; diff --git a/modules/visual_script/doc_classes/VisualScript.xml b/modules/visual_script/doc_classes/VisualScript.xml index 28764aca40..70849c5a80 100644 --- a/modules/visual_script/doc_classes/VisualScript.xml +++ b/modules/visual_script/doc_classes/VisualScript.xml @@ -9,7 +9,7 @@ You are most likely to use this class via the Visual Script editor or when writing plugins for it. </description> <tutorials> - <link>http://docs.godotengine.org/en/3.0/getting_started/scripting/visual_script/index.html</link> + <link>https://docs.godotengine.org/en/latest/getting_started/scripting/visual_script/index.html</link> </tutorials> <demos> </demos> diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index 52a30baaec..186e9e63b1 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -2699,11 +2699,11 @@ VisualScriptLanguage::VisualScriptLanguage() { _debug_parse_err_file = ""; _debug_call_stack_pos = 0; int dmcs = GLOBAL_DEF("debug/settings/visual_script/max_call_stack", 1024); + ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/visual_script/max_call_stack", PropertyInfo(Variant::INT, "debug/settings/visual_script/max_call_stack", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater")); //minimum is 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 { diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 7fdd7fe446..c5f2070963 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -765,6 +765,7 @@ void VisualScriptEditor::_update_graph(int p_only_id) { } void VisualScriptEditor::_update_members() { + ERR_FAIL_COND(!script.is_valid()); updating_members = true; @@ -3018,11 +3019,15 @@ void VisualScriptEditor::_node_filter_changed(const String &p_text) { void VisualScriptEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { + if (p_what == NOTIFICATION_READY || (p_what == NOTIFICATION_THEME_CHANGED && is_visible_in_tree())) { + node_filter->set_right_icon(Control::get_icon("Search", "EditorIcons")); node_filter->set_clear_button_enabled(true); - variable_editor->connect("changed", this, "_update_members"); - signal_editor->connect("changed", this, "_update_members"); + + if (p_what == NOTIFICATION_READY) { + variable_editor->connect("changed", this, "_update_members"); + signal_editor->connect("changed", this, "_update_members"); + } Ref<Theme> tm = EditorNode::get_singleton()->get_theme_base()->get_theme(); @@ -3056,8 +3061,12 @@ void VisualScriptEditor::_notification(int p_what) { node_styles[E->get().first] = frame_style; } } - } - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + + if (is_visible_in_tree() && script.is_valid()) { + _update_members(); + _update_graph(); + } + } else if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { left_vsplit->set_visible(is_visible_in_tree()); } } @@ -3644,7 +3653,7 @@ VisualScriptEditor::VisualScriptEditor() { new_virtual_method_select = memnew(VisualScriptPropertySelector); add_child(new_virtual_method_select); new_virtual_method_select->connect("selected", this, "_selected_new_virtual_method"); - new_virtual_method_select->get_cancel()->connect("pressed", this, "_selected_new_virtual_method"); + new_virtual_method_select->get_cancel(); member_popup = memnew(PopupMenu); add_child(member_popup); diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 97c75dae94..ce3245bc28 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -176,7 +176,7 @@ class VisualScriptEditor : public ScriptEditorBase { void _cancel_connect_node(); void _create_new_node(const String &p_text, const String &p_category, const Vector2 &p_point); - void _selected_new_virtual_method(const String &p_text = String(""), const String &p_category = String(""), const bool p_connecting = true); + void _selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting); int error_line; diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp index 8255ed7116..82a577790e 100644 --- a/modules/websocket/emws_client.cpp +++ b/modules/websocket/emws_client.cpp @@ -31,6 +31,7 @@ #include "emws_client.h" #include "core/io/ip.h" +#include "core/project_settings.h" #include "emscripten.h" extern "C" { @@ -43,8 +44,9 @@ EMSCRIPTEN_KEEPALIVE void _esws_on_connect(void *obj, char *proto) { EMSCRIPTEN_KEEPALIVE void _esws_on_message(void *obj, uint8_t *p_data, int p_data_size, int p_is_string) { EMWSClient *client = static_cast<EMWSClient *>(obj); - static_cast<EMWSPeer *>(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1); - client->_on_peer_packet(); + Error err = static_cast<EMWSPeer *>(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1); + if (err == OK) + client->_on_peer_packet(); } EMSCRIPTEN_KEEPALIVE void _esws_on_error(void *obj) { @@ -159,7 +161,7 @@ Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, }, _js_id, str.utf8().get_data(), proto_string.utf8().get_data()); /* clang-format on */ - static_cast<Ref<EMWSPeer> >(_peer)->set_sock(peer_sock); + static_cast<Ref<EMWSPeer> >(_peer)->set_sock(peer_sock, _in_buf_size, _in_pkt_size); return OK; }; @@ -198,7 +200,13 @@ uint16_t EMWSClient::get_connected_port() const { return 1025; }; +int EMWSClient::get_max_packet_size() const { + return (1 << _in_buf_size) - PROTO_SIZE; +} + EMWSClient::EMWSClient() { + _in_buf_size = GLOBAL_GET(WSC_IN_BUF); + _in_pkt_size = GLOBAL_GET(WSC_IN_PKT); _is_connecting = false; _peer = Ref<EMWSPeer>(memnew(EMWSPeer)); /* clang-format off */ diff --git a/modules/websocket/emws_client.h b/modules/websocket/emws_client.h index b20633baff..a21090a1a3 100644 --- a/modules/websocket/emws_client.h +++ b/modules/websocket/emws_client.h @@ -41,6 +41,8 @@ class EMWSClient : public WebSocketClient { GDCIIMPL(EMWSClient, WebSocketClient); private: + int _in_buf_size; + int _in_pkt_size; int _js_id; public: @@ -52,6 +54,7 @@ public: IP_Address get_connected_host() const; uint16_t get_connected_port() const; virtual ConnectionStatus get_connection_status() const; + int get_max_packet_size() const; virtual void poll(); EMWSClient(); ~EMWSClient(); diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index 68f41165eb..bb97934824 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -32,11 +32,11 @@ #include "emws_peer.h" #include "core/io/ip.h" -void EMWSPeer::set_sock(int p_sock) { +void EMWSPeer::set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size) { peer_sock = p_sock; - in_buffer.clear(); - queue_count = 0; + _in_buffer.resize(p_in_pkt_size, p_in_buf_size); + _packet_buffer.resize((1 << p_in_buf_size)); } void EMWSPeer::set_write_mode(WriteMode p_mode) { @@ -47,18 +47,10 @@ EMWSPeer::WriteMode EMWSPeer::get_write_mode() const { return write_mode; } -void EMWSPeer::read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string) { - - if (in_buffer.space_left() < p_size + 5) { - ERR_EXPLAIN("Buffer full! Dropping data"); - ERR_FAIL(); - } +Error EMWSPeer::read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string) { uint8_t is_string = p_is_string ? 1 : 0; - in_buffer.write((uint8_t *)&p_size, 4); - in_buffer.write((uint8_t *)&is_string, 1); - in_buffer.write(p_data, p_size); - queue_count++; + return _in_buffer.write_packet(p_data, p_size, &is_string); } Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { @@ -89,40 +81,28 @@ Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { Error EMWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - if (queue_count == 0) + if (_in_buffer.packets_left() == 0) return ERR_UNAVAILABLE; - uint32_t to_read = 0; - uint32_t left = 0; - uint8_t is_string = 0; - r_buffer_size = 0; - - in_buffer.read((uint8_t *)&to_read, 4); - --queue_count; - left = in_buffer.data_left(); + PoolVector<uint8_t>::Write rw = _packet_buffer.write(); + int read = 0; + Error err = _in_buffer.read_packet(rw.ptr(), _packet_buffer.size(), &_is_string, read); + ERR_FAIL_COND_V(err != OK, err); - if (left < to_read + 1) { - in_buffer.advance_read(left); - return FAILED; - } - - in_buffer.read(&is_string, 1); - _was_string = is_string == 1; - in_buffer.read(packet_buffer, to_read); - *r_buffer = packet_buffer; - r_buffer_size = to_read; + *r_buffer = rw.ptr(); + r_buffer_size = read; return OK; }; int EMWSPeer::get_available_packet_count() const { - return queue_count; + return _in_buffer.packets_left(); }; bool EMWSPeer::was_string_packet() const { - return _was_string; + return _is_string; }; bool EMWSPeer::is_connected_to_host() const { @@ -143,9 +123,9 @@ void EMWSPeer::close(int p_code, String p_reason) { }, peer_sock, p_code, p_reason.utf8().get_data()); /* clang-format on */ } + _is_string = 0; + _in_buffer.clear(); peer_sock = -1; - queue_count = 0; - in_buffer.clear(); }; IP_Address EMWSPeer::get_connected_host() const { @@ -162,15 +142,12 @@ uint16_t EMWSPeer::get_connected_port() const { EMWSPeer::EMWSPeer() { peer_sock = -1; - queue_count = 0; - _was_string = false; - in_buffer.resize(16); write_mode = WRITE_MODE_BINARY; + close(); }; EMWSPeer::~EMWSPeer() { - in_buffer.resize(0); close(); }; diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h index a4b2c8f50b..4beb86d45b 100644 --- a/modules/websocket/emws_peer.h +++ b/modules/websocket/emws_peer.h @@ -36,6 +36,7 @@ #include "core/io/packet_peer.h" #include "core/ring_buffer.h" #include "emscripten.h" +#include "packet_buffer.h" #include "websocket_peer.h" class EMWSPeer : public WebSocketPeer { @@ -43,25 +44,20 @@ class EMWSPeer : public WebSocketPeer { GDCIIMPL(EMWSPeer, WebSocketPeer); private: - enum { - PACKET_BUFFER_SIZE = 65536 - 5 // 4 bytes for the size, 1 for for type - }; - int peer_sock; WriteMode write_mode; - uint8_t packet_buffer[PACKET_BUFFER_SIZE]; - RingBuffer<uint8_t> in_buffer; - int queue_count; - bool _was_string; + PoolVector<uint8_t> _packet_buffer; + PacketBuffer<uint8_t> _in_buffer; + uint8_t _is_string; public: - void read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string); - void set_sock(int sock); + Error read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string); + void set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size); virtual int get_available_packet_count() const; virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); - virtual int get_max_packet_size() const { return PACKET_BUFFER_SIZE; }; + virtual int get_max_packet_size() const { return _packet_buffer.size(); }; virtual void close(int p_code = 1000, String p_reason = ""); virtual bool is_connected_to_host() const; @@ -72,10 +68,6 @@ public: virtual void set_write_mode(WriteMode p_mode); virtual bool was_string_packet() const; - void set_wsi(struct lws *wsi); - Error read_wsi(void *in, size_t len); - Error write_wsi(); - EMWSPeer(); ~EMWSPeer(); }; diff --git a/modules/websocket/emws_server.cpp b/modules/websocket/emws_server.cpp index db02162699..09f9c1ceec 100644 --- a/modules/websocket/emws_server.cpp +++ b/modules/websocket/emws_server.cpp @@ -74,6 +74,10 @@ void EMWSServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { void EMWSServer::poll() { } +int EMWSServer::get_max_packet_size() const { + return 0; +} + EMWSServer::EMWSServer() { } diff --git a/modules/websocket/emws_server.h b/modules/websocket/emws_server.h index 74b689a29b..2dc455c389 100644 --- a/modules/websocket/emws_server.h +++ b/modules/websocket/emws_server.h @@ -49,6 +49,7 @@ public: IP_Address get_peer_address(int p_peer_id) const; int get_peer_port(int p_peer_id) const; void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = ""); + int get_max_packet_size() const; virtual void poll(); virtual PoolVector<String> get_protocols() const; diff --git a/modules/websocket/lws_client.cpp b/modules/websocket/lws_client.cpp index d71d091720..fa0bb4cfb2 100644 --- a/modules/websocket/lws_client.cpp +++ b/modules/websocket/lws_client.cpp @@ -32,6 +32,7 @@ #include "lws_client.h" #include "core/io/ip.h" #include "core/io/stream_peer_ssl.h" +#include "core/project_settings.h" #include "tls/mbedtls/wrapper/include/openssl/ssl.h" Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { @@ -90,12 +91,13 @@ Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, i.ssl_connection = 0; } - // This String needs to survive till we call lws_client_connect_via_info - String addr_str = (String)addr; - - i.address = addr_str.ascii().get_data(); - i.host = p_host.utf8().get_data(); - i.path = p_path.utf8().get_data(); + // These CharStrings needs to survive till we call lws_client_connect_via_info + CharString addr_ch = ((String)addr).ascii(); + CharString host_ch = p_host.utf8(); + CharString path_ch = p_path.utf8(); + i.address = addr_ch.get_data(); + i.host = host_ch.get_data(); + i.path = path_ch.get_data(); i.port = p_port; lws_client_connect_via_info(&i); @@ -103,6 +105,10 @@ Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, return OK; }; +int LWSClient::get_max_packet_size() const { + return (1 << _out_buf_size) - PROTO_SIZE; +} + void LWSClient::poll() { _lws_poll(); @@ -123,7 +129,7 @@ int LWSClient::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, voi } break; case LWS_CALLBACK_CLIENT_ESTABLISHED: - peer->set_wsi(wsi); + peer->set_wsi(wsi, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); peer_data->peer_id = 0; peer_data->force_close = false; peer_data->clean_close = false; @@ -206,6 +212,11 @@ uint16_t LWSClient::get_connected_port() const { }; LWSClient::LWSClient() { + _in_buf_size = nearest_shift((int)GLOBAL_GET(WSC_IN_BUF) - 1) + 10; + _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_IN_PKT) - 1); + _out_buf_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_BUF) - 1) + 10; + _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_PKT) - 1); + context = NULL; _lws_ref = NULL; _peer = Ref<LWSPeer>(memnew(LWSPeer)); diff --git a/modules/websocket/lws_client.h b/modules/websocket/lws_client.h index 1bbc19f352..fdecb99925 100644 --- a/modules/websocket/lws_client.h +++ b/modules/websocket/lws_client.h @@ -43,8 +43,15 @@ class LWSClient : public WebSocketClient { LWS_HELPER(LWSClient); +private: + int _in_buf_size; + int _in_pkt_size; + int _out_buf_size; + int _out_pkt_size; + public: Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()); + int get_max_packet_size() const; Ref<WebSocketPeer> get_peer(int p_peer_id) const; void disconnect_from_host(int p_code = 1000, String p_reason = ""); IP_Address get_connected_host() const; diff --git a/modules/websocket/lws_peer.cpp b/modules/websocket/lws_peer.cpp index b5c130b308..04e6e7c951 100644 --- a/modules/websocket/lws_peer.cpp +++ b/modules/websocket/lws_peer.cpp @@ -41,11 +41,12 @@ #include "drivers/unix/net_socket_posix.h" -void LWSPeer::set_wsi(struct lws *p_wsi) { +void LWSPeer::set_wsi(struct lws *p_wsi, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size) { ERR_FAIL_COND(wsi != NULL); - rbw.resize(16); - rbr.resize(16); + _in_buffer.resize(p_in_pkt_size, p_in_buf_size); + _out_buffer.resize(p_out_pkt_size, p_out_buf_size); + _packet_buffer.resize((1 << MAX(p_in_buf_size, p_out_buf_size)) + LWS_PRE); wsi = p_wsi; }; @@ -61,24 +62,29 @@ Error LWSPeer::read_wsi(void *in, size_t len) { ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - uint32_t size = in_size; - uint8_t is_string = lws_frame_is_binary(wsi) ? 0 : 1; + if (lws_is_first_fragment(wsi)) + _in_size = 0; + else if (_in_size == -1) // Trash this frame + return ERR_FILE_CORRUPT; - if (rbr.space_left() < len + 5) { - ERR_EXPLAIN("Buffer full! Dropping data"); - ERR_FAIL_V(FAILED); + Error err = _in_buffer.write_packet((const uint8_t *)in, len, NULL); + + if (err != OK) { + _in_buffer.discard_payload(_in_size); + _in_size = -1; + ERR_FAIL_V(err); } - copymem(&(input_buffer[size]), in, len); - size += len; + _in_size += len; - in_size = size; if (lws_is_final_fragment(wsi)) { - rbr.write((uint8_t *)&size, 4); - rbr.write((uint8_t *)&is_string, 1); - rbr.write(input_buffer, size); - in_count++; - in_size = 0; + uint8_t is_string = lws_frame_is_binary(wsi) ? 0 : 1; + err = _in_buffer.write_packet(NULL, _in_size, &is_string); + if (err != OK) { + _in_buffer.discard_payload(_in_size); + _in_size = -1; + ERR_FAIL_V(err); + } } return OK; @@ -89,26 +95,20 @@ Error LWSPeer::write_wsi() { ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); PoolVector<uint8_t> tmp; - int left = rbw.data_left(); - uint32_t to_write = 0; + int count = _out_buffer.packets_left(); - if (left == 0 || out_count == 0) + if (count == 0) return OK; - rbw.read((uint8_t *)&to_write, 4); - out_count--; - - if (left < to_write) { - rbw.advance_read(left); - return FAILED; - } + int read = 0; + uint8_t is_string; + PoolVector<uint8_t>::Write rw = _packet_buffer.write(); + _out_buffer.read_packet(&(rw[LWS_PRE]), _packet_buffer.size() - LWS_PRE, &is_string, read); - tmp.resize(LWS_PRE + to_write); - rbw.read(&(tmp.write()[LWS_PRE]), to_write); - lws_write(wsi, &(tmp.write()[LWS_PRE]), to_write, (enum lws_write_protocol)write_mode); - tmp.resize(0); + enum lws_write_protocol mode = is_string ? LWS_WRITE_TEXT : LWS_WRITE_BINARY; + lws_write(wsi, &(rw[LWS_PRE]), read, mode); - if (out_count > 0) + if (count > 1) lws_callback_on_writable(wsi); // we want to write more! return OK; @@ -118,40 +118,27 @@ Error LWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - rbw.write((uint8_t *)&p_buffer_size, 4); - rbw.write(p_buffer, MIN(p_buffer_size, rbw.space_left())); - out_count++; - + uint8_t is_string = write_mode == WRITE_MODE_TEXT; + _out_buffer.write_packet(p_buffer, p_buffer_size, &is_string); lws_callback_on_writable(wsi); // notify that we want to write return OK; }; Error LWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + r_buffer_size = 0; + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - if (in_count == 0) + if (_in_buffer.packets_left() == 0) return ERR_UNAVAILABLE; - uint32_t to_read = 0; - uint32_t left = 0; - uint8_t is_string = 0; - r_buffer_size = 0; - - rbr.read((uint8_t *)&to_read, 4); - in_count--; - left = rbr.data_left(); - - if (left < to_read + 1) { - rbr.advance_read(left); - return FAILED; - } + int read = 0; + PoolVector<uint8_t>::Write rw = _packet_buffer.write(); + _in_buffer.read_packet(rw.ptr(), _packet_buffer.size(), &_is_string, read); - rbr.read(&is_string, 1); - rbr.read(packet_buffer, to_read); - *r_buffer = packet_buffer; - r_buffer_size = to_read; - _was_string = is_string; + *r_buffer = rw.ptr(); + r_buffer_size = read; return OK; }; @@ -161,12 +148,12 @@ int LWSPeer::get_available_packet_count() const { if (!is_connected_to_host()) return 0; - return in_count; + return _in_buffer.packets_left(); }; bool LWSPeer::was_string_packet() const { - return _was_string; + return _is_string; }; bool LWSPeer::is_connected_to_host() const { @@ -219,12 +206,11 @@ void LWSPeer::close(int p_code, String p_reason) { close_reason = ""; } wsi = NULL; - rbw.resize(0); - rbr.resize(0); - in_count = 0; - in_size = 0; - out_count = 0; - _was_string = false; + _in_buffer.clear(); + _out_buffer.clear(); + _in_size = 0; + _is_string = 0; + _packet_buffer.resize(0); }; IP_Address LWSPeer::get_connected_host() const { diff --git a/modules/websocket/lws_peer.h b/modules/websocket/lws_peer.h index 571445db01..3ded3810d1 100644 --- a/modules/websocket/lws_peer.h +++ b/modules/websocket/lws_peer.h @@ -37,6 +37,7 @@ #include "core/ring_buffer.h" #include "libwebsockets.h" #include "lws_config.h" +#include "packet_buffer.h" #include "websocket_peer.h" class LWSPeer : public WebSocketPeer { @@ -44,14 +45,16 @@ class LWSPeer : public WebSocketPeer { GDCIIMPL(LWSPeer, WebSocketPeer); private: - enum { - PACKET_BUFFER_SIZE = 65536 - 5 // 4 bytes for the size, 1 for the type - }; + int _in_size; + uint8_t _is_string; + // Our packet info is just a boolean (is_string), using uint8_t for it. + PacketBuffer<uint8_t> _in_buffer; + PacketBuffer<uint8_t> _out_buffer; + + PoolVector<uint8_t> _packet_buffer; - uint8_t packet_buffer[PACKET_BUFFER_SIZE]; struct lws *wsi; WriteMode write_mode; - bool _was_string; int close_code; String close_reason; @@ -63,17 +66,10 @@ public: bool clean_close; }; - RingBuffer<uint8_t> rbw; - RingBuffer<uint8_t> rbr; - uint8_t input_buffer[PACKET_BUFFER_SIZE]; - uint32_t in_size; - int in_count; - int out_count; - virtual int get_available_packet_count() const; virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); - virtual int get_max_packet_size() const { return PACKET_BUFFER_SIZE; }; + virtual int get_max_packet_size() const { return _packet_buffer.size(); }; virtual void close(int p_code = 1000, String p_reason = ""); virtual bool is_connected_to_host() const; @@ -84,7 +80,7 @@ public: virtual void set_write_mode(WriteMode p_mode); virtual bool was_string_packet() const; - void set_wsi(struct lws *wsi); + void set_wsi(struct lws *wsi, unsigned int _in_buf_size, unsigned int _in_pkt_size, unsigned int _out_buf_size, unsigned int _out_pkt_size); Error read_wsi(void *in, size_t len); Error write_wsi(); void send_close_status(struct lws *wsi); diff --git a/modules/websocket/lws_server.cpp b/modules/websocket/lws_server.cpp index 58fa043346..0e551eb318 100644 --- a/modules/websocket/lws_server.cpp +++ b/modules/websocket/lws_server.cpp @@ -31,6 +31,7 @@ #include "lws_server.h" #include "core/os/os.h" +#include "core/project_settings.h" Error LWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { @@ -67,6 +68,10 @@ bool LWSServer::is_listening() const { return context != NULL; } +int LWSServer::get_max_packet_size() const { + return (1 << _out_buf_size) - PROTO_SIZE; +} + int LWSServer::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user; @@ -85,7 +90,7 @@ int LWSServer::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, voi int32_t id = _gen_unique_id(); Ref<LWSPeer> peer = Ref<LWSPeer>(memnew(LWSPeer)); - peer->set_wsi(wsi); + peer->set_wsi(wsi, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); _peer_map[id] = peer; peer_data->peer_id = id; @@ -192,6 +197,10 @@ void LWSServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { } LWSServer::LWSServer() { + _in_buf_size = nearest_shift((int)GLOBAL_GET(WSS_IN_BUF) - 1) + 10; + _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_IN_PKT) - 1); + _out_buf_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_BUF) - 1) + 10; + _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_PKT) - 1); context = NULL; _lws_ref = NULL; } diff --git a/modules/websocket/lws_server.h b/modules/websocket/lws_server.h index 346773ebc4..c43044f194 100644 --- a/modules/websocket/lws_server.h +++ b/modules/websocket/lws_server.h @@ -45,11 +45,16 @@ class LWSServer : public WebSocketServer { private: Map<int, Ref<LWSPeer> > peer_map; + int _in_buf_size; + int _in_pkt_size; + int _out_buf_size; + int _out_pkt_size; public: Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); void stop(); bool is_listening() const; + int get_max_packet_size() const; bool has_peer(int p_id) const; Ref<WebSocketPeer> get_peer(int p_id) const; IP_Address get_peer_address(int p_peer_id) const; diff --git a/modules/websocket/packet_buffer.h b/modules/websocket/packet_buffer.h new file mode 100644 index 0000000000..a3af7f728a --- /dev/null +++ b/modules/websocket/packet_buffer.h @@ -0,0 +1,122 @@ +/*************************************************************************/ +/* packet_buffer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef PACKET_BUFFER_H +#define PACKET_BUFFER_H + +#include "core/os/copymem.h" +#include "core/ring_buffer.h" + +template <class T> +class PacketBuffer { + +private: + typedef struct { + uint32_t size; + T info; + } _Packet; + + RingBuffer<_Packet> _packets; + RingBuffer<uint8_t> _payload; + +public: + Error write_packet(const uint8_t *p_payload, uint32_t p_size, const T *p_info) { +#ifdef TOOLS_ENABLED + // Verbose buffer warnings + if (p_payload && _payload.space_left() < p_size) { + ERR_PRINT("Buffer payload full! Dropping data."); + ERR_FAIL_V(ERR_OUT_OF_MEMORY); + } + if (p_info && _packets.space_left() < 1) { + ERR_PRINT("Too many packets in queue! Dropping data."); + ERR_FAIL_V(ERR_OUT_OF_MEMORY); + } +#else + ERR_FAIL_COND_V(p_payload && _payload.space_left() < p_size, ERR_OUT_OF_MEMORY); + ERR_FAIL_COND_V(p_info && _packets.space_left() < 1, ERR_OUT_OF_MEMORY); +#endif + + // If p_info is NULL, only the payload is written + if (p_info) { + _Packet p; + p.size = p_size; + copymem(&p.info, p_info, sizeof(T)); + _packets.write(p); + } + + // If p_payload is NULL, only the packet information is written. + if (p_payload) { + _payload.write((const uint8_t *)p_payload, p_size); + } + + return OK; + } + + Error read_packet(uint8_t *r_payload, int p_bytes, T *r_info, int &r_read) { + ERR_FAIL_COND_V(_packets.data_left() < 1, ERR_UNAVAILABLE); + _Packet p; + _packets.read(&p, 1); + ERR_FAIL_COND_V(_payload.data_left() < p.size, ERR_BUG); + ERR_FAIL_COND_V(p_bytes < p.size, ERR_OUT_OF_MEMORY); + + r_read = p.size; + copymem(r_info, &p.info, sizeof(T)); + _payload.read(r_payload, p.size); + return OK; + } + + void discard_payload(int p_size) { + _packets.decrease_write(p_size); + } + + void resize(int p_pkt_shift, int p_buf_shift) { + _packets.resize(p_pkt_shift); + _payload.resize(p_buf_shift); + } + + int packets_left() const { + return _packets.data_left(); + } + + void clear() { + _payload.resize(0); + _packets.resize(0); + } + + PacketBuffer() { + clear(); + } + + ~PacketBuffer() { + clear(); + } +}; + +#endif // PACKET_BUFFER_H diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp index 538cd40454..8946faffa9 100644 --- a/modules/websocket/register_types.cpp +++ b/modules/websocket/register_types.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "register_types.h" #include "core/error_macros.h" +#include "core/project_settings.h" #ifdef JAVASCRIPT_ENABLED #include "emscripten.h" #include "emws_client.h" @@ -41,6 +42,22 @@ #endif void register_websocket_types() { +#define _SET_HINT(NAME, _VAL_, _MAX_) \ + GLOBAL_DEF(NAME, _VAL_); \ + ProjectSettings::get_singleton()->set_custom_property_info(NAME, PropertyInfo(Variant::INT, NAME, PROPERTY_HINT_RANGE, "2," #_MAX_ ",1,or_greater")); + + // Client buffers project settings + _SET_HINT(WSC_IN_BUF, 64, 4096); + _SET_HINT(WSC_IN_PKT, 1024, 16384); + _SET_HINT(WSC_OUT_BUF, 64, 4096); + _SET_HINT(WSC_OUT_PKT, 1024, 16384); + + // Server buffers project settings + _SET_HINT(WSS_IN_BUF, 64, 4096); + _SET_HINT(WSS_IN_PKT, 1024, 16384); + _SET_HINT(WSS_OUT_BUF, 64, 4096); + _SET_HINT(WSS_OUT_PKT, 1024, 16384); + #ifdef JAVASCRIPT_ENABLED EM_ASM({ var IDHandler = {}; diff --git a/modules/websocket/websocket_client.cpp b/modules/websocket/websocket_client.cpp index f9b94dc519..6c5018bb79 100644 --- a/modules/websocket/websocket_client.cpp +++ b/modules/websocket/websocket_client.cpp @@ -136,7 +136,7 @@ void WebSocketClient::_bind_methods() { ClassDB::bind_method(D_METHOD("set_verify_ssl_enabled", "enabled"), &WebSocketClient::set_verify_ssl_enabled); ClassDB::bind_method(D_METHOD("is_verify_ssl_enabled"), &WebSocketClient::is_verify_ssl_enabled); - ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "verify_ssl", PROPERTY_HINT_NONE, "", 0), "set_verify_ssl_enabled", "is_verify_ssl_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "verify_ssl", PROPERTY_HINT_NONE, "", 0), "set_verify_ssl_enabled", "is_verify_ssl_enabled"); ADD_SIGNAL(MethodInfo("data_received")); ADD_SIGNAL(MethodInfo("connection_established", PropertyInfo(Variant::STRING, "protocol"))); diff --git a/modules/websocket/websocket_macros.h b/modules/websocket/websocket_macros.h index d27fb4d778..45dd30d0ce 100644 --- a/modules/websocket/websocket_macros.h +++ b/modules/websocket/websocket_macros.h @@ -30,6 +30,16 @@ #ifndef WEBSOCKETMACTOS_H #define WEBSOCKETMACTOS_H +#define WSC_IN_BUF "network/limits/websocket_client/max_in_buffer_kb" +#define WSC_IN_PKT "network/limits/websocket_client/max_in_packets" +#define WSC_OUT_BUF "network/limits/websocket_client/max_out_buffer_kb" +#define WSC_OUT_PKT "network/limits/websocket_client/max_out_packets" + +#define WSS_IN_BUF "network/limits/websocket_server/max_in_buffer_kb" +#define WSS_IN_PKT "network/limits/websocket_server/max_in_packets" +#define WSS_OUT_BUF "network/limits/websocket_server/max_out_buffer_kb" +#define WSS_OUT_PKT "network/limits/websocket_server/max_out_packets" + /* clang-format off */ #define GDCICLASS(CNAME) \ public:\ diff --git a/modules/websocket/websocket_multiplayer.cpp b/modules/websocket/websocket_multiplayer.cpp index 873658559a..9a95c17e47 100644 --- a/modules/websocket/websocket_multiplayer.cpp +++ b/modules/websocket/websocket_multiplayer.cpp @@ -100,13 +100,6 @@ int WebSocketMultiplayerPeer::get_available_packet_count() const { return _incoming_packets.size(); } -int WebSocketMultiplayerPeer::get_max_packet_size() const { - - ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); - - return MAX_PACKET_SIZE; -} - Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { r_buffer_size = 0; diff --git a/modules/websocket/websocket_multiplayer.h b/modules/websocket/websocket_multiplayer.h index 8edfc5296e..3cba0011fc 100644 --- a/modules/websocket/websocket_multiplayer.h +++ b/modules/websocket/websocket_multiplayer.h @@ -51,9 +51,7 @@ protected: SYS_DEL = 2, SYS_ID = 3, - PROTO_SIZE = 9, - SYS_PACKET_SIZE = 13, - MAX_PACKET_SIZE = 65536 - 14 // 5 websocket, 9 multiplayer + PROTO_SIZE = 9 }; struct Packet { @@ -93,7 +91,7 @@ public: /* PacketPeer */ virtual int get_available_packet_count() const; - virtual int get_max_packet_size() const; + virtual int get_max_packet_size() const = 0; virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h index 5918fda3c2..4966cdfc72 100644 --- a/modules/websocket/websocket_peer.h +++ b/modules/websocket/websocket_peer.h @@ -32,7 +32,6 @@ #include "core/error_list.h" #include "core/io/packet_peer.h" -#include "core/ring_buffer.h" #include "websocket_macros.h" class WebSocketPeer : public PacketPeer { |