diff options
55 files changed, 1204 insertions, 225 deletions
diff --git a/core/os/input_event.cpp b/core/os/input_event.cpp index 12060f31df..b9607632f7 100644 --- a/core/os/input_event.cpp +++ b/core/os/input_event.cpp @@ -962,6 +962,11 @@ Ref<InputEvent> InputEventMagnifyGesture::xformed_by(const Transform2D &p_xform, return ev; } +String InputEventMagnifyGesture::as_text() const { + + return "InputEventMagnifyGesture : factor=" + rtos(get_factor()) + ", position=(" + String(get_position()) + ")"; +} + void InputEventMagnifyGesture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_factor", "factor"), &InputEventMagnifyGesture::set_factor); @@ -999,6 +1004,11 @@ Ref<InputEvent> InputEventPanGesture::xformed_by(const Transform2D &p_xform, con return ev; } +String InputEventPanGesture::as_text() const { + + return "InputEventPanGesture : delta=(" + String(get_delta()) + "), position=(" + String(get_position()) + ")"; +} + void InputEventPanGesture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_delta", "delta"), &InputEventPanGesture::set_delta); diff --git a/core/os/input_event.h b/core/os/input_event.h index ad754d0d1f..0a33ab18a7 100644 --- a/core/os/input_event.h +++ b/core/os/input_event.h @@ -494,6 +494,7 @@ public: real_t get_factor() const; virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const; + virtual String as_text() const; InputEventMagnifyGesture(); }; @@ -511,6 +512,7 @@ public: Vector2 get_delta() const; virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const; + virtual String as_text() const; InputEventPanGesture(); }; diff --git a/core/ustring.cpp b/core/ustring.cpp index d445e4ed47..a7a7810837 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -1135,6 +1135,36 @@ String String::num_int64(int64_t p_num, int base, bool capitalize_hex) { return s; } +String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) { + + uint64_t n = p_num; + + int chars = 0; + do { + n /= base; + chars++; + } while (n); + + String s; + s.resize(chars + 1); + CharType *c = s.ptrw(); + c[chars] = 0; + n = p_num; + do { + int mod = ABS(n % base); + if (mod >= 10) { + char a = (capitalize_hex ? 'A' : 'a'); + c[--chars] = a + (mod - 10); + } else { + c[--chars] = '0' + mod; + } + + n /= base; + } while (n); + + return s; +} + String String::num_real(double p_num) { String s; diff --git a/core/ustring.h b/core/ustring.h index 90496b71b6..bb676ce623 100644 --- a/core/ustring.h +++ b/core/ustring.h @@ -146,6 +146,7 @@ public: static String num_scientific(double p_num); static String num_real(double p_num); static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false); + static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false); static String chr(CharType p_char); static String md5(const uint8_t *p_md5); static String hex_encode_buffer(const uint8_t *p_buffer, int p_len); diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index 1e129d70d0..498445a027 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -265,6 +265,11 @@ Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array. </description> </method> + <method name="shuffle"> + <description> + Shuffle the array such that the items will have a random order. + </description> + </method> <method name="size"> <return type="int"> </return> @@ -296,11 +301,6 @@ [/codeblock] </description> </method> - <method name="shuffle"> - <description> - Shuffle the array such that the items will have a random order. - </description> - </method> </methods> <constants> </constants> diff --git a/doc/classes/BitMap.xml b/doc/classes/BitMap.xml index 1d2fc950f0..036e968b65 100644 --- a/doc/classes/BitMap.xml +++ b/doc/classes/BitMap.xml @@ -25,8 +25,10 @@ </return> <argument index="0" name="image" type="Image"> </argument> + <argument index="1" name="threshold" type="float" default="0.1"> + </argument> <description> - Creates a bitmap that matches the given image dimensions, every element of the bitmap is set to false if the alpha value of the image at that position is 0, and true in other case. + Creates a bitmap that matches the given image dimensions, every element of the bitmap is set to false if the alpha value of the image at that position is equal to [code]threshold[/code] or less, and true in other case. </description> </method> <method name="get_bit" qualifiers="const"> diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index e3bf2b9577..ff9eb6bfe9 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -90,6 +90,18 @@ Draws a line from a 2D point to another, with a given color and width. It can be optionally antialiased. </description> </method> + <method name="draw_mesh"> + <return type="void"> + </return> + <argument index="0" name="mesh" type="Mesh"> + </argument> + <argument index="1" name="texture" type="Texture"> + </argument> + <argument index="2" name="normal_map" type="Texture" default="null"> + </argument> + <description> + </description> + </method> <method name="draw_multiline"> <return type="void"> </return> @@ -120,6 +132,18 @@ Draws multiple, parallel lines with a uniform [code]width[/code], segment-by-segment coloring, and optional antialiasing. Colors assigned to line segments match by index between [code]points[/code] and [code]colors[/code]. </description> </method> + <method name="draw_multimesh"> + <return type="void"> + </return> + <argument index="0" name="mesh" type="Mesh"> + </argument> + <argument index="1" name="texture" type="Texture"> + </argument> + <argument index="2" name="normal_map" type="Texture" default="null"> + </argument> + <description> + </description> + </method> <method name="draw_polygon"> <return type="void"> </return> diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml index 8113ee3415..490583b427 100644 --- a/doc/classes/Color.xml +++ b/doc/classes/Color.xml @@ -117,6 +117,24 @@ [/codeblock] </description> </method> + <method name="from_hsv"> + <return type="Color"> + </return> + <argument index="0" name="h" type="float"> + </argument> + <argument index="1" name="s" type="float"> + </argument> + <argument index="2" name="v" type="float"> + </argument> + <argument index="3" name="a" type="float" default="1"> + </argument> + <description> + Constructs a color from an HSV profile. [code]h[/code] is a value between 0 and 360. [code]s[/code] and [code]v[/code] are values between 0 and 1. + [codeblock] + var c = Color.from_hsv(210, 0.5, 0.79, 0.8) # equivalent to HSV(210, 50, 79, 0.8) or Color8(100, 151, 201, 0.8) + [/codeblock] + </description> + </method> <method name="gray"> <return type="float"> </return> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index b7f7d74f54..d0ba6c2ce0 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -12,6 +12,17 @@ <demos> </demos> <methods> + <method name="add_autoload_singleton"> + <return type="void"> + </return> + <argument index="0" name="name" type="String"> + </argument> + <argument index="1" name="path" type="String"> + </argument> + <description> + Add a script at [code]path[/code] to the Autoload list as [code]name[/code]. + </description> + </method> <method name="add_control_to_bottom_panel"> <return type="ToolButton"> </return> @@ -265,6 +276,15 @@ Queue save the project's editor layout. </description> </method> + <method name="remove_autoload_singleton"> + <return type="void"> + </return> + <argument index="0" name="name" type="String"> + </argument> + <description> + Remove an Autoload [code]name[/code] from the list. + </description> + </method> <method name="remove_control_from_bottom_panel"> <return type="void"> </return> diff --git a/doc/classes/KinematicBody.xml b/doc/classes/KinematicBody.xml index 7ebea43cb9..7a20331115 100644 --- a/doc/classes/KinematicBody.xml +++ b/doc/classes/KinematicBody.xml @@ -63,6 +63,8 @@ </return> <argument index="0" name="rel_vec" type="Vector3"> </argument> + <argument index="1" name="infinite_inertia" type="bool" default="true"> + </argument> <description> Moves the body along the vector [code]rel_vec[/code]. The body will stop if it collides. Returns a [KinematicCollision], which contains information about the collision. </description> @@ -74,11 +76,13 @@ </argument> <argument index="1" name="floor_normal" type="Vector3" default="Vector3( 0, 0, 0 )"> </argument> - <argument index="2" name="slope_stop_min_velocity" type="float" default="0.05"> + <argument index="2" name="infinite_inertia" type="bool" default="true"> + </argument> + <argument index="3" name="slope_stop_min_velocity" type="float" default="0.05"> </argument> - <argument index="3" name="max_slides" type="int" default="4"> + <argument index="4" name="max_slides" type="int" default="4"> </argument> - <argument index="4" name="floor_max_angle" type="float" default="0.785398"> + <argument index="5" name="floor_max_angle" type="float" default="0.785398"> </argument> <description> Moves the body along a vector. If the body collides with another, it will slide along the other body rather than stop immediately. If the other body is a [code]KinematicBody[/code] or [RigidBody], it will also be affected by the motion of the other body. You can use this to make moving or rotating platforms, or to make nodes push other nodes. @@ -97,6 +101,8 @@ </argument> <argument index="1" name="rel_vec" type="Vector3"> </argument> + <argument index="2" name="infinite_inertia" type="bool"> + </argument> <description> Checks for collisions without moving the body. Virtually sets the node's position, scale and rotation to that of the given [Transform], then tries to move the body along the vector [code]rel_vec[/code]. Returns [code]true[/code] if a collision would occur. </description> diff --git a/doc/classes/KinematicBody2D.xml b/doc/classes/KinematicBody2D.xml index da7b34440e..df4b719eae 100644 --- a/doc/classes/KinematicBody2D.xml +++ b/doc/classes/KinematicBody2D.xml @@ -62,6 +62,8 @@ </return> <argument index="0" name="rel_vec" type="Vector2"> </argument> + <argument index="1" name="infinite_inertia" type="bool" default="true"> + </argument> <description> Moves the body along the vector [code]rel_vec[/code]. The body will stop if it collides. Returns a [KinematicCollision2D], which contains information about the collision. </description> @@ -73,11 +75,13 @@ </argument> <argument index="1" name="floor_normal" type="Vector2" default="Vector2( 0, 0 )"> </argument> - <argument index="2" name="slope_stop_min_velocity" type="float" default="5"> + <argument index="2" name="infinite_inertia" type="bool" default="true"> + </argument> + <argument index="3" name="slope_stop_min_velocity" type="float" default="5"> </argument> - <argument index="3" name="max_bounces" type="int" default="4"> + <argument index="4" name="max_bounces" type="int" default="4"> </argument> - <argument index="4" name="floor_max_angle" type="float" default="0.785398"> + <argument index="5" name="floor_max_angle" type="float" default="0.785398"> </argument> <description> Moves the body along a vector. If the body collides with another, it will slide along the other body rather than stop immediately. If the other body is a [code]KinematicBody2D[/code] or [RigidBody2D], it will also be affected by the motion of the other body. You can use this to make moving or rotating platforms, or to make nodes push other nodes. @@ -96,6 +100,8 @@ </argument> <argument index="1" name="rel_vec" type="Vector2"> </argument> + <argument index="2" name="infinite_inertia" type="bool"> + </argument> <description> Checks for collisions without moving the body. Virtually sets the node's position, scale and rotation to that of the given [Transform2D], then tries to move the body along the vector [code]rel_vec[/code]. Returns [code]true[/code] if a collision would occur. </description> diff --git a/doc/classes/MeshInstance2D.xml b/doc/classes/MeshInstance2D.xml new file mode 100644 index 0000000000..5e5af692fd --- /dev/null +++ b/doc/classes/MeshInstance2D.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="MeshInstance2D" inherits="Node2D" category="Core" version="3.1-dev"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh"> + </member> + <member name="normal_map" type="Texture" setter="set_normal_map" getter="get_normal_map"> + </member> + <member name="texture" type="Texture" setter="set_texture" getter="get_texture"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index f37c80efc9..0b11070ec2 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -385,6 +385,10 @@ </return> <description> Returns the absolute directory path where user data is written ([code]user://[/code]). + On Linux, this is [code]~/.local/share/godot/app_userdata/[project_name][/code], or [code]~/.local/share/[custom_name][/code] if [code]use_custom_user_dir[/code] is set. + On macOS, this is [code]~/Library/Application Support/Godot/app_userdata/[project_name][/code], or [code]~/Library/Application Support/[custom_name][/code] if [code]use_custom_user_dir[/code] is set. + On Windows, this is [code]%APPDATA%/Godot/app_userdata/[project_name][/code], or [code]%APPDATA%/[custom_name][/code] if [code]use_custom_user_dir[/code] is set. + If the project name is empty, [code]user://[/code] falls back to [code]res://[/code]. </description> </method> <method name="get_virtual_keyboard_height"> diff --git a/doc/classes/Physics2DServer.xml b/doc/classes/Physics2DServer.xml index 644731516f..a97a198e4b 100644 --- a/doc/classes/Physics2DServer.xml +++ b/doc/classes/Physics2DServer.xml @@ -713,9 +713,11 @@ </argument> <argument index="2" name="motion" type="Vector2"> </argument> - <argument index="3" name="margin" type="float" default="0.08"> + <argument index="3" name="infinite_inertia" type="bool"> </argument> - <argument index="4" name="result" type="Physics2DTestMotionResult" default="null"> + <argument index="4" name="margin" type="float" default="0.08"> + </argument> + <argument index="5" name="result" type="Physics2DTestMotionResult" default="null"> </argument> <description> Returns whether a body can move from a given point in a given direction. Apart from the boolean return value, a [Physics2DTestMotionResult] can be passed to return additional information in. diff --git a/doc/classes/PhysicsDirectBodyState.xml b/doc/classes/PhysicsDirectBodyState.xml index fa0e529a73..1c92ea1cc4 100644 --- a/doc/classes/PhysicsDirectBodyState.xml +++ b/doc/classes/PhysicsDirectBodyState.xml @@ -9,6 +9,14 @@ <demos> </demos> <methods> + <method name="add_central_force"> + <return type="void"> + </return> + <argument index="0" name="force" type="Vector3"> + </argument> + <description> + </description> + </method> <method name="add_force"> <return type="void"> </return> @@ -19,6 +27,14 @@ <description> </description> </method> + <method name="add_torque"> + <return type="void"> + </return> + <argument index="0" name="torque" type="Vector3"> + </argument> + <description> + </description> + </method> <method name="apply_impulse"> <return type="void"> </return> diff --git a/doc/classes/RayShape.xml b/doc/classes/RayShape.xml index 5d488cb3f5..8df910a5c6 100644 --- a/doc/classes/RayShape.xml +++ b/doc/classes/RayShape.xml @@ -16,6 +16,9 @@ <member name="length" type="float" setter="set_length" getter="get_length"> The ray's length. </member> + <member name="slips_on_slope" type="bool" setter="set_slips_on_slope" getter="get_slips_on_slope"> + If [code]true[/code] allow the shape to return the correct normal. Default value: [code]false[/code]. + </member> </members> <constants> </constants> diff --git a/doc/classes/RayShape2D.xml b/doc/classes/RayShape2D.xml index 3137bbd1ad..f0d63f9998 100644 --- a/doc/classes/RayShape2D.xml +++ b/doc/classes/RayShape2D.xml @@ -16,6 +16,9 @@ <member name="length" type="float" setter="set_length" getter="get_length"> The ray's length. </member> + <member name="slips_on_slope" type="bool" setter="set_slips_on_slope" getter="get_slips_on_slope"> + If [code]true[/code] allow the shape to return the correct normal. Default value: [code]false[/code]. + </member> </members> <constants> </constants> diff --git a/doc/classes/RigidBody.xml b/doc/classes/RigidBody.xml index a1aa479ea1..21bd60e138 100644 --- a/doc/classes/RigidBody.xml +++ b/doc/classes/RigidBody.xml @@ -38,7 +38,7 @@ <method name="apply_torque_impulse"> <return type="void"> </return> - <argument index="1" name="impulse" type="Vector3"> + <argument index="0" name="impulse" type="Vector3"> </argument> <description> Apply a torque impulse (which will be affected by the body mass and shape). This will rotate the body around the passed in vector. diff --git a/doc/classes/RigidBody2D.xml b/doc/classes/RigidBody2D.xml index 719ddfffe4..d94c917af2 100644 --- a/doc/classes/RigidBody2D.xml +++ b/doc/classes/RigidBody2D.xml @@ -66,9 +66,11 @@ </return> <argument index="0" name="motion" type="Vector2"> </argument> - <argument index="1" name="margin" type="float" default="0.08"> + <argument index="1" name="infinite_inertia" type="bool" default="true"> </argument> - <argument index="2" name="result" type="Physics2DTestMotionResult" default="null"> + <argument index="2" name="margin" type="float" default="0.08"> + </argument> + <argument index="3" name="result" type="Physics2DTestMotionResult" default="null"> </argument> <description> Returns [code]true[/code] if a collision would result from moving in the given vector. [code]margin[/code] increases the size of the shapes involved in the collision detection, and [code]result[/code] is an object of type [Physics2DTestMotionResult], which contains additional information about the collision (should there be one). diff --git a/doc/classes/Sprite.xml b/doc/classes/Sprite.xml index a3423a0739..dc0310acb5 100644 --- a/doc/classes/Sprite.xml +++ b/doc/classes/Sprite.xml @@ -11,6 +11,13 @@ <demos> </demos> <methods> + <method name="get_rect" qualifiers="const"> + <return type="Rect2"> + </return> + <description> + Returns a Rect2 representing the Sprite's boundary relative to its local coordinates. + </description> + </method> </methods> <members> <member name="centered" type="bool" setter="set_centered" getter="is_centered"> diff --git a/doc/classes/VehicleWheel.xml b/doc/classes/VehicleWheel.xml index ccc4b964c1..4dceecedef 100644 --- a/doc/classes/VehicleWheel.xml +++ b/doc/classes/VehicleWheel.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="VehicleWheel" inherits="Spatial" category="Core" version="3.1-dev"> <brief_description> - Physics object that simulates the behaviour of a wheel. + Physics object that simulates the behaviour of a wheel. </brief_description> <description> - This node needs to be used as a child node of [VehicleBody] and simulates the behaviour of one of its wheels. This node also acts as a collider to detect if the wheel is touching a surface. + This node needs to be used as a child node of [VehicleBody] and simulates the behaviour of one of its wheels. This node also acts as a collider to detect if the wheel is touching a surface. </description> <tutorials> </tutorials> @@ -34,7 +34,7 @@ The damping applied to the spring when relaxing. This value should be between 0.0 (no damping) and 1.0. This value should always be slighly higher then the [member damping_compression] property. For a [member damping_compression] value of 0.3, try a relaxation value of 0.5 </member> <member name="suspension_max_force" type="float" setter="set_suspension_max_force" getter="get_suspension_max_force"> - The maximum force the spring can resist. This value should be higher then a quarter of the [member RigidBody.mass] of the [VehicleBody] or the spring will not carry the weight of the vehicle. Good results are often obtained by a value that is about 3x to 4x this number. + The maximum force the spring can resist. This value should be higher then a quarter of the [member RigidBody.mass] of the [VehicleBody] or the spring will not carry the weight of the vehicle. Good results are often obtained by a value that is about 3x to 4x this number. </member> <member name="suspension_stiffness" type="float" setter="set_suspension_stiffness" getter="get_suspension_stiffness"> This value defines the stiffness of the suspension. Use a value lower then 50 for an offroad car, a value between 50 and 100 for a race car and try something around 200 for something like a Formula 1 car. @@ -56,7 +56,7 @@ The radius of the wheel in meters. </member> <member name="wheel_rest_length" type="float" setter="set_suspension_rest_length" getter="get_suspension_rest_length"> - This is the distance in meters the wheel is lowered from its origin point. Don't set this to 0.0 and move the wheel into position, instead move the origin point of your wheel (the gizmo in Godot) to the position the wheel will take when bottoming out, then use the rest lenght to move the wheel down to the position it should be in when the car is in rest. + This is the distance in meters the wheel is lowered from its origin point. Don't set this to 0.0 and move the wheel into position, instead move the origin point of your wheel (the gizmo in Godot) to the position the wheel will take when bottoming out, then use the rest lenght to move the wheel down to the position it should be in when the car is in rest. </member> <member name="wheel_roll_influence" type="float" setter="set_roll_influence" getter="get_roll_influence"> This value effects the roll of your vehicle. If set to 0.0 for all wheels your vehicle will be prone to rolling over while a value of 1.0 will resist body roll. diff --git a/doc/classes/VisualServer.xml b/doc/classes/VisualServer.xml index 505a3d1474..b709883dad 100644 --- a/doc/classes/VisualServer.xml +++ b/doc/classes/VisualServer.xml @@ -175,7 +175,9 @@ </argument> <argument index="1" name="mesh" type="RID"> </argument> - <argument index="2" name="skeleton" type="RID"> + <argument index="2" name="texture" type="RID"> + </argument> + <argument index="3" name="normal_map" type="RID"> </argument> <description> Adds a [Mesh] to the [CanvasItem]'s draw commands. Only affects its aabb at the moment. @@ -188,7 +190,9 @@ </argument> <argument index="1" name="mesh" type="RID"> </argument> - <argument index="2" name="skeleton" type="RID"> + <argument index="2" name="texture" type="RID"> + </argument> + <argument index="3" name="normal_map" type="RID"> </argument> <description> Adds a [MultiMesh] to the [CanvasItem]'s draw commands. Only affects its aabb at the moment. diff --git a/drivers/dummy/audio_driver_dummy.h b/drivers/dummy/audio_driver_dummy.h index c3d8e10767..b3f0fcee07 100644 --- a/drivers/dummy/audio_driver_dummy.h +++ b/drivers/dummy/audio_driver_dummy.h @@ -43,13 +43,13 @@ public: virtual Error init() { return OK; } virtual void start(){}; - virtual int get_mix_rate() const {}; - virtual SpeakerMode get_speaker_mode() const {}; + virtual int get_mix_rate() const { return DEFAULT_MIX_RATE; }; + virtual SpeakerMode get_speaker_mode() const { return SPEAKER_MODE_STEREO; }; virtual void lock(){}; virtual void unlock(){}; virtual void finish(){}; - virtual float get_latency(){}; + virtual float get_latency() { return 0; }; AudioDriverDummy(){}; ~AudioDriverDummy(){}; diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index 5836f5a700..ea0d3ec706 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -269,7 +269,10 @@ public: PoolVector<uint8_t> p; return p; } - PoolVector<uint8_t> mesh_surface_get_index_array(RID p_mesh, int p_surface) const {} + PoolVector<uint8_t> mesh_surface_get_index_array(RID p_mesh, int p_surface) const { + PoolVector<uint8_t> p; + return p; + } uint32_t mesh_surface_get_format(RID p_mesh, int p_surface) const { return 0; } VS::PrimitiveType mesh_surface_get_primitive_type(RID p_mesh, int p_surface) const { return VS::PRIMITIVE_POINTS; } @@ -301,9 +304,9 @@ public: RID multimesh_get_mesh(RID p_multimesh) const { return RID(); } - Transform multimesh_instance_get_transform(RID p_multimesh, int p_index) const {} - Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const {} - Color multimesh_instance_get_color(RID p_multimesh, int p_index) const {} + Transform multimesh_instance_get_transform(RID p_multimesh, int p_index) const { return Transform(); } + Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const { return Transform2D(); } + Color multimesh_instance_get_color(RID p_multimesh, int p_index) const { return Color(); } void multimesh_set_visible_instances(RID p_multimesh, int p_visible) {} int multimesh_get_visible_instances(RID p_multimesh) const { return 0; } @@ -370,7 +373,7 @@ public: VS::LightType light_get_type(RID p_light) const { return VS::LIGHT_OMNI; } AABB light_get_aabb(RID p_light) const { return AABB(); } float light_get_param(RID p_light, VS::LightParam p_param) { return 0.0; } - Color light_get_color(RID p_light) {} + Color light_get_color(RID p_light) { return Color(); } uint64_t light_get_version(RID p_light) const { return 0; } /* PROBE API */ @@ -391,7 +394,7 @@ public: void reflection_probe_set_cull_mask(RID p_probe, uint32_t p_layers) {} AABB reflection_probe_get_aabb(RID p_probe) const { return AABB(); } - VS::ReflectionProbeUpdateMode reflection_probe_get_update_mode(RID p_probe) const {} + VS::ReflectionProbeUpdateMode reflection_probe_get_update_mode(RID p_probe) const { return VisualServer::REFLECTION_PROBE_UPDATE_ONCE; } uint32_t reflection_probe_get_cull_mask(RID p_probe) const { return 0; } Vector3 reflection_probe_get_extents(RID p_probe) const { return Vector3(); } Vector3 reflection_probe_get_origin_offset(RID p_probe) const { return Vector3(); } @@ -581,7 +584,7 @@ public: RID canvas_light_occluder_create() { return RID(); } void canvas_light_occluder_set_polylines(RID p_occluder, const PoolVector<Vector2> &p_lines) {} - VS::InstanceType get_base_type(RID p_rid) const {} + VS::InstanceType get_base_type(RID p_rid) const { return VS::INSTANCE_NONE; } bool free(RID p_rid) { if (texture_owner.owns(p_rid)) { @@ -590,9 +593,10 @@ public: texture_owner.free(p_rid); memdelete(texture); } + return true; } - bool has_os_feature(const String &p_feature) const {} + bool has_os_feature(const String &p_feature) const { return false; } void update_dirty_resources() {} @@ -600,9 +604,9 @@ public: void render_info_begin_capture() {} void render_info_end_capture() {} - int get_captured_render_info(VS::RenderInfo p_info) {} + int get_captured_render_info(VS::RenderInfo p_info) { return 0; } - int get_render_info(VS::RenderInfo p_info) {} + int get_render_info(VS::RenderInfo p_info) { return 0; } static RasterizerStorage *base_singleton; @@ -619,7 +623,7 @@ public: void canvas_begin(){}; void canvas_end(){}; - void canvas_render_items(Item *p_item_list, int p_z, const Color &p_modulate, Light *p_light,const Transform2D& p_transform){}; + void canvas_render_items(Item *p_item_list, int p_z, const Color &p_modulate, Light *p_light, const Transform2D &p_transform){}; void canvas_debug_viewport_shadows(Light *p_lights_with_shadow){}; void canvas_light_shadow_buffer_update(RID p_buffer, const Transform2D &p_light_xform, int p_light_mask, float p_near, float p_far, LightOccluderInstance *p_occluders, CameraMatrix *p_xform_cache) {} diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp index 2e64b55430..cf4d82fb07 100644 --- a/drivers/windows/dir_access_windows.cpp +++ b/drivers/windows/dir_access_windows.cpp @@ -261,13 +261,30 @@ Error DirAccessWindows::rename(String p_path, String p_new_path) { p_new_path = fix_path(p_new_path); - if (file_exists(p_new_path)) { - if (remove(p_new_path) != OK) { + // If we're only changing file name case we need to do a little juggling + if (p_path.to_lower() == p_new_path.to_lower()) { + WCHAR tmpfile[MAX_PATH]; + + if (!GetTempFileNameW(fix_path(get_current_dir()).c_str(), NULL, 0, tmpfile)) { return FAILED; - }; - }; + } + + if (!::ReplaceFileW(tmpfile, p_path.c_str(), NULL, 0, NULL, NULL)) { + DeleteFileW(tmpfile); + return FAILED; + } - return ::_wrename(p_path.c_str(), p_new_path.c_str()) == 0 ? OK : FAILED; + return ::_wrename(tmpfile, p_new_path.c_str()) == 0 ? OK : FAILED; + + } else { + if (file_exists(p_new_path)) { + if (remove(p_new_path) != OK) { + return FAILED; + } + } + + return ::_wrename(p_path.c_str(), p_new_path.c_str()) == 0 ? OK : FAILED; + } } Error DirAccessWindows::remove(String p_path) { diff --git a/editor/export_template_manager.cpp b/editor/export_template_manager.cpp index d0f008bd43..101deb9126 100644 --- a/editor/export_template_manager.cpp +++ b/editor/export_template_manager.cpp @@ -217,25 +217,15 @@ void ExportTemplateManager::_install_from_file(const String &p_file, bool p_use_ data_str.parse_utf8((const char *)data.ptr(), data.size()); data_str = data_str.strip_edges(); - if (data_str.get_slice_count("-") != 2 || data_str.get_slice_count(".") != 2) { - EditorNode::get_singleton()->show_warning(TTR("Invalid version.txt format inside templates.")); + // Version number should be of the form major.minor[.patch].status[.module_config] + // so it can in theory have 3 or more slices. + if (data_str.get_slice_count(".") < 3) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Invalid version.txt format inside templates: %s."), data_str)); unzClose(pkg); return; } - String ver = data_str.get_slice("-", 0); - - int major = ver.get_slice(".", 0).to_int(); - int minor = ver.get_slice(".", 1).to_int(); - String rev = data_str.get_slice("-", 1); - - if (!rev.is_valid_identifier()) { - EditorNode::get_singleton()->show_warning(TTR("Invalid version.txt format inside templates. Revision is not a valid identifier.")); - unzClose(pkg); - return; - } - - version = itos(major) + "." + itos(minor) + "-" + rev; + version = data_str; } fc++; diff --git a/editor/icons/icon_add_split.svg b/editor/icons/icon_add_split.svg new file mode 100644 index 0000000000..6cfd419e7f --- /dev/null +++ b/editor/icons/icon_add_split.svg @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg4" + sodipodi:docname="icon_add_split.svg" + inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="3066" + inkscape:window-height="1689" + id="namedview6" + showgrid="false" + inkscape:zoom="41.7193" + inkscape:cx="7.7924561" + inkscape:cy="6.0148972" + inkscape:window-x="134" + inkscape:window-y="55" + inkscape:window-maximized="1" + inkscape:current-layer="svg4" /> + <rect + style="fill:#800000" + id="rect12" + width="1.8456686" + height="2.0853658" + x="0.62321275" + y="6.9394455" /> + <rect + style="fill:#800000" + id="rect14" + width="1.6299411" + height="1.9894869" + x="12.488225" + y="7.1791425" /> + <rect + style="fill:#e9afaf" + id="rect16" + width="10.067283" + height="0.69512194" + x="2.492851" + y="7.7304463" /> +</svg> diff --git a/editor/icons/icon_delete_split.svg b/editor/icons/icon_delete_split.svg new file mode 100644 index 0000000000..c24f7449d6 --- /dev/null +++ b/editor/icons/icon_delete_split.svg @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg4" + sodipodi:docname="icon_delete_split.svg" + inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="3066" + inkscape:window-height="1689" + id="namedview6" + showgrid="false" + inkscape:zoom="41.7193" + inkscape:cx="7.7924561" + inkscape:cy="6.0148972" + inkscape:window-x="134" + inkscape:window-y="55" + inkscape:window-maximized="1" + inkscape:current-layer="svg4" /> + <rect + style="fill:#800000" + id="rect12" + width="1.8456686" + height="2.0853658" + x="0.62321275" + y="6.9394455" /> + <rect + style="fill:#800000" + id="rect14" + width="1.6299411" + height="1.9894869" + x="12.488225" + y="7.1791425" /> + <rect + style="fill:#e9afaf" + id="rect37" + width="3.1640031" + height="0.40748528" + x="2.5407906" + y="7.9701433" /> + <rect + style="fill:#e9afaf" + id="rect39" + width="3.5235491" + height="0.52733386" + x="9.0126152" + y="8.0420523" /> + <rect + style="fill:#e9afaf" + id="rect41" + width="3.6433976" + height="0.4554247" + x="1.5110972" + y="-9.732645" + transform="rotate(123.99908)" /> + <rect + style="fill:#e9afaf" + id="rect41-3" + width="3.6433976" + height="0.4554247" + x="0.072069742" + y="-12.144793" + transform="rotate(123.99908)" /> +</svg> diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index f106d2a78e..3a169bd780 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -64,6 +64,8 @@ void Polygon2DEditor::_notification(int p_what) { uv_button[UV_MODE_MOVE]->set_icon(get_icon("ToolMove", "EditorIcons")); uv_button[UV_MODE_ROTATE]->set_icon(get_icon("ToolRotate", "EditorIcons")); uv_button[UV_MODE_SCALE]->set_icon(get_icon("ToolScale", "EditorIcons")); + uv_button[UV_MODE_ADD_SPLIT]->set_icon(get_icon("AddSplit", "EditorIcons")); + uv_button[UV_MODE_REMOVE_SPLIT]->set_icon(get_icon("DeleteSplit", "EditorIcons")); b_snap_grid->set_icon(get_icon("Grid", "EditorIcons")); b_snap_enable->set_icon(get_icon("SnapGrid", "EditorIcons")); @@ -79,12 +81,28 @@ void Polygon2DEditor::_notification(int p_what) { void Polygon2DEditor::_uv_edit_mode_select(int p_mode) { if (p_mode == 0) { - if (uv_button[UV_MODE_CREATE]->is_pressed()) { - _uv_mode(1); - } uv_button[UV_MODE_CREATE]->hide(); + for (int i = UV_MODE_MOVE; i <= UV_MODE_SCALE; i++) { + uv_button[i]->show(); + } + uv_button[UV_MODE_ADD_SPLIT]->hide(); + uv_button[UV_MODE_REMOVE_SPLIT]->hide(); + _uv_mode(UV_MODE_EDIT_POINT); + + } else if (p_mode == 1) { + for (int i = 0; i <= UV_MODE_SCALE; i++) { + uv_button[i]->show(); + } + uv_button[UV_MODE_ADD_SPLIT]->hide(); + uv_button[UV_MODE_REMOVE_SPLIT]->hide(); + _uv_mode(UV_MODE_EDIT_POINT); } else { - uv_button[UV_MODE_CREATE]->show(); + for (int i = 0; i <= UV_MODE_SCALE; i++) { + uv_button[i]->hide(); + } + uv_button[UV_MODE_ADD_SPLIT]->show(); + uv_button[UV_MODE_REMOVE_SPLIT]->show(); + _uv_mode(UV_MODE_ADD_SPLIT); } uv_edit_draw->update(); @@ -195,6 +213,10 @@ void Polygon2DEditor::_set_snap_step_y(float p_val) { void Polygon2DEditor::_uv_mode(int p_mode) { + split_create = false; + uv_drag = false; + uv_create = false; + uv_mode = UVMode(p_mode); for (int i = 0; i < UV_MODE_MAX; i++) { uv_button[i]->set_pressed(p_mode == i); @@ -219,10 +241,11 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { uv_drag = true; uv_prev = node->get_uv(); - if (uv_edit_mode[0]->is_pressed()) //edit uv + if (uv_edit_mode[0]->is_pressed()) { //edit uv uv_prev = node->get_uv(); - else + } else { //edit polygon uv_prev = node->get_polygon(); + } uv_move_current = uv_mode; if (uv_move_current == UV_MODE_CREATE) { @@ -238,6 +261,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { uv_create = true; uv_create_uv_prev = node->get_uv(); uv_create_poly_prev = node->get_polygon(); + splits_prev = node->get_splits(); node->set_polygon(uv_prev); node->set_uv(uv_prev); @@ -292,6 +316,109 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { uv_drag = false; } } + + if (uv_move_current == UV_MODE_ADD_SPLIT) { + + int drag_index = -1; + drag_index = -1; + for (int i = 0; i < uv_prev.size(); i++) { + + Vector2 tuv = mtx.xform(uv_prev[i]); + if (tuv.distance_to(Vector2(mb->get_position().x, mb->get_position().y)) < 8) { + drag_index = i; + } + } + + if (drag_index == -1) { + split_create = false; + return; + } + + if (split_create) { + + split_create = false; + if (drag_index < uv_drag_index) { + SWAP(drag_index, uv_drag_index); + } + bool valid = true; + if (drag_index == uv_drag_index) { + valid = false; + } + if (drag_index + 1 == uv_drag_index) { + //not a split,goes along the edge + valid = false; + } + if (drag_index == uv_prev.size() - 1 && uv_drag_index == 0) { + //not a split,goes along the edge + valid = false; + } + for (int i = 0; i < splits_prev.size(); i += 2) { + if (splits_prev[i] == uv_drag_index && splits_prev[i + 1] == drag_index) { + //already exists + valid = false; + } + if (splits_prev[i] > uv_drag_index && splits_prev[i + 1] > drag_index) { + //crossing + valid = false; + } + + if (splits_prev[i] < uv_drag_index && splits_prev[i + 1] < drag_index) { + //crossing opposite direction + valid = false; + } + } + + if (valid) { + + splits_prev.push_back(uv_drag_index); + splits_prev.push_back(drag_index); + + undo_redo->create_action(TTR("Add Split")); + + undo_redo->add_do_method(node, "set_splits", splits_prev); + undo_redo->add_undo_method(node, "set_splits", node->get_splits()); + undo_redo->add_do_method(uv_edit_draw, "update"); + undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->commit_action(); + } else { + error->set_text(TTR("Invalid Split")); + error->popup_centered_minsize(); + } + + } else { + uv_drag_index = drag_index; + split_create = true; + uv_create_to = mtx.affine_inverse().xform(Vector2(mb->get_position().x, mb->get_position().y)); + } + } + + if (uv_move_current == UV_MODE_REMOVE_SPLIT) { + + for (int i = 0; i < splits_prev.size(); i += 2) { + if (splits_prev[i] < 0 || splits_prev[i] >= uv_prev.size()) + continue; + if (splits_prev[i + 1] < 0 || splits_prev[i] >= uv_prev.size()) + continue; + Vector2 e[2] = { mtx.xform(uv_prev[splits_prev[i]]), mtx.xform(uv_prev[splits_prev[i + 1]]) }; + Vector2 mp = Vector2(mb->get_position().x, mb->get_position().y); + Vector2 cp = Geometry::get_closest_point_to_segment_2d(mp, e); + if (cp.distance_to(mp) < 8) { + splits_prev.remove(i); + splits_prev.remove(i); + + undo_redo->create_action(TTR("Remove Split")); + + undo_redo->add_do_method(node, "set_splits", splits_prev); + undo_redo->add_undo_method(node, "set_splits", node->get_splits()); + undo_redo->add_do_method(uv_edit_draw, "update"); + undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->commit_action(); + + break; + } + } + } + } else if (uv_drag && !uv_create) { undo_redo->create_action(TTR("Transform UV Map")); @@ -299,7 +426,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { if (uv_edit_mode[0]->is_pressed()) { //edit uv undo_redo->add_do_method(node, "set_uv", node->get_uv()); undo_redo->add_undo_method(node, "set_uv", uv_prev); - } else { + } else if (uv_edit_mode[1]->is_pressed()) { //edit polygon undo_redo->add_do_method(node, "set_polygon", node->get_polygon()); undo_redo->add_undo_method(node, "set_polygon", uv_prev); } @@ -318,16 +445,20 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { uv_create = false; node->set_uv(uv_create_uv_prev); node->set_polygon(uv_create_poly_prev); + node->set_splits(splits_prev); uv_edit_draw->update(); } else if (uv_drag) { uv_drag = false; if (uv_edit_mode[0]->is_pressed()) { //edit uv node->set_uv(uv_prev); - } else { + } else if (uv_edit_mode[1]->is_pressed()) { //edit polygon node->set_polygon(uv_prev); } uv_edit_draw->update(); + } else if (split_create) { + split_create = false; + uv_edit_draw->update(); } } else if (mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed()) { @@ -368,7 +499,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { if (uv_edit_mode[0]->is_pressed()) { //edit uv node->set_uv(uv_new); - } else { + } else if (uv_edit_mode[1]->is_pressed()) { //edit polygon node->set_polygon(uv_new); } } break; @@ -380,7 +511,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { if (uv_edit_mode[0]->is_pressed()) { //edit uv node->set_uv(uv_new); - } else { + } else if (uv_edit_mode[1]->is_pressed()) { //edit polygon node->set_polygon(uv_new); } @@ -404,7 +535,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { if (uv_edit_mode[0]->is_pressed()) { //edit uv node->set_uv(uv_new); - } else { + } else if (uv_edit_mode[1]->is_pressed()) { //edit polygon node->set_polygon(uv_new); } @@ -433,12 +564,15 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { if (uv_edit_mode[0]->is_pressed()) { //edit uv node->set_uv(uv_new); - } else { + } else if (uv_edit_mode[1]->is_pressed()) { //edit polygon node->set_polygon(uv_new); } } break; } uv_edit_draw->update(); + } else if (split_create) { + uv_create_to = mtx.affine_inverse().xform(Vector2(mm->get_position().x, mm->get_position().y)); + uv_edit_draw->update(); } } @@ -509,10 +643,11 @@ void Polygon2DEditor::_uv_draw() { } PoolVector<Vector2> uvs; - if (uv_edit_mode[0]->is_pressed()) //edit uv + if (uv_edit_mode[0]->is_pressed()) { //edit uv uvs = node->get_uv(); - else + } else { //edit polygon uvs = node->get_polygon(); + } Ref<Texture> handle = get_icon("EditorHandle", "EditorIcons"); @@ -531,6 +666,22 @@ void Polygon2DEditor::_uv_draw() { rect.expand_to(mtx.basis_xform(uvs[i])); } + if (split_create) { + Vector2 from = uvs[uv_drag_index]; + Vector2 to = uv_create_to; + uv_edit_draw->draw_line(mtx.xform(from), mtx.xform(to), Color(0.9, 0.5, 0.5), 2); + } + + PoolVector<int> splits = node->get_splits(); + + for (int i = 0; i < splits.size(); i += 2) { + int idx_from = splits[i]; + int idx_to = splits[i + 1]; + if (idx_from < 0 || idx_to >= uvs.size()) + continue; + uv_edit_draw->draw_line(mtx.xform(uvs[idx_from]), mtx.xform(uvs[idx_to]), Color(0.9, 0.5, 0.5), 2); + } + rect = rect.grow(200); updating_uv_scroll = true; uv_hscroll->set_min(rect.position.x); @@ -601,16 +752,22 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : uv_edit_mode[1] = memnew(ToolButton); uv_mode_hb->add_child(uv_edit_mode[1]); uv_edit_mode[1]->set_toggle_mode(true); + uv_edit_mode[2] = memnew(ToolButton); + uv_mode_hb->add_child(uv_edit_mode[2]); + uv_edit_mode[2]->set_toggle_mode(true); uv_edit_mode[0]->set_text(TTR("UV")); uv_edit_mode[0]->set_pressed(true); uv_edit_mode[1]->set_text(TTR("Poly")); + uv_edit_mode[2]->set_text(TTR("Splits")); uv_edit_mode[0]->set_button_group(uv_edit_group); uv_edit_mode[1]->set_button_group(uv_edit_group); + uv_edit_mode[2]->set_button_group(uv_edit_group); uv_edit_mode[0]->connect("pressed", this, "_uv_edit_mode_select", varray(0)); uv_edit_mode[1]->connect("pressed", this, "_uv_edit_mode_select", varray(1)); + uv_edit_mode[2]->connect("pressed", this, "_uv_edit_mode_select", varray(2)); uv_mode_hb->add_child(memnew(VSeparator)); @@ -629,8 +786,12 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : uv_button[2]->set_tooltip(TTR("Move Polygon")); uv_button[3]->set_tooltip(TTR("Rotate Polygon")); uv_button[4]->set_tooltip(TTR("Scale Polygon")); + uv_button[5]->set_tooltip(TTR("Connect two points to make a split")); + uv_button[6]->set_tooltip(TTR("Select a split to erase it")); uv_button[0]->hide(); + uv_button[5]->hide(); + uv_button[6]->hide(); uv_button[1]->set_pressed(true); HBoxContainer *uv_main_hb = memnew(HBoxContainer); uv_main_vb->add_child(uv_main_hb); @@ -737,8 +898,9 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : uv_draw_zoom = 1.0; uv_drag_index = -1; uv_drag = false; - uv_create = true; + uv_create = false; updating_uv_scroll = false; + split_create = false; error = memnew(AcceptDialog); add_child(error); diff --git a/editor/plugins/polygon_2d_editor_plugin.h b/editor/plugins/polygon_2d_editor_plugin.h index 2ea2082052..8631ffb9a7 100644 --- a/editor/plugins/polygon_2d_editor_plugin.h +++ b/editor/plugins/polygon_2d_editor_plugin.h @@ -55,17 +55,19 @@ class Polygon2DEditor : public AbstractPolygon2DEditor { UV_MODE_MOVE, UV_MODE_ROTATE, UV_MODE_SCALE, + UV_MODE_ADD_SPLIT, + UV_MODE_REMOVE_SPLIT, UV_MODE_MAX }; - ToolButton *uv_edit_mode[2]; + ToolButton *uv_edit_mode[3]; Ref<ButtonGroup> uv_edit_group; Polygon2D *node; UVMode uv_mode; AcceptDialog *uv_edit; - ToolButton *uv_button[5]; + ToolButton *uv_button[UV_MODE_MAX]; ToolButton *b_snap_enable; ToolButton *b_snap_grid; Control *uv_edit_draw; @@ -81,10 +83,13 @@ class Polygon2DEditor : public AbstractPolygon2DEditor { PoolVector<Vector2> uv_prev; PoolVector<Vector2> uv_create_uv_prev; PoolVector<Vector2> uv_create_poly_prev; + PoolVector<int> splits_prev; + Vector2 uv_create_to; int uv_drag_index; bool uv_drag; bool uv_create; + bool split_create; UVMode uv_move_current; Vector2 uv_drag_from; bool updating_uv_scroll; diff --git a/editor/script_editor_debugger.cpp b/editor/script_editor_debugger.cpp index da3a4ee919..7fa3ed34f0 100644 --- a/editor/script_editor_debugger.cpp +++ b/editor/script_editor_debugger.cpp @@ -1646,12 +1646,20 @@ void ScriptEditorDebugger::_error_selected(int p_idx) { md.push_back(st[i + 1]); md.push_back(st[i + 2]); - String str = func + " in " + script.get_file() + ":line " + itos(line); + String str = func; + String tooltip_str = TTR("Function:") + " " + func; + if (script.length() > 0) { + str += " in " + script.get_file(); + tooltip_str = TTR("File:") + " " + script + "\n" + tooltip_str; + if (line > 0) { + str += ":line " + itos(line); + tooltip_str += "\n" + TTR("Line:") + " " + itos(line); + } + } error_stack->add_item(str); error_stack->set_item_metadata(error_stack->get_item_count() - 1, md); - error_stack->set_item_tooltip(error_stack->get_item_count() - 1, - TTR("File:") + " " + script + "\n" + TTR("Function:") + " " + func + "\n" + TTR("Line:") + " " + itos(line)); + error_stack->set_item_tooltip(error_stack->get_item_count() - 1, tooltip_str); } } diff --git a/main/main.cpp b/main/main.cpp index 89a47d110f..efb5fa8dd9 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -206,6 +206,7 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print(" -e, --editor Start the editor instead of running the scene.\n"); OS::get_singleton()->print(" -p, --project-manager Start the project manager, even if a project is auto-detected.\n"); #endif + OS::get_singleton()->print(" -q, --quit Quit after the first iteration.\n"); OS::get_singleton()->print(" -l, --language <locale> Use a specific locale (<locale> being a two-letter code).\n"); OS::get_singleton()->print(" --path <directory> Path to a project (<directory> must contain a 'project.godot' file).\n"); OS::get_singleton()->print(" -u, --upwards Scan folders upwards for project.godot file.\n"); @@ -263,7 +264,7 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print(" --export-debug Use together with --export, enables debug mode for the template.\n"); OS::get_singleton()->print(" --doctool <path> Dump the engine API reference to the given <path> in XML format, merging if existing files are found.\n"); OS::get_singleton()->print(" --no-docbase Disallow dumping the base types (used with --doctool).\n"); - OS::get_singleton()->print(" --build-solutions Builds the scripting solutions (IE. C#).\n"); + OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects).\n"); #ifdef DEBUG_METHODS_ENABLED OS::get_singleton()->print(" --gdnative-generate-json-api Generate JSON dump of the Godot API for GDNative bindings.\n"); #endif @@ -348,6 +349,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph Vector<String> breakpoints; bool use_custom_res = true; bool force_res = false; + bool found_project = false; packed_data = PackedData::get_singleton(); if (!packed_data) @@ -558,7 +560,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } } else if (I->get() == "-u" || I->get() == "--upwards") { // scan folders upwards upwards = true; - } else if (I->get() == "--quit" || I->get() == "-q") { // Auto quit at the end of the first main loop iteration + } else if (I->get() == "-q" || I->get() == "--quit") { // Auto quit at the end of the first main loop iteration auto_quit = true; } else if (I->get().ends_with("project.godot")) { String path; @@ -759,7 +761,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph #endif - if (globals->setup(game_path, main_pack, upwards) != OK) { + if (globals->setup(game_path, main_pack, upwards) == OK) { + found_project = true; + } else { #ifdef TOOLS_ENABLED editor = false; @@ -768,15 +772,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph goto error; #endif - } else if (String(GLOBAL_DEF("application/run/main_scene", "")) == "") { -#ifdef TOOLS_ENABLED - if (!editor) { -#endif - OS::get_singleton()->print("Error: Can't run project: no main scene defined.\n"); - goto error; -#ifdef TOOLS_ENABLED - } -#endif } GLOBAL_DEF("logging/file_logging/enable_file_logging", false); @@ -800,14 +795,21 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (!project_manager) { // Determine if the project manager should be requested - project_manager = - main_args.size() == 0 && - !ProjectSettings::get_singleton()->has_setting("application/run/main_loop_type") && - (!ProjectSettings::get_singleton()->has_setting("application/run/main_scene") || - String(ProjectSettings::get_singleton()->get("application/run/main_scene")) == ""); + project_manager = main_args.size() == 0 && !found_project; } #endif + if (main_args.size() == 0 && String(GLOBAL_DEF("application/run/main_scene", "")) == "") { +#ifdef TOOLS_ENABLED + if (!editor && !project_manager) { +#endif + OS::get_singleton()->print("Error: Can't run project: no main scene defined.\n"); + goto error; +#ifdef TOOLS_ENABLED + } +#endif + } + if (editor || project_manager) { use_custom_res = false; input_map->load_default(); //keys for editor diff --git a/misc/dist/linux/godot.6 b/misc/dist/linux/godot.6 index 22836e03ed..04982d9919 100644 --- a/misc/dist/linux/godot.6 +++ b/misc/dist/linux/godot.6 @@ -1,4 +1,4 @@ -.TH GODOT "6" "January 2018" "godot 3.0" "Games" +.TH GODOT "6" "February 2018" "godot 3.1" "Games" .SH NAME godot \- multi\-platform 2D and 3D game engine with a feature\-rich editor .SH SYNOPSIS @@ -31,6 +31,9 @@ Start the editor instead of running the scene. \fB\-p\fR, \fB\-\-project\-manager\fR Start the project manager, even if a project is auto\-detected. .TP +\fB\-q\fR, \fB\-\-quit\fR +Quit after the first iteration. +.TP \fB\-l\fR, \fB\-\-language\fR <locale> Use a specific locale (<locale> being a two\-letter code). .TP @@ -68,6 +71,9 @@ Request a maximized window. \fB\-w\fR, \fB\-\-windowed\fR Request windowed mode. .TP +\fB\-t\fR, \fB\-\-always\-on\-top\fR +Request an always\-on\-top window. +.TP \fB\-\-resolution\fR <W>x<H> Request window resolution. .TP @@ -130,6 +136,9 @@ Dump the engine API reference to the given <path> in XML format, merging if exis \fB\-\-no\-docbase\fR Disallow dumping the base types (used with \fB\-\-doctool\fR). .TP +\fB\-\-build\-solutions\fR +Build the scripting solutions (e.g. for C# projects). +.TP \fB\-\-gdnative\-generate\-json\-api\fR Generate JSON dump of the Godot API for GDNative bindings. .TP diff --git a/modules/mono/SCsub b/modules/mono/SCsub index aa8626e6da..e65682484e 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -31,11 +31,18 @@ def make_cs_files_header(src, dst): if i > 0: header.write(', ') header.write(byte_to_str(buf[buf_idx])) - inserted_files += '\tr_files.insert(\"' + file + '\", ' \ + inserted_files += '\tr_files.insert("' + file + '", ' \ 'CompressedFile(_cs_' + name + '_compressed_size, ' \ '_cs_' + name + '_uncompressed_size, ' \ '_cs_' + name + '_compressed));\n' header.write(' };\n') + version_file = os.path.join(src, 'VERSION.txt') + with open(version_file, 'r') as content_file: + try: + glue_version = int(content_file.read()) # make sure the format is valid + header.write('\n#define CS_GLUE_VERSION UINT32_C(' + str(glue_version) + ')\n') + except ValueError: + raise ValueError('Invalid C# glue version in: ' + version_file) header.write('\nstruct CompressedFile\n' '{\n' '\tint compressed_size;\n' '\tint uncompressed_size;\n' '\tconst unsigned char* data;\n' '\n\tCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n' @@ -80,23 +87,23 @@ def find_msbuild_unix(filename): import sys hint_dirs = ['/opt/novell/mono/bin'] - if sys.platform == "darwin": + if sys.platform == 'darwin': hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin'] + hint_dirs for hint_dir in hint_dirs: hint_path = os.path.join(hint_dir, filename) if os.path.isfile(hint_path): return hint_path - elif os.path.isfile(hint_path + ".exe"): - return hint_path + ".exe" + elif os.path.isfile(hint_path + '.exe'): + return hint_path + '.exe' - for hint_dir in os.environ["PATH"].split(os.pathsep): + for hint_dir in os.environ['PATH'].split(os.pathsep): hint_dir = hint_dir.strip('"') hint_path = os.path.join(hint_dir, filename) 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" + if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK): + return hint_path + '.exe' return None @@ -152,7 +159,7 @@ def mono_build_solution(source, target, env): xbuild_fallback = env['xbuild_fallback'] if xbuild_fallback and os.name == 'nt': - print("Option 'xbuild_fallback' not supported on Windows") + print('Option \'xbuild_fallback\' not supported on Windows') xbuild_fallback = False if xbuild_fallback: diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index b0854658ce..525b918b1f 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -456,7 +456,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() #ifdef DEBUG_ENABLED // Printing an error here will result in endless recursion, so we must be careful - if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_api_assembly() || !GDMonoUtils::mono_cache.corlib_cache_updated) + if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoUtils::mono_cache.corlib_cache_updated) return Vector<StackInfo>(); MonoObject *stack_trace = mono_object_new(mono_domain_get(), CACHED_CLASS(System_Diagnostics_StackTrace)->get_mono_ptr()); @@ -1552,7 +1552,6 @@ bool CSharpScript::_update_exports() { } bool CSharpScript::_update_signals() { -#ifdef TOOLS_ENABLED if (!valid) return false; @@ -1583,8 +1582,6 @@ bool CSharpScript::_update_signals() { } return changed; -#endif - return false; } bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> ¶ms) { @@ -2137,9 +2134,7 @@ void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { } void CSharpScript::update_signals() { -#ifdef TOOLS_ENABLED _update_signals(); -#endif } Ref<Script> CSharpScript::get_base_script() const { diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index ffb1d2e0f4..1f609627de 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -90,15 +90,15 @@ class CSharpScript : public Script { Variant::Type type; }; + Map<StringName, Vector<Argument> > _signals; + bool signals_invalidated; + #ifdef TOOLS_ENABLED List<PropertyInfo> exported_members_cache; // members_cache Map<StringName, Variant> exported_members_defval_cache; // member_default_values_cache Set<PlaceHolderScriptInstance *> placeholders; bool source_changed_cache; bool exports_invalidated; - Map<StringName, Vector<Argument> > _signals; - bool signals_invalidated; - void _update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames); virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); #endif diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 952e033565..315c1e2f1c 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -70,8 +70,6 @@ #define LOCAL_RET "ret" -#define CS_CLASS_NATIVECALLS "NativeCalls" -#define CS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls" #define CS_FIELD_MEMORYOWN "memoryOwn" #define CS_PARAM_METHODBIND "method" #define CS_PARAM_INSTANCE "ptr" @@ -105,6 +103,8 @@ #define C_METHOD_MANAGED_TO_DICT C_NS_MONOMARSHAL "::mono_object_to_Dictionary" #define C_METHOD_MANAGED_FROM_DICT C_NS_MONOMARSHAL "::Dictionary_to_mono_object" +#define BINDINGS_GENERATOR_VERSION UINT32_C(1) + const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN = "\t%0 %1_in = %1;\n"; bool BindingsGenerator::verbose_output = false; @@ -529,7 +529,15 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo "using System.Collections.Generic;\n" "\n"); cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - cs_icalls_content.push_back(INDENT1 "internal static class " CS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK); + cs_icalls_content.push_back(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK); + + cs_icalls_content.push_back(INDENT2 "internal static ulong godot_api_hash = "); + cs_icalls_content.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n"); + cs_icalls_content.push_back(INDENT2 "internal static uint bindings_version = "); + cs_icalls_content.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); + cs_icalls_content.push_back(INDENT2 "internal static uint cs_glue_version = "); + cs_icalls_content.push_back(String::num_uint64(CS_GLUE_VERSION) + ";\n"); + cs_icalls_content.push_back("\n"); #define ADD_INTERNAL_CALL(m_icall) \ if (!m_icall.editor_only) { \ @@ -551,7 +559,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo cs_icalls_content.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); - String internal_methods_file = path_join(core_dir, CS_CLASS_NATIVECALLS ".cs"); + String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS ".cs"); Error err = _save_file(internal_methods_file, cs_icalls_content); if (err != OK) @@ -626,7 +634,15 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_output_dir, "using System.Collections.Generic;\n" "\n"); cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - cs_icalls_content.push_back(INDENT1 "internal static class " CS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK); + cs_icalls_content.push_back(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK); + + cs_icalls_content.push_back(INDENT2 "internal static ulong godot_api_hash = "); + cs_icalls_content.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + ";\n"); + cs_icalls_content.push_back(INDENT2 "internal static uint bindings_version = "); + cs_icalls_content.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); + cs_icalls_content.push_back(INDENT2 "internal static uint cs_glue_version = "); + cs_icalls_content.push_back(String::num_uint64(CS_GLUE_VERSION) + ";\n"); + cs_icalls_content.push_back("\n"); #define ADD_INTERNAL_CALL(m_icall) \ if (m_icall.editor_only) { \ @@ -648,7 +664,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_output_dir, cs_icalls_content.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); - String internal_methods_file = path_join(core_dir, CS_CLASS_NATIVECALLS_EDITOR ".cs"); + String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs"); Error err = _save_file(internal_methods_file, cs_icalls_content); if (err != OK) @@ -882,7 +898,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.push_back("\";\n"); output.push_back(INDENT2 "internal static IntPtr " BINDINGS_PTR_FIELD " = "); - output.push_back(itype.api_type == ClassDB::API_EDITOR ? CS_CLASS_NATIVECALLS_EDITOR : CS_CLASS_NATIVECALLS); + output.push_back(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); output.push_back("." ICALL_PREFIX); output.push_back(itype.name); output.push_back(SINGLETON_ICALL_SUFFIX "();\n"); @@ -912,7 +928,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // The engine will initialize the pointer field of the managed side before calling the constructor // This is why we only allocate a new native object from the constructor if the pointer field is not set output.push_back(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = "); - output.push_back(itype.api_type == ClassDB::API_EDITOR ? CS_CLASS_NATIVECALLS_EDITOR : CS_CLASS_NATIVECALLS); + output.push_back(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); output.push_back("." + ctor_method); output.push_back("(this);\n" CLOSE_BLOCK_L2); } else { @@ -956,7 +972,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str "if (disposed) return;\n" INDENT3 "if (" BINDINGS_PTR_FIELD " != IntPtr.Zero)\n" OPEN_BLOCK_L3 "if (" CS_FIELD_MEMORYOWN ")\n" OPEN_BLOCK_L4 CS_FIELD_MEMORYOWN - " = false;\n" INDENT5 CS_CLASS_NATIVECALLS "." ICALL_OBJECT_DTOR + " = false;\n" INDENT5 BINDINGS_CLASS_NATIVECALLS "." ICALL_OBJECT_DTOR "(this, " BINDINGS_PTR_FIELD ");\n" CLOSE_BLOCK_L4 CLOSE_BLOCK_L3 INDENT3 "this." BINDINGS_PTR_FIELD " = IntPtr.Zero;\n" INDENT3 "GC.SuppressFinalize(this);\n" INDENT3 "disposed = true;\n" CLOSE_BLOCK_L2); @@ -1229,7 +1245,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf { if (p_itype.is_object_type && !p_imethod.is_virtual && !p_imethod.requires_object_call) { p_output.push_back(MEMBER_BEGIN "private static IntPtr "); - p_output.push_back(method_bind_field + " = " CS_CLASS_NATIVECALLS "." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); + p_output.push_back(method_bind_field + " = " BINDINGS_CLASS_NATIVECALLS "." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); p_output.push_back(p_imethod.name); p_output.push_back("\");\n"); } @@ -1310,7 +1326,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf const InternalCall *im_icall = match->value(); - String im_call = im_icall->editor_only ? CS_CLASS_NATIVECALLS_EDITOR : CS_CLASS_NATIVECALLS; + String im_call = im_icall->editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS; im_call += "." + im_icall->name + "(" + icall_params + ");\n"; if (p_imethod.arguments.size()) @@ -1400,25 +1416,33 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { } output.push_back("namespace GodotSharpBindings\n" OPEN_BLOCK); + output.push_back("uint64_t get_core_api_hash() { return "); - output.push_back(itos(GDMono::get_singleton()->get_api_core_hash()) + "; }\n"); + output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "; }\n"); + output.push_back("#ifdef TOOLS_ENABLED\n" "uint64_t get_editor_api_hash() { return "); - output.push_back(itos(GDMono::get_singleton()->get_api_editor_hash()) + + output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + "; }\n#endif // TOOLS_ENABLED\n"); + + output.push_back("uint32_t get_bindings_version() { return "); + output.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n"); + output.push_back("uint32_t get_cs_glue_version() { return "); + output.push_back(String::num_uint64(CS_GLUE_VERSION) + "; }\n"); + output.push_back("void register_generated_icalls() " OPEN_BLOCK); output.push_back("\tgodot_register_header_icalls();"); -#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \ - { \ - output.push_back("\tmono_add_internal_call("); \ - output.push_back("\"" BINDINGS_NAMESPACE "."); \ - output.push_back(m_icall.editor_only ? CS_CLASS_NATIVECALLS_EDITOR : CS_CLASS_NATIVECALLS); \ - output.push_back("::"); \ - output.push_back(m_icall.name); \ - output.push_back("\", (void*)"); \ - output.push_back(m_icall.name); \ - output.push_back(");\n"); \ +#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \ + { \ + output.push_back("\tmono_add_internal_call("); \ + output.push_back("\"" BINDINGS_NAMESPACE "."); \ + output.push_back(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \ + output.push_back("::"); \ + output.push_back(m_icall.name); \ + output.push_back("\", (void*)"); \ + output.push_back(m_icall.name); \ + output.push_back(");\n"); \ } bool tools_sequence = false; @@ -1486,6 +1510,14 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { return OK; } +uint32_t BindingsGenerator::get_version() { + return BINDINGS_GENERATOR_VERSION; +} + +uint32_t BindingsGenerator::get_cs_glue_version() { + return CS_GLUE_VERSION; +} + Error BindingsGenerator::_save_file(const String &p_path, const List<String> &p_content) { FileAccessRef file = FileAccess::open(p_path, FileAccess::WRITE); @@ -1528,9 +1560,9 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte if (p_imethod.is_vararg) { if (i < p_imethod.arguments.size() - 1) { c_in_statements += sformat(arg_type->c_in.size() ? arg_type->c_in : TypeInterface::DEFAULT_VARARG_C_IN, "Variant", c_param_name); - c_in_statements += "\t" C_LOCAL_PTRCALL_ARGS ".set(0, "; - c_in_statements += sformat("&%s_in", c_param_name); - c_in_statements += ");\n"; + c_in_statements += "\t" C_LOCAL_PTRCALL_ARGS ".set("; + c_in_statements += itos(i); + c_in_statements += sformat(", &%s_in);\n", c_param_name); } } else { if (i > 0) diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 9b5a9cea88..f6194139af 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -536,6 +536,9 @@ public: Error generate_cs_editor_project(const String &p_output_dir, const String &p_core_dll_path, bool p_verbose_output = true); Error generate_glue(const String &p_output_dir); + static uint32_t get_version(); + static uint32_t get_cs_glue_version(); + void initialize(); _FORCE_INLINE_ static BindingsGenerator *get_singleton() { diff --git a/modules/mono/editor/godotsharp_builds.cpp b/modules/mono/editor/godotsharp_builds.cpp index 51e6d76ae6..ad07f043b2 100644 --- a/modules/mono/editor/godotsharp_builds.cpp +++ b/modules/mono/editor/godotsharp_builds.cpp @@ -33,7 +33,6 @@ #include "main/main.h" #include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono.h" #include "../mono_gd/gd_mono_class.h" #include "../mono_gd/gd_mono_marshal.h" #include "../utils/path_utils.h" @@ -178,13 +177,15 @@ bool GodotSharpBuilds::build_api_sln(const String &p_name, const String &p_api_s return true; } -bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name) { +bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) { String assembly_file = p_assembly_name + ".dll"; String assembly_src = p_src_dir.plus_file(assembly_file); String assembly_dst = p_dst_dir.plus_file(assembly_file); - if (!FileAccess::exists(assembly_dst) || FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst)) { + 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); String xml_file = p_assembly_name + ".xml"; @@ -203,33 +204,46 @@ bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String & show_build_error_dialog("Failed to copy " + assembly_file); return false; } + + GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false); } return true; } -bool GodotSharpBuilds::make_api_sln(GodotSharpBuilds::APIType p_api_type) { +String GodotSharpBuilds::_api_folder_name(APIAssembly::Type p_api_type) { + + uint64_t api_hash = p_api_type == APIAssembly::API_CORE ? + GDMono::get_singleton()->get_api_core_hash() : + GDMono::get_singleton()->get_api_editor_hash(); + return String::num_uint64(api_hash) + + "_" + String::num_uint64(BindingsGenerator::get_version()) + + "_" + String::num_uint64(BindingsGenerator::get_cs_glue_version()); +} - String api_name = p_api_type == API_CORE ? API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; +bool GodotSharpBuilds::make_api_sln(APIAssembly::Type p_api_type) { + + String api_name = p_api_type == APIAssembly::API_CORE ? API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; String api_build_config = "Release"; EditorProgress pr("mono_build_release_" + api_name, "Building " + api_name + " solution...", 4); pr.step("Generating " + api_name + " solution"); - uint64_t core_hash = GDMono::get_singleton()->get_api_core_hash(); - uint64_t editor_hash = GDMono::get_singleton()->get_api_editor_hash(); - - String core_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir().plus_file(API_ASSEMBLY_NAME "_" + itos(core_hash)); - String editor_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir().plus_file(EDITOR_API_ASSEMBLY_NAME "_" + itos(editor_hash)); + 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 = p_api_type == API_CORE ? core_api_sln_dir : editor_api_sln_dir; + 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"); if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) { String core_api_assembly; - if (p_api_type == API_EDITOR) { + 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"); @@ -242,7 +256,7 @@ bool GodotSharpBuilds::make_api_sln(GodotSharpBuilds::APIType p_api_type) { BindingsGenerator *gen = BindingsGenerator::get_singleton(); bool gen_verbose = OS::get_singleton()->is_stdout_verbose(); - Error err = p_api_type == API_CORE ? + 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); @@ -275,7 +289,7 @@ bool GodotSharpBuilds::make_api_sln(GodotSharpBuilds::APIType p_api_type) { // Copy the built assembly to the assemblies directory String api_assembly_dir = api_sln_dir.plus_file("bin").plus_file(api_build_config); - if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name)) + if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name, p_api_type)) return false; pr.step("Done"); @@ -288,10 +302,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(GodotSharpBuilds::API_CORE)) + if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_CORE)) return false; - if (!GodotSharpBuilds::make_api_sln(GodotSharpBuilds::API_EDITOR)) + if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_EDITOR)) return false; EditorProgress pr("mono_project_debug_build", "Building project solution...", 2); diff --git a/modules/mono/editor/godotsharp_builds.h b/modules/mono/editor/godotsharp_builds.h index 0574fbfe8c..27b771e324 100644 --- a/modules/mono/editor/godotsharp_builds.h +++ b/modules/mono/editor/godotsharp_builds.h @@ -31,6 +31,7 @@ #ifndef GODOTSHARP_BUILDS_H #define GODOTSHARP_BUILDS_H +#include "../mono_gd/gd_mono.h" #include "mono_bottom_panel.h" #include "mono_build_info.h" @@ -56,17 +57,14 @@ private: HashMap<MonoBuildInfo, BuildProcess, MonoBuildInfo::Hasher> builds; + static String _api_folder_name(APIAssembly::Type p_api_type); + static GodotSharpBuilds *singleton; friend class GDMono; static void _register_internal_calls(); public: - enum APIType { - API_CORE, - API_EDITOR - }; - enum BuildTool { MSBUILD_MONO, #ifdef WINDOWS_ENABLED @@ -89,9 +87,9 @@ public: 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 copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name); + 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(APIType p_api_type); + static bool make_api_sln(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 0919a7355b..998da8bda3 100644 --- a/modules/mono/editor/godotsharp_editor.cpp +++ b/modules/mono/editor/godotsharp_editor.cpp @@ -85,10 +85,10 @@ bool GodotSharpEditor::_create_project_solution() { return false; } - if (!GodotSharpBuilds::make_api_sln(GodotSharpBuilds::API_CORE)) + if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_CORE)) return false; - if (!GodotSharpBuilds::make_api_sln(GodotSharpBuilds::API_EDITOR)) + if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_EDITOR)) return false; pr.step(TTR("Done")); diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index 387929a02e..cd09e6516a 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -55,6 +55,9 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug // TODO right now there is no way to stop the export process with an error + ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); + ERR_FAIL_NULL(GDMono::get_singleton()->get_tools_domain()); + String build_config = p_debug ? "Debug" : "Release"; ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config)); diff --git a/modules/mono/glue/cs_files/VERSION.txt b/modules/mono/glue/cs_files/VERSION.txt new file mode 100755 index 0000000000..d00491fd7e --- /dev/null +++ b/modules/mono/glue/cs_files/VERSION.txt @@ -0,0 +1 @@ +1 diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h index 4c26c3e6bd..f604464e8f 100644 --- a/modules/mono/godotsharp_defs.h +++ b/modules/mono/godotsharp_defs.h @@ -39,4 +39,7 @@ #define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor" #define EDITOR_TOOLS_ASSEMBLY_NAME "GodotSharpTools" +#define BINDINGS_CLASS_NATIVECALLS "NativeCalls" +#define BINDINGS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls" + #endif // GODOTSHARP_DEFS_H diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 519c30045a..1ebef04561 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -42,7 +42,10 @@ #include "project_settings.h" #include "../csharp_script.h" +#include "../godotsharp_dirs.h" #include "../utils/path_utils.h" +#include "gd_mono_class.h" +#include "gd_mono_marshal.h" #include "gd_mono_utils.h" #ifdef TOOLS_ENABLED @@ -208,7 +211,46 @@ void GDMono::initialize() { _register_internal_calls(); // The following assemblies are not required at initialization - _load_all_script_assemblies(); +#ifndef MONO_GLUE_DISABLED + if (_load_api_assemblies()) { + if (!core_api_assembly_out_of_sync && !editor_api_assembly_out_of_sync && GDMonoUtils::mono_cache.godot_api_cache_updated) { + // Everything is fine with the api assemblies, load the project assembly + _load_project_assembly(); + } else { +#ifdef TOOLS_ENABLED + // The assembly was successfuly loaded, but the full api could not be cached. + // This is most likely an outdated assembly loaded because of an invalid version in the metadata, + // so we invalidate the version in the metadata and unload the script domain. + + if (core_api_assembly_out_of_sync) { + ERR_PRINT("The loaded Core API assembly is out of sync"); + metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); + } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) { + ERR_PRINT("The loaded Core API assembly is in sync, but the cache update failed"); + metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); + } + + if (editor_api_assembly_out_of_sync) { + ERR_PRINT("The loaded Editor API assembly is out of sync"); + metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true); + } + + OS::get_singleton()->print("Mono: Proceeding to unload scripts domain because of invalid API assemblies\n"); + + Error err = _unload_scripts_domain(); + if (err != OK) { + WARN_PRINT("Mono: Failed to unload scripts domain"); + } +#else + ERR_PRINT("The loaded API assembly is invalid"); + CRASH_NOW(); +#endif + } + } +#else + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print("Mono: Glue disabled, ignoring script assemblies\n"); +#endif mono_install_unhandled_exception_hook(gdmono_unhandled_exception_hook, NULL); @@ -219,7 +261,11 @@ void GDMono::initialize() { namespace GodotSharpBindings { uint64_t get_core_api_hash(); +#ifdef TOOLS_ENABLED uint64_t get_editor_api_hash(); +#endif // TOOLS_ENABLED +uint32_t get_bindings_version(); +uint32_t get_cs_glue_version(); void register_generated_icalls(); } // namespace GodotSharpBindings @@ -313,6 +359,36 @@ bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMo return true; } +APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, APIAssembly::Type p_api_type) { + APIAssembly::Version api_assembly_version; + + const char *nativecalls_name = p_api_type == APIAssembly::API_CORE ? + BINDINGS_CLASS_NATIVECALLS : + BINDINGS_CLASS_NATIVECALLS_EDITOR; + + GDMonoClass *nativecalls_klass = p_api_assembly->get_class(BINDINGS_NAMESPACE, nativecalls_name); + + if (nativecalls_klass) { + GDMonoField *api_hash_field = nativecalls_klass->get_field("godot_api_hash"); + if (api_hash_field) + api_assembly_version.godot_api_hash = GDMonoMarshal::unbox<uint64_t>(api_hash_field->get_value(NULL)); + + GDMonoField *binds_ver_field = nativecalls_klass->get_field("bindings_version"); + if (binds_ver_field) + api_assembly_version.bindings_version = GDMonoMarshal::unbox<uint32_t>(binds_ver_field->get_value(NULL)); + + GDMonoField *cs_glue_ver_field = nativecalls_klass->get_field("cs_glue_version"); + if (cs_glue_ver_field) + api_assembly_version.cs_glue_version = GDMonoMarshal::unbox<uint32_t>(cs_glue_ver_field->get_value(NULL)); + } + + return api_assembly_version; +} + +String APIAssembly::to_string(APIAssembly::Type p_type) { + return p_type == APIAssembly::API_CORE ? "API_CORE" : "API_EDITOR"; +} + bool GDMono::_load_corlib_assembly() { if (corlib_assembly) @@ -328,13 +404,25 @@ bool GDMono::_load_corlib_assembly() { bool GDMono::_load_core_api_assembly() { - if (api_assembly) + if (core_api_assembly) return true; - bool success = load_assembly(API_ASSEMBLY_NAME, &api_assembly); +#ifdef TOOLS_ENABLED + if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) + return false; +#endif + + bool success = load_assembly(API_ASSEMBLY_NAME, &core_api_assembly); - if (success) + if (success) { +#ifndef MONO_GLUE_DISABLED + APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(core_api_assembly, APIAssembly::API_CORE); + core_api_assembly_out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash || + GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || + GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; +#endif GDMonoUtils::update_godot_api_cache(); + } return success; } @@ -345,7 +433,23 @@ bool GDMono::_load_editor_api_assembly() { if (editor_api_assembly) return true; - return load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly); +#ifdef TOOLS_ENABLED + if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) + return false; +#endif + + bool success = load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly); + + if (success) { +#ifndef MONO_GLUE_DISABLED + APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(editor_api_assembly, APIAssembly::API_EDITOR); + editor_api_assembly_out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash || + GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || + GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; +#endif + } + + return success; } #endif @@ -373,15 +477,18 @@ bool GDMono::_load_project_assembly() { bool success = load_assembly(name, &project_assembly); - if (success) + if (success) { mono_assembly_set_main(project_assembly->get_assembly()); + } else { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->printerr("Mono: Failed to load project assembly\n"); + } return success; } -bool GDMono::_load_all_script_assemblies() { +bool GDMono::_load_api_assemblies() { -#ifndef MONO_GLUE_DISABLED if (!_load_core_api_assembly()) { if (OS::get_singleton()->is_stdout_verbose()) OS::get_singleton()->printerr("Mono: Failed to load Core API assembly\n"); @@ -396,20 +503,72 @@ bool GDMono::_load_all_script_assemblies() { #endif } - if (!_load_project_assembly()) { - if (OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->printerr("Mono: Failed to load project assembly\n"); - return false; + return true; +} + +#ifdef TOOLS_ENABLED +String GDMono::_get_api_assembly_metadata_path() { + + return GodotSharpDirs::get_res_metadata_dir().plus_file("api_assemblies.cfg"); +} + +void GDMono::metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated) { + + String section = APIAssembly::to_string(p_api_type); + String path = _get_api_assembly_metadata_path(); + + Ref<ConfigFile> metadata; + metadata.instance(); + metadata->load(path); + + metadata->set_value(section, "invalidated", p_invalidated); + + String assembly_path = GodotSharpDirs::get_res_assemblies_dir() + .plus_file(p_api_type == APIAssembly::API_CORE ? + API_ASSEMBLY_NAME ".dll" : + EDITOR_API_ASSEMBLY_NAME ".dll"); + + ERR_FAIL_COND(!FileAccess::exists(assembly_path)); + + uint64_t modified_time = FileAccess::get_modified_time(assembly_path); + + metadata->set_value(section, "invalidated_asm_modified_time", String::num_uint64(modified_time)); + + String dir = path.get_base_dir(); + if (!DirAccess::exists(dir)) { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND(!da); + Error err = da->make_dir_recursive(ProjectSettings::get_singleton()->globalize_path(dir)); + ERR_FAIL_COND(err != OK); } - return true; -#else - if (OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->print("Mono: Glue disbled, ignoring script assemblies\n"); + Error save_err = metadata->save(path); + ERR_FAIL_COND(save_err != OK); +} - return true; -#endif +bool GDMono::metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type) { + + String section = APIAssembly::to_string(p_api_type); + + Ref<ConfigFile> metadata; + metadata.instance(); + metadata->load(_get_api_assembly_metadata_path()); + + String assembly_path = GodotSharpDirs::get_res_assemblies_dir() + .plus_file(p_api_type == APIAssembly::API_CORE ? + API_ASSEMBLY_NAME ".dll" : + EDITOR_API_ASSEMBLY_NAME ".dll"); + + if (!FileAccess::exists(assembly_path)) + return false; + + uint64_t modified_time = FileAccess::get_modified_time(assembly_path); + + uint64_t stored_modified_time = metadata->get_value(section, "invalidated_asm_modified_time", 0); + + return metadata->get_value(section, "invalidated", false) && modified_time <= stored_modified_time; } +#endif Error GDMono::_load_scripts_domain() { @@ -452,12 +611,15 @@ Error GDMono::_unload_scripts_domain() { _domain_assemblies_cleanup(mono_domain_get_id(scripts_domain)); - api_assembly = NULL; + core_api_assembly = NULL; project_assembly = NULL; #ifdef TOOLS_ENABLED editor_api_assembly = NULL; #endif + core_api_assembly_out_of_sync = false; + editor_api_assembly_out_of_sync = false; + MonoDomain *domain = scripts_domain; scripts_domain = NULL; @@ -512,12 +674,45 @@ Error GDMono::reload_scripts_domain() { return err; } - if (!_load_all_script_assemblies()) { - if (OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->printerr("Mono: Failed to load script assemblies\n"); +#ifndef MONO_GLUE_DISABLED + if (!_load_api_assemblies()) { return ERR_CANT_OPEN; } + if (!core_api_assembly_out_of_sync && !editor_api_assembly_out_of_sync && GDMonoUtils::mono_cache.godot_api_cache_updated) { + // Everything is fine with the api assemblies, load the project assembly + _load_project_assembly(); + } else { + // The assembly was successfuly loaded, but the full api could not be cached. + // This is most likely an outdated assembly loaded because of an invalid version in the metadata, + // so we invalidate the version in the metadata and unload the script domain. + + if (core_api_assembly_out_of_sync) { + metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); + } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) { + ERR_PRINT("Core API assembly is in sync, but the cache update failed"); + metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); + } + + if (editor_api_assembly_out_of_sync) { + metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true); + } + + Error err = _unload_scripts_domain(); + if (err != OK) { + WARN_PRINT("Mono: Failed to unload scripts domain"); + } + + return ERR_CANT_RESOLVE; + } + + if (!_load_project_assembly()) + return ERR_CANT_OPEN; +#else + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print("Mono: Glue disabled, ignoring script assemblies\n"); +#endif + return OK; } #endif @@ -604,8 +799,11 @@ GDMono::GDMono() { tools_domain = NULL; #endif + core_api_assembly_out_of_sync = false; + editor_api_assembly_out_of_sync = false; + corlib_assembly = NULL; - api_assembly = NULL; + core_api_assembly = NULL; project_assembly = NULL; #ifdef TOOLS_ENABLED editor_api_assembly = NULL; diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 07f649c442..5e01152870 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -31,6 +31,8 @@ #ifndef GD_MONO_H #define GD_MONO_H +#include "core/io/config_file.h" + #include "../godotsharp_defs.h" #include "gd_mono_assembly.h" #include "gd_mono_log.h" @@ -39,6 +41,43 @@ #include "../utils/mono_reg_utils.h" #endif +namespace APIAssembly { +enum Type { + API_CORE, + API_EDITOR +}; + +struct Version { + uint64_t godot_api_hash; + uint32_t bindings_version; + uint32_t cs_glue_version; + + bool operator==(const Version &p_other) const { + return godot_api_hash == p_other.godot_api_hash && + bindings_version == p_other.bindings_version && + cs_glue_version == p_other.cs_glue_version; + } + + Version() : + godot_api_hash(0), + bindings_version(0), + cs_glue_version(0) { + } + + Version(uint64_t p_godot_api_hash, + uint32_t p_bindings_version, + uint32_t p_cs_glue_version) : + godot_api_hash(p_godot_api_hash), + bindings_version(p_bindings_version), + cs_glue_version(p_cs_glue_version) { + } + + static Version get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, Type p_api_type); +}; + +String to_string(Type p_type); +} // namespace APIAssembly + #define SCRIPTS_DOMAIN GDMono::get_singleton()->get_scripts_domain() #ifdef TOOLS_ENABLED #define TOOLS_DOMAIN GDMono::get_singleton()->get_tools_domain() @@ -55,8 +94,11 @@ class GDMono { MonoDomain *tools_domain; #endif + bool core_api_assembly_out_of_sync; + bool editor_api_assembly_out_of_sync; + GDMonoAssembly *corlib_assembly; - GDMonoAssembly *api_assembly; + GDMonoAssembly *core_api_assembly; GDMonoAssembly *project_assembly; #ifdef TOOLS_ENABLED GDMonoAssembly *editor_api_assembly; @@ -75,7 +117,11 @@ class GDMono { #endif bool _load_project_assembly(); - bool _load_all_script_assemblies(); + bool _load_api_assemblies(); + +#ifdef TOOLS_ENABLED + String _get_api_assembly_metadata_path(); +#endif void _register_internal_calls(); @@ -111,6 +157,11 @@ public: #endif #endif +#ifdef TOOLS_ENABLED + void metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated); + bool metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type); +#endif + static GDMono *get_singleton() { return singleton; } // Do not use these, unless you know what you're doing @@ -126,7 +177,7 @@ public: #endif _FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; } - _FORCE_INLINE_ GDMonoAssembly *get_api_assembly() const { return api_assembly; } + _FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly; } _FORCE_INLINE_ GDMonoAssembly *get_project_assembly() const { return project_assembly; } #ifdef TOOLS_ENABLED _FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly; } diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index 42e307cf08..297992f625 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -143,7 +143,7 @@ void MonoCache::cleanup() { godot_api_cache_updated = false; } -#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class)) +#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class)) void update_corlib_cache() { @@ -245,7 +245,7 @@ void update_godot_api_cache() { mono_runtime_object_init(task_scheduler); mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler); - mono_cache.corlib_cache_updated = true; + mono_cache.godot_api_cache_updated = true; } void clear_cache() { @@ -305,7 +305,7 @@ GDMonoClass *type_get_proxy_class(const StringName &p_type) { if (class_name[0] == '_') class_name = class_name.substr(1, class_name.length()); - GDMonoClass *klass = GDMono::get_singleton()->get_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name); + GDMonoClass *klass = GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name); #ifdef TOOLS_ENABLED if (!klass) { @@ -321,7 +321,7 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class) { do { const GDMonoAssembly *assembly = klass->get_assembly(); - if (assembly == GDMono::get_singleton()->get_api_assembly()) + if (assembly == GDMono::get_singleton()->get_core_api_assembly()) return klass; #ifdef TOOLS_ENABLED if (assembly == GDMono::get_singleton()->get_editor_api_assembly()) @@ -420,37 +420,56 @@ void print_unhandled_exception(MonoObject *p_exc, bool p_recursion_caution) { if (!ScriptDebugger::get_singleton()) return; - GDMonoClass *st_klass = CACHED_CLASS(System_Diagnostics_StackTrace); - MonoObject *stack_trace = mono_object_new(mono_domain_get(), st_klass->get_mono_ptr()); + ScriptLanguage::StackInfo separator; + separator.file = ""; + separator.func = "--- " + TTR("End of inner exception stack trace") + " ---"; + separator.line = 0; + + Vector<ScriptLanguage::StackInfo> si; + String exc_msg = ""; + + while (p_exc != NULL) { + GDMonoClass *st_klass = CACHED_CLASS(System_Diagnostics_StackTrace); + MonoObject *stack_trace = mono_object_new(mono_domain_get(), st_klass->get_mono_ptr()); - MonoBoolean need_file_info = true; - void *ctor_args[2] = { p_exc, &need_file_info }; + MonoBoolean need_file_info = true; + void *ctor_args[2] = { p_exc, &need_file_info }; - MonoObject *unexpected_exc = NULL; - CACHED_METHOD(System_Diagnostics_StackTrace, ctor_Exception_bool)->invoke_raw(stack_trace, ctor_args, &unexpected_exc); + MonoObject *unexpected_exc = NULL; + CACHED_METHOD(System_Diagnostics_StackTrace, ctor_Exception_bool)->invoke_raw(stack_trace, ctor_args, &unexpected_exc); - if (unexpected_exc != NULL) { - mono_print_unhandled_exception(unexpected_exc); + if (unexpected_exc != NULL) { + mono_print_unhandled_exception(unexpected_exc); - if (p_recursion_caution) { - // Called from CSharpLanguage::get_current_stack_info, - // so printing an error here could result in endless recursion - OS::get_singleton()->printerr("Mono: Method GDMonoUtils::print_unhandled_exception failed"); - return; - } else { - ERR_FAIL(); + if (p_recursion_caution) { + // Called from CSharpLanguage::get_current_stack_info, + // so printing an error here could result in endless recursion + OS::get_singleton()->printerr("Mono: Method GDMonoUtils::print_unhandled_exception failed"); + return; + } else { + ERR_FAIL(); + } } - } - Vector<ScriptLanguage::StackInfo> si; - if (stack_trace != NULL && !p_recursion_caution) - si = CSharpLanguage::get_singleton()->stack_trace_get_info(stack_trace); + Vector<ScriptLanguage::StackInfo> _si; + if (stack_trace != NULL && !p_recursion_caution) { + _si = CSharpLanguage::get_singleton()->stack_trace_get_info(stack_trace); + for (int i = _si.size() - 1; i >= 0; i--) + si.insert(0, _si[i]); + } + + exc_msg += (exc_msg.length() > 0 ? " ---> " : "") + GDMonoUtils::get_exception_name_and_message(p_exc); + + GDMonoProperty *p_prop = GDMono::get_singleton()->get_class(mono_object_get_class(p_exc))->get_property("InnerException"); + p_exc = p_prop != NULL ? p_prop->get_value(p_exc) : NULL; + if (p_exc != NULL) + si.insert(0, separator); + } String file = si.size() ? si[0].file : __FILE__; String func = si.size() ? si[0].func : FUNCTION_STR; int line = si.size() ? si[0].line : __LINE__; String error_msg = "Unhandled exception"; - String exc_msg = GDMonoUtils::get_exception_name_and_message(p_exc); ScriptDebugger::get_singleton()->send_error(func, file, line, error_msg, exc_msg, ERR_HANDLER_ERROR, si); #endif diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm index 7508a480ce..a9748bf562 100644 --- a/platform/iphone/icloud.mm +++ b/platform/iphone/icloud.mm @@ -131,6 +131,8 @@ Variant nsobject_to_variant(NSObject *object) { return Variant([num floatValue]); } else if (strcmp([num objCType], @encode(double)) == 0) { return Variant((float)[num doubleValue]); + } else { + return Variant(); } } else if ([object isKindOfClass:[NSDate class]]) { //this is a type that icloud supports...but how did you submit it in the first place? diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp index f0ab9652b4..ba4a5c5571 100644 --- a/scene/2d/line_2d.cpp +++ b/scene/2d/line_2d.cpp @@ -252,12 +252,15 @@ void Line2D::_draw() { lb.sharp_limit = _sharp_limit; lb.width = _width; - lb.build(); - RID texture_rid; - if (_texture.is_valid()) + if (_texture.is_valid()) { texture_rid = (**_texture).get_rid(); + lb.tile_aspect = _texture->get_size().aspect(); + } + + lb.build(); + VS::get_singleton()->canvas_item_add_triangle_array( get_canvas_item(), lb.indices, diff --git a/scene/2d/line_builder.cpp b/scene/2d/line_builder.cpp index b1a072729f..845788bada 100644 --- a/scene/2d/line_builder.cpp +++ b/scene/2d/line_builder.cpp @@ -101,6 +101,7 @@ LineBuilder::LineBuilder() { round_precision = 8; begin_cap_mode = Line2D::LINE_CAP_NONE; end_cap_mode = Line2D::LINE_CAP_NONE; + tile_aspect = 1.f; _interpolate_color = false; _last_index[0] = 0; @@ -111,6 +112,7 @@ void LineBuilder::clear_output() { vertices.clear(); colors.clear(); indices.clear(); + uvs.clear(); } void LineBuilder::build() { @@ -121,6 +123,8 @@ void LineBuilder::build() { return; } + ERR_FAIL_COND(tile_aspect <= 0.f); + const float hw = width / 2.f; const float hw_sq = hw * hw; const float sharp_limit_sq = sharp_limit * sharp_limit; @@ -164,7 +168,7 @@ void LineBuilder::build() { current_distance1 = current_distance0; } else if (begin_cap_mode == Line2D::LINE_CAP_ROUND) { if (texture_mode == Line2D::LINE_TEXTURE_TILE) { - uvx0 = 0.5f; + uvx0 = 0.5f / tile_aspect; } new_arc(pos0, pos_up0 - pos0, -Math_PI, color0, Rect2(0.f, 0.f, 1.f, 1.f)); total_distance += width; @@ -286,8 +290,8 @@ void LineBuilder::build() { color1 = gradient->get_color_at_offset(current_distance1 / total_distance); } if (texture_mode == Line2D::LINE_TEXTURE_TILE) { - uvx0 = current_distance0 / width; - uvx1 = current_distance1 / width; + uvx0 = current_distance0 / (width * tile_aspect); + uvx1 = current_distance1 / (width * tile_aspect); } strip_add_quad(pos_up1, pos_down1, color1, uvx1); @@ -373,7 +377,7 @@ void LineBuilder::build() { color1 = gradient->get_color(gradient->get_points_count() - 1); } if (texture_mode == Line2D::LINE_TEXTURE_TILE) { - uvx1 = current_distance1 / width; + uvx1 = current_distance1 / (width * tile_aspect); } strip_add_quad(pos_up1, pos_down1, color1, uvx1); diff --git a/scene/2d/line_builder.h b/scene/2d/line_builder.h index daa2ed7c24..b1c62f84e2 100644 --- a/scene/2d/line_builder.h +++ b/scene/2d/line_builder.h @@ -50,6 +50,7 @@ public: Line2D::LineTextureMode texture_mode; float sharp_limit; int round_precision; + float tile_aspect; // w/h // TODO offset_joints option (offers alternative implementation of round joints) // TODO Move in a struct and reference it diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp index e63dc4e515..2cb1e86f51 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -192,7 +192,80 @@ void Polygon2D::_notification(int p_what) { // Vector<int> indices = Geometry::triangulate_polygon(points); // VS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), indices, points, colors, uvs, texture.is_valid() ? texture->get_rid() : RID()); - VS::get_singleton()->canvas_item_add_polygon(get_canvas_item(), points, colors, uvs, texture.is_valid() ? texture->get_rid() : RID(), RID(), antialiased); + if (invert || splits.size() == 0) { + VS::get_singleton()->canvas_item_add_polygon(get_canvas_item(), points, colors, uvs, texture.is_valid() ? texture->get_rid() : RID(), RID(), antialiased); + } else { + //use splits + Vector<int> loop; + int sc = splits.size(); + PoolVector<int>::Read r = splits.read(); + int last = points.size(); + + Vector<Vector<int> > loops; + + for (int i = 0; i < last; i++) { + + int split; + int min_end = -1; + + do { + + loop.push_back(i); + + split = -1; + int end = -1; + + for (int j = 0; j < sc; j += 2) { + if (r[j + 1] >= last) + continue; //no longer valid + if (min_end != -1 && r[j + 1] >= min_end) + continue; + if (r[j] == i) { + if (split == -1 || r[j + 1] > end) { + split = r[j]; + end = r[j + 1]; + } + } + } + + if (split != -1) { + for (int j = end; j < last; j++) { + loop.push_back(j); + } + loops.push_back(loop); + last = end + 1; + loop.clear(); + min_end = end; //avoid this split from repeating + } + + } while (split != -1); + } + + if (loop.size()) { + loops.push_back(loop); + } + + Vector<int> indices; + + for (int i = 0; i < loops.size(); i++) { + Vector<int> loop = loops[i]; + Vector<Vector2> vertices; + vertices.resize(loop.size()); + for (int j = 0; j < vertices.size(); j++) { + vertices[j] = points[loop[j]]; + } + Vector<int> sub_indices = Geometry::triangulate_polygon(vertices); + int from = indices.size(); + indices.resize(from + sub_indices.size()); + for (int j = 0; j < sub_indices.size(); j++) { + indices[from + j] = loop[sub_indices[j]]; + } + } + + //print_line("loops: " + itos(loops.size()) + " indices: " + itos(indices.size())); + + VS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), indices, points, colors, uvs, texture.is_valid() ? texture->get_rid() : RID()); + } } break; } @@ -220,6 +293,18 @@ PoolVector<Vector2> Polygon2D::get_uv() const { return uv; } +void Polygon2D::set_splits(const PoolVector<int> &p_splits) { + + ERR_FAIL_COND(p_splits.size() & 1); //splits should be multiple of 2 + splits = p_splits; + update(); +} + +PoolVector<int> Polygon2D::get_splits() const { + + return splits; +} + void Polygon2D::set_color(const Color &p_color) { color = p_color; @@ -352,6 +437,9 @@ void Polygon2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_color", "color"), &Polygon2D::set_color); ClassDB::bind_method(D_METHOD("get_color"), &Polygon2D::get_color); + ClassDB::bind_method(D_METHOD("set_splits", "splits"), &Polygon2D::set_splits); + ClassDB::bind_method(D_METHOD("get_splits"), &Polygon2D::get_splits); + ClassDB::bind_method(D_METHOD("set_vertex_colors", "vertex_colors"), &Polygon2D::set_vertex_colors); ClassDB::bind_method(D_METHOD("get_vertex_colors"), &Polygon2D::get_vertex_colors); @@ -384,6 +472,7 @@ void Polygon2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon"); ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "uv"), "set_uv", "get_uv"); + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "splits"), "set_splits", "get_splits"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "vertex_colors"), "set_vertex_colors", "get_vertex_colors"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); diff --git a/scene/2d/polygon_2d.h b/scene/2d/polygon_2d.h index 66f44cb3f1..3a24177548 100644 --- a/scene/2d/polygon_2d.h +++ b/scene/2d/polygon_2d.h @@ -40,6 +40,8 @@ class Polygon2D : public Node2D { PoolVector<Vector2> polygon; PoolVector<Vector2> uv; PoolVector<Color> vertex_colors; + PoolVector<int> splits; + Color color; Ref<Texture> texture; Size2 tex_scale; @@ -75,6 +77,9 @@ public: void set_uv(const PoolVector<Vector2> &p_uv); PoolVector<Vector2> get_uv() const; + void set_splits(const PoolVector<int> &p_uv); + PoolVector<int> get_splits() const; + void set_color(const Color &p_color); Color get_color() const; diff --git a/scene/3d/spatial.cpp b/scene/3d/spatial.cpp index 721641e09b..f8a5c7f400 100644 --- a/scene/3d/spatial.cpp +++ b/scene/3d/spatial.cpp @@ -188,7 +188,9 @@ void Spatial::_notification(int p_what) { if (data.gizmo.is_valid()) { data.gizmo->create(); if (data.gizmo->can_draw()) { - data.gizmo->redraw(); + if (is_visible_in_tree()) { + data.gizmo->redraw(); + } } data.gizmo->transform(); } @@ -409,7 +411,9 @@ void Spatial::set_gizmo(const Ref<SpatialGizmo> &p_gizmo) { data.gizmo->create(); if (data.gizmo->can_draw()) { - data.gizmo->redraw(); + if (is_visible_in_tree()) { + data.gizmo->redraw(); + } } data.gizmo->transform(); } diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index 47577a3359..f8640720cb 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -1828,8 +1828,8 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("canvas_item_add_primitive", "item", "points", "colors", "uvs", "texture", "width", "normal_map"), &VisualServer::canvas_item_add_primitive, DEFVAL(1.0), DEFVAL(RID())); ClassDB::bind_method(D_METHOD("canvas_item_add_polygon", "item", "points", "colors", "uvs", "texture", "normal_map", "antialiased"), &VisualServer::canvas_item_add_polygon, DEFVAL(Vector<Point2>()), DEFVAL(RID()), DEFVAL(RID()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("canvas_item_add_triangle_array", "item", "indices", "points", "colors", "uvs", "texture", "count", "normal_map"), &VisualServer::canvas_item_add_triangle_array, DEFVAL(Vector<Point2>()), DEFVAL(RID()), DEFVAL(-1), DEFVAL(RID())); - ClassDB::bind_method(D_METHOD("canvas_item_add_mesh", "item", "mesh", "skeleton"), &VisualServer::canvas_item_add_mesh, DEFVAL(RID())); - ClassDB::bind_method(D_METHOD("canvas_item_add_multimesh", "item", "mesh", "skeleton"), &VisualServer::canvas_item_add_multimesh, DEFVAL(RID())); + ClassDB::bind_method(D_METHOD("canvas_item_add_mesh", "item", "mesh", "texture", "normal_map"), &VisualServer::canvas_item_add_mesh, DEFVAL(RID())); + ClassDB::bind_method(D_METHOD("canvas_item_add_multimesh", "item", "mesh", "texture", "normal_map"), &VisualServer::canvas_item_add_multimesh, DEFVAL(RID())); ClassDB::bind_method(D_METHOD("canvas_item_add_particles", "item", "particles", "texture", "normal_map", "h_frames", "v_frames"), &VisualServer::canvas_item_add_particles); ClassDB::bind_method(D_METHOD("canvas_item_add_set_transform", "item", "transform"), &VisualServer::canvas_item_add_set_transform); ClassDB::bind_method(D_METHOD("canvas_item_add_clip_ignore", "item", "ignore"), &VisualServer::canvas_item_add_clip_ignore); |