summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/arkit/arkit_interface.h13
-rw-r--r--modules/arkit/arkit_interface.mm12
-rw-r--r--modules/bullet/area_bullet.cpp27
-rw-r--r--modules/bullet/area_bullet.h24
-rw-r--r--modules/bullet/bullet_physics_server.h2
-rw-r--r--modules/bullet/bullet_types_converter.cpp59
-rw-r--r--modules/bullet/collision_object_bullet.cpp76
-rw-r--r--modules/bullet/collision_object_bullet.h52
-rw-r--r--modules/bullet/rigid_body_bullet.cpp118
-rw-r--r--modules/bullet/rigid_body_bullet.h52
-rw-r--r--modules/bullet/shape_bullet.cpp57
-rw-r--r--modules/bullet/shape_bullet.h28
-rw-r--r--modules/bullet/soft_body_bullet.cpp24
-rw-r--r--modules/bullet/soft_body_bullet.h20
-rw-r--r--modules/bullet/space_bullet.cpp111
-rw-r--r--modules/bullet/space_bullet.h20
-rw-r--r--modules/camera/camera_ios.mm24
-rw-r--r--modules/denoise/lightmap_denoiser.h2
-rw-r--r--modules/gdnative/gdnative_library_editor_plugin.cpp1
-rw-r--r--modules/gdnative/tests/test_string.h1980
-rw-r--r--modules/gdnavigation/rvo_agent.h3
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml77
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp7
-rw-r--r--modules/gdscript/gdscript.cpp40
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp2
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp9
-rw-r--r--modules/gdscript/gdscript_compiler.cpp24
-rw-r--r--modules/gdscript/gdscript_compiler.h4
-rw-r--r--modules/gdscript/gdscript_function.h3
-rw-r--r--modules/gdscript/gdscript_parser.cpp6
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp2
-rw-r--r--modules/gdscript/tests/test_gdscript.cpp76
-rw-r--r--modules/gridmap/doc_classes/GridMap.xml2
-rw-r--r--modules/jsonrpc/jsonrpc.cpp4
-rw-r--r--modules/jsonrpc/jsonrpc.h2
-rw-r--r--modules/mbedtls/crypto_mbedtls.cpp21
-rw-r--r--modules/mono/build_scripts/mono_configure.py3
-rw-r--r--modules/mono/build_scripts/solution_builder.py3
-rw-r--r--modules/mono/config.py10
-rw-r--r--modules/mono/csharp_script.cpp194
-rw-r--r--modules/mono/csharp_script.h1
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props16
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs15
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs2
-rwxr-xr-xmodules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs12
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs34
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs79
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs7
-rw-r--r--modules/mono/glue/base_object_glue.cpp1
-rw-r--r--modules/mono/glue/collections_glue.cpp23
-rw-r--r--modules/mono/glue/glue_header.h1
-rw-r--r--modules/mono/glue/rid_glue.cpp2
-rw-r--r--modules/mono/godotsharp_dirs.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono_internals.h2
-rw-r--r--modules/mono/mono_gd/gd_mono_log.cpp25
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.h2
-rw-r--r--modules/mono/mono_gd/support/ios_support.mm3
-rw-r--r--modules/pvr/texture_loader_pvr.cpp8
-rw-r--r--modules/theora/doc_classes/VideoStreamTheora.xml5
-rw-r--r--modules/visual_script/visual_script_editor.cpp28
-rw-r--r--modules/visual_script/visual_script_editor.h3
-rw-r--r--modules/visual_script/visual_script_func_nodes.cpp1
-rw-r--r--modules/visual_script/visual_script_nodes.cpp4
-rw-r--r--modules/webm/doc_classes/VideoStreamWebm.xml3
-rw-r--r--modules/webrtc/doc_classes/WebRTCPeerConnection.xml2
74 files changed, 2777 insertions, 736 deletions
diff --git a/modules/arkit/arkit_interface.h b/modules/arkit/arkit_interface.h
index 5a2c50e213..29e09411ff 100644
--- a/modules/arkit/arkit_interface.h
+++ b/modules/arkit/arkit_interface.h
@@ -44,6 +44,15 @@
// forward declaration for some needed objects
class ARKitShader;
+#ifdef __OBJC__
+
+typedef ARAnchor GodotARAnchor;
+
+#else
+
+typedef void GodotARAnchor;
+#endif
+
class ARKitInterface : public XRInterface {
GDCLASS(ARKitInterface, XRInterface);
@@ -115,8 +124,8 @@ public:
virtual void process() override;
// called by delegate (void * because C++ and Obj-C don't always mix, should really change all platform/iphone/*.cpp files to .mm)
- void _add_or_update_anchor(void *p_anchor);
- void _remove_anchor(void *p_anchor);
+ void _add_or_update_anchor(GodotARAnchor *p_anchor);
+ void _remove_anchor(GodotARAnchor *p_anchor);
ARKitInterface();
~ARKitInterface();
diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm
index 3fb2cc933d..e8fa023ac7 100644
--- a/modules/arkit/arkit_interface.mm
+++ b/modules/arkit/arkit_interface.mm
@@ -306,12 +306,10 @@ void ARKitInterface::uninitialize() {
remove_all_anchors();
if (@available(iOS 11.0, *)) {
- [ar_session release];
- ar_session = NULL;
+ ar_session = nil;
}
- [ar_delegate release];
- ar_delegate = NULL;
+ ar_delegate = nil;
initialized = false;
session_was_started = false;
}
@@ -687,7 +685,7 @@ void ARKitInterface::process() {
}
}
-void ARKitInterface::_add_or_update_anchor(void *p_anchor) {
+void ARKitInterface::_add_or_update_anchor(GodotARAnchor *p_anchor) {
// _THREAD_SAFE_METHOD_
if (@available(iOS 11.0, *)) {
@@ -749,7 +747,7 @@ void ARKitInterface::_add_or_update_anchor(void *p_anchor) {
}
}
-void ARKitInterface::_remove_anchor(void *p_anchor) {
+void ARKitInterface::_remove_anchor(GodotARAnchor *p_anchor) {
// _THREAD_SAFE_METHOD_
if (@available(iOS 11.0, *)) {
@@ -768,7 +766,7 @@ ARKitInterface::ARKitInterface() {
plane_detection_is_enabled = false;
light_estimation_is_enabled = false;
if (@available(iOS 11.0, *)) {
- ar_session = NULL;
+ ar_session = nil;
}
z_near = 0.01;
z_far = 1000.0;
diff --git a/modules/bullet/area_bullet.cpp b/modules/bullet/area_bullet.cpp
index b35019bea3..f8f7d79a11 100644
--- a/modules/bullet/area_bullet.cpp
+++ b/modules/bullet/area_bullet.cpp
@@ -65,11 +65,14 @@ AreaBullet::~AreaBullet() {
}
void AreaBullet::dispatch_callbacks() {
- RigidCollisionObjectBullet::dispatch_callbacks();
+ if (!isScratched) {
+ return;
+ }
+ isScratched = false;
// Reverse order because I've to remove EXIT objects
for (int i = overlappingObjects.size() - 1; 0 <= i; --i) {
- OverlappingObjectData &otherObj = overlappingObjects[i];
+ OverlappingObjectData &otherObj = overlappingObjects.write[i];
switch (otherObj.state) {
case OVERLAP_STATE_ENTER:
@@ -109,9 +112,10 @@ void AreaBullet::call_event(CollisionObjectBullet *p_otherObject, PhysicsServer3
}
void AreaBullet::scratch() {
- if (space != nullptr) {
- space->add_to_pre_flush_queue(this);
+ if (isScratched) {
+ return;
}
+ isScratched = true;
}
void AreaBullet::clear_overlaps(bool p_notify) {
@@ -160,7 +164,7 @@ void AreaBullet::main_shape_changed() {
btGhost->setCollisionShape(get_main_shape());
}
-void AreaBullet::do_reload_body() {
+void AreaBullet::reload_body() {
if (space) {
space->remove_area(this);
space->add_area(this);
@@ -169,25 +173,22 @@ void AreaBullet::do_reload_body() {
void AreaBullet::set_space(SpaceBullet *p_space) {
// Clear the old space if there is one
-
if (space) {
clear_overlaps(false);
+ isScratched = false;
// Remove this object form the physics world
- space->unregister_collision_object(this);
space->remove_area(this);
}
space = p_space;
if (space) {
- space->register_collision_object(this);
- reload_body();
- scratch();
+ space->add_area(this);
}
}
-void AreaBullet::do_reload_collision_filters() {
+void AreaBullet::on_collision_filters_change() {
if (space) {
space->reload_collision_filters(this);
}
@@ -201,13 +202,13 @@ void AreaBullet::add_overlap(CollisionObjectBullet *p_otherObject) {
void AreaBullet::put_overlap_as_exit(int p_index) {
scratch();
- overlappingObjects[p_index].state = OVERLAP_STATE_EXIT;
+ overlappingObjects.write[p_index].state = OVERLAP_STATE_EXIT;
}
void AreaBullet::put_overlap_as_inside(int p_index) {
// This check is required to be sure this body was inside
if (OVERLAP_STATE_DIRTY == overlappingObjects[p_index].state) {
- overlappingObjects[p_index].state = OVERLAP_STATE_INSIDE;
+ overlappingObjects.write[p_index].state = OVERLAP_STATE_INSIDE;
}
}
diff --git a/modules/bullet/area_bullet.h b/modules/bullet/area_bullet.h
index 51fbc1f71d..c0bcc858fe 100644
--- a/modules/bullet/area_bullet.h
+++ b/modules/bullet/area_bullet.h
@@ -32,7 +32,7 @@
#define AREABULLET_H
#include "collision_object_bullet.h"
-#include "core/local_vector.h"
+#include "core/vector.h"
#include "servers/physics_server_3d.h"
#include "space_bullet.h"
@@ -83,7 +83,7 @@ private:
Variant *call_event_res_ptr[5];
btGhostObject *btGhost;
- LocalVector<OverlappingObjectData> overlappingObjects;
+ Vector<OverlappingObjectData> overlappingObjects;
bool monitorable = true;
PhysicsServer3D::AreaSpaceOverrideMode spOv_mode = PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED;
@@ -96,6 +96,8 @@ private:
real_t spOv_angularDump = 0.1;
int spOv_priority = 0;
+ bool isScratched = false;
+
InOutEventCallback eventsCallbacks[2];
public:
@@ -137,11 +139,11 @@ public:
_FORCE_INLINE_ void set_spOv_priority(int p_priority) { spOv_priority = p_priority; }
_FORCE_INLINE_ int get_spOv_priority() { return spOv_priority; }
- virtual void main_shape_changed() override;
- virtual void do_reload_body() override;
- virtual void set_space(SpaceBullet *p_space) override;
+ virtual void main_shape_changed();
+ virtual void reload_body();
+ virtual void set_space(SpaceBullet *p_space);
- virtual void dispatch_callbacks() override;
+ virtual void dispatch_callbacks();
void call_event(CollisionObjectBullet *p_otherObject, PhysicsServer3D::AreaBodyStatus p_status);
void set_on_state_change(ObjectID p_id, const StringName &p_method, const Variant &p_udata = Variant());
void scratch();
@@ -150,9 +152,9 @@ public:
// Dispatch the callbacks and removes from overlapping list
void remove_overlap(CollisionObjectBullet *p_object, bool p_notify);
- virtual void do_reload_collision_filters() override;
- virtual void on_collision_checker_start() override {}
- virtual void on_collision_checker_end() override { isTransformChanged = false; }
+ virtual void on_collision_filters_change();
+ virtual void on_collision_checker_start() {}
+ virtual void on_collision_checker_end() { isTransformChanged = false; }
void add_overlap(CollisionObjectBullet *p_otherObject);
void put_overlap_as_exit(int p_index);
@@ -164,8 +166,8 @@ public:
void set_event_callback(Type p_callbackObjectType, ObjectID p_id, const StringName &p_method);
bool has_event_callback(Type p_callbackObjectType);
- virtual void on_enter_area(AreaBullet *p_area) override;
- virtual void on_exit_area(AreaBullet *p_area) override;
+ virtual void on_enter_area(AreaBullet *p_area);
+ virtual void on_exit_area(AreaBullet *p_area);
};
#endif
diff --git a/modules/bullet/bullet_physics_server.h b/modules/bullet/bullet_physics_server.h
index eb95120f74..6078babaf8 100644
--- a/modules/bullet/bullet_physics_server.h
+++ b/modules/bullet/bullet_physics_server.h
@@ -52,7 +52,7 @@ class BulletPhysicsServer3D : public PhysicsServer3D {
bool active = true;
char active_spaces_count = 0;
- LocalVector<SpaceBullet *> active_spaces;
+ Vector<SpaceBullet *> active_spaces;
mutable RID_PtrOwner<SpaceBullet> space_owner;
mutable RID_PtrOwner<ShapeBullet> shape_owner;
diff --git a/modules/bullet/bullet_types_converter.cpp b/modules/bullet/bullet_types_converter.cpp
index c9493d8892..09b90fe09e 100644
--- a/modules/bullet/bullet_types_converter.cpp
+++ b/modules/bullet/bullet_types_converter.cpp
@@ -95,12 +95,61 @@ void G_TO_B(Transform const &inVal, btTransform &outVal) {
}
void UNSCALE_BT_BASIS(btTransform &scaledBasis) {
- btMatrix3x3 &m(scaledBasis.getBasis());
- btVector3 column0(m[0][0], m[1][0], m[2][0]);
- btVector3 column1(m[0][1], m[1][1], m[2][1]);
- btVector3 column2(m[0][2], m[1][2], m[2][2]);
+ btMatrix3x3 &basis(scaledBasis.getBasis());
+ btVector3 column0 = basis.getColumn(0);
+ btVector3 column1 = basis.getColumn(1);
+ btVector3 column2 = basis.getColumn(2);
+
+ // Check for zero scaling.
+ if (column0.fuzzyZero()) {
+ if (column1.fuzzyZero()) {
+ if (column2.fuzzyZero()) {
+ // All dimensions are fuzzy zero. Create a default basis.
+ column0 = btVector3(1, 0, 0);
+ column1 = btVector3(0, 1, 0);
+ column2 = btVector3(0, 0, 1);
+ } else { // Column 2 scale not fuzzy zero.
+ // Create two vectors orthogonal to row 2.
+ // Ensure that a default basis is created if row 2 = <0, 0, 1>
+ column1 = btVector3(0, column2[2], -column2[1]);
+ column0 = column1.cross(column2);
+ }
+ } else { // Column 1 scale not fuzzy zero.
+ if (column2.fuzzyZero()) {
+ // Create two vectors othogonal to column 1.
+ // Ensure that a default basis is created if column 1 = <0, 1, 0>
+ column0 = btVector3(column1[1], -column1[0], 0);
+ column2 = column0.cross(column1);
+ } else { // Column 1 and column 2 scales not fuzzy zero.
+ // Create column 0 orthogonal to column 1 and column 2.
+ column0 = column1.cross(column2);
+ }
+ }
+ } else { // Column 0 scale not fuzzy zero.
+ if (column1.fuzzyZero()) {
+ if (column2.fuzzyZero()) {
+ // Create two vectors orthogonal to column 0.
+ // Ensure that a default basis is created if column 0 = <1, 0, 0>
+ column2 = btVector3(-column0[2], 0, column0[0]);
+ column1 = column2.cross(column0);
+ } else { // Column 0 and column 2 scales not fuzzy zero.
+ // Create column 1 orthogonal to column 0 and column 2.
+ column1 = column2.cross(column0);
+ }
+ } else { // Column 0 and column 1 scales not fuzzy zero.
+ if (column2.fuzzyZero()) {
+ // Create column 2 orthogonal to column 0 and column 1.
+ column2 = column0.cross(column1);
+ }
+ }
+ }
+
+ // Normalize
column0.normalize();
column1.normalize();
column2.normalize();
- m.setValue(column0[0], column1[0], column2[0], column0[1], column1[1], column2[1], column0[2], column1[2], column2[2]);
+
+ basis.setValue(column0[0], column1[0], column2[0],
+ column0[1], column1[1], column2[1],
+ column0[2], column1[2], column2[2]);
}
diff --git a/modules/bullet/collision_object_bullet.cpp b/modules/bullet/collision_object_bullet.cpp
index 660e9afc5e..a3158a15e5 100644
--- a/modules/bullet/collision_object_bullet.cpp
+++ b/modules/bullet/collision_object_bullet.cpp
@@ -79,7 +79,7 @@ btTransform CollisionObjectBullet::ShapeWrapper::get_adjusted_transform() const
}
void CollisionObjectBullet::ShapeWrapper::claim_bt_shape(const btVector3 &body_scale) {
- if (bt_shape == nullptr) {
+ if (!bt_shape) {
if (active) {
bt_shape = shape->create_bt_shape(scale * body_scale);
} else {
@@ -88,13 +88,6 @@ void CollisionObjectBullet::ShapeWrapper::claim_bt_shape(const btVector3 &body_s
}
}
-void CollisionObjectBullet::ShapeWrapper::release_bt_shape() {
- if (bt_shape != nullptr) {
- shape->destroy_bt_shape(bt_shape);
- bt_shape = nullptr;
- }
-}
-
CollisionObjectBullet::CollisionObjectBullet(Type p_type) :
RIDBullet(),
type(p_type) {}
@@ -165,22 +158,6 @@ bool CollisionObjectBullet::has_collision_exception(const CollisionObjectBullet
return !bt_collision_object->checkCollideWith(p_otherCollisionObject->bt_collision_object);
}
-void CollisionObjectBullet::reload_body() {
- needs_body_reload = true;
-}
-
-void CollisionObjectBullet::dispatch_callbacks() {}
-
-void CollisionObjectBullet::pre_process() {
- if (needs_body_reload) {
- do_reload_body();
- } else if (needs_collision_filters_reload) {
- do_reload_collision_filters();
- }
- needs_body_reload = false;
- needs_collision_filters_reload = false;
-}
-
void CollisionObjectBullet::set_collision_enabled(bool p_enabled) {
collisionsEnabled = p_enabled;
if (collisionsEnabled) {
@@ -254,7 +231,7 @@ void RigidCollisionObjectBullet::add_shape(ShapeBullet *p_shape, const Transform
}
void RigidCollisionObjectBullet::set_shape(int p_index, ShapeBullet *p_shape) {
- ShapeWrapper &shp = shapes[p_index];
+ ShapeWrapper &shp = shapes.write[p_index];
shp.shape->remove_owner(this);
p_shape->add_owner(this);
shp.shape = p_shape;
@@ -316,7 +293,7 @@ void RigidCollisionObjectBullet::remove_all_shapes(bool p_permanentlyFromThisBod
void RigidCollisionObjectBullet::set_shape_transform(int p_index, const Transform &p_transform) {
ERR_FAIL_INDEX(p_index, get_shape_count());
- shapes[p_index].set_transform(p_transform);
+ shapes.write[p_index].set_transform(p_transform);
shape_changed(p_index);
}
@@ -334,7 +311,7 @@ void RigidCollisionObjectBullet::set_shape_disabled(int p_index, bool p_disabled
if (shapes[p_index].active != p_disabled) {
return;
}
- shapes[p_index].active = !p_disabled;
+ shapes.write[p_index].active = !p_disabled;
shape_changed(p_index);
}
@@ -342,28 +319,16 @@ bool RigidCollisionObjectBullet::is_shape_disabled(int p_index) {
return !shapes[p_index].active;
}
-void RigidCollisionObjectBullet::pre_process() {
- if (need_shape_reload) {
- do_reload_shapes();
- need_shape_reload = false;
- }
- CollisionObjectBullet::pre_process();
-}
-
void RigidCollisionObjectBullet::shape_changed(int p_shape_index) {
- ShapeWrapper &shp = shapes[p_shape_index];
+ ShapeWrapper &shp = shapes.write[p_shape_index];
if (shp.bt_shape == mainShape) {
mainShape = nullptr;
}
- shp.release_bt_shape();
+ bulletdelete(shp.bt_shape);
reload_shapes();
}
void RigidCollisionObjectBullet::reload_shapes() {
- need_shape_reload = true;
-}
-
-void RigidCollisionObjectBullet::do_reload_shapes() {
if (mainShape && mainShape->isCompound()) {
// Destroy compound
bulletdelete(mainShape);
@@ -371,38 +336,41 @@ void RigidCollisionObjectBullet::do_reload_shapes() {
mainShape = nullptr;
+ ShapeWrapper *shpWrapper;
const int shape_count = shapes.size();
- // Reset all shapes if required
+ // Reset shape if required
if (force_shape_reset) {
for (int i(0); i < shape_count; ++i) {
- shapes[i].release_bt_shape();
+ shpWrapper = &shapes.write[i];
+ bulletdelete(shpWrapper->bt_shape);
}
force_shape_reset = false;
}
const btVector3 body_scale(get_bt_body_scale());
+ // Try to optimize by not using compound
if (1 == shape_count) {
- // Is it possible to optimize by not using compound?
- btTransform transform = shapes[0].get_adjusted_transform();
+ shpWrapper = &shapes.write[0];
+ btTransform transform = shpWrapper->get_adjusted_transform();
if (transform.getOrigin().isZero() && transform.getBasis() == transform.getBasis().getIdentity()) {
- shapes[0].claim_bt_shape(body_scale);
- mainShape = shapes[0].bt_shape;
+ shpWrapper->claim_bt_shape(body_scale);
+ mainShape = shpWrapper->bt_shape;
main_shape_changed();
- // Nothing more to do
return;
}
}
- // Optimization not possible use a compound shape.
+ // Optimization not possible use a compound shape
btCompoundShape *compoundShape = bulletnew(btCompoundShape(enableDynamicAabbTree, shape_count));
for (int i(0); i < shape_count; ++i) {
- shapes[i].claim_bt_shape(body_scale);
- btTransform scaled_shape_transform(shapes[i].get_adjusted_transform());
+ shpWrapper = &shapes.write[i];
+ shpWrapper->claim_bt_shape(body_scale);
+ btTransform scaled_shape_transform(shpWrapper->get_adjusted_transform());
scaled_shape_transform.getOrigin() *= body_scale;
- compoundShape->addChildShape(scaled_shape_transform, shapes[i].bt_shape);
+ compoundShape->addChildShape(scaled_shape_transform, shpWrapper->bt_shape);
}
compoundShape->recalculateLocalAabb();
@@ -416,10 +384,10 @@ void RigidCollisionObjectBullet::body_scale_changed() {
}
void RigidCollisionObjectBullet::internal_shape_destroy(int p_index, bool p_permanentlyFromThisBody) {
- ShapeWrapper &shp = shapes[p_index];
+ ShapeWrapper &shp = shapes.write[p_index];
shp.shape->remove_owner(this, p_permanentlyFromThisBody);
if (shp.bt_shape == mainShape) {
mainShape = nullptr;
}
- shp.release_bt_shape();
+ bulletdelete(shp.bt_shape);
}
diff --git a/modules/bullet/collision_object_bullet.h b/modules/bullet/collision_object_bullet.h
index 920d80af23..36b3a9550c 100644
--- a/modules/bullet/collision_object_bullet.h
+++ b/modules/bullet/collision_object_bullet.h
@@ -31,10 +31,9 @@
#ifndef COLLISION_OBJECT_BULLET_H
#define COLLISION_OBJECT_BULLET_H
-#include "core/local_vector.h"
+#include "core/class_db.h"
#include "core/math/transform.h"
#include "core/math/vector3.h"
-#include "core/object.h"
#include "core/vset.h"
#include "shape_owner_bullet.h"
@@ -71,12 +70,11 @@ public:
struct ShapeWrapper {
ShapeBullet *shape = nullptr;
+ btCollisionShape *bt_shape = nullptr;
btTransform transform;
btVector3 scale;
bool active = true;
- btCollisionShape *bt_shape = nullptr;
- public:
ShapeWrapper() {}
ShapeWrapper(ShapeBullet *p_shape, const btTransform &p_transform, bool p_active) :
@@ -109,7 +107,6 @@ public:
btTransform get_adjusted_transform() const;
void claim_bt_shape(const btVector3 &body_scale);
- void release_bt_shape();
};
protected:
@@ -127,20 +124,13 @@ protected:
VSet<RID> exceptions;
- bool needs_body_reload = true;
- bool needs_collision_filters_reload = true;
-
/// This array is used to know all areas where this Object is overlapped in
/// New area is added when overlap with new area (AreaBullet::addOverlap), then is removed when it exit (CollisionObjectBullet::onExitArea)
/// This array is used mainly to know which area hold the pointer of this object
- LocalVector<AreaBullet *> areasOverlapped;
+ Vector<AreaBullet *> areasOverlapped;
bool isTransformChanged = false;
public:
- bool is_in_world = false;
- bool is_in_flush_queue = false;
-
-public:
CollisionObjectBullet(Type p_type);
virtual ~CollisionObjectBullet();
@@ -174,7 +164,7 @@ public:
_FORCE_INLINE_ void set_collision_layer(uint32_t p_layer) {
if (collisionLayer != p_layer) {
collisionLayer = p_layer;
- needs_collision_filters_reload = true;
+ on_collision_filters_change();
}
}
_FORCE_INLINE_ uint32_t get_collision_layer() const { return collisionLayer; }
@@ -182,32 +172,25 @@ public:
_FORCE_INLINE_ void set_collision_mask(uint32_t p_mask) {
if (collisionMask != p_mask) {
collisionMask = p_mask;
- needs_collision_filters_reload = true;
+ on_collision_filters_change();
}
}
_FORCE_INLINE_ uint32_t get_collision_mask() const { return collisionMask; }
- virtual void do_reload_collision_filters() = 0;
+ virtual void on_collision_filters_change() = 0;
_FORCE_INLINE_ bool test_collision_mask(CollisionObjectBullet *p_other) const {
return collisionLayer & p_other->collisionMask || p_other->collisionLayer & collisionMask;
}
- bool need_reload_body() const {
- return needs_body_reload;
- }
-
- void reload_body();
-
- virtual void do_reload_body() = 0;
+ virtual void reload_body() = 0;
virtual void set_space(SpaceBullet *p_space) = 0;
_FORCE_INLINE_ SpaceBullet *get_space() const { return space; }
virtual void on_collision_checker_start() = 0;
virtual void on_collision_checker_end() = 0;
- virtual void dispatch_callbacks();
- virtual void pre_process();
+ virtual void dispatch_callbacks() = 0;
void set_collision_enabled(bool p_enabled);
bool is_collisions_response_enabled();
@@ -231,15 +214,14 @@ public:
class RigidCollisionObjectBullet : public CollisionObjectBullet, public ShapeOwnerBullet {
protected:
btCollisionShape *mainShape = nullptr;
- LocalVector<ShapeWrapper> shapes;
- bool need_shape_reload = true;
+ Vector<ShapeWrapper> shapes;
public:
RigidCollisionObjectBullet(Type p_type) :
CollisionObjectBullet(p_type) {}
~RigidCollisionObjectBullet();
- _FORCE_INLINE_ const LocalVector<ShapeWrapper> &get_shapes_wrappers() const { return shapes; }
+ _FORCE_INLINE_ const Vector<ShapeWrapper> &get_shapes_wrappers() const { return shapes; }
_FORCE_INLINE_ btCollisionShape *get_main_shape() const { return mainShape; }
@@ -250,9 +232,9 @@ public:
ShapeBullet *get_shape(int p_index) const;
btCollisionShape *get_bt_shape(int p_index) const;
- virtual int find_shape(ShapeBullet *p_shape) const override;
+ int find_shape(ShapeBullet *p_shape) const;
- virtual void remove_shape_full(ShapeBullet *p_shape) override;
+ virtual void remove_shape_full(ShapeBullet *p_shape);
void remove_shape_full(int p_index);
void remove_all_shapes(bool p_permanentlyFromThisBody = false, bool p_force_not_reload = false);
@@ -264,15 +246,11 @@ public:
void set_shape_disabled(int p_index, bool p_disabled);
bool is_shape_disabled(int p_index);
- virtual void pre_process() override;
-
- virtual void shape_changed(int p_shape_index) override;
- virtual void reload_shapes() override;
- bool need_reload_shapes() const { return need_shape_reload; }
- virtual void do_reload_shapes();
+ virtual void shape_changed(int p_shape_index);
+ virtual void reload_shapes();
virtual void main_shape_changed() = 0;
- virtual void body_scale_changed() override;
+ virtual void body_scale_changed();
private:
void internal_shape_destroy(int p_index, bool p_permanentlyFromThisBody = false);
diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp
index 32c3240a35..eb599df74c 100644
--- a/modules/bullet/rigid_body_bullet.cpp
+++ b/modules/bullet/rigid_body_bullet.cpp
@@ -51,7 +51,9 @@
BulletPhysicsDirectBodyState3D *BulletPhysicsDirectBodyState3D::singleton = nullptr;
Vector3 BulletPhysicsDirectBodyState3D::get_total_gravity() const {
- return body->total_gravity;
+ Vector3 gVec;
+ B_TO_G(body->btBody->getGravity(), gVec);
+ return gVec;
}
float BulletPhysicsDirectBodyState3D::get_total_angular_damp() const {
@@ -181,7 +183,7 @@ int BulletPhysicsDirectBodyState3D::get_contact_collider_shape(int p_contact_idx
}
Vector3 BulletPhysicsDirectBodyState3D::get_contact_collider_velocity_at_position(int p_contact_idx) const {
- RigidBodyBullet::CollisionData &colDat = body->collisions[p_contact_idx];
+ RigidBodyBullet::CollisionData &colDat = body->collisions.write[p_contact_idx];
btVector3 hitLocation;
G_TO_B(colDat.hitLocalLocation, hitLocation);
@@ -211,7 +213,7 @@ void RigidBodyBullet::KinematicUtilities::setSafeMargin(btScalar p_margin) {
}
void RigidBodyBullet::KinematicUtilities::copyAllOwnerShapes() {
- const LocalVector<CollisionObjectBullet::ShapeWrapper> &shapes_wrappers(owner->get_shapes_wrappers());
+ const Vector<CollisionObjectBullet::ShapeWrapper> &shapes_wrappers(owner->get_shapes_wrappers());
const int shapes_count = shapes_wrappers.size();
just_delete_shapes(shapes_count);
@@ -226,8 +228,8 @@ void RigidBodyBullet::KinematicUtilities::copyAllOwnerShapes() {
continue;
}
- shapes[i].transform = shape_wrapper->transform;
- shapes[i].transform.getOrigin() *= owner_scale;
+ shapes.write[i].transform = shape_wrapper->transform;
+ shapes.write[i].transform.getOrigin() *= owner_scale;
switch (shape_wrapper->shape->get_type()) {
case PhysicsServer3D::SHAPE_SPHERE:
case PhysicsServer3D::SHAPE_BOX:
@@ -235,11 +237,11 @@ void RigidBodyBullet::KinematicUtilities::copyAllOwnerShapes() {
case PhysicsServer3D::SHAPE_CYLINDER:
case PhysicsServer3D::SHAPE_CONVEX_POLYGON:
case PhysicsServer3D::SHAPE_RAY: {
- shapes[i].shape = static_cast<btConvexShape *>(shape_wrapper->shape->internal_create_bt_shape(owner_scale * shape_wrapper->scale, safe_margin));
+ shapes.write[i].shape = static_cast<btConvexShape *>(shape_wrapper->shape->create_bt_shape(owner_scale * shape_wrapper->scale, safe_margin));
} break;
default:
WARN_PRINT("This shape is not supported for kinematic collision.");
- shapes[i].shape = nullptr;
+ shapes.write[i].shape = nullptr;
}
}
}
@@ -247,7 +249,7 @@ void RigidBodyBullet::KinematicUtilities::copyAllOwnerShapes() {
void RigidBodyBullet::KinematicUtilities::just_delete_shapes(int new_size) {
for (int i = shapes.size() - 1; 0 <= i; --i) {
if (shapes[i].shape) {
- bulletdelete(shapes[i].shape);
+ bulletdelete(shapes.write[i].shape);
}
}
shapes.resize(new_size);
@@ -262,6 +264,7 @@ RigidBodyBullet::RigidBodyBullet() :
btRigidBody::btRigidBodyConstructionInfo cInfo(mass, godotMotionState, nullptr, localInertia);
btBody = bulletnew(btRigidBody(cInfo));
+ btBody->setFriction(1.0);
reload_shapes();
setupBulletCollisionObject(btBody);
@@ -269,8 +272,8 @@ RigidBodyBullet::RigidBodyBullet() :
reload_axis_lock();
areasWhereIam.resize(maxAreasWhereIam);
- for (uint32_t i = 0; i < areasWhereIam.size(); i += 1) {
- areasWhereIam[i] = nullptr;
+ for (int i = areasWhereIam.size() - 1; 0 <= i; --i) {
+ areasWhereIam.write[i] = nullptr;
}
btBody->setSleepingThresholds(0.2, 0.2);
@@ -305,7 +308,7 @@ void RigidBodyBullet::main_shape_changed() {
set_continuous_collision_detection(is_continuous_collision_detection_enabled()); // Reset
}
-void RigidBodyBullet::do_reload_body() {
+void RigidBodyBullet::reload_body() {
if (space) {
space->remove_rigid_body(this);
if (get_main_shape()) {
@@ -324,24 +327,23 @@ void RigidBodyBullet::set_space(SpaceBullet *p_space) {
assert_no_constraints();
// Remove this object form the physics world
- space->unregister_collision_object(this);
space->remove_rigid_body(this);
}
space = p_space;
if (space) {
- space->register_collision_object(this);
- reload_body();
- space->add_to_flush_queue(this);
+ space->add_rigid_body(this);
}
}
void RigidBodyBullet::dispatch_callbacks() {
- RigidCollisionObjectBullet::dispatch_callbacks();
-
/// The check isFirstTransformChanged is necessary in order to call integrated forces only when the first transform is sent
if ((btBody->isKinematicObject() || btBody->isActive() || previousActiveState != btBody->isActive()) && force_integration_callback && can_integrate_forces) {
+ if (omit_forces_integration) {
+ btBody->clearForces();
+ }
+
BulletPhysicsDirectBodyState3D *bodyDirect = BulletPhysicsDirectBodyState3D::get_singleton(this);
Variant variantBodyDirect = bodyDirect;
@@ -359,22 +361,16 @@ void RigidBodyBullet::dispatch_callbacks() {
}
}
- previousActiveState = btBody->isActive();
-}
-
-void RigidBodyBullet::pre_process() {
- RigidCollisionObjectBullet::pre_process();
-
if (isScratchedSpaceOverrideModificator || 0 < countGravityPointSpaces) {
isScratchedSpaceOverrideModificator = false;
reload_space_override_modificator();
}
- if (is_active()) {
- /// Lock axis
- btBody->setLinearVelocity(btBody->getLinearVelocity() * btBody->getLinearFactor());
- btBody->setAngularVelocity(btBody->getAngularVelocity() * btBody->getAngularFactor());
- }
+ /// Lock axis
+ btBody->setLinearVelocity(btBody->getLinearVelocity() * btBody->getLinearFactor());
+ btBody->setAngularVelocity(btBody->getAngularVelocity() * btBody->getAngularFactor());
+
+ previousActiveState = btBody->isActive();
}
void RigidBodyBullet::set_force_integration_callback(ObjectID p_id, const StringName &p_method, const Variant &p_udata) {
@@ -395,7 +391,7 @@ void RigidBodyBullet::scratch_space_override_modificator() {
isScratchedSpaceOverrideModificator = true;
}
-void RigidBodyBullet::do_reload_collision_filters() {
+void RigidBodyBullet::on_collision_filters_change() {
if (space) {
space->reload_collision_filters(this);
}
@@ -408,15 +404,14 @@ void RigidBodyBullet::on_collision_checker_start() {
collisionsCount = 0;
// Swap array
- SWAP(prev_collision_traces, curr_collision_traces);
+ Vector<RigidBodyBullet *> *s = prev_collision_traces;
+ prev_collision_traces = curr_collision_traces;
+ curr_collision_traces = s;
}
void RigidBodyBullet::on_collision_checker_end() {
// Always true if active and not a static or kinematic body
isTransformChanged = btBody->isActive() && !btBody->isStaticOrKinematicObject();
- if (isTransformChanged && space != nullptr) {
- space->add_to_flush_queue(this);
- }
}
bool RigidBodyBullet::add_collision_object(RigidBodyBullet *p_otherObject, const Vector3 &p_hitWorldLocation, const Vector3 &p_hitLocalLocation, const Vector3 &p_hitNormal, const float &p_appliedImpulse, int p_other_shape_index, int p_local_shape_index) {
@@ -424,7 +419,7 @@ bool RigidBodyBullet::add_collision_object(RigidBodyBullet *p_otherObject, const
return false;
}
- CollisionData &cd = collisions[collisionsCount];
+ CollisionData &cd = collisions.write[collisionsCount];
cd.hitLocalLocation = p_hitLocalLocation;
cd.otherObject = p_otherObject;
cd.hitWorldLocation = p_hitWorldLocation;
@@ -433,7 +428,7 @@ bool RigidBodyBullet::add_collision_object(RigidBodyBullet *p_otherObject, const
cd.other_object_shape = p_other_shape_index;
cd.local_shape = p_local_shape_index;
- (*curr_collision_traces)[collisionsCount] = p_otherObject;
+ curr_collision_traces->write[collisionsCount] = p_otherObject;
++collisionsCount;
return true;
@@ -468,7 +463,6 @@ bool RigidBodyBullet::is_active() const {
void RigidBodyBullet::set_omit_forces_integration(bool p_omit) {
omit_forces_integration = p_omit;
- scratch_space_override_modificator();
}
void RigidBodyBullet::set_param(PhysicsServer3D::BodyParameter p_param, real_t p_value) {
@@ -811,8 +805,8 @@ const btTransform &RigidBodyBullet::get_transform__bullet() const {
}
}
-void RigidBodyBullet::do_reload_shapes() {
- RigidCollisionObjectBullet::do_reload_shapes();
+void RigidBodyBullet::reload_shapes() {
+ RigidCollisionObjectBullet::reload_shapes();
const btScalar invMass = btBody->getInvMass();
const btScalar mass = invMass == 0 ? 0 : 1 / invMass;
@@ -844,15 +838,15 @@ void RigidBodyBullet::on_enter_area(AreaBullet *p_area) {
for (int i = 0; i < areaWhereIamCount; ++i) {
if (nullptr == areasWhereIam[i]) {
// This area has the highest priority
- areasWhereIam[i] = p_area;
+ areasWhereIam.write[i] = p_area;
break;
} else {
if (areasWhereIam[i]->get_spOv_priority() > p_area->get_spOv_priority()) {
// The position was found, just shift all elements
- for (int j = i; j < areaWhereIamCount; ++j) {
- areasWhereIam[j + 1] = areasWhereIam[j];
+ for (int j = areaWhereIamCount; j > i; j--) {
+ areasWhereIam.write[j] = areasWhereIam[j - 1];
}
- areasWhereIam[i] = p_area;
+ areasWhereIam.write[i] = p_area;
break;
}
}
@@ -876,7 +870,7 @@ void RigidBodyBullet::on_exit_area(AreaBullet *p_area) {
if (p_area == areasWhereIam[i]) {
// The area was found, just shift down all elements
for (int j = i; j < areaWhereIamCount; ++j) {
- areasWhereIam[j] = areasWhereIam[j + 1];
+ areasWhereIam.write[j] = areasWhereIam[j + 1];
}
wasTheAreaFound = true;
break;
@@ -889,7 +883,7 @@ void RigidBodyBullet::on_exit_area(AreaBullet *p_area) {
}
--areaWhereIamCount;
- areasWhereIam[areaWhereIamCount] = nullptr; // Even if this is not required, I clear the last element to be safe
+ areasWhereIam.write[areaWhereIamCount] = nullptr; // Even if this is not required, I clear the last element to be safe
if (PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED != p_area->get_spOv_mode()) {
scratch_space_override_modificator();
}
@@ -901,31 +895,36 @@ void RigidBodyBullet::reload_space_override_modificator() {
return;
}
- Vector3 newGravity;
+ Vector3 newGravity(0.0, 0.0, 0.0);
real_t newLinearDamp = MAX(0.0, linearDamp);
real_t newAngularDamp = MAX(0.0, angularDamp);
+ AreaBullet *currentArea;
+ // Variable used to calculate new gravity for gravity point areas, it is pointed by currentGravity pointer
+ Vector3 support_gravity(0, 0, 0);
+
bool stopped = false;
- for (int i = 0; i < areaWhereIamCount && !stopped; i += 1) {
- AreaBullet *currentArea = areasWhereIam[i];
+ for (int i = areaWhereIamCount - 1; (0 <= i) && !stopped; --i) {
+ currentArea = areasWhereIam[i];
if (!currentArea || PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED == currentArea->get_spOv_mode()) {
continue;
}
- Vector3 support_gravity;
-
/// Here is calculated the gravity
if (currentArea->is_spOv_gravityPoint()) {
/// It calculates the direction of new gravity
support_gravity = currentArea->get_transform().xform(currentArea->get_spOv_gravityVec()) - get_transform().get_origin();
-
- const real_t distanceMag = support_gravity.length();
+ real_t distanceMag = support_gravity.length();
// Normalized in this way to avoid the double call of function "length()"
if (distanceMag == 0) {
- support_gravity = Vector3();
+ support_gravity.x = 0;
+ support_gravity.y = 0;
+ support_gravity.z = 0;
} else {
- support_gravity /= distanceMag;
+ support_gravity.x /= distanceMag;
+ support_gravity.y /= distanceMag;
+ support_gravity.z /= distanceMag;
}
/// Here is calculated the final gravity
@@ -987,17 +986,10 @@ void RigidBodyBullet::reload_space_override_modificator() {
newAngularDamp += space->get_angular_damp();
}
- total_gravity = newGravity;
-
- if (omit_forces_integration) {
- // Custom behaviour.
- btBody->setGravity(btVector3(0, 0, 0));
- } else {
- btVector3 newBtGravity;
- G_TO_B(newGravity * gravity_scale, newBtGravity);
- btBody->setGravity(newBtGravity);
- }
+ btVector3 newBtGravity;
+ G_TO_B(newGravity * gravity_scale, newBtGravity);
+ btBody->setGravity(newBtGravity);
btBody->setDamping(newLinearDamp, newAngularDamp);
}
diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h
index 047645677b..c643611397 100644
--- a/modules/bullet/rigid_body_bullet.h
+++ b/modules/bullet/rigid_body_bullet.h
@@ -171,7 +171,7 @@ public:
struct KinematicUtilities {
RigidBodyBullet *owner;
btScalar safe_margin;
- LocalVector<KinematicShape> shapes;
+ Vector<KinematicShape> shapes;
KinematicUtilities(RigidBodyBullet *p_owner);
~KinematicUtilities();
@@ -193,7 +193,6 @@ private:
PhysicsServer3D::BodyMode mode;
GodotMotionState *godotMotionState;
btRigidBody *btBody;
- Vector3 total_gravity;
uint16_t locked_axis = 0;
real_t mass = 1;
real_t gravity_scale = 1;
@@ -203,18 +202,18 @@ private:
bool omit_forces_integration = false;
bool can_integrate_forces = false;
- LocalVector<CollisionData> collisions;
- LocalVector<RigidBodyBullet *> collision_traces_1;
- LocalVector<RigidBodyBullet *> collision_traces_2;
- LocalVector<RigidBodyBullet *> *prev_collision_traces;
- LocalVector<RigidBodyBullet *> *curr_collision_traces;
+ Vector<CollisionData> collisions;
+ Vector<RigidBodyBullet *> collision_traces_1;
+ Vector<RigidBodyBullet *> collision_traces_2;
+ Vector<RigidBodyBullet *> *prev_collision_traces;
+ Vector<RigidBodyBullet *> *curr_collision_traces;
// these parameters are used to avoid vector resize
- uint32_t maxCollisionsDetection = 0;
- uint32_t collisionsCount = 0;
- uint32_t prev_collision_count = 0;
+ int maxCollisionsDetection = 0;
+ int collisionsCount = 0;
+ int prev_collision_count = 0;
- LocalVector<AreaBullet *> areasWhereIam;
+ Vector<AreaBullet *> areasWhereIam;
// these parameters are used to avoid vector resize
int maxAreasWhereIam = 10;
int areaWhereIamCount = 0;
@@ -236,20 +235,21 @@ public:
_FORCE_INLINE_ btRigidBody *get_bt_rigid_body() { return btBody; }
- virtual void main_shape_changed() override;
- virtual void do_reload_body() override;
- virtual void set_space(SpaceBullet *p_space) override;
+ virtual void main_shape_changed();
+ virtual void reload_body();
+ virtual void set_space(SpaceBullet *p_space);
- virtual void dispatch_callbacks() override;
- virtual void pre_process() override;
+ virtual void dispatch_callbacks();
void set_force_integration_callback(ObjectID p_id, const StringName &p_method, const Variant &p_udata = Variant());
void scratch_space_override_modificator();
- virtual void do_reload_collision_filters() override;
- virtual void on_collision_checker_start() override;
- virtual void on_collision_checker_end() override;
+ virtual void on_collision_filters_change();
+ virtual void on_collision_checker_start();
+ virtual void on_collision_checker_end();
+
+ void set_max_collisions_detection(int p_maxCollisionsDetection) {
+ ERR_FAIL_COND(0 > p_maxCollisionsDetection);
- void set_max_collisions_detection(uint32_t p_maxCollisionsDetection) {
maxCollisionsDetection = p_maxCollisionsDetection;
collisions.resize(p_maxCollisionsDetection);
@@ -312,19 +312,19 @@ public:
void set_angular_velocity(const Vector3 &p_velocity);
Vector3 get_angular_velocity() const;
- virtual void set_transform__bullet(const btTransform &p_global_transform) override;
- virtual const btTransform &get_transform__bullet() const override;
+ virtual void set_transform__bullet(const btTransform &p_global_transform);
+ virtual const btTransform &get_transform__bullet() const;
- virtual void do_reload_shapes() override;
+ virtual void reload_shapes();
- virtual void on_enter_area(AreaBullet *p_area) override;
- virtual void on_exit_area(AreaBullet *p_area) override;
+ virtual void on_enter_area(AreaBullet *p_area);
+ virtual void on_exit_area(AreaBullet *p_area);
void reload_space_override_modificator();
/// Kinematic
void reload_kinematic_shapes();
- virtual void notify_transform_changed() override;
+ virtual void notify_transform_changed();
private:
void _internal_set_mass(real_t p_mass);
diff --git a/modules/bullet/shape_bullet.cpp b/modules/bullet/shape_bullet.cpp
index 274493ed17..fdc2944dad 100644
--- a/modules/bullet/shape_bullet.cpp
+++ b/modules/bullet/shape_bullet.cpp
@@ -46,15 +46,9 @@
@author AndreaCatania
*/
-ShapeBullet::ShapeBullet() {
-}
+ShapeBullet::ShapeBullet() {}
-ShapeBullet::~ShapeBullet() {
- if (default_shape != nullptr) {
- bulletdelete(default_shape);
- default_shape = nullptr;
- }
-}
+ShapeBullet::~ShapeBullet() {}
btCollisionShape *ShapeBullet::create_bt_shape(const Vector3 &p_implicit_scale, real_t p_extra_edge) {
btVector3 s;
@@ -62,22 +56,6 @@ btCollisionShape *ShapeBullet::create_bt_shape(const Vector3 &p_implicit_scale,
return create_bt_shape(s, p_extra_edge);
}
-btCollisionShape *ShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
- if (p_extra_edge == 0.0 && (p_implicit_scale - btVector3(1, 1, 1)).length2() <= CMP_EPSILON) {
- return default_shape;
- }
-
- return internal_create_bt_shape(p_implicit_scale, p_extra_edge);
-}
-
-void ShapeBullet::destroy_bt_shape(btCollisionShape *p_shape) const {
- if (p_shape != default_shape && p_shape != old_default_shape) {
- if (likely(p_shape != nullptr)) {
- bulletdelete(p_shape);
- }
- }
-}
-
btCollisionShape *ShapeBullet::prepare(btCollisionShape *p_btShape) const {
p_btShape->setUserPointer(const_cast<ShapeBullet *>(this));
p_btShape->setMargin(margin);
@@ -85,21 +63,10 @@ btCollisionShape *ShapeBullet::prepare(btCollisionShape *p_btShape) const {
}
void ShapeBullet::notifyShapeChanged() {
- // Store the old shape ptr so to not lose the reference pointer.
- old_default_shape = default_shape;
- // Create the new default shape with the new data.
- default_shape = internal_create_bt_shape(btVector3(1, 1, 1));
-
for (Map<ShapeOwnerBullet *, int>::Element *E = owners.front(); E; E = E->next()) {
ShapeOwnerBullet *owner = static_cast<ShapeOwnerBullet *>(E->key());
owner->shape_changed(owner->find_shape(this));
}
-
- if (old_default_shape) {
- // At this point now one has the old default shape; just delete it.
- bulletdelete(old_default_shape);
- old_default_shape = nullptr;
- }
}
void ShapeBullet::add_owner(ShapeOwnerBullet *p_owner) {
@@ -219,7 +186,7 @@ void PlaneShapeBullet::setup(const Plane &p_plane) {
notifyShapeChanged();
}
-btCollisionShape *PlaneShapeBullet::internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
+btCollisionShape *PlaneShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
btVector3 btPlaneNormal;
G_TO_B(plane.normal, btPlaneNormal);
return prepare(PlaneShapeBullet::create_shape_plane(btPlaneNormal, plane.d));
@@ -247,7 +214,7 @@ void SphereShapeBullet::setup(real_t p_radius) {
notifyShapeChanged();
}
-btCollisionShape *SphereShapeBullet::internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
+btCollisionShape *SphereShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
return prepare(ShapeBullet::create_shape_sphere(radius * p_implicit_scale[0] + p_extra_edge));
}
@@ -274,7 +241,7 @@ void BoxShapeBullet::setup(const Vector3 &p_half_extents) {
notifyShapeChanged();
}
-btCollisionShape *BoxShapeBullet::internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
+btCollisionShape *BoxShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
return prepare(ShapeBullet::create_shape_box((half_extents * p_implicit_scale) + btVector3(p_extra_edge, p_extra_edge, p_extra_edge)));
}
@@ -307,8 +274,8 @@ void CapsuleShapeBullet::setup(real_t p_height, real_t p_radius) {
notifyShapeChanged();
}
-btCollisionShape *CapsuleShapeBullet::internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
- return prepare(ShapeBullet::create_shape_capsule(radius * p_implicit_scale[0] + p_extra_edge, height * p_implicit_scale[1] + p_extra_edge));
+btCollisionShape *CapsuleShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
+ return prepare(ShapeBullet::create_shape_capsule(radius * p_implicit_scale[0] + p_extra_edge, height * p_implicit_scale[1]));
}
/* Cylinder */
@@ -340,7 +307,7 @@ void CylinderShapeBullet::setup(real_t p_height, real_t p_radius) {
notifyShapeChanged();
}
-btCollisionShape *CylinderShapeBullet::internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin) {
+btCollisionShape *CylinderShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin) {
return prepare(ShapeBullet::create_shape_cylinder(radius * p_implicit_scale[0] + p_margin, height * p_implicit_scale[1] + p_margin));
}
@@ -382,7 +349,7 @@ void ConvexPolygonShapeBullet::setup(const Vector<Vector3> &p_vertices) {
notifyShapeChanged();
}
-btCollisionShape *ConvexPolygonShapeBullet::internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
+btCollisionShape *ConvexPolygonShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
if (!vertices.size()) {
// This is necessary since 0 vertices
return prepare(ShapeBullet::create_shape_empty());
@@ -464,7 +431,7 @@ void ConcavePolygonShapeBullet::setup(Vector<Vector3> p_faces) {
notifyShapeChanged();
}
-btCollisionShape *ConcavePolygonShapeBullet::internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
+btCollisionShape *ConcavePolygonShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
btCollisionShape *cs = ShapeBullet::create_shape_concave(meshShape);
if (!cs) {
// This is necessary since if 0 faces the creation of concave return null
@@ -591,7 +558,7 @@ void HeightMapShapeBullet::setup(Vector<real_t> &p_heights, int p_width, int p_d
notifyShapeChanged();
}
-btCollisionShape *HeightMapShapeBullet::internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
+btCollisionShape *HeightMapShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
btCollisionShape *cs(ShapeBullet::create_shape_height_field(heights, width, depth, min_height, max_height));
cs->setLocalScaling(p_implicit_scale);
prepare(cs);
@@ -624,6 +591,6 @@ void RayShapeBullet::setup(real_t p_length, bool p_slips_on_slope) {
notifyShapeChanged();
}
-btCollisionShape *RayShapeBullet::internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
+btCollisionShape *RayShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
return prepare(ShapeBullet::create_shape_ray(length * p_implicit_scale[1] + p_extra_edge, slips_on_slope));
}
diff --git a/modules/bullet/shape_bullet.h b/modules/bullet/shape_bullet.h
index 6ca4d36a23..a35a1d8a18 100644
--- a/modules/bullet/shape_bullet.h
+++ b/modules/bullet/shape_bullet.h
@@ -53,10 +53,6 @@ class ShapeBullet : public RIDBullet {
Map<ShapeOwnerBullet *, int> owners;
real_t margin = 0.04;
- // Contains the default shape.
- btCollisionShape *default_shape = nullptr;
- btCollisionShape *old_default_shape = nullptr;
-
protected:
/// return self
btCollisionShape *prepare(btCollisionShape *p_btShape) const;
@@ -67,11 +63,7 @@ public:
virtual ~ShapeBullet();
btCollisionShape *create_bt_shape(const Vector3 &p_implicit_scale, real_t p_extra_edge = 0);
- btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
-
- void destroy_bt_shape(btCollisionShape *p_shape) const;
-
- virtual btCollisionShape *internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0) = 0;
+ virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0) = 0;
void add_owner(ShapeOwnerBullet *p_owner);
void remove_owner(ShapeOwnerBullet *p_owner, bool p_permanentlyFromThisBody = false);
@@ -110,7 +102,7 @@ public:
virtual void set_data(const Variant &p_data);
virtual Variant get_data() const;
virtual PhysicsServer3D::ShapeType get_type() const;
- virtual btCollisionShape *internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
+ virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
private:
void setup(const Plane &p_plane);
@@ -126,7 +118,7 @@ public:
virtual void set_data(const Variant &p_data);
virtual Variant get_data() const;
virtual PhysicsServer3D::ShapeType get_type() const;
- virtual btCollisionShape *internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
+ virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
private:
void setup(real_t p_radius);
@@ -142,7 +134,7 @@ public:
virtual void set_data(const Variant &p_data);
virtual Variant get_data() const;
virtual PhysicsServer3D::ShapeType get_type() const;
- virtual btCollisionShape *internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
+ virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
private:
void setup(const Vector3 &p_half_extents);
@@ -160,7 +152,7 @@ public:
virtual void set_data(const Variant &p_data);
virtual Variant get_data() const;
virtual PhysicsServer3D::ShapeType get_type() const;
- virtual btCollisionShape *internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
+ virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
private:
void setup(real_t p_height, real_t p_radius);
@@ -178,7 +170,7 @@ public:
virtual void set_data(const Variant &p_data);
virtual Variant get_data() const;
virtual PhysicsServer3D::ShapeType get_type() const;
- virtual btCollisionShape *internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin = 0);
+ virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_margin = 0);
private:
void setup(real_t p_height, real_t p_radius);
@@ -194,7 +186,7 @@ public:
void get_vertices(Vector<Vector3> &out_vertices);
virtual Variant get_data() const;
virtual PhysicsServer3D::ShapeType get_type() const;
- virtual btCollisionShape *internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
+ virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
private:
void setup(const Vector<Vector3> &p_vertices);
@@ -212,7 +204,7 @@ public:
virtual void set_data(const Variant &p_data);
virtual Variant get_data() const;
virtual PhysicsServer3D::ShapeType get_type() const;
- virtual btCollisionShape *internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
+ virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
private:
void setup(Vector<Vector3> p_faces);
@@ -231,7 +223,7 @@ public:
virtual void set_data(const Variant &p_data);
virtual Variant get_data() const;
virtual PhysicsServer3D::ShapeType get_type() const;
- virtual btCollisionShape *internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
+ virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
private:
void setup(Vector<real_t> &p_heights, int p_width, int p_depth, real_t p_min_height, real_t p_max_height);
@@ -247,7 +239,7 @@ public:
virtual void set_data(const Variant &p_data);
virtual Variant get_data() const;
virtual PhysicsServer3D::ShapeType get_type() const;
- virtual btCollisionShape *internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
+ virtual btCollisionShape *create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge = 0);
private:
void setup(real_t p_length, bool p_slips_on_slope);
diff --git a/modules/bullet/soft_body_bullet.cpp b/modules/bullet/soft_body_bullet.cpp
index ee48b3c5f0..6794d6c313 100644
--- a/modules/bullet/soft_body_bullet.cpp
+++ b/modules/bullet/soft_body_bullet.cpp
@@ -41,7 +41,7 @@ SoftBodyBullet::SoftBodyBullet() :
SoftBodyBullet::~SoftBodyBullet() {
}
-void SoftBodyBullet::do_reload_body() {
+void SoftBodyBullet::reload_body() {
if (space) {
space->remove_soft_body(this);
space->add_soft_body(this);
@@ -51,15 +51,13 @@ void SoftBodyBullet::do_reload_body() {
void SoftBodyBullet::set_space(SpaceBullet *p_space) {
if (space) {
isScratched = false;
- space->unregister_collision_object(this);
space->remove_soft_body(this);
}
space = p_space;
if (space) {
- space->register_collision_object(this);
- reload_body();
+ space->add_soft_body(this);
}
}
@@ -346,14 +344,14 @@ void SoftBodyBullet::set_trimesh_body_shape(Vector<int> p_indices, Vector<Vector
indices_table.push_back(Vector<int>());
}
- indices_table[vertex_id].push_back(vs_vertex_index);
+ indices_table.write[vertex_id].push_back(vs_vertex_index);
vs_indices_to_physics_table.push_back(vertex_id);
}
}
const int indices_map_size(indices_table.size());
- LocalVector<btScalar> bt_vertices;
+ Vector<btScalar> bt_vertices;
{ // Parse vertices to bullet
@@ -361,13 +359,13 @@ void SoftBodyBullet::set_trimesh_body_shape(Vector<int> p_indices, Vector<Vector
const Vector3 *p_vertices_read = p_vertices.ptr();
for (int i = 0; i < indices_map_size; ++i) {
- bt_vertices[3 * i + 0] = p_vertices_read[indices_table[i][0]].x;
- bt_vertices[3 * i + 1] = p_vertices_read[indices_table[i][0]].y;
- bt_vertices[3 * i + 2] = p_vertices_read[indices_table[i][0]].z;
+ bt_vertices.write[3 * i + 0] = p_vertices_read[indices_table[i][0]].x;
+ bt_vertices.write[3 * i + 1] = p_vertices_read[indices_table[i][0]].y;
+ bt_vertices.write[3 * i + 2] = p_vertices_read[indices_table[i][0]].z;
}
}
- LocalVector<int> bt_triangles;
+ Vector<int> bt_triangles;
const int triangles_size(p_indices.size() / 3);
{ // Parse indices
@@ -377,9 +375,9 @@ void SoftBodyBullet::set_trimesh_body_shape(Vector<int> p_indices, Vector<Vector
const int *p_indices_read = p_indices.ptr();
for (int i = 0; i < triangles_size; ++i) {
- bt_triangles[3 * i + 0] = vs_indices_to_physics_table[p_indices_read[3 * i + 2]];
- bt_triangles[3 * i + 1] = vs_indices_to_physics_table[p_indices_read[3 * i + 1]];
- bt_triangles[3 * i + 2] = vs_indices_to_physics_table[p_indices_read[3 * i + 0]];
+ bt_triangles.write[3 * i + 0] = vs_indices_to_physics_table[p_indices_read[3 * i + 2]];
+ bt_triangles.write[3 * i + 1] = vs_indices_to_physics_table[p_indices_read[3 * i + 1]];
+ bt_triangles.write[3 * i + 2] = vs_indices_to_physics_table[p_indices_read[3 * i + 0]];
}
}
diff --git a/modules/bullet/soft_body_bullet.h b/modules/bullet/soft_body_bullet.h
index 229204b539..da8a2412ed 100644
--- a/modules/bullet/soft_body_bullet.h
+++ b/modules/bullet/soft_body_bullet.h
@@ -32,6 +32,7 @@
#define SOFT_BODY_BULLET_H
#include "collision_object_bullet.h"
+#include "scene/resources/material.h" // TODO remove this please
#ifdef None
/// This is required to remove the macro None defined by x11 compiler because this word "None" is used internally by Bullet
@@ -57,7 +58,7 @@
class SoftBodyBullet : public CollisionObjectBullet {
private:
btSoftBody *bt_soft_body = nullptr;
- LocalVector<Vector<int>> indices_table;
+ Vector<Vector<int>> indices_table;
btSoftBody::Material *mat0; // This is just a copy of pointer managed by btSoftBody
bool isScratched = false;
@@ -72,7 +73,7 @@ private:
real_t pose_matching_coefficient = 0.; // [0,1]
real_t damping_coefficient = 0.01; // [0,1]
real_t drag_coefficient = 0.; // [0,1]
- LocalVector<int> pinned_nodes;
+ Vector<int> pinned_nodes;
// Other property to add
//btScalar kVC; // Volume conversation coefficient [0,+inf]
@@ -86,14 +87,15 @@ public:
SoftBodyBullet();
~SoftBodyBullet();
- virtual void do_reload_body() override;
- virtual void set_space(SpaceBullet *p_space) override;
+ virtual void reload_body();
+ virtual void set_space(SpaceBullet *p_space);
- virtual void do_reload_collision_filters() override {}
- virtual void on_collision_checker_start() override {}
- virtual void on_collision_checker_end() override {}
- virtual void on_enter_area(AreaBullet *p_area) override;
- virtual void on_exit_area(AreaBullet *p_area) override;
+ virtual void dispatch_callbacks() {}
+ virtual void on_collision_filters_change() {}
+ virtual void on_collision_checker_start() {}
+ virtual void on_collision_checker_end() {}
+ virtual void on_enter_area(AreaBullet *p_area);
+ virtual void on_exit_area(AreaBullet *p_area);
_FORCE_INLINE_ btSoftBody *get_bt_soft_body() const { return bt_soft_body; }
diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp
index d0515e7c97..c581d1804e 100644
--- a/modules/bullet/space_bullet.cpp
+++ b/modules/bullet/space_bullet.cpp
@@ -127,7 +127,7 @@ int BulletPhysicsDirectSpaceState::intersect_shape(const RID &p_shape, const Tra
btCollisionShape *btShape = shape->create_bt_shape(p_xform.basis.get_scale_abs(), p_margin);
if (!btShape->isConvex()) {
- shape->destroy_bt_shape(btShape);
+ bulletdelete(btShape);
ERR_PRINT("The shape is not a convex shape, then is not supported: shape type: " + itos(shape->get_type()));
return 0;
}
@@ -147,7 +147,7 @@ int BulletPhysicsDirectSpaceState::intersect_shape(const RID &p_shape, const Tra
btQuery.m_closestDistanceThreshold = 0;
space->dynamicsWorld->contactTest(&collision_object, btQuery);
- shape->destroy_bt_shape(btShape);
+ bulletdelete(btConvex);
return btQuery.m_count;
}
@@ -163,7 +163,7 @@ bool BulletPhysicsDirectSpaceState::cast_motion(const RID &p_shape, const Transf
btCollisionShape *btShape = shape->create_bt_shape(p_xform.basis.get_scale(), p_margin);
if (!btShape->isConvex()) {
- shape->destroy_bt_shape(btShape);
+ bulletdelete(btShape);
ERR_PRINT("The shape is not a convex shape, then is not supported: shape type: " + itos(shape->get_type()));
return false;
}
@@ -177,7 +177,7 @@ bool BulletPhysicsDirectSpaceState::cast_motion(const RID &p_shape, const Transf
bt_xform_to.getOrigin() += bt_motion;
if ((bt_xform_to.getOrigin() - bt_xform_from.getOrigin()).fuzzyZero()) {
- shape->destroy_bt_shape(btShape);
+ bulletdelete(btShape);
return false;
}
@@ -207,7 +207,7 @@ bool BulletPhysicsDirectSpaceState::cast_motion(const RID &p_shape, const Transf
r_closest_unsafe = 1.0f;
}
- shape->destroy_bt_shape(btShape);
+ bulletdelete(bt_convex_shape);
return true; // Mean success
}
@@ -222,7 +222,7 @@ bool BulletPhysicsDirectSpaceState::collide_shape(RID p_shape, const Transform &
btCollisionShape *btShape = shape->create_bt_shape(p_shape_xform.basis.get_scale_abs(), p_margin);
if (!btShape->isConvex()) {
- shape->destroy_bt_shape(btShape);
+ bulletdelete(btShape);
ERR_PRINT("The shape is not a convex shape, then is not supported: shape type: " + itos(shape->get_type()));
return false;
}
@@ -243,7 +243,7 @@ bool BulletPhysicsDirectSpaceState::collide_shape(RID p_shape, const Transform &
space->dynamicsWorld->contactTest(&collision_object, btQuery);
r_result_count = btQuery.m_count;
- shape->destroy_bt_shape(btShape);
+ bulletdelete(btConvex);
return btQuery.m_count;
}
@@ -254,7 +254,7 @@ bool BulletPhysicsDirectSpaceState::rest_info(RID p_shape, const Transform &p_sh
btCollisionShape *btShape = shape->create_bt_shape(p_shape_xform.basis.get_scale_abs(), p_margin);
if (!btShape->isConvex()) {
- shape->destroy_bt_shape(btShape);
+ bulletdelete(btShape);
ERR_PRINT("The shape is not a convex shape, then is not supported: shape type: " + itos(shape->get_type()));
return false;
}
@@ -274,7 +274,7 @@ bool BulletPhysicsDirectSpaceState::rest_info(RID p_shape, const Transform &p_sh
btQuery.m_closestDistanceThreshold = 0;
space->dynamicsWorld->contactTest(&collision_object, btQuery);
- shape->destroy_bt_shape(btShape);
+ bulletdelete(btConvex);
if (btQuery.m_collided) {
if (btCollisionObject::CO_RIGID_BODY == btQuery.m_rest_info_collision_object->getInternalType()) {
@@ -349,46 +349,14 @@ SpaceBullet::~SpaceBullet() {
destroy_world();
}
-void SpaceBullet::add_to_pre_flush_queue(CollisionObjectBullet *p_co) {
- if (p_co->is_in_flush_queue == false) {
- p_co->is_in_flush_queue = true;
- queue_pre_flush.push_back(p_co);
- }
-}
-
-void SpaceBullet::add_to_flush_queue(CollisionObjectBullet *p_co) {
- if (p_co->is_in_flush_queue == false) {
- p_co->is_in_flush_queue = true;
- queue_flush.push_back(p_co);
- }
-}
-
-void SpaceBullet::remove_from_any_queue(CollisionObjectBullet *p_co) {
- if (p_co->is_in_flush_queue) {
- p_co->is_in_flush_queue = false;
- queue_pre_flush.erase(p_co);
- queue_flush.erase(p_co);
- }
-}
-
void SpaceBullet::flush_queries() {
- for (uint32_t i = 0; i < queue_pre_flush.size(); i += 1) {
- queue_pre_flush[i]->dispatch_callbacks();
- queue_pre_flush[i]->is_in_flush_queue = false;
- }
- for (uint32_t i = 0; i < queue_flush.size(); i += 1) {
- queue_flush[i]->dispatch_callbacks();
- queue_flush[i]->is_in_flush_queue = false;
+ const btCollisionObjectArray &colObjArray = dynamicsWorld->getCollisionObjectArray();
+ for (int i = colObjArray.size() - 1; 0 <= i; --i) {
+ static_cast<CollisionObjectBullet *>(colObjArray[i]->getUserPointer())->dispatch_callbacks();
}
- queue_pre_flush.clear();
- queue_flush.clear();
}
void SpaceBullet::step(real_t p_delta_time) {
- for (uint32_t i = 0; i < collision_objects.size(); i += 1) {
- collision_objects[i]->pre_process();
- }
-
delta_time = p_delta_time;
dynamicsWorld->stepSimulation(p_delta_time, 0, 0);
}
@@ -481,30 +449,16 @@ real_t SpaceBullet::get_param(PhysicsServer3D::SpaceParameter p_param) {
}
void SpaceBullet::add_area(AreaBullet *p_area) {
-#ifdef TOOLS_ENABLED
- // This never happen, and there is no way for the user to trigger it.
- // If in future a bug is introduced into this bullet integration and this
- // function is called twice, the crash will notify the developer that will
- // fix it even before do the eventual PR.
- CRASH_COND(p_area->is_in_world);
-#endif
areas.push_back(p_area);
dynamicsWorld->addCollisionObject(p_area->get_bt_ghost(), p_area->get_collision_layer(), p_area->get_collision_mask());
- p_area->is_in_world = true;
}
void SpaceBullet::remove_area(AreaBullet *p_area) {
- if (p_area->is_in_world) {
- areas.erase(p_area);
- dynamicsWorld->removeCollisionObject(p_area->get_bt_ghost());
- p_area->is_in_world = false;
- }
+ areas.erase(p_area);
+ dynamicsWorld->removeCollisionObject(p_area->get_bt_ghost());
}
void SpaceBullet::reload_collision_filters(AreaBullet *p_area) {
- if (p_area->is_in_world == false) {
- return;
- }
btGhostObject *ghost_object = p_area->get_bt_ghost();
btBroadphaseProxy *ghost_proxy = ghost_object->getBroadphaseHandle();
@@ -514,47 +468,24 @@ void SpaceBullet::reload_collision_filters(AreaBullet *p_area) {
dynamicsWorld->refreshBroadphaseProxy(ghost_object);
}
-void SpaceBullet::register_collision_object(CollisionObjectBullet *p_object) {
- collision_objects.push_back(p_object);
-}
-
-void SpaceBullet::unregister_collision_object(CollisionObjectBullet *p_object) {
- remove_from_any_queue(p_object);
- collision_objects.erase(p_object);
-}
-
void SpaceBullet::add_rigid_body(RigidBodyBullet *p_body) {
-#ifdef TOOLS_ENABLED
- // This never happen, and there is no way for the user to trigger it.
- // If in future a bug is introduced into this bullet integration and this
- // function is called twice, the crash will notify the developer that will
- // fix it even before do the eventual PR.
- CRASH_COND(p_body->is_in_world);
-#endif
if (p_body->is_static()) {
dynamicsWorld->addCollisionObject(p_body->get_bt_rigid_body(), p_body->get_collision_layer(), p_body->get_collision_mask());
} else {
dynamicsWorld->addRigidBody(p_body->get_bt_rigid_body(), p_body->get_collision_layer(), p_body->get_collision_mask());
p_body->scratch_space_override_modificator();
}
- p_body->is_in_world = true;
}
void SpaceBullet::remove_rigid_body(RigidBodyBullet *p_body) {
- if (p_body->is_in_world) {
- if (p_body->is_static()) {
- dynamicsWorld->removeCollisionObject(p_body->get_bt_rigid_body());
- } else {
- dynamicsWorld->removeRigidBody(p_body->get_bt_rigid_body());
- }
- p_body->is_in_world = false;
+ if (p_body->is_static()) {
+ dynamicsWorld->removeCollisionObject(p_body->get_bt_rigid_body());
+ } else {
+ dynamicsWorld->removeRigidBody(p_body->get_bt_rigid_body());
}
}
void SpaceBullet::reload_collision_filters(RigidBodyBullet *p_body) {
- if (p_body->is_in_world == false) {
- return;
- }
btRigidBody *rigid_body = p_body->get_bt_rigid_body();
btBroadphaseProxy *body_proxy = rigid_body->getBroadphaseProxy();
@@ -734,7 +665,7 @@ void SpaceBullet::check_ghost_overlaps() {
/// 1. Reset all states
for (i = area->overlappingObjects.size() - 1; 0 <= i; --i) {
- AreaBullet::OverlappingObjectData &otherObj = area->overlappingObjects[i];
+ AreaBullet::OverlappingObjectData &otherObj = area->overlappingObjects.write[i];
// This check prevent the overwrite of ENTER state
// if this function is called more times before dispatchCallbacks
if (otherObj.state != AreaBullet::OVERLAP_STATE_ENTER) {
@@ -959,8 +890,8 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f
SceneTree::get_singleton()->get_current_scene()->add_child(motionVec);
SceneTree::get_singleton()->get_current_scene()->add_child(normalLine);
- motionVec->set_as_toplevel(true);
- normalLine->set_as_toplevel(true);
+ motionVec->set_as_top_level(true);
+ normalLine->set_as_top_level(true);
red_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
red_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
diff --git a/modules/bullet/space_bullet.h b/modules/bullet/space_bullet.h
index 897f902fe1..de281064af 100644
--- a/modules/bullet/space_bullet.h
+++ b/modules/bullet/space_bullet.h
@@ -31,8 +31,8 @@
#ifndef SPACE_BULLET_H
#define SPACE_BULLET_H
-#include "core/local_vector.h"
#include "core/variant.h"
+#include "core/vector.h"
#include "godot_result_callbacks.h"
#include "rid_bullet.h"
#include "servers/physics_server_3d.h"
@@ -110,23 +110,16 @@ class SpaceBullet : public RIDBullet {
real_t linear_damp = 0.0;
real_t angular_damp = 0.0;
- LocalVector<CollisionObjectBullet *> queue_pre_flush;
- LocalVector<CollisionObjectBullet *> queue_flush;
- LocalVector<CollisionObjectBullet *> collision_objects;
- LocalVector<AreaBullet *> areas;
+ Vector<AreaBullet *> areas;
- LocalVector<Vector3> contactDebug;
- uint32_t contactDebugCount = 0;
+ Vector<Vector3> contactDebug;
+ int contactDebugCount = 0;
real_t delta_time = 0.;
public:
SpaceBullet();
virtual ~SpaceBullet();
- void add_to_flush_queue(CollisionObjectBullet *p_co);
- void add_to_pre_flush_queue(CollisionObjectBullet *p_co);
- void remove_from_any_queue(CollisionObjectBullet *p_co);
-
void flush_queries();
real_t get_delta_time() { return delta_time; }
void step(real_t p_delta_time);
@@ -157,9 +150,6 @@ public:
void remove_area(AreaBullet *p_area);
void reload_collision_filters(AreaBullet *p_area);
- void register_collision_object(CollisionObjectBullet *p_object);
- void unregister_collision_object(CollisionObjectBullet *p_object);
-
void add_rigid_body(RigidBodyBullet *p_body);
void remove_rigid_body(RigidBodyBullet *p_body);
void reload_collision_filters(RigidBodyBullet *p_body);
@@ -183,7 +173,7 @@ public:
}
_FORCE_INLINE_ void add_debug_contact(const Vector3 &p_contact) {
if (contactDebugCount < contactDebug.size()) {
- contactDebug[contactDebugCount++] = p_contact;
+ contactDebug.write[contactDebugCount++] = p_contact;
}
}
_FORCE_INLINE_ Vector<Vector3> get_debug_contacts() { return contactDebug; }
diff --git a/modules/camera/camera_ios.mm b/modules/camera/camera_ios.mm
index c10b13b2af..e4cb928805 100644
--- a/modules/camera/camera_ios.mm
+++ b/modules/camera/camera_ios.mm
@@ -124,18 +124,12 @@
if (output) {
[self removeOutput:output];
[output setSampleBufferDelegate:nil queue:NULL];
- [output release];
output = nil;
}
[self commitConfiguration];
}
-- (void)dealloc {
- // bye bye
- [super dealloc];
-}
-
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
// This gets called every time our camera has a new image for us to process.
// May need to investigate in a way to throttle this if we get more images then we're rendering frames..
@@ -272,7 +266,6 @@ CameraFeedIOS::CameraFeedIOS() {
void CameraFeedIOS::set_device(AVCaptureDevice *p_device) {
device = p_device;
- [device retain];
// get some info
NSString *device_name = p_device.localizedName;
@@ -286,14 +279,12 @@ void CameraFeedIOS::set_device(AVCaptureDevice *p_device) {
};
CameraFeedIOS::~CameraFeedIOS() {
- if (capture_session != NULL) {
- [capture_session release];
- capture_session = NULL;
+ if (capture_session) {
+ capture_session = nil;
};
- if (device != NULL) {
- [device release];
- device = NULL;
+ if (device) {
+ device = nil;
};
};
@@ -312,8 +303,7 @@ void CameraFeedIOS::deactivate_feed() {
// end camera capture if we have one
if (capture_session) {
[capture_session cleanup];
- [capture_session release];
- capture_session = NULL;
+ capture_session = nil;
};
};
@@ -347,8 +337,6 @@ void CameraFeedIOS::deactivate_feed() {
// remove notifications
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
-
- [super dealloc];
}
@end
@@ -453,5 +441,5 @@ CameraIOS::CameraIOS() {
};
CameraIOS::~CameraIOS() {
- [device_notifications release];
+ device_notifications = nil;
};
diff --git a/modules/denoise/lightmap_denoiser.h b/modules/denoise/lightmap_denoiser.h
index d01bbd10a5..74a9d8af86 100644
--- a/modules/denoise/lightmap_denoiser.h
+++ b/modules/denoise/lightmap_denoiser.h
@@ -31,7 +31,7 @@
#ifndef LIGHTMAP_DENOISER_H
#define LIGHTMAP_DENOISER_H
-#include "core/object.h"
+#include "core/class_db.h"
#include "scene/3d/lightmapper.h"
struct OIDNDeviceImpl;
diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp
index fdd755845f..f0f095ddf5 100644
--- a/modules/gdnative/gdnative_library_editor_plugin.cpp
+++ b/modules/gdnative/gdnative_library_editor_plugin.cpp
@@ -318,6 +318,7 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() {
platform_ios.name = "iOS";
platform_ios.entries.push_back("armv7");
platform_ios.entries.push_back("arm64");
+ platform_ios.entries.push_back("x86_64");
// iOS can use both Static and Dynamic libraries.
// Frameworks is actually a folder with files.
platform_ios.library_extension = "*.framework; Framework, *.xcframework; Binary Framework, *.a; Static Library, *.dylib; Dynamic Library";
diff --git a/modules/gdnative/tests/test_string.h b/modules/gdnative/tests/test_string.h
new file mode 100644
index 0000000000..aeb855a1c4
--- /dev/null
+++ b/modules/gdnative/tests/test_string.h
@@ -0,0 +1,1980 @@
+/*************************************************************************/
+/* test_string.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_GDNATIVE_STRING_H
+#define TEST_GDNATIVE_STRING_H
+
+namespace TestGDNativeString {
+
+#include "gdnative/string.h"
+
+#include "tests/test_macros.h"
+
+int u32scmp(const char32_t *l, const char32_t *r) {
+ for (; *l == *r && *l && *r; l++, r++)
+ ;
+ return *l - *r;
+}
+
+TEST_CASE("[GDNative String] Construct from Latin-1 char string") {
+ godot_string s;
+
+ godot_string_new_with_latin1_chars(&s, "Hello");
+ CHECK(u32scmp(godot_string_get_data(&s), U"Hello") == 0);
+ godot_string_destroy(&s);
+
+ godot_string_new_with_latin1_chars_and_len(&s, "Hello", 3);
+ CHECK(u32scmp(godot_string_get_data(&s), U"Hel") == 0);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Construct from wchar_t string") {
+ godot_string s;
+
+ godot_string_new_with_wide_chars(&s, L"Give me");
+ CHECK(u32scmp(godot_string_get_data(&s), U"Give me") == 0);
+ godot_string_destroy(&s);
+
+ godot_string_new_with_wide_chars_and_len(&s, L"Give me", 3);
+ CHECK(u32scmp(godot_string_get_data(&s), U"Giv") == 0);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Construct from UTF-8 char string") {
+ static const char32_t u32str[] = { 0x0045, 0x0020, 0x304A, 0x360F, 0x3088, 0x3046, 0x1F3A4, 0 };
+ static const char32_t u32str_short[] = { 0x0045, 0x0020, 0x304A, 0 };
+ static const uint8_t u8str[] = { 0x45, 0x20, 0xE3, 0x81, 0x8A, 0xE3, 0x98, 0x8F, 0xE3, 0x82, 0x88, 0xE3, 0x81, 0x86, 0xF0, 0x9F, 0x8E, 0xA4, 0 };
+
+ godot_string s;
+
+ godot_string_new_with_utf8_chars(&s, (const char *)u8str);
+ CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0);
+ godot_string_destroy(&s);
+
+ godot_string_new_with_utf8_chars_and_len(&s, (const char *)u8str, 5);
+ CHECK(u32scmp(godot_string_get_data(&s), u32str_short) == 0);
+ godot_string_destroy(&s);
+
+ godot_string_new_with_utf32_chars(&s, u32str);
+ godot_char_string cs = godot_string_utf8(&s);
+ godot_string_parse_utf8(&s, godot_char_string_get_data(&cs));
+ CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0);
+ godot_string_destroy(&s);
+ godot_char_string_destroy(&cs);
+
+ godot_string_new_with_utf32_chars(&s, u32str);
+ cs = godot_string_utf8(&s);
+ godot_string_parse_utf8_with_len(&s, godot_char_string_get_data(&cs), godot_char_string_length(&cs));
+ CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0);
+ godot_string_destroy(&s);
+ godot_char_string_destroy(&cs);
+}
+
+TEST_CASE("[GDNative String] Construct from UTF-8 string with BOM") {
+ static const char32_t u32str[] = { 0x0045, 0x0020, 0x304A, 0x360F, 0x3088, 0x3046, 0x1F3A4, 0 };
+ static const char32_t u32str_short[] = { 0x0045, 0x0020, 0x304A, 0 };
+ static const uint8_t u8str[] = { 0xEF, 0xBB, 0xBF, 0x45, 0x20, 0xE3, 0x81, 0x8A, 0xE3, 0x98, 0x8F, 0xE3, 0x82, 0x88, 0xE3, 0x81, 0x86, 0xF0, 0x9F, 0x8E, 0xA4, 0 };
+
+ godot_string s;
+
+ godot_string_new_with_utf8_chars(&s, (const char *)u8str);
+ CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0);
+ godot_string_destroy(&s);
+
+ godot_string_new_with_utf8_chars_and_len(&s, (const char *)u8str, 8);
+ CHECK(u32scmp(godot_string_get_data(&s), u32str_short) == 0);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Construct from UTF-16 string") {
+ static const char32_t u32str[] = { 0x0045, 0x0020, 0x1F3A4, 0x360F, 0x3088, 0x3046, 0x1F3A4, 0 };
+ static const char32_t u32str_short[] = { 0x0045, 0x0020, 0x1F3A4, 0 };
+ static const char16_t u16str[] = { 0x0045, 0x0020, 0xD83C, 0xDFA4, 0x360F, 0x3088, 0x3046, 0xD83C, 0xDFA4, 0 };
+
+ godot_string s;
+
+ godot_string_new_with_utf16_chars(&s, u16str);
+ CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0);
+ godot_string_destroy(&s);
+
+ godot_string_new_with_utf16_chars_and_len(&s, u16str, 4);
+ CHECK(u32scmp(godot_string_get_data(&s), u32str_short) == 0);
+ godot_string_destroy(&s);
+
+ godot_string_new_with_utf32_chars(&s, u32str);
+ godot_char16_string cs = godot_string_utf16(&s);
+ godot_string_parse_utf16(&s, godot_char16_string_get_data(&cs));
+ CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0);
+ godot_string_destroy(&s);
+ godot_char16_string_destroy(&cs);
+
+ godot_string_new_with_utf32_chars(&s, u32str);
+ cs = godot_string_utf16(&s);
+ godot_string_parse_utf16_with_len(&s, godot_char16_string_get_data(&cs), godot_char16_string_length(&cs));
+ CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0);
+ godot_string_destroy(&s);
+ godot_char16_string_destroy(&cs);
+}
+
+TEST_CASE("[GDNative String] Construct from UTF-16 string with BOM ") {
+ static const char32_t u32str[] = { 0x0045, 0x0020, 0x1F3A4, 0x360F, 0x3088, 0x3046, 0x1F3A4, 0 };
+ static const char32_t u32str_short[] = { 0x0045, 0x0020, 0x1F3A4, 0 };
+ static const char16_t u16str[] = { 0xFEFF, 0x0045, 0x0020, 0xD83C, 0xDFA4, 0x360F, 0x3088, 0x3046, 0xD83C, 0xDFA4, 0 };
+ static const char16_t u16str_swap[] = { 0xFFFE, 0x4500, 0x2000, 0x3CD8, 0xA4DF, 0x0F36, 0x8830, 0x4630, 0x3CD8, 0xA4DF, 0 };
+
+ godot_string s;
+
+ godot_string_new_with_utf16_chars(&s, u16str);
+ CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0);
+ godot_string_destroy(&s);
+
+ godot_string_new_with_utf16_chars(&s, u16str_swap);
+ CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0);
+ godot_string_destroy(&s);
+
+ godot_string_new_with_utf16_chars_and_len(&s, u16str, 5);
+ CHECK(u32scmp(godot_string_get_data(&s), u32str_short) == 0);
+ godot_string_destroy(&s);
+
+ godot_string_new_with_utf16_chars_and_len(&s, u16str_swap, 5);
+ CHECK(u32scmp(godot_string_get_data(&s), u32str_short) == 0);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Construct string copy") {
+ godot_string s, t;
+
+ godot_string_new_with_latin1_chars(&s, "Hello");
+ godot_string_new_copy(&t, &s);
+ CHECK(u32scmp(godot_string_get_data(&t), U"Hello") == 0);
+ godot_string_destroy(&t);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Construct empty string") {
+ godot_string s;
+
+ godot_string_new(&s);
+ CHECK(u32scmp(godot_string_get_data(&s), U"") == 0);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] ASCII/Latin-1") {
+ godot_string s;
+ godot_string_new_with_utf32_chars(&s, U"Primero Leche");
+
+ godot_char_string cs = godot_string_ascii(&s);
+ CHECK(strcmp(godot_char_string_get_data(&cs), "Primero Leche") == 0);
+ godot_char_string_destroy(&cs);
+
+ cs = godot_string_latin1(&s);
+ CHECK(strcmp(godot_char_string_get_data(&cs), "Primero Leche") == 0);
+ godot_char_string_destroy(&cs);
+
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Comparisons (equal)") {
+ godot_string s, t;
+
+ godot_string_new_with_latin1_chars(&s, "Test Compare");
+ godot_string_new_with_latin1_chars(&t, "Test Compare");
+ CHECK(godot_string_operator_equal(&s, &t));
+ godot_string_destroy(&s);
+ godot_string_destroy(&t);
+}
+
+TEST_CASE("[GDNative String] Comparisons (operator <)") {
+ godot_string s, t;
+
+ godot_string_new_with_latin1_chars(&s, "Bees");
+
+ godot_string_new_with_latin1_chars(&t, "Elephant");
+ CHECK(godot_string_operator_less(&s, &t));
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "Amber");
+ CHECK(!godot_string_operator_less(&s, &t));
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "Beatrix");
+ CHECK(!godot_string_operator_less(&s, &t));
+ godot_string_destroy(&t);
+
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Concatenation (operator +)") {
+ godot_string s, t, x;
+
+ godot_string_new_with_latin1_chars(&s, "Hel");
+ godot_string_new_with_latin1_chars(&t, "lo");
+ x = godot_string_operator_plus(&s, &t);
+ CHECK(u32scmp(godot_string_get_data(&x), U"Hello") == 0);
+ godot_string_destroy(&x);
+ godot_string_destroy(&s);
+ godot_string_destroy(&t);
+}
+
+TEST_CASE("[GDNative String] Testing size and length of string") {
+ godot_string s;
+
+ godot_string_new_with_latin1_chars(&s, "Mellon");
+ CHECK(godot_string_length(&s) == 6);
+ godot_string_destroy(&s);
+
+ godot_string_new_with_latin1_chars(&s, "Mellon1");
+ CHECK(godot_string_length(&s) == 7);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Testing for empty string") {
+ godot_string s;
+
+ godot_string_new_with_latin1_chars(&s, "Mellon");
+ CHECK(!godot_string_empty(&s));
+ godot_string_destroy(&s);
+
+ godot_string_new_with_latin1_chars(&s, "");
+ CHECK(godot_string_empty(&s));
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Test chr") {
+ godot_string s;
+
+ s = godot_string_chr('H');
+ CHECK(u32scmp(godot_string_get_data(&s), U"H") == 0);
+ godot_string_destroy(&s);
+
+ s = godot_string_chr(0x3012);
+ CHECK(godot_string_operator_index_const(&s, 0) == 0x3012);
+ godot_string_destroy(&s);
+
+ ERR_PRINT_OFF
+ s = godot_string_chr(0xd812);
+ CHECK(godot_string_operator_index_const(&s, 0) == 0xfffd); // Unpaired UTF-16 surrogate
+ godot_string_destroy(&s);
+
+ s = godot_string_chr(0x20d812);
+ CHECK(godot_string_operator_index_const(&s, 0) == 0xfffd); // Outside UTF-32 range
+ godot_string_destroy(&s);
+ ERR_PRINT_ON
+}
+
+TEST_CASE("[GDNative String] Operator []") {
+ godot_string s;
+
+ godot_string_new_with_latin1_chars(&s, "Hello");
+ CHECK(*godot_string_operator_index(&s, 1) == 'e');
+ CHECK(godot_string_operator_index_const(&s, 0) == 'H');
+ CHECK(godot_string_ord_at(&s, 0) == 'H');
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Case function test") {
+ godot_string s, t;
+
+ godot_string_new_with_latin1_chars(&s, "MoMoNgA");
+
+ t = godot_string_to_upper(&s);
+ CHECK(u32scmp(godot_string_get_data(&t), U"MOMONGA") == 0);
+ godot_string_destroy(&t);
+
+ t = godot_string_to_lower(&s);
+ CHECK(u32scmp(godot_string_get_data(&t), U"momonga") == 0);
+ godot_string_destroy(&t);
+
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Case compare function test") {
+ godot_string s, t;
+
+ godot_string_new_with_latin1_chars(&s, "MoMoNgA");
+ godot_string_new_with_latin1_chars(&t, "momonga");
+
+ CHECK(godot_string_casecmp_to(&s, &t) != 0);
+ CHECK(godot_string_nocasecmp_to(&s, &t) == 0);
+
+ godot_string_destroy(&s);
+ godot_string_destroy(&t);
+}
+
+TEST_CASE("[GDNative String] Natural compare function test") {
+ godot_string s, t;
+
+ godot_string_new_with_latin1_chars(&s, "img2.png");
+ godot_string_new_with_latin1_chars(&t, "img10.png");
+
+ CHECK(godot_string_nocasecmp_to(&s, &t) > 0);
+ CHECK(godot_string_naturalnocasecmp_to(&s, &t) < 0);
+
+ godot_string_destroy(&s);
+ godot_string_destroy(&t);
+}
+
+TEST_CASE("[GDNative String] hex_encode_buffer") {
+ static const uint8_t u8str[] = { 0x45, 0xE3, 0x81, 0x8A, 0x8F, 0xE3 };
+ godot_string s = godot_string_hex_encode_buffer(u8str, 6);
+ CHECK(u32scmp(godot_string_get_data(&s), U"45e3818a8fe3") == 0);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Substr") {
+ godot_string s, t;
+ godot_string_new_with_latin1_chars(&s, "Killer Baby");
+ t = godot_string_substr(&s, 3, 4);
+ CHECK(u32scmp(godot_string_get_data(&t), U"ler ") == 0);
+ godot_string_destroy(&t);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Find") {
+ godot_string s, t;
+ godot_string_new_with_latin1_chars(&s, "Pretty Woman Woman");
+
+ godot_string_new_with_latin1_chars(&t, "Revenge of the Monster Truck");
+ CHECK(godot_string_find(&s, &t) == -1);
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "tty");
+ CHECK(godot_string_find(&s, &t) == 3);
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "Wo");
+ CHECK(godot_string_find_from(&s, &t, 9) == 13);
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "man");
+ CHECK(godot_string_rfind(&s, &t) == 15);
+ godot_string_destroy(&t);
+
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Find no case") {
+ godot_string s, t;
+ godot_string_new_with_latin1_chars(&s, "Pretty Whale Whale");
+
+ godot_string_new_with_latin1_chars(&t, "WHA");
+ CHECK(godot_string_findn(&s, &t) == 7);
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "WHA");
+ CHECK(godot_string_findn_from(&s, &t, 9) == 13);
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "WHA");
+ CHECK(godot_string_rfindn(&s, &t) == 13);
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "Revenge of the Monster SawFish");
+ CHECK(godot_string_findn(&s, &t) == -1);
+ godot_string_destroy(&t);
+
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Find MK") {
+ godot_packed_string_array keys;
+ godot_packed_string_array_new(&keys);
+
+#define PUSH_KEY(x) \
+ { \
+ godot_string t; \
+ godot_string_new_with_latin1_chars(&t, x); \
+ godot_packed_string_array_push_back(&keys, &t); \
+ godot_string_destroy(&t); \
+ }
+
+ PUSH_KEY("sty")
+ PUSH_KEY("tty")
+ PUSH_KEY("man")
+
+ godot_string s;
+ godot_string_new_with_latin1_chars(&s, "Pretty Woman");
+ godot_int key = 0;
+
+ CHECK(godot_string_findmk(&s, &keys) == 3);
+ CHECK(godot_string_findmk_from_in_place(&s, &keys, 0, &key) == 3);
+ CHECK(key == 1);
+
+ CHECK(godot_string_findmk_from(&s, &keys, 5) == 9);
+ CHECK(godot_string_findmk_from_in_place(&s, &keys, 5, &key) == 9);
+ CHECK(key == 2);
+
+ godot_string_destroy(&s);
+ godot_packed_string_array_destroy(&keys);
+
+#undef PUSH_KEY
+}
+
+TEST_CASE("[GDNative String] Find and replace") {
+ godot_string s, c, w;
+ godot_string_new_with_latin1_chars(&s, "Happy Birthday, Anna!");
+ godot_string_new_with_latin1_chars(&c, "Birthday");
+ godot_string_new_with_latin1_chars(&w, "Halloween");
+ godot_string t = godot_string_replace(&s, &c, &w);
+ CHECK(u32scmp(godot_string_get_data(&t), U"Happy Halloween, Anna!") == 0);
+ godot_string_destroy(&s);
+ godot_string_destroy(&c);
+ godot_string_destroy(&w);
+
+ godot_string_new_with_latin1_chars(&c, "H");
+ godot_string_new_with_latin1_chars(&w, "W");
+ s = godot_string_replace_first(&t, &c, &w);
+ godot_string_destroy(&t);
+ godot_string_destroy(&c);
+ godot_string_destroy(&w);
+
+ CHECK(u32scmp(godot_string_get_data(&s), U"Wappy Halloween, Anna!") == 0);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Insertion") {
+ godot_string s, t, r, u;
+ godot_string_new_with_latin1_chars(&s, "Who is Frederic?");
+ godot_string_new_with_latin1_chars(&t, "?");
+ godot_string_new_with_latin1_chars(&r, " Chopin");
+
+ u = godot_string_insert(&s, godot_string_find(&s, &t), &r);
+ CHECK(u32scmp(godot_string_get_data(&u), U"Who is Frederic Chopin?") == 0);
+
+ godot_string_destroy(&s);
+ godot_string_destroy(&t);
+ godot_string_destroy(&r);
+ godot_string_destroy(&u);
+}
+
+TEST_CASE("[GDNative String] Number to string") {
+ godot_string s;
+ s = godot_string_num(3.141593);
+ CHECK(u32scmp(godot_string_get_data(&s), U"3.141593") == 0);
+ godot_string_destroy(&s);
+
+ s = godot_string_num_with_decimals(3.141593, 3);
+ CHECK(u32scmp(godot_string_get_data(&s), U"3.142") == 0);
+ godot_string_destroy(&s);
+
+ s = godot_string_num_real(3.141593);
+ CHECK(u32scmp(godot_string_get_data(&s), U"3.141593") == 0);
+ godot_string_destroy(&s);
+
+ s = godot_string_num_scientific(30000000);
+ CHECK(u32scmp(godot_string_get_data(&s), U"3e+07") == 0);
+ godot_string_destroy(&s);
+
+ s = godot_string_num_int64(3141593, 10);
+ CHECK(u32scmp(godot_string_get_data(&s), U"3141593") == 0);
+ godot_string_destroy(&s);
+
+ s = godot_string_num_int64(0xA141593, 16);
+ CHECK(u32scmp(godot_string_get_data(&s), U"a141593") == 0);
+ godot_string_destroy(&s);
+
+ s = godot_string_num_int64_capitalized(0xA141593, 16, true);
+ CHECK(u32scmp(godot_string_get_data(&s), U"A141593") == 0);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] String to integer") {
+ static const wchar_t *wnums[4] = { L"1237461283", L"- 22", L"0", L" - 1123412" };
+ static const char *nums[4] = { "1237461283", "- 22", "0", " - 1123412" };
+ static const int num[4] = { 1237461283, -22, 0, -1123412 };
+
+ for (int i = 0; i < 4; i++) {
+ godot_string s;
+ godot_string_new_with_latin1_chars(&s, nums[i]);
+ CHECK(godot_string_to_int(&s) == num[i]);
+ godot_string_destroy(&s);
+
+ CHECK(godot_string_char_to_int(nums[i]) == num[i]);
+ CHECK(godot_string_wchar_to_int(wnums[i]) == num[i]);
+ }
+}
+
+TEST_CASE("[GDNative String] Hex to integer") {
+ static const char *nums[4] = { "0xFFAE", "22", "0", "AADDAD" };
+ static const int64_t num[4] = { 0xFFAE, 0x22, 0, 0xAADDAD };
+ static const bool wo_prefix[4] = { false, true, true, true };
+ static const bool w_prefix[4] = { true, false, true, false };
+
+ for (int i = 0; i < 4; i++) {
+ godot_string s;
+ godot_string_new_with_latin1_chars(&s, nums[i]);
+ CHECK((godot_string_hex_to_int_with_prefix(&s) == num[i]) == w_prefix[i]);
+ CHECK((godot_string_hex_to_int(&s) == num[i]) == wo_prefix[i]);
+ godot_string_destroy(&s);
+ }
+}
+
+TEST_CASE("[GDNative String] String to float") {
+ static const wchar_t *wnums[4] = { L"-12348298412.2", L"0.05", L"2.0002", L" -0.0001" };
+ static const char *nums[4] = { "-12348298412.2", "0.05", "2.0002", " -0.0001" };
+ static const double num[4] = { -12348298412.2, 0.05, 2.0002, -0.0001 };
+
+ for (int i = 0; i < 4; i++) {
+ godot_string s;
+ godot_string_new_with_latin1_chars(&s, nums[i]);
+ CHECK(!(ABS(godot_string_to_float(&s) - num[i]) > 0.00001));
+ godot_string_destroy(&s);
+
+ CHECK(!(ABS(godot_string_char_to_float(nums[i]) - num[i]) > 0.00001));
+ CHECK(!(ABS(godot_string_wchar_to_float(wnums[i], nullptr) - num[i]) > 0.00001));
+ }
+}
+
+TEST_CASE("[GDNative String] CamelCase to underscore") {
+ godot_string s, t;
+ godot_string_new_with_latin1_chars(&s, "TestTestStringGD");
+
+ t = godot_string_camelcase_to_underscore(&s);
+ CHECK(u32scmp(godot_string_get_data(&t), U"Test_Test_String_GD") == 0);
+ godot_string_destroy(&t);
+
+ t = godot_string_camelcase_to_underscore_lowercased(&s);
+ CHECK(u32scmp(godot_string_get_data(&t), U"test_test_string_gd") == 0);
+ godot_string_destroy(&t);
+
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Slicing") {
+ godot_string s, c;
+ godot_string_new_with_latin1_chars(&s, "Mars,Jupiter,Saturn,Uranus");
+ godot_string_new_with_latin1_chars(&c, ",");
+
+ const char32_t *slices[4] = { U"Mars", U"Jupiter", U"Saturn", U"Uranus" };
+ for (int i = 0; i < godot_string_get_slice_count(&s, &c); i++) {
+ godot_string t;
+ t = godot_string_get_slice(&s, &c, i);
+ CHECK(u32scmp(godot_string_get_data(&t), slices[i]) == 0);
+ godot_string_destroy(&t);
+
+ t = godot_string_get_slicec(&s, U',', i);
+ CHECK(u32scmp(godot_string_get_data(&t), slices[i]) == 0);
+ godot_string_destroy(&t);
+ }
+
+ godot_string_destroy(&c);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Splitting") {
+ godot_string s, c;
+ godot_string_new_with_latin1_chars(&s, "Mars,Jupiter,Saturn,Uranus");
+ godot_string_new_with_latin1_chars(&c, ",");
+
+ godot_packed_string_array l;
+
+ const char32_t *slices_l[3] = { U"Mars", U"Jupiter", U"Saturn,Uranus" };
+ const char32_t *slices_r[3] = { U"Mars,Jupiter", U"Saturn", U"Uranus" };
+
+ l = godot_string_split_with_maxsplit(&s, &c, true, 2);
+ CHECK(godot_packed_string_array_size(&l) == 3);
+ for (int i = 0; i < godot_packed_string_array_size(&l); i++) {
+ godot_string t = godot_packed_string_array_get(&l, i);
+ CHECK(u32scmp(godot_string_get_data(&t), slices_l[i]) == 0);
+ godot_string_destroy(&t);
+ }
+ godot_packed_string_array_destroy(&l);
+
+ l = godot_string_rsplit_with_maxsplit(&s, &c, true, 2);
+ CHECK(godot_packed_string_array_size(&l) == 3);
+ for (int i = 0; i < godot_packed_string_array_size(&l); i++) {
+ godot_string t = godot_packed_string_array_get(&l, i);
+ CHECK(u32scmp(godot_string_get_data(&t), slices_r[i]) == 0);
+ godot_string_destroy(&t);
+ }
+ godot_packed_string_array_destroy(&l);
+ godot_string_destroy(&s);
+
+ godot_string_new_with_latin1_chars(&s, "Mars Jupiter Saturn Uranus");
+ const char32_t *slices_s[4] = { U"Mars", U"Jupiter", U"Saturn", U"Uranus" };
+ l = godot_string_split_spaces(&s);
+ for (int i = 0; i < godot_packed_string_array_size(&l); i++) {
+ godot_string t = godot_packed_string_array_get(&l, i);
+ CHECK(u32scmp(godot_string_get_data(&t), slices_s[i]) == 0);
+ godot_string_destroy(&t);
+ }
+ godot_packed_string_array_destroy(&l);
+ godot_string_destroy(&s);
+
+ godot_string c1, c2;
+ godot_string_new_with_latin1_chars(&c1, ";");
+ godot_string_new_with_latin1_chars(&c2, " ");
+
+ godot_string_new_with_latin1_chars(&s, "1.2;2.3 4.5");
+ const double slices_d[3] = { 1.2, 2.3, 4.5 };
+
+ godot_packed_float32_array lf = godot_string_split_floats_allow_empty(&s, &c1);
+ CHECK(godot_packed_float32_array_size(&lf) == 2);
+ for (int i = 0; i < godot_packed_float32_array_size(&lf); i++) {
+ CHECK(ABS(godot_packed_float32_array_get(&lf, i) - slices_d[i]) <= 0.00001);
+ }
+ godot_packed_float32_array_destroy(&lf);
+
+ godot_packed_string_array keys;
+ godot_packed_string_array_new(&keys);
+ godot_packed_string_array_push_back(&keys, &c1);
+ godot_packed_string_array_push_back(&keys, &c2);
+
+ lf = godot_string_split_floats_mk_allow_empty(&s, &keys);
+ CHECK(godot_packed_float32_array_size(&lf) == 3);
+ for (int i = 0; i < godot_packed_float32_array_size(&lf); i++) {
+ CHECK(ABS(godot_packed_float32_array_get(&lf, i) - slices_d[i]) <= 0.00001);
+ }
+ godot_packed_float32_array_destroy(&lf);
+
+ godot_string_destroy(&s);
+ godot_string_new_with_latin1_chars(&s, "1;2 4");
+ const int slices_i[3] = { 1, 2, 4 };
+
+ godot_packed_int32_array li = godot_string_split_ints_allow_empty(&s, &c1);
+ CHECK(godot_packed_int32_array_size(&li) == 2);
+ for (int i = 0; i < godot_packed_int32_array_size(&li); i++) {
+ CHECK(godot_packed_int32_array_get(&li, i) == slices_i[i]);
+ }
+ godot_packed_int32_array_destroy(&li);
+
+ li = godot_string_split_ints_mk_allow_empty(&s, &keys);
+ CHECK(godot_packed_int32_array_size(&li) == 3);
+ for (int i = 0; i < godot_packed_int32_array_size(&li); i++) {
+ CHECK(godot_packed_int32_array_get(&li, i) == slices_i[i]);
+ }
+ godot_packed_int32_array_destroy(&li);
+
+ godot_string_destroy(&s);
+ godot_string_destroy(&c);
+ godot_string_destroy(&c1);
+ godot_string_destroy(&c2);
+ godot_packed_string_array_destroy(&keys);
+}
+
+TEST_CASE("[GDNative String] Erasing") {
+ godot_string s, t;
+ godot_string_new_with_latin1_chars(&s, "Josephine is such a cute girl!");
+ godot_string_new_with_latin1_chars(&t, "cute ");
+
+ godot_string_erase(&s, godot_string_find(&s, &t), godot_string_length(&t));
+
+ CHECK(u32scmp(godot_string_get_data(&s), U"Josephine is such a girl!") == 0);
+
+ godot_string_destroy(&s);
+ godot_string_destroy(&t);
+}
+
+struct test_27_data {
+ char const *data;
+ char const *part;
+ bool expected;
+};
+
+TEST_CASE("[GDNative String] Begins with") {
+ test_27_data tc[] = {
+ { "res://foobar", "res://", true },
+ { "res", "res://", false },
+ { "abc", "abc", true }
+ };
+ size_t count = sizeof(tc) / sizeof(tc[0]);
+ bool state = true;
+ for (size_t i = 0; state && i < count; ++i) {
+ godot_string s;
+ godot_string_new_with_latin1_chars(&s, tc[i].data);
+
+ state = godot_string_begins_with_char_array(&s, tc[i].part) == tc[i].expected;
+ if (state) {
+ godot_string t;
+ godot_string_new_with_latin1_chars(&t, tc[i].part);
+ state = godot_string_begins_with(&s, &t) == tc[i].expected;
+ godot_string_destroy(&t);
+ }
+ godot_string_destroy(&s);
+
+ CHECK(state);
+ if (!state) {
+ break;
+ }
+ };
+ CHECK(state);
+}
+
+TEST_CASE("[GDNative String] Ends with") {
+ test_27_data tc[] = {
+ { "res://foobar", "foobar", true },
+ { "res", "res://", false },
+ { "abc", "abc", true }
+ };
+ size_t count = sizeof(tc) / sizeof(tc[0]);
+ bool state = true;
+ for (size_t i = 0; state && i < count; ++i) {
+ godot_string s;
+ godot_string_new_with_latin1_chars(&s, tc[i].data);
+
+ state = godot_string_ends_with_char_array(&s, tc[i].part) == tc[i].expected;
+ if (state) {
+ godot_string t;
+ godot_string_new_with_latin1_chars(&t, tc[i].part);
+ state = godot_string_ends_with(&s, &t) == tc[i].expected;
+ godot_string_destroy(&t);
+ }
+ godot_string_destroy(&s);
+
+ CHECK(state);
+ if (!state) {
+ break;
+ }
+ };
+ CHECK(state);
+}
+
+TEST_CASE("[GDNative String] format") {
+ godot_string value_format, t;
+ godot_string_new_with_latin1_chars(&value_format, "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\"");
+
+ godot_variant key_v, val_v;
+ godot_dictionary value_dictionary;
+ godot_dictionary_new(&value_dictionary);
+
+ godot_string_new_with_latin1_chars(&t, "red");
+ godot_variant_new_string(&key_v, &t);
+ godot_string_destroy(&t);
+ godot_variant_new_int(&val_v, 10);
+ godot_dictionary_set(&value_dictionary, &key_v, &val_v);
+ godot_variant_destroy(&key_v);
+ godot_variant_destroy(&val_v);
+
+ godot_string_new_with_latin1_chars(&t, "green");
+ godot_variant_new_string(&key_v, &t);
+ godot_string_destroy(&t);
+ godot_variant_new_int(&val_v, 20);
+ godot_dictionary_set(&value_dictionary, &key_v, &val_v);
+ godot_variant_destroy(&key_v);
+ godot_variant_destroy(&val_v);
+
+ godot_string_new_with_latin1_chars(&t, "blue");
+ godot_variant_new_string(&key_v, &t);
+ godot_string_destroy(&t);
+ godot_string_new_with_latin1_chars(&t, "bla");
+ godot_variant_new_string(&val_v, &t);
+ godot_string_destroy(&t);
+ godot_dictionary_set(&value_dictionary, &key_v, &val_v);
+ godot_variant_destroy(&key_v);
+ godot_variant_destroy(&val_v);
+
+ godot_string_new_with_latin1_chars(&t, "alpha");
+ godot_variant_new_string(&key_v, &t);
+ godot_string_destroy(&t);
+ godot_variant_new_real(&val_v, 0.4);
+ godot_dictionary_set(&value_dictionary, &key_v, &val_v);
+ godot_variant_destroy(&key_v);
+ godot_variant_destroy(&val_v);
+
+ godot_variant dict_v;
+ godot_variant_new_dictionary(&dict_v, &value_dictionary);
+ godot_string s = godot_string_format_with_custom_placeholder(&value_format, &dict_v, "$_");
+
+ CHECK(u32scmp(godot_string_get_data(&s), U"red=\"10\" green=\"20\" blue=\"bla\" alpha=\"0.4\"") == 0);
+
+ godot_dictionary_destroy(&value_dictionary);
+ godot_string_destroy(&s);
+ godot_variant_destroy(&dict_v);
+ godot_string_destroy(&value_format);
+}
+
+TEST_CASE("[GDNative String] sprintf") {
+ //godot_string GDAPI (const godot_string *p_self, const godot_array *p_values, godot_bool *p_error);
+ godot_string format, output;
+ godot_array args;
+ bool error;
+
+#define ARRAY_PUSH_STRING(x) \
+ { \
+ godot_variant v; \
+ godot_string t; \
+ godot_string_new_with_latin1_chars(&t, x); \
+ godot_variant_new_string(&v, &t); \
+ godot_string_destroy(&t); \
+ godot_array_push_back(&args, &v); \
+ godot_variant_destroy(&v); \
+ }
+
+#define ARRAY_PUSH_INT(x) \
+ { \
+ godot_variant v; \
+ godot_variant_new_int(&v, x); \
+ godot_array_push_back(&args, &v); \
+ godot_variant_destroy(&v); \
+ }
+
+#define ARRAY_PUSH_REAL(x) \
+ { \
+ godot_variant v; \
+ godot_variant_new_real(&v, x); \
+ godot_array_push_back(&args, &v); \
+ godot_variant_destroy(&v); \
+ }
+
+ godot_array_new(&args);
+
+ // %%
+ godot_string_new_with_latin1_chars(&format, "fish %% frog");
+ godot_array_clear(&args);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish % frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+ //////// INTS
+
+ // Int
+ godot_string_new_with_latin1_chars(&format, "fish %d frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(5);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 5 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Int left padded with zeroes.
+ godot_string_new_with_latin1_chars(&format, "fish %05d frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(5);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 00005 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Int left padded with spaces.
+ godot_string_new_with_latin1_chars(&format, "fish %5d frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(5);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 5 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Int right padded with spaces.
+ godot_string_new_with_latin1_chars(&format, "fish %-5d frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(5);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 5 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Int with sign (positive).
+ godot_string_new_with_latin1_chars(&format, "fish %+d frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(5);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish +5 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Negative int.
+ godot_string_new_with_latin1_chars(&format, "fish %d frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(-5);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish -5 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Hex (lower)
+ godot_string_new_with_latin1_chars(&format, "fish %x frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(45);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 2d frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Hex (upper)
+ godot_string_new_with_latin1_chars(&format, "fish %X frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(45);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 2D frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Octal
+ godot_string_new_with_latin1_chars(&format, "fish %o frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 143 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+ ////// REALS
+
+ // Real
+ godot_string_new_with_latin1_chars(&format, "fish %f frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_REAL(99.99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 99.990000 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Real left-padded
+ godot_string_new_with_latin1_chars(&format, "fish %11f frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_REAL(99.99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 99.990000 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Real right-padded
+ godot_string_new_with_latin1_chars(&format, "fish %-11f frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_REAL(99.99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 99.990000 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Real given int.
+ godot_string_new_with_latin1_chars(&format, "fish %f frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_REAL(99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 99.000000 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Real with sign (positive).
+ godot_string_new_with_latin1_chars(&format, "fish %+f frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_REAL(99.99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish +99.990000 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Real with 1 decimals.
+ godot_string_new_with_latin1_chars(&format, "fish %.1f frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_REAL(99.99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 100.0 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Real with 12 decimals.
+ godot_string_new_with_latin1_chars(&format, "fish %.12f frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_REAL(99.99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 99.990000000000 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Real with no decimals.
+ godot_string_new_with_latin1_chars(&format, "fish %.f frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_REAL(99.99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 100 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ /////// Strings.
+
+ // String
+ godot_string_new_with_latin1_chars(&format, "fish %s frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_STRING("cheese");
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish cheese frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // String left-padded
+ godot_string_new_with_latin1_chars(&format, "fish %10s frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_STRING("cheese");
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish cheese frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // String right-padded
+ godot_string_new_with_latin1_chars(&format, "fish %-10s frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_STRING("cheese");
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish cheese frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ ///// Characters
+
+ // Character as string.
+ godot_string_new_with_latin1_chars(&format, "fish %c frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_STRING("A");
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish A frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Character as int.
+ godot_string_new_with_latin1_chars(&format, "fish %c frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(65);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish A frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ ///// Dynamic width
+
+ // String dynamic width
+ godot_string_new_with_latin1_chars(&format, "fish %*s frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(10);
+ ARRAY_PUSH_STRING("cheese");
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ REQUIRE(u32scmp(godot_string_get_data(&output), U"fish cheese frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Int dynamic width
+ godot_string_new_with_latin1_chars(&format, "fish %*d frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(10);
+ ARRAY_PUSH_INT(99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ REQUIRE(u32scmp(godot_string_get_data(&output), U"fish 99 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Float dynamic width
+ godot_string_new_with_latin1_chars(&format, "fish %*.*f frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_INT(10);
+ ARRAY_PUSH_INT(3);
+ ARRAY_PUSH_REAL(99.99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error == false);
+ CHECK(u32scmp(godot_string_get_data(&output), U"fish 99.990 frog") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ ///// Errors
+
+ // More formats than arguments.
+ godot_string_new_with_latin1_chars(&format, "fish %s %s frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_STRING("cheese");
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error);
+ CHECK(u32scmp(godot_string_get_data(&output), U"not enough arguments for format string") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // More arguments than formats.
+ godot_string_new_with_latin1_chars(&format, "fish %s frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_STRING("hello");
+ ARRAY_PUSH_STRING("cheese");
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error);
+ CHECK(u32scmp(godot_string_get_data(&output), U"not all arguments converted during string formatting") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Incomplete format.
+ godot_string_new_with_latin1_chars(&format, "fish %10");
+ godot_array_clear(&args);
+ ARRAY_PUSH_STRING("cheese");
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error);
+ CHECK(u32scmp(godot_string_get_data(&output), U"incomplete format") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Bad character in format string
+ godot_string_new_with_latin1_chars(&format, "fish %&f frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_STRING("cheese");
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error);
+ CHECK(u32scmp(godot_string_get_data(&output), U"unsupported format character") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Too many decimals.
+ godot_string_new_with_latin1_chars(&format, "fish %2.2.2f frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_REAL(99.99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error);
+ CHECK(u32scmp(godot_string_get_data(&output), U"too many decimal points in format") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // * not a number
+ godot_string_new_with_latin1_chars(&format, "fish %*f frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_STRING("cheese");
+ ARRAY_PUSH_REAL(99.99);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error);
+ CHECK(u32scmp(godot_string_get_data(&output), U"* wants number") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Character too long.
+ godot_string_new_with_latin1_chars(&format, "fish %c frog");
+ godot_array_clear(&args);
+ ARRAY_PUSH_STRING("sc");
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error);
+ CHECK(u32scmp(godot_string_get_data(&output), U"%c requires number or single-character string") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ // Character bad type.
+ godot_string_new_with_latin1_chars(&format, "fish %c frog");
+ godot_array_clear(&args);
+ godot_array t;
+ godot_array_new(&t);
+ godot_variant v;
+ godot_variant_new_array(&v, &t);
+ godot_array_destroy(&t);
+ godot_array_push_back(&args, &v);
+ godot_variant_destroy(&v);
+ output = godot_string_sprintf(&format, &args, &error);
+ REQUIRE(error);
+ CHECK(u32scmp(godot_string_get_data(&output), U"%c requires number or single-character string") == 0);
+ godot_string_destroy(&format);
+ godot_string_destroy(&output);
+
+ godot_array_destroy(&args);
+#undef ARRAY_PUSH_INT
+#undef ARRAY_PUSH_REAL
+#undef ARRAY_PUSH_STRING
+}
+
+TEST_CASE("[GDNative String] is_numeric") {
+#define IS_NUM_TEST(x, r) \
+ { \
+ godot_string t; \
+ godot_string_new_with_latin1_chars(&t, x); \
+ CHECK(godot_string_is_numeric(&t) == r); \
+ godot_string_destroy(&t); \
+ }
+
+ IS_NUM_TEST("12", true);
+ IS_NUM_TEST("1.2", true);
+ IS_NUM_TEST("AF", false);
+ IS_NUM_TEST("-12", true);
+ IS_NUM_TEST("-1.2", true);
+
+#undef IS_NUM_TEST
+}
+
+TEST_CASE("[GDNative String] pad") {
+ godot_string s, c;
+ godot_string_new_with_latin1_chars(&s, "test");
+ godot_string_new_with_latin1_chars(&c, "x");
+
+ godot_string l = godot_string_lpad_with_custom_character(&s, 10, &c);
+ CHECK(u32scmp(godot_string_get_data(&l), U"xxxxxxtest") == 0);
+ godot_string_destroy(&l);
+
+ godot_string r = godot_string_rpad_with_custom_character(&s, 10, &c);
+ CHECK(u32scmp(godot_string_get_data(&r), U"testxxxxxx") == 0);
+ godot_string_destroy(&r);
+
+ godot_string_destroy(&s);
+ godot_string_destroy(&c);
+
+ godot_string_new_with_latin1_chars(&s, "10.10");
+ c = godot_string_pad_decimals(&s, 4);
+ CHECK(u32scmp(godot_string_get_data(&c), U"10.1000") == 0);
+ godot_string_destroy(&c);
+ c = godot_string_pad_zeros(&s, 4);
+ CHECK(u32scmp(godot_string_get_data(&c), U"0010.10") == 0);
+ godot_string_destroy(&c);
+
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] is_subsequence_of") {
+ godot_string a, t;
+ godot_string_new_with_latin1_chars(&a, "is subsequence of");
+
+ godot_string_new_with_latin1_chars(&t, "sub");
+ CHECK(godot_string_is_subsequence_of(&t, &a));
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "Sub");
+ CHECK(!godot_string_is_subsequence_of(&t, &a));
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "Sub");
+ CHECK(godot_string_is_subsequence_ofi(&t, &a));
+ godot_string_destroy(&t);
+
+ godot_string_destroy(&a);
+}
+
+TEST_CASE("[GDNative String] match") {
+ godot_string s, t;
+ godot_string_new_with_latin1_chars(&s, "*.png");
+
+ godot_string_new_with_latin1_chars(&t, "img1.png");
+ CHECK(godot_string_match(&t, &s));
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "img1.jpeg");
+ CHECK(!godot_string_match(&t, &s));
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "img1.Png");
+ CHECK(!godot_string_match(&t, &s));
+ CHECK(godot_string_matchn(&t, &s));
+ godot_string_destroy(&t);
+
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] IPVX address to string") {
+ godot_string ip;
+
+ godot_string_new_with_latin1_chars(&ip, "192.168.0.1");
+ CHECK(godot_string_is_valid_ip_address(&ip));
+ godot_string_destroy(&ip);
+
+ godot_string_new_with_latin1_chars(&ip, "192.368.0.1");
+ CHECK(!godot_string_is_valid_ip_address(&ip));
+ godot_string_destroy(&ip);
+
+ godot_string_new_with_latin1_chars(&ip, "2001:0db8:85a3:0000:0000:8a2e:0370:7334");
+ CHECK(godot_string_is_valid_ip_address(&ip));
+ godot_string_destroy(&ip);
+
+ godot_string_new_with_latin1_chars(&ip, "2001:0db8:85j3:0000:0000:8a2e:0370:7334");
+ CHECK(!godot_string_is_valid_ip_address(&ip));
+ godot_string_destroy(&ip);
+
+ godot_string_new_with_latin1_chars(&ip, "2001:0db8:85f345:0000:0000:8a2e:0370:7334");
+ CHECK(!godot_string_is_valid_ip_address(&ip));
+ godot_string_destroy(&ip);
+
+ godot_string_new_with_latin1_chars(&ip, "2001:0db8::0:8a2e:370:7334");
+ CHECK(godot_string_is_valid_ip_address(&ip));
+ godot_string_destroy(&ip);
+
+ godot_string_new_with_latin1_chars(&ip, "::ffff:192.168.0.1");
+ CHECK(godot_string_is_valid_ip_address(&ip));
+ godot_string_destroy(&ip);
+}
+
+TEST_CASE("[GDNative String] Capitalize against many strings") {
+#define CAP_TEST(i, o) \
+ godot_string_new_with_latin1_chars(&input, i); \
+ godot_string_new_with_latin1_chars(&output, o); \
+ test = godot_string_capitalize(&input); \
+ CHECK(u32scmp(godot_string_get_data(&output), godot_string_get_data(&test)) == 0); \
+ godot_string_destroy(&input); \
+ godot_string_destroy(&output); \
+ godot_string_destroy(&test);
+
+ godot_string input, output, test;
+
+ CAP_TEST("bytes2var", "Bytes 2 Var");
+ CAP_TEST("linear2db", "Linear 2 Db");
+ CAP_TEST("vector3", "Vector 3");
+ CAP_TEST("sha256", "Sha 256");
+ CAP_TEST("2db", "2 Db");
+ CAP_TEST("PascalCase", "Pascal Case");
+ CAP_TEST("PascalPascalCase", "Pascal Pascal Case");
+ CAP_TEST("snake_case", "Snake Case");
+ CAP_TEST("snake_snake_case", "Snake Snake Case");
+ CAP_TEST("sha256sum", "Sha 256 Sum");
+ CAP_TEST("cat2dog", "Cat 2 Dog");
+ CAP_TEST("function(name)", "Function(name)");
+ CAP_TEST("snake_case_function(snake_case_arg)", "Snake Case Function(snake Case Arg)");
+ CAP_TEST("snake_case_function( snake_case_arg )", "Snake Case Function( Snake Case Arg )");
+
+#undef CAP_TEST
+}
+
+TEST_CASE("[GDNative String] lstrip and rstrip") {
+#define LSTRIP_TEST(x, y, z) \
+ { \
+ godot_string xx, yy, zz, rr; \
+ godot_string_new_with_latin1_chars(&xx, x); \
+ godot_string_new_with_latin1_chars(&yy, y); \
+ godot_string_new_with_latin1_chars(&zz, z); \
+ rr = godot_string_lstrip(&xx, &yy); \
+ state = state && (u32scmp(godot_string_get_data(&rr), godot_string_get_data(&zz)) == 0); \
+ godot_string_destroy(&xx); \
+ godot_string_destroy(&yy); \
+ godot_string_destroy(&zz); \
+ godot_string_destroy(&rr); \
+ }
+
+#define RSTRIP_TEST(x, y, z) \
+ { \
+ godot_string xx, yy, zz, rr; \
+ godot_string_new_with_latin1_chars(&xx, x); \
+ godot_string_new_with_latin1_chars(&yy, y); \
+ godot_string_new_with_latin1_chars(&zz, z); \
+ rr = godot_string_rstrip(&xx, &yy); \
+ state = state && (u32scmp(godot_string_get_data(&rr), godot_string_get_data(&zz)) == 0); \
+ godot_string_destroy(&xx); \
+ godot_string_destroy(&yy); \
+ godot_string_destroy(&zz); \
+ godot_string_destroy(&rr); \
+ }
+
+#define LSTRIP_UTF8_TEST(x, y, z) \
+ { \
+ godot_string xx, yy, zz, rr; \
+ godot_string_new_with_utf8_chars(&xx, x); \
+ godot_string_new_with_utf8_chars(&yy, y); \
+ godot_string_new_with_utf8_chars(&zz, z); \
+ rr = godot_string_lstrip(&xx, &yy); \
+ state = state && (u32scmp(godot_string_get_data(&rr), godot_string_get_data(&zz)) == 0); \
+ godot_string_destroy(&xx); \
+ godot_string_destroy(&yy); \
+ godot_string_destroy(&zz); \
+ godot_string_destroy(&rr); \
+ }
+
+#define RSTRIP_UTF8_TEST(x, y, z) \
+ { \
+ godot_string xx, yy, zz, rr; \
+ godot_string_new_with_utf8_chars(&xx, x); \
+ godot_string_new_with_utf8_chars(&yy, y); \
+ godot_string_new_with_utf8_chars(&zz, z); \
+ rr = godot_string_rstrip(&xx, &yy); \
+ state = state && (u32scmp(godot_string_get_data(&rr), godot_string_get_data(&zz)) == 0); \
+ godot_string_destroy(&xx); \
+ godot_string_destroy(&yy); \
+ godot_string_destroy(&zz); \
+ godot_string_destroy(&rr); \
+ }
+
+ bool state = true;
+
+ // strip none
+ LSTRIP_TEST("abc", "", "abc");
+ RSTRIP_TEST("abc", "", "abc");
+ // strip one
+ LSTRIP_TEST("abc", "a", "bc");
+ RSTRIP_TEST("abc", "c", "ab");
+ // strip lots
+ LSTRIP_TEST("bababbababccc", "ab", "ccc");
+ RSTRIP_TEST("aaabcbcbcbbcbbc", "cb", "aaa");
+ // strip empty string
+ LSTRIP_TEST("", "", "");
+ RSTRIP_TEST("", "", "");
+ // strip to empty string
+ LSTRIP_TEST("abcabcabc", "bca", "");
+ RSTRIP_TEST("abcabcabc", "bca", "");
+ // don't strip wrong end
+ LSTRIP_TEST("abc", "c", "abc");
+ LSTRIP_TEST("abca", "a", "bca");
+ RSTRIP_TEST("abc", "a", "abc");
+ RSTRIP_TEST("abca", "a", "abc");
+ // in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5)
+ // and the same second as "ÿ" (\u00ff)
+ LSTRIP_UTF8_TEST("¿", "µÿ", "¿");
+ RSTRIP_UTF8_TEST("¿", "µÿ", "¿");
+ LSTRIP_UTF8_TEST("µ¿ÿ", "µÿ", "¿ÿ");
+ RSTRIP_UTF8_TEST("µ¿ÿ", "µÿ", "µ¿");
+
+ // the above tests repeated with additional superfluous strip chars
+
+ // strip none
+ LSTRIP_TEST("abc", "qwjkl", "abc");
+ RSTRIP_TEST("abc", "qwjkl", "abc");
+ // strip one
+ LSTRIP_TEST("abc", "qwajkl", "bc");
+ RSTRIP_TEST("abc", "qwcjkl", "ab");
+ // strip lots
+ LSTRIP_TEST("bababbababccc", "qwabjkl", "ccc");
+ RSTRIP_TEST("aaabcbcbcbbcbbc", "qwcbjkl", "aaa");
+ // strip empty string
+ LSTRIP_TEST("", "qwjkl", "");
+ RSTRIP_TEST("", "qwjkl", "");
+ // strip to empty string
+ LSTRIP_TEST("abcabcabc", "qwbcajkl", "");
+ RSTRIP_TEST("abcabcabc", "qwbcajkl", "");
+ // don't strip wrong end
+ LSTRIP_TEST("abc", "qwcjkl", "abc");
+ LSTRIP_TEST("abca", "qwajkl", "bca");
+ RSTRIP_TEST("abc", "qwajkl", "abc");
+ RSTRIP_TEST("abca", "qwajkl", "abc");
+ // in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5)
+ // and the same second as "ÿ" (\u00ff)
+ LSTRIP_UTF8_TEST("¿", "qwaµÿjkl", "¿");
+ RSTRIP_UTF8_TEST("¿", "qwaµÿjkl", "¿");
+ LSTRIP_UTF8_TEST("µ¿ÿ", "qwaµÿjkl", "¿ÿ");
+ RSTRIP_UTF8_TEST("µ¿ÿ", "qwaµÿjkl", "µ¿");
+
+ CHECK(state);
+
+#undef LSTRIP_TEST
+#undef RSTRIP_TEST
+#undef LSTRIP_UTF8_TEST
+#undef RSTRIP_UTF8_TEST
+}
+
+TEST_CASE("[GDNative String] Cyrillic to_lower()") {
+ godot_string upper, lower, test;
+ godot_string_new_with_utf8_chars(&upper, "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ");
+ godot_string_new_with_utf8_chars(&lower, "абвгдеёжзийклмнопрстуфхцчшщъыьэюя");
+
+ test = godot_string_to_lower(&upper);
+
+ CHECK((u32scmp(godot_string_get_data(&test), godot_string_get_data(&lower)) == 0));
+
+ godot_string_destroy(&upper);
+ godot_string_destroy(&lower);
+ godot_string_destroy(&test);
+}
+
+TEST_CASE("[GDNative String] Count and countn functionality") {
+#define COUNT_TEST(x, y, r) \
+ { \
+ godot_string s, t; \
+ godot_string_new_with_latin1_chars(&s, x); \
+ godot_string_new_with_latin1_chars(&t, y); \
+ state = state && (godot_string_count(&s, &t, 0, 0) == r); \
+ godot_string_destroy(&s); \
+ godot_string_destroy(&t); \
+ }
+
+#define COUNTR_TEST(x, y, a, b, r) \
+ { \
+ godot_string s, t; \
+ godot_string_new_with_latin1_chars(&s, x); \
+ godot_string_new_with_latin1_chars(&t, y); \
+ state = state && (godot_string_count(&s, &t, a, b) == r); \
+ godot_string_destroy(&s); \
+ godot_string_destroy(&t); \
+ }
+
+#define COUNTN_TEST(x, y, r) \
+ { \
+ godot_string s, t; \
+ godot_string_new_with_latin1_chars(&s, x); \
+ godot_string_new_with_latin1_chars(&t, y); \
+ state = state && (godot_string_countn(&s, &t, 0, 0) == r); \
+ godot_string_destroy(&s); \
+ godot_string_destroy(&t); \
+ }
+
+#define COUNTNR_TEST(x, y, a, b, r) \
+ { \
+ godot_string s, t; \
+ godot_string_new_with_latin1_chars(&s, x); \
+ godot_string_new_with_latin1_chars(&t, y); \
+ state = state && (godot_string_countn(&s, &t, a, b) == r); \
+ godot_string_destroy(&s); \
+ godot_string_destroy(&t); \
+ }
+ bool state = true;
+
+ COUNT_TEST("", "Test", 0);
+ COUNT_TEST("Test", "", 0);
+ COUNT_TEST("Test", "test", 0);
+ COUNT_TEST("Test", "TEST", 0);
+ COUNT_TEST("TEST", "TEST", 1);
+ COUNT_TEST("Test", "Test", 1);
+ COUNT_TEST("aTest", "Test", 1);
+ COUNT_TEST("Testa", "Test", 1);
+ COUNT_TEST("TestTestTest", "Test", 3);
+ COUNT_TEST("TestTestTest", "TestTest", 1);
+ COUNT_TEST("TestGodotTestGodotTestGodot", "Test", 3);
+
+ COUNTR_TEST("TestTestTestTest", "Test", 4, 8, 1);
+ COUNTR_TEST("TestTestTestTest", "Test", 4, 12, 2);
+ COUNTR_TEST("TestTestTestTest", "Test", 4, 16, 3);
+ COUNTR_TEST("TestTestTestTest", "Test", 4, 0, 3);
+
+ COUNTN_TEST("Test", "test", 1);
+ COUNTN_TEST("Test", "TEST", 1);
+ COUNTN_TEST("testTest-Testatest", "tEst", 4);
+ COUNTNR_TEST("testTest-TeStatest", "tEsT", 4, 16, 2);
+
+ CHECK(state);
+
+#undef COUNT_TEST
+#undef COUNTR_TEST
+#undef COUNTN_TEST
+#undef COUNTNR_TEST
+}
+
+TEST_CASE("[GDNative String] Bigrams") {
+ godot_string s, t;
+ godot_string_new_with_latin1_chars(&s, "abcd");
+ godot_packed_string_array bigr = godot_string_bigrams(&s);
+ godot_string_destroy(&s);
+
+ CHECK(godot_packed_string_array_size(&bigr) == 3);
+
+ t = godot_packed_string_array_get(&bigr, 0);
+ CHECK(u32scmp(godot_string_get_data(&t), U"ab") == 0);
+ godot_string_destroy(&t);
+
+ t = godot_packed_string_array_get(&bigr, 1);
+ CHECK(u32scmp(godot_string_get_data(&t), U"bc") == 0);
+ godot_string_destroy(&t);
+
+ t = godot_packed_string_array_get(&bigr, 2);
+ CHECK(u32scmp(godot_string_get_data(&t), U"cd") == 0);
+ godot_string_destroy(&t);
+
+ godot_packed_string_array_destroy(&bigr);
+}
+
+TEST_CASE("[GDNative String] c-escape/unescape") {
+ godot_string s;
+ godot_string_new_with_latin1_chars(&s, "\\1\a2\b\f3\n45\r6\t7\v8\'9\?0\"");
+ godot_string t = godot_string_c_escape(&s);
+ godot_string u = godot_string_c_unescape(&t);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&s)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] dedent") {
+ godot_string s, t;
+ godot_string_new_with_latin1_chars(&s, " aaa\n bbb");
+ godot_string_new_with_latin1_chars(&t, "aaa\nbbb");
+ godot_string u = godot_string_dedent(&s);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Path functions") {
+ static const char *path[4] = { "C:\\Godot\\project\\test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot\\test.doc" };
+ static const char *base_dir[4] = { "C:\\Godot\\project", "/Godot/project", "../Godot/project", "Godot" };
+ static const char *base_name[4] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test" };
+ static const char *ext[4] = { "tscn", "xscn", "scn", "doc" };
+ static const char *file[4] = { "test.tscn", "test.xscn", "test.scn", "test.doc" };
+ static const bool abs[4] = { true, true, false, false };
+
+ for (int i = 0; i < 4; i++) {
+ godot_string s, t, u, f;
+ godot_string_new_with_latin1_chars(&s, path[i]);
+
+ t = godot_string_get_base_dir(&s);
+ godot_string_new_with_latin1_chars(&u, base_dir[i]);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+
+ t = godot_string_get_basename(&s);
+ godot_string_new_with_latin1_chars(&u, base_name[i]);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+
+ t = godot_string_get_extension(&s);
+ godot_string_new_with_latin1_chars(&u, ext[i]);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+
+ t = godot_string_get_file(&s);
+ godot_string_new_with_latin1_chars(&u, file[i]);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+
+ godot_string s_simp;
+ s_simp = godot_string_simplify_path(&s);
+ t = godot_string_get_base_dir(&s_simp);
+ godot_string_new_with_latin1_chars(&u, file[i]);
+ f = godot_string_plus_file(&t, &u);
+ CHECK(u32scmp(godot_string_get_data(&f), godot_string_get_data(&s_simp)) == 0);
+ godot_string_destroy(&f);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+ godot_string_destroy(&s_simp);
+
+ CHECK(godot_string_is_abs_path(&s) == abs[i]);
+ CHECK(godot_string_is_rel_path(&s) != abs[i]);
+
+ godot_string_destroy(&s);
+ }
+
+ static const char *file_name[3] = { "test.tscn", "test://.xscn", "?tes*t.scn" };
+ static const bool valid[3] = { true, false, false };
+ for (int i = 0; i < 3; i++) {
+ godot_string s;
+ godot_string_new_with_latin1_chars(&s, file_name[i]);
+ CHECK(godot_string_is_valid_filename(&s) == valid[i]);
+ godot_string_destroy(&s);
+ }
+}
+
+TEST_CASE("[GDNative String] hash") {
+ godot_string a, b, c;
+ godot_string_new_with_latin1_chars(&a, "Test");
+ godot_string_new_with_latin1_chars(&b, "Test");
+ godot_string_new_with_latin1_chars(&c, "West");
+ CHECK(godot_string_hash(&a) == godot_string_hash(&b));
+ CHECK(godot_string_hash(&a) != godot_string_hash(&c));
+
+ CHECK(godot_string_hash64(&a) == godot_string_hash64(&b));
+ CHECK(godot_string_hash64(&a) != godot_string_hash64(&c));
+
+ godot_string_destroy(&a);
+ godot_string_destroy(&b);
+ godot_string_destroy(&c);
+}
+
+TEST_CASE("[GDNative String] http_escape/unescape") {
+ godot_string s, t, u;
+ godot_string_new_with_latin1_chars(&s, "Godot Engine:'docs'");
+ godot_string_new_with_latin1_chars(&t, "Godot%20Engine%3A%27docs%27");
+
+ u = godot_string_http_escape(&s);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+
+ u = godot_string_http_unescape(&t);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&s)) == 0);
+ godot_string_destroy(&u);
+
+ godot_string_destroy(&s);
+ godot_string_destroy(&t);
+}
+
+TEST_CASE("[GDNative String] percent_encode/decode") {
+ godot_string s, t, u;
+ godot_string_new_with_latin1_chars(&s, "Godot Engine:'docs'");
+ godot_string_new_with_latin1_chars(&t, "Godot%20Engine%3a%27docs%27");
+
+ u = godot_string_percent_encode(&s);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+
+ u = godot_string_percent_decode(&t);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&s)) == 0);
+ godot_string_destroy(&u);
+
+ godot_string_destroy(&s);
+ godot_string_destroy(&t);
+}
+
+TEST_CASE("[GDNative String] xml_escape/unescape") {
+ godot_string s, t, u;
+ godot_string_new_with_latin1_chars(&s, "\"Test\" <test@test&'test'>");
+
+ t = godot_string_xml_escape_with_quotes(&s);
+ u = godot_string_xml_unescape(&t);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&s)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+
+ t = godot_string_xml_escape(&s);
+ u = godot_string_xml_unescape(&t);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&s)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Strip escapes") {
+ godot_string s, t, u;
+ godot_string_new_with_latin1_chars(&s, "\t\tTest Test\r\n Test");
+ godot_string_new_with_latin1_chars(&t, "Test Test Test");
+
+ u = godot_string_strip_escapes(&s);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+
+ godot_string_destroy(&t);
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Strip edges") {
+ godot_string s, t, u;
+ godot_string_new_with_latin1_chars(&s, "\t Test Test ");
+
+ godot_string_new_with_latin1_chars(&t, "Test Test ");
+ u = godot_string_strip_edges(&s, true, false);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "\t Test Test");
+ u = godot_string_strip_edges(&s, false, true);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "Test Test");
+ u = godot_string_strip_edges(&s, true, true);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Similarity") {
+ godot_string a, b, c;
+ godot_string_new_with_latin1_chars(&a, "Test");
+ godot_string_new_with_latin1_chars(&b, "West");
+ godot_string_new_with_latin1_chars(&c, "Toad");
+
+ CHECK(godot_string_similarity(&a, &b) > godot_string_similarity(&a, &c));
+
+ godot_string_destroy(&a);
+ godot_string_destroy(&b);
+ godot_string_destroy(&c);
+}
+
+TEST_CASE("[GDNative String] Trim") {
+ godot_string s, t, u, p;
+ godot_string_new_with_latin1_chars(&s, "aaaTestbbb");
+
+ godot_string_new_with_latin1_chars(&p, "aaa");
+ godot_string_new_with_latin1_chars(&t, "Testbbb");
+ u = godot_string_trim_prefix(&s, &p);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+ godot_string_destroy(&p);
+
+ godot_string_new_with_latin1_chars(&p, "bbb");
+ godot_string_new_with_latin1_chars(&t, "aaaTest");
+ u = godot_string_trim_suffix(&s, &p);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+ godot_string_destroy(&p);
+
+ godot_string_new_with_latin1_chars(&p, "Test");
+ u = godot_string_trim_suffix(&s, &p);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&s)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&p);
+
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Right/Left") {
+ godot_string s, t, u;
+ godot_string_new_with_latin1_chars(&s, "aaaTestbbb");
+ // ^
+
+ godot_string_new_with_latin1_chars(&t, "tbbb");
+ u = godot_string_right(&s, 6);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&t, "aaaTes");
+ u = godot_string_left(&s, 6);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+
+ godot_string_destroy(&s);
+}
+
+TEST_CASE("[GDNative String] Repeat") {
+ godot_string t, u;
+ godot_string_new_with_latin1_chars(&t, "ab");
+
+ u = godot_string_repeat(&t, 4);
+ CHECK(u32scmp(godot_string_get_data(&u), U"abababab") == 0);
+ godot_string_destroy(&u);
+
+ godot_string_destroy(&t);
+}
+
+TEST_CASE("[GDNative String] SHA1/SHA256/MD5") {
+ godot_string s, t, sha1, sha256, md5;
+ godot_string_new_with_latin1_chars(&s, "Godot");
+ godot_string_new_with_latin1_chars(&sha1, "a1e91f39b9fce6a9998b14bdbe2aa2b39dc2d201");
+ static uint8_t sha1_buf[20] = {
+ 0xA1, 0xE9, 0x1F, 0x39, 0xB9, 0xFC, 0xE6, 0xA9, 0x99, 0x8B, 0x14, 0xBD, 0xBE, 0x2A, 0xA2, 0xB3,
+ 0x9D, 0xC2, 0xD2, 0x01
+ };
+ godot_string_new_with_latin1_chars(&sha256, "2a02b2443f7985d89d09001086ae3dcfa6eb0f55c6ef170715d42328e16e6cb8");
+ static uint8_t sha256_buf[32] = {
+ 0x2A, 0x02, 0xB2, 0x44, 0x3F, 0x79, 0x85, 0xD8, 0x9D, 0x09, 0x00, 0x10, 0x86, 0xAE, 0x3D, 0xCF,
+ 0xA6, 0xEB, 0x0F, 0x55, 0xC6, 0xEF, 0x17, 0x07, 0x15, 0xD4, 0x23, 0x28, 0xE1, 0x6E, 0x6C, 0xB8
+ };
+ godot_string_new_with_latin1_chars(&md5, "4a336d087aeb0390da10ee2ea7cb87f8");
+ static uint8_t md5_buf[16] = {
+ 0x4A, 0x33, 0x6D, 0x08, 0x7A, 0xEB, 0x03, 0x90, 0xDA, 0x10, 0xEE, 0x2E, 0xA7, 0xCB, 0x87, 0xF8
+ };
+
+ godot_packed_byte_array buf = godot_string_sha1_buffer(&s);
+ CHECK(memcmp(sha1_buf, godot_packed_byte_array_ptr(&buf), 20) == 0);
+ godot_packed_byte_array_destroy(&buf);
+
+ t = godot_string_sha1_text(&s);
+ CHECK(u32scmp(godot_string_get_data(&t), godot_string_get_data(&sha1)) == 0);
+ godot_string_destroy(&t);
+
+ buf = godot_string_sha256_buffer(&s);
+ CHECK(memcmp(sha256_buf, godot_packed_byte_array_ptr(&buf), 32) == 0);
+ godot_packed_byte_array_destroy(&buf);
+
+ t = godot_string_sha256_text(&s);
+ CHECK(u32scmp(godot_string_get_data(&t), godot_string_get_data(&sha256)) == 0);
+ godot_string_destroy(&t);
+
+ buf = godot_string_md5_buffer(&s);
+ CHECK(memcmp(md5_buf, godot_packed_byte_array_ptr(&buf), 16) == 0);
+ godot_packed_byte_array_destroy(&buf);
+
+ t = godot_string_md5_text(&s);
+ CHECK(u32scmp(godot_string_get_data(&t), godot_string_get_data(&md5)) == 0);
+ godot_string_destroy(&t);
+
+ godot_string_destroy(&s);
+ godot_string_destroy(&sha1);
+ godot_string_destroy(&sha256);
+ godot_string_destroy(&md5);
+}
+
+TEST_CASE("[GDNative String] Join") {
+ godot_string s, t, u;
+ godot_string_new_with_latin1_chars(&s, ", ");
+
+ godot_packed_string_array parts;
+ godot_packed_string_array_new(&parts);
+ godot_string_new_with_latin1_chars(&t, "One");
+ godot_packed_string_array_push_back(&parts, &t);
+ godot_string_destroy(&t);
+ godot_string_new_with_latin1_chars(&t, "B");
+ godot_packed_string_array_push_back(&parts, &t);
+ godot_string_destroy(&t);
+ godot_string_new_with_latin1_chars(&t, "C");
+ godot_packed_string_array_push_back(&parts, &t);
+ godot_string_destroy(&t);
+
+ godot_string_new_with_latin1_chars(&u, "One, B, C");
+ t = godot_string_join(&s, &parts);
+ CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0);
+ godot_string_destroy(&u);
+ godot_string_destroy(&t);
+
+ godot_string_destroy(&s);
+ godot_packed_string_array_destroy(&parts);
+}
+
+TEST_CASE("[GDNative String] Is_*") {
+ static const char *data[12] = { "-30", "100", "10.1", "10,1", "1e2", "1e-2", "1e2e3", "0xAB", "AB", "Test1", "1Test", "Test*1" };
+ static bool isnum[12] = { true, true, true, false, false, false, false, false, false, false, false, false };
+ static bool isint[12] = { true, true, false, false, false, false, false, false, false, false, false, false };
+ static bool ishex[12] = { true, true, false, false, true, false, true, false, true, false, false, false };
+ static bool ishex_p[12] = { false, false, false, false, false, false, false, true, false, false, false, false };
+ static bool isflt[12] = { true, true, true, false, true, true, false, false, false, false, false, false };
+ static bool isid[12] = { false, false, false, false, false, false, false, false, true, true, false, false };
+
+ for (int i = 0; i < 12; i++) {
+ godot_string s;
+ godot_string_new_with_latin1_chars(&s, data[i]);
+ CHECK(godot_string_is_numeric(&s) == isnum[i]);
+ CHECK(godot_string_is_valid_integer(&s) == isint[i]);
+ CHECK(godot_string_is_valid_hex_number(&s, false) == ishex[i]);
+ CHECK(godot_string_is_valid_hex_number(&s, true) == ishex_p[i]);
+ CHECK(godot_string_is_valid_float(&s) == isflt[i]);
+ CHECK(godot_string_is_valid_identifier(&s) == isid[i]);
+ godot_string_destroy(&s);
+ }
+}
+
+TEST_CASE("[GDNative String] humanize_size") {
+ godot_string s;
+
+ s = godot_string_humanize_size(1000);
+ CHECK(u32scmp(godot_string_get_data(&s), U"1000 B") == 0);
+ godot_string_destroy(&s);
+
+ s = godot_string_humanize_size(1025);
+ CHECK(u32scmp(godot_string_get_data(&s), U"1.00 KiB") == 0);
+ godot_string_destroy(&s);
+
+ s = godot_string_humanize_size(1025300);
+ CHECK(u32scmp(godot_string_get_data(&s), U"1001.2 KiB") == 0);
+ godot_string_destroy(&s);
+
+ s = godot_string_humanize_size(100523550);
+ CHECK(u32scmp(godot_string_get_data(&s), U"95.86 MiB") == 0);
+ godot_string_destroy(&s);
+
+ s = godot_string_humanize_size(5345555000);
+ CHECK(u32scmp(godot_string_get_data(&s), U"4.97 GiB") == 0);
+ godot_string_destroy(&s);
+}
+
+} // namespace TestGDNativeString
+
+#endif // TEST_GDNATIVE_STRING_H
diff --git a/modules/gdnavigation/rvo_agent.h b/modules/gdnavigation/rvo_agent.h
index f5c579ba84..1ad9d3ed76 100644
--- a/modules/gdnavigation/rvo_agent.h
+++ b/modules/gdnavigation/rvo_agent.h
@@ -31,8 +31,9 @@
#ifndef RVO_AGENT_H
#define RVO_AGENT_H
-#include "core/object.h"
+#include "core/class_db.h"
#include "nav_rid.h"
+
#include <Agent.h>
/**
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index e528fc6623..e170667a30 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -7,6 +7,7 @@
List of core built-in GDScript functions. Math functions and other utilities. Everything else is provided by objects. (Keywords: builtin, built in, global functions.)
</description>
<tutorials>
+ <link title="Random number generation">https://docs.godotengine.org/en/latest/tutorials/math/random_number_generation.html</link>
</tutorials>
<methods>
<method name="Color8">
@@ -369,24 +370,19 @@
<description>
Returns the floating-point modulus of [code]a/b[/code] that wraps equally in positive and negative.
[codeblock]
- var i = -6
- while i &lt; 5:
- prints(i, fposmod(i, 3))
- i += 1
+ for i in 7:
+ var x = 0.5 * i - 1.5
+ print("%4.1f %4.1f %4.1f" % [x, fmod(x, 1.5), fposmod(x, 1.5)])
[/codeblock]
Produces:
[codeblock]
- -6 0
- -5 1
- -4 2
- -3 0
- -2 1
- -1 2
- 0 0
- 1 1
- 2 2
- 3 0
- 4 1
+ -1.5 -0.0 0.0
+ -1.0 -1.0 0.5
+ -0.5 -0.5 1.0
+ 0.0 0.0 0.0
+ 0.5 0.5 0.5
+ 1.0 1.0 1.0
+ 1.5 0.0 0.0
[/codeblock]
</description>
</method>
@@ -632,6 +628,7 @@
var main = load("res://main.tscn") # main will contain a PackedScene resource.
[/codeblock]
[b]Important:[/b] The path must be absolute, a local path will just return [code]null[/code].
+ This method is a simplified version of [method ResourceLoader.load], which can be used for more advanced scenarios.
</description>
</method>
<method name="log">
@@ -771,24 +768,18 @@
<description>
Returns the integer modulus of [code]a/b[/code] that wraps equally in positive and negative.
[codeblock]
- var i = -6
- while i &lt; 5:
- prints(i, posmod(i, 3))
- i += 1
+ for i in range(-3, 4):
+ print("%2d %2d %2d" % [i, i % 3, posmod(i, 3)])
[/codeblock]
Produces:
[codeblock]
- -6 0
- -5 1
- -4 2
- -3 0
- -2 1
- -1 2
- 0 0
- 1 1
- 2 2
- 3 0
- 4 1
+ -3 0 0
+ -2 -2 1
+ -1 -1 2
+ 0 0 0
+ 1 1 1
+ 2 2 2
+ 3 0 0
[/codeblock]
</description>
</method>
@@ -829,6 +820,7 @@
a = [1, 2, 3]
print("a", "b", a) # Prints ab[1, 2, 3]
[/codeblock]
+ [b]Note:[/b] Consider using [method push_error] and [method push_warning] to print error and warning messages instead of [method print]. This distinguishes them from print messages used for debugging purposes, while also displaying a stack trace when an error or warning is printed.
</description>
</method>
<method name="print_debug" qualifiers="vararg">
@@ -902,6 +894,7 @@
[codeblock]
push_error("test error") # Prints "test error" to debugger and terminal as error call
[/codeblock]
+ [b]Note:[/b] Errors printed this way will not pause project execution. To print an error message and pause project execution in debug builds, use [code]assert(false, "test error")[/code] instead.
</description>
</method>
<method name="push_warning">
@@ -991,27 +984,15 @@
<description>
Returns an array with the given range. Range can be 1 argument N (0 to N-1), two arguments (initial, final-1) or three arguments (initial, final-1, increment).
[codeblock]
- for i in range(4):
- print(i)
- for i in range(2, 5):
- print(i)
- for i in range(0, 6, 2):
- print(i)
+ print(range(4))
+ print(range(2, 5))
+ print(range(0, 6, 2))
[/codeblock]
Output:
[codeblock]
- 0
- 1
- 2
- 3
-
- 2
- 3
- 4
-
- 0
- 2
- 4
+ [0, 1, 2, 3]
+ [2, 3, 4]
+ [0, 2, 4]
[/codeblock]
</description>
</method>
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 9a3273d201..7f7410a92c 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -73,6 +73,13 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
color_region_cache[p_line] = -1;
int in_region = -1;
if (p_line != 0) {
+ int prev_region_line = p_line - 1;
+ while (prev_region_line > 0 && !color_region_cache.has(prev_region_line)) {
+ prev_region_line--;
+ }
+ for (int i = prev_region_line; i < p_line - 1; i++) {
+ get_line_syntax_highlighting(i);
+ }
if (!color_region_cache.has(p_line - 1)) {
get_line_syntax_highlighting(p_line - 1);
}
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 0263e32c5b..e70e3f7272 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -1044,8 +1044,10 @@ GDScript::~GDScript() {
MutexLock lock(GDScriptLanguage::get_singleton()->lock);
while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) {
- E->self()->_clear_stack();
+ // Order matters since clearing the stack may already cause
+ // the GDSCriptFunctionState to be destroyed and thus removed from the list.
pending_func_states.remove(E);
+ E->self()->_clear_stack();
}
}
@@ -1053,7 +1055,9 @@ GDScript::~GDScript() {
memdelete(E->get());
}
- GDScriptCache::remove_script(get_path());
+ if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown.
+ GDScriptCache::remove_script(get_path());
+ }
_save_orphaned_subclasses();
@@ -1449,8 +1453,10 @@ GDScriptInstance::~GDScriptInstance() {
MutexLock lock(GDScriptLanguage::get_singleton()->lock);
while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) {
- E->self()->_clear_stack();
+ // Order matters since clearing the stack may already cause
+ // the GDSCriptFunctionState to be destroyed and thus removed from the list.
pending_func_states.remove(E);
+ E->self()->_clear_stack();
}
if (script.is_valid() && owner) {
@@ -2035,7 +2041,33 @@ GDScriptLanguage::~GDScriptLanguage() {
if (_call_stack) {
memdelete_arr(_call_stack);
}
- singleton = nullptr;
+
+ // Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit).
+ SelfList<GDScript> *s = script_list.first();
+ while (s) {
+ GDScript *script = s->self();
+ // This ensures the current script is not released before we can check what's the next one
+ // in the list (we can't get the next upfront because we don't know if the reference breaking
+ // will cause it -or any other after it, for that matter- to be released so the next one
+ // is not the same as before).
+ script->reference();
+
+ for (Map<StringName, GDScriptFunction *>::Element *E = script->member_functions.front(); E; E = E->next()) {
+ GDScriptFunction *func = E->get();
+ for (int i = 0; i < func->argument_types.size(); i++) {
+ func->argument_types.write[i].script_type_ref = Ref<Script>();
+ }
+ func->return_type.script_type_ref = Ref<Script>();
+ }
+ for (Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.front(); E; E = E->next()) {
+ E->get().data_type.script_type_ref = Ref<Script>();
+ }
+
+ s = s->next();
+ script->unreference();
+ }
+
+ singleton = NULL;
}
void GDScriptLanguage::add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass) {
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 38965adbb2..5250115608 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -1297,7 +1297,7 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame
if (p_parameter->default_value != nullptr) {
if (!is_type_compatible(result, p_parameter->default_value->get_datatype())) {
- push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with paremeter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value);
+ push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with parameter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value);
} else if (p_parameter->default_value->get_datatype().is_variant()) {
mark_node_unsafe(p_parameter);
}
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 8f0ce99de6..cc9e87b882 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -529,6 +529,7 @@ void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant
append(p_arguments[i]);
}
append(p_target);
+ alloc_call(p_arguments.size());
}
void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) {
@@ -579,8 +580,8 @@ void GDScriptByteCodeGenerator::write_endif() {
}
void GDScriptByteCodeGenerator::write_for(const Address &p_variable, const Address &p_list) {
- int counter_pos = increase_stack() | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
- int container_pos = increase_stack() | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ int counter_pos = add_temporary() | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ int container_pos = add_temporary() | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
current_breaks_to_patch.push_back(List<int>());
@@ -628,7 +629,9 @@ void GDScriptByteCodeGenerator::write_endfor() {
}
current_breaks_to_patch.pop_back();
- current_stack_size -= 2; // Remove loop temporaries.
+ // Remove loop temporaries.
+ pop_temporary();
+ pop_temporary();
}
void GDScriptByteCodeGenerator::start_while_condition() {
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index c3d651ee79..bad450c9f9 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -76,7 +76,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N
}
}
-GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const {
+GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) const {
if (!p_datatype.is_set() || !p_datatype.is_hard_type()) {
return GDScriptDataType();
}
@@ -98,7 +98,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
} break;
case GDScriptParser::DataType::SCRIPT: {
result.kind = GDScriptDataType::SCRIPT;
- result.script_type = p_datatype.script_type;
+ result.script_type = Ref<Script>(p_datatype.script_type).ptr();
result.native_type = result.script_type->get_instance_base_type();
} break;
case GDScriptParser::DataType::CLASS: {
@@ -124,11 +124,11 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
names.pop_back();
}
result.kind = GDScriptDataType::GDSCRIPT;
- result.script_type = script;
+ result.script_type = script.ptr();
result.native_type = script->get_instance_base_type();
} else {
result.kind = GDScriptDataType::GDSCRIPT;
- result.script_type = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path);
+ result.script_type = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path).ptr();
result.native_type = p_datatype.native_type;
}
}
@@ -149,6 +149,12 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
}
}
+ // Only hold strong reference to the script if it's not the owner of the
+ // element qualified with this type, to avoid cyclic references (leaks).
+ if (result.script_type && result.script_type != p_owner) {
+ result.script_type_ref = Ref<Script>(result.script_type);
+ }
+
return result;
}
@@ -1668,7 +1674,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
func_name = p_func->identifier->name;
is_static = p_func->is_static;
rpc_mode = p_func->rpc_mode;
- return_type = _gdtype_from_datatype(p_func->get_datatype());
+ return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script);
} else {
if (p_for_ready) {
func_name = "_ready";
@@ -1685,7 +1691,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
if (p_func) {
for (int i = 0; i < p_func->parameters.size(); i++) {
const GDScriptParser::ParameterNode *parameter = p_func->parameters[i];
- GDScriptDataType par_type = _gdtype_from_datatype(parameter->get_datatype());
+ GDScriptDataType par_type = _gdtype_from_datatype(parameter->get_datatype(), p_script);
uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->default_value != nullptr, par_type);
codegen.parameters[parameter->identifier->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, par_type);
@@ -1830,7 +1836,7 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP
return_type.kind = GDScriptDataType::BUILTIN;
return_type.builtin_type = Variant::NIL;
} else {
- return_type = _gdtype_from_datatype(p_variable->get_datatype());
+ return_type = _gdtype_from_datatype(p_variable->get_datatype(), p_script);
}
codegen.generator->write_start(p_script, func_name, false, p_variable->rpc_mode, return_type);
@@ -1927,7 +1933,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
p_script->native = native;
} break;
case GDScriptDataType::GDSCRIPT: {
- Ref<GDScript> base = base_type.script_type;
+ Ref<GDScript> base = Ref<GDScript>(base_type.script_type);
p_script->base = base;
p_script->_base = base.ptr();
@@ -1994,7 +2000,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
break;
}
minfo.rpc_mode = variable->rpc_mode;
- minfo.data_type = _gdtype_from_datatype(variable->get_datatype());
+ minfo.data_type = _gdtype_from_datatype(variable->get_datatype(), p_script);
PropertyInfo prop_info = minfo.data_type;
prop_info.name = name;
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index 80fba6a934..db02079d26 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -84,7 +84,7 @@ class GDScriptCompiler {
Ref<Script> script = obj->get_script();
if (script.is_valid()) {
- type.script_type = script;
+ type.script_type = script.ptr();
Ref<GDScript> gdscript = script;
if (gdscript.is_valid()) {
type.kind = GDScriptDataType::GDSCRIPT;
@@ -125,7 +125,7 @@ class GDScriptCompiler {
Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
- GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const;
+ GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner = nullptr) const;
GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index d1c98a5456..c98ac09310 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -56,7 +56,8 @@ struct GDScriptDataType {
bool has_type = false;
Variant::Type builtin_type = Variant::NIL;
StringName native_type;
- Ref<Script> script_type;
+ Script *script_type = nullptr;
+ Ref<Script> script_type_ref;
bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const {
if (!has_type) {
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index e8dea8180a..2a69db130b 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -1042,7 +1042,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
break; // Allow trailing comma.
}
- if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) {
+ if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for enum key.)")) {
EnumNode::Value item;
item.identifier = parse_identifier();
item.parent_enum = enum_node;
@@ -2516,7 +2516,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) {
if (match(GDScriptTokenizer::Token::LITERAL)) {
if (previous.literal.get_type() != Variant::STRING) {
- push_error(R"(Expect node path as string or identifer after "$".)");
+ push_error(R"(Expect node path as string or identifier after "$".)");
return nullptr;
}
GetNodeNode *get_node = alloc_node<GetNodeNode>();
@@ -2539,7 +2539,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
} while (match(GDScriptTokenizer::Token::SLASH));
return get_node;
} else {
- push_error(R"(Expect node path as string or identifer after "$".)");
+ push_error(R"(Expect node path as string or identifier after "$".)");
return nullptr;
}
}
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index ed27604aec..9a40aa50ac 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -578,7 +578,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
}
void GDScriptTokenizer::newline(bool p_make_token) {
- // Don't overwrite previous newline, nor create if we want a line contination.
+ // Don't overwrite previous newline, nor create if we want a line continuation.
if (p_make_token && !pending_newline && !line_continuation) {
Token newline(Token::NEWLINE);
newline.start_line = line;
diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp
index 68d9984b43..931b683a44 100644
--- a/modules/gdscript/tests/test_gdscript.cpp
+++ b/modules/gdscript/tests/test_gdscript.cpp
@@ -30,10 +30,13 @@
#include "test_gdscript.h"
+#include "core/io/file_access_pack.h"
#include "core/os/file_access.h"
#include "core/os/main_loop.h"
#include "core/os/os.h"
+#include "core/project_settings.h"
#include "core/string_builder.h"
+#include "scene/resources/packed_scene.h"
#include "modules/gdscript/gdscript_analyzer.h"
#include "modules/gdscript/gdscript_compiler.h"
@@ -179,6 +182,60 @@ static void test_compiler(const String &p_code, const String &p_script_path, con
}
}
+void init_autoloads() {
+ Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+
+ // First pass, add the constants so they exist before any script is loaded.
+ for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
+ const ProjectSettings::AutoloadInfo &info = E->get();
+
+ if (info.is_singleton) {
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->add_global_constant(info.name, Variant());
+ }
+ }
+ }
+
+ // Second pass, load into global constants.
+ for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
+ const ProjectSettings::AutoloadInfo &info = E->get();
+
+ if (!info.is_singleton) {
+ // Skip non-singletons since we don't have a scene tree here anyway.
+ continue;
+ }
+
+ RES res = ResourceLoader::load(info.path);
+ ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path);
+ Node *n = nullptr;
+ if (res->is_class("PackedScene")) {
+ Ref<PackedScene> ps = res;
+ n = ps->instance();
+ } else if (res->is_class("Script")) {
+ Ref<Script> script_res = res;
+ StringName ibt = script_res->get_instance_base_type();
+ bool valid_type = ClassDB::is_parent_class(ibt, "Node");
+ ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path);
+
+ Object *obj = ClassDB::instance(ibt);
+
+ ERR_CONTINUE_MSG(obj == nullptr,
+ "Cannot instance script for autoload, expected 'Node' inheritance, got: " +
+ String(ibt));
+
+ n = Object::cast_to<Node>(obj);
+ n->set_script(script_res);
+ }
+
+ ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path);
+ n->set_name(info.name);
+
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->add_global_constant(info.name, n);
+ }
+ }
+}
+
void test(TestType p_type) {
List<String> cmdlargs = OS::get_singleton()->get_cmdline_args();
@@ -195,6 +252,21 @@ void test(TestType p_type) {
FileAccessRef fa = FileAccess::open(test, FileAccess::READ);
ERR_FAIL_COND_MSG(!fa, "Could not open file: " + test);
+ // Init PackedData since it's used by ProjectSettings.
+ PackedData *packed_data = memnew(PackedData);
+
+ // Setup project settings since it's needed by the languages to get the global scripts.
+ // This also sets up the base resource path.
+ Error err = ProjectSettings::get_singleton()->setup(fa->get_path_absolute().get_base_dir(), String(), true);
+ if (err) {
+ print_line("Could not load project settings.");
+ // Keep going since some scripts still work without this.
+ }
+
+ // Initialize the language for the test routine.
+ ScriptServer::init_languages();
+ init_autoloads();
+
Vector<uint8_t> buf;
int flen = fa->get_len();
buf.resize(fa->get_len() + 1);
@@ -226,6 +298,10 @@ void test(TestType p_type) {
case TEST_BYTECODE:
print_line("Not implemented.");
}
+
+ // Destroy stuff we set up earlier.
+ ScriptServer::finish_languages();
+ memdelete(packed_data);
}
} // namespace TestGDScript
diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml
index 57fbc5bfc0..4dccb03369 100644
--- a/modules/gridmap/doc_classes/GridMap.xml
+++ b/modules/gridmap/doc_classes/GridMap.xml
@@ -11,6 +11,8 @@
</description>
<tutorials>
<link title="Using gridmaps">https://docs.godotengine.org/en/latest/tutorials/3d/using_gridmaps.html</link>
+ <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link>
+ <link title="3D Kinematic Character Demo">https://godotengine.org/asset-library/asset/126</link>
</tutorials>
<methods>
<method name="clear">
diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp
index 7bb70b098f..320da182f8 100644
--- a/modules/jsonrpc/jsonrpc.cpp
+++ b/modules/jsonrpc/jsonrpc.cpp
@@ -98,6 +98,10 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem
if (p_action.get_type() == Variant::DICTIONARY) {
Dictionary dict = p_action;
String method = dict.get("method", "");
+ if (method.begins_with("$/")) {
+ return ret;
+ }
+
Array args;
if (dict.has("params")) {
Variant params = dict.get("params", Variant());
diff --git a/modules/jsonrpc/jsonrpc.h b/modules/jsonrpc/jsonrpc.h
index e2b7ab0975..6f3f8003e0 100644
--- a/modules/jsonrpc/jsonrpc.h
+++ b/modules/jsonrpc/jsonrpc.h
@@ -31,7 +31,7 @@
#ifndef GODOT_JSON_RPC_H
#define GODOT_JSON_RPC_H
-#include "core/object.h"
+#include "core/class_db.h"
#include "core/variant.h"
class JSONRPC : public Object {
diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp
index 501bfff075..12a982df6e 100644
--- a/modules/mbedtls/crypto_mbedtls.cpp
+++ b/modules/mbedtls/crypto_mbedtls.cpp
@@ -43,8 +43,8 @@
#define PEM_BEGIN_CRT "-----BEGIN CERTIFICATE-----\n"
#define PEM_END_CRT "-----END CERTIFICATE-----\n"
-#include "mbedtls/pem.h"
#include <mbedtls/debug.h>
+#include <mbedtls/pem.h>
CryptoKey *CryptoKeyMbedTLS::create() {
return memnew(CryptoKeyMbedTLS);
@@ -294,20 +294,15 @@ Ref<X509Certificate> CryptoMbedTLS::generate_self_signed_certificate(Ref<CryptoK
unsigned char buf[4096];
memset(buf, 0, 4096);
- Ref<X509CertificateMbedTLS> out;
- out.instance();
- mbedtls_x509write_crt_pem(&crt, buf, 4096, mbedtls_ctr_drbg_random, &ctr_drbg);
-
- int err = mbedtls_x509_crt_parse(&(out->cert), buf, 4096);
- if (err != 0) {
- mbedtls_mpi_free(&serial);
- mbedtls_x509write_crt_free(&crt);
- ERR_PRINT("Generated invalid certificate: " + itos(err));
- return nullptr;
- }
-
+ int ret = mbedtls_x509write_crt_pem(&crt, buf, 4096, mbedtls_ctr_drbg_random, &ctr_drbg);
mbedtls_mpi_free(&serial);
mbedtls_x509write_crt_free(&crt);
+ ERR_FAIL_COND_V_MSG(ret != 0, nullptr, "Failed to generate certificate: " + itos(ret));
+ buf[4095] = '\0'; // Make sure strlen can't fail.
+
+ Ref<X509CertificateMbedTLS> out;
+ out.instance();
+ out->load_from_memory(buf, strlen((char *)buf) + 1); // Use strlen to find correct output size.
return out;
}
diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py
index e959312393..6057004166 100644
--- a/modules/mono/build_scripts/mono_configure.py
+++ b/modules/mono/build_scripts/mono_configure.py
@@ -396,9 +396,8 @@ def configure(env, env_mono):
mono_root = subprocess.check_output(["pkg-config", "mono-2", "--variable=prefix"]).decode("utf8").strip()
if tools_enabled:
+ # Only supported for editor builds.
copy_mono_root_files(env, mono_root)
- else:
- print("Ignoring option: 'copy_mono_root'; only available for builds with 'tools' enabled.")
def make_template_dir(env, mono_root):
diff --git a/modules/mono/build_scripts/solution_builder.py b/modules/mono/build_scripts/solution_builder.py
index 03f4e57f02..6a621c3c8b 100644
--- a/modules/mono/build_scripts/solution_builder.py
+++ b/modules/mono/build_scripts/solution_builder.py
@@ -8,9 +8,6 @@ def find_dotnet_cli():
import os.path
if os.name == "nt":
- windows_exts = os.environ["PATHEXT"]
- windows_exts = windows_exts.split(os.pathsep) if windows_exts else []
-
for hint_dir in os.environ["PATH"].split(os.pathsep):
hint_dir = hint_dir.strip('"')
hint_path = os.path.join(hint_dir, "dotnet")
diff --git a/modules/mono/config.py b/modules/mono/config.py
index cd659057ef..d060ae9b28 100644
--- a/modules/mono/config.py
+++ b/modules/mono/config.py
@@ -23,18 +23,16 @@ def configure(env):
envvars.Add(
PathVariable(
"mono_prefix",
- "Path to the mono installation directory for the target platform and architecture",
+ "Path to the Mono installation directory for the target platform and architecture",
"",
PathVariable.PathAccept,
)
)
- envvars.Add(BoolVariable("mono_static", "Statically link mono", default_mono_static))
- envvars.Add(BoolVariable("mono_glue", "Build with the mono glue sources", True))
+ envvars.Add(BoolVariable("mono_static", "Statically link Mono", default_mono_static))
+ envvars.Add(BoolVariable("mono_glue", "Build with the Mono glue sources", True))
envvars.Add(BoolVariable("build_cil", "Build C# solutions", True))
envvars.Add(
- BoolVariable(
- "copy_mono_root", "Make a copy of the mono installation directory to bundle with the editor", False
- )
+ BoolVariable("copy_mono_root", "Make a copy of the Mono installation directory to bundle with the editor", True)
)
# TODO: It would be great if this could be detected automatically instead
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index bbdec224f0..70b731d611 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -975,7 +975,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
script->signals_invalidated = true;
script->reload(p_soft_reload);
- script->update_exports();
if (!script->valid) {
script->pending_reload_instances.clear();
@@ -2702,7 +2701,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
if (!property->has_getter()) {
#ifdef TOOLS_ENABLED
if (exported) {
- ERR_PRINT("Read-only property cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+ ERR_PRINT("Cannot export a property without a getter: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
}
#endif
return false;
@@ -2710,7 +2709,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
if (!property->has_setter()) {
#ifdef TOOLS_ENABLED
if (exported) {
- ERR_PRINT("Write-only property (without getter) cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+ ERR_PRINT("Cannot export a property without a setter: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
}
#endif
return false;
@@ -2955,13 +2954,24 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon
CRASH_COND(p_script->native == nullptr);
+ p_script->valid = true;
+
+ update_script_class_info(p_script);
+
+#ifdef TOOLS_ENABLED
+ p_script->_update_member_info_no_exports();
+#endif
+}
+
+// Extract information about the script using the mono class.
+void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
GDMonoClass *base = p_script->script_class->get_parent_class();
+ // `base` should only be set if the script is a user defined type.
if (base != p_script->native) {
p_script->base = base;
}
- p_script->valid = true;
p_script->tool = p_script->script_class->has_attribute(CACHED_CLASS(ToolAttribute));
if (!p_script->tool) {
@@ -2996,17 +3006,74 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon
p_script->script_class->fetch_methods_with_godot_api_checks(p_script->native);
- // Need to fetch method from base classes as well
+ p_script->rpc_functions.clear();
+ p_script->rpc_variables.clear();
+
GDMonoClass *top = p_script->script_class;
while (top && top != p_script->native) {
+ // Fetch methods from base classes as well
top->fetch_methods_with_godot_api_checks(p_script->native);
+
+ // Update RPC info
+ {
+ Vector<GDMonoMethod *> methods = top->get_all_methods();
+ for (int i = 0; i < methods.size(); i++) {
+ if (!methods[i]->is_static()) {
+ MultiplayerAPI::RPCMode mode = p_script->_member_get_rpc_mode(methods[i]);
+ if (MultiplayerAPI::RPC_MODE_DISABLED != mode) {
+ ScriptNetData nd;
+ nd.name = methods[i]->get_name();
+ nd.mode = mode;
+ if (-1 == p_script->rpc_functions.find(nd)) {
+ p_script->rpc_functions.push_back(nd);
+ }
+ }
+ }
+ }
+ }
+
+ {
+ Vector<GDMonoField *> fields = top->get_all_fields();
+ for (int i = 0; i < fields.size(); i++) {
+ if (!fields[i]->is_static()) {
+ MultiplayerAPI::RPCMode mode = p_script->_member_get_rpc_mode(fields[i]);
+ if (MultiplayerAPI::RPC_MODE_DISABLED != mode) {
+ ScriptNetData nd;
+ nd.name = fields[i]->get_name();
+ nd.mode = mode;
+ if (-1 == p_script->rpc_variables.find(nd)) {
+ p_script->rpc_variables.push_back(nd);
+ }
+ }
+ }
+ }
+ }
+
+ {
+ Vector<GDMonoProperty *> properties = top->get_all_properties();
+ for (int i = 0; i < properties.size(); i++) {
+ if (!properties[i]->is_static()) {
+ MultiplayerAPI::RPCMode mode = p_script->_member_get_rpc_mode(properties[i]);
+ if (MultiplayerAPI::RPC_MODE_DISABLED != mode) {
+ ScriptNetData nd;
+ nd.name = properties[i]->get_name();
+ nd.mode = mode;
+ if (-1 == p_script->rpc_variables.find(nd)) {
+ p_script->rpc_variables.push_back(nd);
+ }
+ }
+ }
+ }
+ }
+
top = top->get_parent_class();
}
+ // Sort so we are 100% that they are always the same.
+ p_script->rpc_functions.sort_custom<SortNetData>();
+ p_script->rpc_variables.sort_custom<SortNetData>();
+
p_script->load_script_signals(p_script->script_class, p_script->native);
-#ifdef TOOLS_ENABLED
- p_script->_update_member_info_no_exports();
-#endif
}
bool CSharpScript::can_instance() const {
@@ -3305,124 +3372,15 @@ Error CSharpScript::reload(bool p_keep_state) {
print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
#endif
- tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute));
-
- if (!tool) {
- GDMonoClass *nesting_class = script_class->get_nesting_class();
- tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
- }
-
-#if TOOLS_ENABLED
- if (!tool) {
- tool = script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly();
- }
-#endif
-
native = GDMonoUtils::get_class_native_base(script_class);
CRASH_COND(native == nullptr);
- GDMonoClass *base_class = script_class->get_parent_class();
-
- if (base_class != native) {
- base = base_class;
- }
-
-#ifdef DEBUG_ENABLED
- // For debug builds, we must fetch from all native base methods as well.
- // Native base methods must be fetched before the current class.
- // Not needed if the script class itself is a native class.
+ update_script_class_info(this);
- if (script_class != native) {
- GDMonoClass *native_top = native;
- while (native_top) {
- native_top->fetch_methods_with_godot_api_checks(native);
-
- if (native_top == CACHED_CLASS(GodotObject)) {
- break;
- }
-
- native_top = native_top->get_parent_class();
- }
- }
-#endif
-
- script_class->fetch_methods_with_godot_api_checks(native);
-
- // Need to fetch method from base classes as well
- GDMonoClass *top = script_class;
- while (top && top != native) {
- top->fetch_methods_with_godot_api_checks(native);
- top = top->get_parent_class();
- }
-
- load_script_signals(script_class, native);
_update_exports();
}
- rpc_functions.clear();
- rpc_variables.clear();
-
- GDMonoClass *top = script_class;
- while (top && top != native) {
- {
- Vector<GDMonoMethod *> methods = top->get_all_methods();
- for (int i = 0; i < methods.size(); i++) {
- if (!methods[i]->is_static()) {
- MultiplayerAPI::RPCMode mode = _member_get_rpc_mode(methods[i]);
- if (MultiplayerAPI::RPC_MODE_DISABLED != mode) {
- ScriptNetData nd;
- nd.name = methods[i]->get_name();
- nd.mode = mode;
- if (-1 == rpc_functions.find(nd)) {
- rpc_functions.push_back(nd);
- }
- }
- }
- }
- }
-
- {
- Vector<GDMonoField *> fields = top->get_all_fields();
- for (int i = 0; i < fields.size(); i++) {
- if (!fields[i]->is_static()) {
- MultiplayerAPI::RPCMode mode = _member_get_rpc_mode(fields[i]);
- if (MultiplayerAPI::RPC_MODE_DISABLED != mode) {
- ScriptNetData nd;
- nd.name = fields[i]->get_name();
- nd.mode = mode;
- if (-1 == rpc_variables.find(nd)) {
- rpc_variables.push_back(nd);
- }
- }
- }
- }
- }
-
- {
- Vector<GDMonoProperty *> properties = top->get_all_properties();
- for (int i = 0; i < properties.size(); i++) {
- if (!properties[i]->is_static()) {
- MultiplayerAPI::RPCMode mode = _member_get_rpc_mode(properties[i]);
- if (MultiplayerAPI::RPC_MODE_DISABLED != mode) {
- ScriptNetData nd;
- nd.name = properties[i]->get_name();
- nd.mode = mode;
- if (-1 == rpc_variables.find(nd)) {
- rpc_variables.push_back(nd);
- }
- }
- }
- }
- }
-
- top = top->get_parent_class();
- }
-
- // Sort so we are 100% that they are always the same.
- rpc_functions.sort_custom<SortNetData>();
- rpc_variables.sort_custom<SortNetData>();
-
return OK;
}
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index f0b43a40f9..cfe070a371 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -164,6 +164,7 @@ private:
// Do not use unless you know what you are doing
friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
static Ref<CSharpScript> create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native);
+ static void update_script_class_info(Ref<CSharpScript> p_script);
static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native);
MultiplayerAPI::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec
index 5b5cefe80e..ba68a4da43 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec
@@ -17,6 +17,6 @@
<repository url="$projecturl$" />
</metadata>
<files>
- <file src="Sdk\**" target="Sdk" />\
+ <file src="Sdk\**" target="Sdk" />
</files>
</package>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
index dfc59e6ccb..5febcf3175 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
@@ -14,15 +14,15 @@
<GodotProjectDir Condition=" '$(SolutionDir)' == '' ">$(MSBuildProjectDirectory)</GodotProjectDir>
<GodotProjectDir>$([MSBuild]::EnsureTrailingSlash('$(GodotProjectDir)'))</GodotProjectDir>
- <!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.mono\temp\'. -->
- <BaseOutputPath>$(GodotProjectDir).mono\temp\bin\</BaseOutputPath>
- <OutputPath>$(GodotProjectDir).mono\temp\bin\$(Configuration)\</OutputPath>
+ <!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.godot\mono\temp\'. -->
+ <BaseOutputPath>$(GodotProjectDir).godot\mono\temp\bin\</BaseOutputPath>
+ <OutputPath>$(GodotProjectDir).godot\mono\temp\bin\$(Configuration)\</OutputPath>
<!--
Use custom IntermediateOutputPath and BaseIntermediateOutputPath only if it wasn't already set.
Otherwise the old values may have already been changed by MSBuild which can cause problems with NuGet.
-->
- <IntermediateOutputPath Condition=" '$(IntermediateOutputPath)' == '' ">$(GodotProjectDir).mono\temp\obj\$(Configuration)\</IntermediateOutputPath>
- <BaseIntermediateOutputPath Condition=" '$(BaseIntermediateOutputPath)' == '' ">$(GodotProjectDir).mono\temp\obj\</BaseIntermediateOutputPath>
+ <IntermediateOutputPath Condition=" '$(IntermediateOutputPath)' == '' ">$(GodotProjectDir).godot\mono\temp\obj\$(Configuration)\</IntermediateOutputPath>
+ <BaseIntermediateOutputPath Condition=" '$(BaseIntermediateOutputPath)' == '' ">$(GodotProjectDir).godot\mono\temp\obj\</BaseIntermediateOutputPath>
<!-- Do not append the target framework name to the output path. -->
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
@@ -88,7 +88,7 @@
<PropertyGroup>
<!-- ExportDebug also defines DEBUG like Debug does. -->
<DefineConstants Condition=" '$(Configuration)' == 'ExportDebug' ">$(DefineConstants);DEBUG</DefineConstants>
- <!-- Debug defines TOOLS to differenciate between Debug and ExportDebug configurations. -->
+ <!-- Debug defines TOOLS to differentiate between Debug and ExportDebug configurations. -->
<DefineConstants Condition=" '$(Configuration)' == 'Debug' ">$(DefineConstants);TOOLS</DefineConstants>
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
@@ -102,11 +102,11 @@
-->
<Reference Include="GodotSharp">
<Private>false</Private>
- <HintPath>$(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath>
+ <HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath>
</Reference>
<Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' ">
<Private>false</Private>
- <HintPath>$(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath>
+ <HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
index 05e06babd4..b217ae4bf7 100644
--- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Runtime.InteropServices;
namespace GodotTools.Core
{
@@ -35,7 +36,17 @@ namespace GodotTools.Core
path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim();
- return rooted ? Path.DirectorySeparatorChar + path : path;
+ if (!rooted)
+ return path;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ string maybeDrive = parts[0];
+ if (maybeDrive.Length == 2 && maybeDrive[1] == ':')
+ return path; // Already has drive letter
+ }
+
+ return Path.DirectorySeparatorChar + path;
}
private static readonly string DriveRoot = Path.GetPathRoot(Environment.CurrentDirectory);
@@ -49,7 +60,7 @@ namespace GodotTools.Core
public static string ToSafeDirName(this string dirName, bool allowDirSeparator = false)
{
- var invalidChars = new List<string> { ":", "*", "?", "\"", "<", ">", "|" };
+ var invalidChars = new List<string> {":", "*", "?", "\"", "<", ">", "|"};
if (allowDirSeparator)
{
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs
index 572c541412..0f50c90531 100644
--- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs
@@ -121,7 +121,7 @@ namespace GodotTools.IdeMessaging
this.messageHandler = messageHandler;
this.logger = logger;
- string projectMetadataDir = Path.Combine(godotProjectDir, ".mono", "metadata");
+ string projectMetadataDir = Path.Combine(godotProjectDir, ".godot", "mono", "metadata");
MetaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
index 7bfba779fb..93aae2e03e 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
@@ -119,7 +119,7 @@ namespace GodotTools.Build
{
var result = new List<string>();
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/");
result.Add("/usr/local/var/homebrew/linked/mono/bin/");
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
index f60e469503..5bb8d444c2 100755
--- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
@@ -120,7 +120,7 @@ namespace GodotTools.Export
string assemblyPath = assembly.Value;
string outputFileExtension = platform == OS.Platforms.Windows ? ".dll" :
- platform == OS.Platforms.OSX ? ".dylib" :
+ platform == OS.Platforms.MacOS ? ".dylib" :
".so";
string outputFileName = assemblyName + ".dll" + outputFileExtension;
@@ -132,7 +132,7 @@ namespace GodotTools.Export
ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir);
- if (platform == OS.Platforms.OSX)
+ if (platform == OS.Platforms.MacOS)
{
exporter.AddSharedObject(tempOutputFilePath, tags: null);
}
@@ -328,7 +328,7 @@ MONO_AOT_MODE_LAST = 1000,
if (lipoExitCode != 0)
throw new Exception($"Command 'lipo' exited with code: {lipoExitCode}");
- // TODO: Add the AOT lib and interpreter libs as device only to supress warnings when targeting the simulator
+ // TODO: Add the AOT lib and interpreter libs as device only to suppress warnings when targeting the simulator
// Add the fat AOT static library to the Xcode project
exporter.AddIosProjectStaticLib(fatOutputFilePath);
@@ -581,7 +581,7 @@ MONO_AOT_MODE_LAST = 1000,
string arch = bits == "64" ? "x86_64" : "i686";
return $"windows-{arch}";
}
- case OS.Platforms.OSX:
+ case OS.Platforms.MacOS:
{
Debug.Assert(bits == null || bits == "64");
string arch = "x86_64";
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 599ca94699..cd188509b4 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -340,7 +340,7 @@ namespace GodotTools.Export
private static bool PlatformHasTemplateDir(string platform)
{
// OSX export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest.
- return !new[] {OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform);
+ return !new[] {OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform);
}
private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, out string platform)
@@ -411,7 +411,7 @@ namespace GodotTools.Export
case OS.Platforms.Windows:
case OS.Platforms.UWP:
return "net_4_x_win";
- case OS.Platforms.OSX:
+ case OS.Platforms.MacOS:
case OS.Platforms.LinuxBSD:
case OS.Platforms.Server:
case OS.Platforms.Haiku:
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 2a450c5b87..57d334b93e 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -272,7 +272,7 @@ namespace GodotTools
bool osxAppBundleInstalled = false;
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
// The package path is '/Applications/Visual Studio Code.app'
const string vscodeBundleId = "com.microsoft.VSCode";
@@ -312,7 +312,7 @@ namespace GodotTools
string command;
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
if (!osxAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath))
{
@@ -504,7 +504,7 @@ namespace GodotTools
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
$",JetBrains Rider:{(int)ExternalEditorId.Rider}";
}
- else if (OS.IsOSX)
+ else if (OS.IsMacOS)
{
settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudioForMac}" +
$",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
index e4932ca217..451ce39f5c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
@@ -111,7 +111,7 @@ namespace GodotTools.Ides
{
MonoDevelop.Instance GetMonoDevelopInstance(string solutionPath)
{
- if (Utils.OS.IsOSX && editorId == ExternalEditorId.VisualStudioForMac)
+ if (Utils.OS.IsMacOS && editorId == ExternalEditorId.VisualStudioForMac)
{
vsForMacInstance = (vsForMacInstance?.IsDisposed ?? true ? null : vsForMacInstance) ??
new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
index d6fa2eeba7..fd7bbd5578 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
@@ -26,7 +26,7 @@ namespace GodotTools.Ides.MonoDevelop
string command;
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
string bundleId = BundleIds[editorId];
@@ -85,7 +85,7 @@ namespace GodotTools.Ides.MonoDevelop
public Instance(string solutionFile, EditorId editorId)
{
- if (editorId == EditorId.VisualStudioForMac && !OS.IsOSX)
+ if (editorId == EditorId.VisualStudioForMac && !OS.IsMacOS)
throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform");
this.solutionFile = solutionFile;
@@ -103,7 +103,7 @@ namespace GodotTools.Ides.MonoDevelop
static Instance()
{
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
ExecutableNames = new Dictionary<EditorId, string>
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs
index e22e9af919..94fc5da425 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs
@@ -32,7 +32,7 @@ namespace GodotTools.Ides.Rider
{
return CollectRiderInfosWindows();
}
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
return CollectRiderInfosMac();
}
@@ -138,7 +138,7 @@ namespace GodotTools.Ides.Rider
return GetToolboxRiderRootPath(localAppData);
}
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
var home = Environment.GetEnvironmentVariable("HOME");
if (string.IsNullOrEmpty(home))
@@ -211,7 +211,7 @@ namespace GodotTools.Ides.Rider
{
if (OS.IsWindows || OS.IsUnixLike)
return "../../build.txt";
- if (OS.IsOSX)
+ if (OS.IsMacOS)
return "Contents/Resources/build.txt";
throw new Exception("Unknown OS.");
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
index 6c05891f2c..e745966435 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
@@ -21,7 +21,7 @@ namespace GodotTools.Utils
public static class Names
{
public const string Windows = "Windows";
- public const string OSX = "OSX";
+ public const string MacOS = "macOS";
public const string Linux = "Linux";
public const string FreeBSD = "FreeBSD";
public const string NetBSD = "NetBSD";
@@ -37,7 +37,7 @@ namespace GodotTools.Utils
public static class Platforms
{
public const string Windows = "windows";
- public const string OSX = "osx";
+ public const string MacOS = "osx";
public const string LinuxBSD = "linuxbsd";
public const string Server = "server";
public const string UWP = "uwp";
@@ -50,7 +50,7 @@ namespace GodotTools.Utils
public static readonly Dictionary<string, string> PlatformNameMap = new Dictionary<string, string>
{
[Names.Windows] = Platforms.Windows,
- [Names.OSX] = Platforms.OSX,
+ [Names.MacOS] = Platforms.MacOS,
[Names.Linux] = Platforms.LinuxBSD,
[Names.FreeBSD] = Platforms.LinuxBSD,
[Names.NetBSD] = Platforms.LinuxBSD,
@@ -77,11 +77,11 @@ namespace GodotTools.Utils
new[] {Names.Linux, Names.FreeBSD, Names.NetBSD, Names.BSD};
private static readonly IEnumerable<string> UnixLikePlatforms =
- new[] {Names.OSX, Names.Server, Names.Haiku, Names.Android, Names.iOS}
+ new[] {Names.MacOS, Names.Server, Names.Haiku, Names.Android, Names.iOS}
.Concat(LinuxBSDPlatforms).ToArray();
private static readonly Lazy<bool> _isWindows = new Lazy<bool>(() => IsOS(Names.Windows));
- private static readonly Lazy<bool> _isOSX = new Lazy<bool>(() => IsOS(Names.OSX));
+ private static readonly Lazy<bool> _isMacOS = new Lazy<bool>(() => IsOS(Names.MacOS));
private static readonly Lazy<bool> _isLinuxBSD = new Lazy<bool>(() => IsAnyOS(LinuxBSDPlatforms));
private static readonly Lazy<bool> _isServer = new Lazy<bool>(() => IsOS(Names.Server));
private static readonly Lazy<bool> _isUWP = new Lazy<bool>(() => IsOS(Names.UWP));
@@ -92,7 +92,7 @@ namespace GodotTools.Utils
private static readonly Lazy<bool> _isUnixLike = new Lazy<bool>(() => IsAnyOS(UnixLikePlatforms));
public static bool IsWindows => _isWindows.Value || IsUWP;
- public static bool IsOSX => _isOSX.Value;
+ public static bool IsMacOS => _isMacOS.Value;
public static bool IsLinuxBSD => _isLinuxBSD.Value;
public static bool IsServer => _isServer.Value;
public static bool IsUWP => _isUWP.Value;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
index a963810d63..f77d3052f4 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
@@ -44,6 +44,15 @@ namespace Godot.Collections
Add(element);
}
+ public Array(params object[] array) : this()
+ {
+ if (array == null)
+ {
+ throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'");
+ }
+ safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor_MonoArray(array));
+ }
+
internal Array(ArraySafeHandle handle)
{
safeHandle = handle;
@@ -72,6 +81,11 @@ namespace Godot.Collections
return godot_icall_Array_Resize(GetPtr(), newSize);
}
+ public static Array operator +(Array left, Array right)
+ {
+ return new Array(godot_icall_Array_Concatenate(left.GetPtr(), right.GetPtr()));
+ }
+
// IDisposable
public void Dispose()
@@ -155,6 +169,9 @@ namespace Godot.Collections
internal extern static IntPtr godot_icall_Array_Ctor();
[MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static IntPtr godot_icall_Array_Ctor_MonoArray(System.Array array);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_Array_Dtor(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -176,6 +193,9 @@ namespace Godot.Collections
internal extern static void godot_icall_Array_Clear(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static IntPtr godot_icall_Array_Concatenate(IntPtr left, IntPtr right);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
internal extern static bool godot_icall_Array_Contains(IntPtr ptr, object item);
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -231,6 +251,15 @@ namespace Godot.Collections
objectArray = new Array(collection);
}
+ public Array(params T[] array) : this()
+ {
+ if (array == null)
+ {
+ throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'");
+ }
+ objectArray = new Array(array);
+ }
+
public Array(Array array)
{
objectArray = array;
@@ -266,6 +295,11 @@ namespace Godot.Collections
return objectArray.Resize(newSize);
}
+ public static Array<T> operator +(Array<T> left, Array<T> right)
+ {
+ return new Array<T>(left.objectArray + right.objectArray);
+ }
+
// IList<T>
public T this[int index]
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
index 5aba31c622..3f1120575f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
@@ -207,7 +207,7 @@ namespace Godot
}
}
- internal Quat RotationQuat()
+ public Quat RotationQuat()
{
Basis orthonormalizedBasis = Orthonormalized();
real_t det = orthonormalizedBasis.Determinant();
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
index 41b4e9367f..d63db0f905 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
@@ -393,6 +393,35 @@ namespace Godot
return instance.Substring(sep + 1);
}
+ /// <summary>
+ /// Converts the given byte array of ASCII encoded text to a string.
+ /// Faster alternative to <see cref="GetStringFromUTF8"/> if the
+ /// content is ASCII-only. Unlike the UTF-8 function this function
+ /// maps every byte to a character in the array. Multibyte sequences
+ /// will not be interpreted correctly. For parsing user input always
+ /// use <see cref="GetStringFromUTF8"/>.
+ /// </summary>
+ /// <param name="bytes">A byte array of ASCII characters (on the range of 0-127).</param>
+ /// <returns>A string created from the bytes.</returns>
+ public static string GetStringFromASCII(this byte[] bytes)
+ {
+ return Encoding.ASCII.GetString(bytes);
+ }
+
+ /// <summary>
+ /// Converts the given byte array of UTF-8 encoded text to a string.
+ /// Slower than <see cref="GetStringFromASCII"/> but supports UTF-8
+ /// encoded data. Use this function if you are unsure about the
+ /// source of the data. For user input this function
+ /// should always be preferred.
+ /// </summary>
+ /// <param name="bytes">A byte array of UTF-8 characters (a character may take up multiple bytes).</param>
+ /// <returns>A string created from the bytes.</returns>
+ public static string GetStringFromUTF8(this byte[] bytes)
+ {
+ return Encoding.UTF8.GetString(bytes);
+ }
+
// <summary>
// Hash the string and return a 32 bits integer.
// </summary>
@@ -440,7 +469,12 @@ namespace Godot
// </summary>
public static bool IsAbsPath(this string instance)
{
- return System.IO.Path.IsPathRooted(instance);
+ if (string.IsNullOrEmpty(instance))
+ return false;
+ else if (instance.Length > 1)
+ return instance[0] == '/' || instance[0] == '\\' || instance.Contains(":/") || instance.Contains(":\\");
+ else
+ return instance[0] == '/' || instance[0] == '\\';
}
// <summary>
@@ -448,7 +482,7 @@ namespace Godot
// </summary>
public static bool IsRelPath(this string instance)
{
- return !System.IO.Path.IsPathRooted(instance);
+ return !IsAbsPath(instance);
}
// <summary>
@@ -624,41 +658,46 @@ namespace Godot
return instance.Length;
}
- // <summary>
- // Do a simple expression match, where '*' matches zero or more arbitrary characters and '?' matches any single character except '.'.
- // </summary>
- public static bool ExprMatch(this string instance, string expr, bool caseSensitive)
+ /// <summary>
+ /// Do a simple expression match, where '*' matches zero or more arbitrary characters and '?' matches any single character except '.'.
+ /// </summary>
+ private static bool ExprMatch(this string instance, string expr, bool caseSensitive)
{
- if (expr.Length == 0 || instance.Length == 0)
- return false;
+ // case '\0':
+ if (expr.Length == 0)
+ return instance.Length == 0;
switch (expr[0])
{
- case '\0':
- return instance[0] == 0;
case '*':
- return ExprMatch(expr + 1, instance, caseSensitive) || instance[0] != 0 && ExprMatch(expr, instance + 1, caseSensitive);
+ return ExprMatch(instance, expr.Substring(1), caseSensitive) || (instance.Length > 0 && ExprMatch(instance.Substring(1), expr, caseSensitive));
case '?':
- return instance[0] != 0 && instance[0] != '.' && ExprMatch(expr + 1, instance + 1, caseSensitive);
+ return instance.Length > 0 && instance[0] != '.' && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive);
default:
- return (caseSensitive ? instance[0] == expr[0] : char.ToUpper(instance[0]) == char.ToUpper(expr[0])) &&
- ExprMatch(expr + 1, instance + 1, caseSensitive);
+ if (instance.Length == 0) return false;
+ return (caseSensitive ? instance[0] == expr[0] : char.ToUpper(instance[0]) == char.ToUpper(expr[0])) && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive);
}
}
- // <summary>
- // Do a simple case sensitive expression match, using ? and * wildcards (see [method expr_match]).
- // </summary>
+ /// <summary>
+ /// Do a simple case sensitive expression match, using ? and * wildcards (see [method expr_match]).
+ /// </summary>
public static bool Match(this string instance, string expr, bool caseSensitive = true)
{
+ if (instance.Length == 0 || expr.Length == 0)
+ return false;
+
return instance.ExprMatch(expr, caseSensitive);
}
- // <summary>
- // Do a simple case insensitive expression match, using ? and * wildcards (see [method expr_match]).
- // </summary>
+ /// <summary>
+ /// Do a simple case insensitive expression match, using ? and * wildcards (see [method expr_match]).
+ /// </summary>
public static bool MatchN(this string instance, string expr)
{
+ if (instance.Length == 0 || expr.Length == 0)
+ return false;
+
return instance.ExprMatch(expr, caseSensitive: false);
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
index 3dff37279b..d536b14eac 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
@@ -437,8 +437,11 @@ namespace Godot
/// <returns>The rotated vector.</returns>
public Vector2 Rotated(real_t phi)
{
- real_t rads = Angle() + phi;
- return new Vector2(Mathf.Cos(rads), Mathf.Sin(rads)) * Length();
+ real_t sine = Mathf.Sin(phi);
+ real_t cosi = Mathf.Cos(phi);
+ return new Vector2(
+ x * cosi - y * sine,
+ x * sine + y * cosi);
}
/// <summary>
diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp
index ebcd6d5e9c..439fb1ab0a 100644
--- a/modules/mono/glue/base_object_glue.cpp
+++ b/modules/mono/glue/base_object_glue.cpp
@@ -31,7 +31,6 @@
#ifdef MONO_GLUE_ENABLED
#include "core/class_db.h"
-#include "core/object.h"
#include "core/reference.h"
#include "core/string_name.h"
diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp
index 766b00d612..3313e8cb77 100644
--- a/modules/mono/glue/collections_glue.cpp
+++ b/modules/mono/glue/collections_glue.cpp
@@ -104,10 +104,31 @@ void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index) {
}
}
+Array *godot_icall_Array_Ctor_MonoArray(MonoArray *mono_array) {
+ Array *godot_array = memnew(Array);
+ unsigned int count = mono_array_length(mono_array);
+ godot_array->resize(count);
+ for (unsigned int i = 0; i < count; i++) {
+ MonoObject *item = mono_array_get(mono_array, MonoObject *, i);
+ godot_icall_Array_SetAt(godot_array, i, item);
+ }
+ return godot_array;
+}
+
Array *godot_icall_Array_Duplicate(Array *ptr, MonoBoolean deep) {
return memnew(Array(ptr->duplicate(deep)));
}
+Array *godot_icall_Array_Concatenate(Array *left, Array *right) {
+ int count = left->size() + right->size();
+ Array *new_array = memnew(Array(left->duplicate(false)));
+ new_array->resize(count);
+ for (unsigned int i = 0; i < (unsigned int)right->size(); i++) {
+ new_array->operator[](i + left->size()) = right->operator[](i);
+ }
+ return new_array;
+}
+
int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) {
return ptr->find(GDMonoMarshal::mono_object_to_variant(item));
}
@@ -284,6 +305,7 @@ MonoString *godot_icall_Dictionary_ToString(Dictionary *ptr) {
void godot_register_collections_icalls() {
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor", (void *)godot_icall_Array_Ctor);
+ mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor_MonoArray", (void *)godot_icall_Array_Ctor_MonoArray);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Dtor", (void *)godot_icall_Array_Dtor);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_At", (void *)godot_icall_Array_At);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_At_Generic", (void *)godot_icall_Array_At_Generic);
@@ -291,6 +313,7 @@ void godot_register_collections_icalls() {
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Count", (void *)godot_icall_Array_Count);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Add", (void *)godot_icall_Array_Add);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Clear", (void *)godot_icall_Array_Clear);
+ mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Concatenate", (void *)godot_icall_Array_Concatenate);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Contains", (void *)godot_icall_Array_Contains);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_CopyTo", (void *)godot_icall_Array_CopyTo);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Duplicate", (void *)godot_icall_Array_Duplicate);
diff --git a/modules/mono/glue/glue_header.h b/modules/mono/glue/glue_header.h
index c1f1936711..dc3bf47838 100644
--- a/modules/mono/glue/glue_header.h
+++ b/modules/mono/glue/glue_header.h
@@ -64,7 +64,6 @@ void godot_register_glue_header_icalls() {
#include "core/engine.h"
#include "core/method_bind.h"
#include "core/node_path.h"
-#include "core/object.h"
#include "core/reference.h"
#include "core/typedefs.h"
#include "core/ustring.h"
diff --git a/modules/mono/glue/rid_glue.cpp b/modules/mono/glue/rid_glue.cpp
index 6d2e6b559f..a7b18c36dd 100644
--- a/modules/mono/glue/rid_glue.cpp
+++ b/modules/mono/glue/rid_glue.cpp
@@ -30,7 +30,7 @@
#ifdef MONO_GLUE_ENABLED
-#include "core/object.h"
+#include "core/class_db.h"
#include "core/resource.h"
#include "core/rid.h"
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 692da991c7..df31823deb 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -122,7 +122,7 @@ public:
private:
_GodotSharpDirs() {
- res_data_dir = "res://.mono";
+ res_data_dir = "res://.godot/mono";
res_metadata_dir = res_data_dir.plus_file("metadata");
res_assemblies_base_dir = res_data_dir.plus_file("assemblies");
res_assemblies_dir = res_assemblies_base_dir.plus_file(GDMono::get_expected_api_build_config());
diff --git a/modules/mono/mono_gd/gd_mono_internals.h b/modules/mono/mono_gd/gd_mono_internals.h
index 038d17f782..cf7efdecd6 100644
--- a/modules/mono/mono_gd/gd_mono_internals.h
+++ b/modules/mono/mono_gd/gd_mono_internals.h
@@ -35,7 +35,7 @@
#include "../utils/macros.h"
-#include "core/object.h"
+#include "core/class_db.h"
namespace GDMonoInternals {
diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp
index c5a988b8c3..b8ee0204c4 100644
--- a/modules/mono/mono_gd/gd_mono_log.cpp
+++ b/modules/mono/mono_gd/gd_mono_log.cpp
@@ -64,25 +64,32 @@ static int get_log_level_id(const char *p_log_level) {
return -1;
}
+static String make_text(const char *log_domain, const char *log_level, const char *message) {
+ String text(message);
+ text += " (in domain ";
+ text += log_domain;
+ if (log_level) {
+ text += ", ";
+ text += log_level;
+ }
+ text += ")";
+ return text;
+}
+
void GDMonoLog::mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *) {
FileAccess *f = GDMonoLog::get_singleton()->log_file;
if (GDMonoLog::get_singleton()->log_level_id >= get_log_level_id(log_level)) {
- String text(message);
- text += " (in domain ";
- text += log_domain;
- if (log_level) {
- text += ", ";
- text += log_level;
- }
- text += ")\n";
+ String text = make_text(log_domain, log_level, message);
+ text += "\n";
f->seek_end();
f->store_string(text);
}
if (fatal) {
- ERR_PRINT("Mono: FATAL ERROR, ABORTING! Logfile: '" + GDMonoLog::get_singleton()->log_file_path + "'.");
+ String text = make_text(log_domain, log_level, message);
+ ERR_PRINT("Mono: FATAL ERROR '" + text + "', ABORTING! Logfile: '" + GDMonoLog::get_singleton()->log_file_path + "'.");
// Make sure to flush before aborting
f->flush();
f->close();
diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h
index 5958bf3cc1..80569cb1c7 100644
--- a/modules/mono/mono_gd/gd_mono_utils.h
+++ b/modules/mono/mono_gd/gd_mono_utils.h
@@ -37,7 +37,7 @@
#include "../utils/macros.h"
#include "gd_mono_header.h"
-#include "core/object.h"
+#include "core/class_db.h"
#include "core/reference.h"
#define UNHANDLED_EXCEPTION(m_exc) \
diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm
index e3d1a647fd..dc23c06eba 100644
--- a/modules/mono/mono_gd/support/ios_support.mm
+++ b/modules/mono/mono_gd/support/ios_support.mm
@@ -131,8 +131,7 @@ GD_PINVOKE_EXPORT void *xamarin_timezone_get_data(const char *p_name, uint32_t *
NSTimeZone *tz = nil;
if (p_name) {
NSString *n = [[NSString alloc] initWithUTF8String:p_name];
- tz = [[[NSTimeZone alloc] initWithName:n] autorelease];
- [n release];
+ tz = [[NSTimeZone alloc] initWithName:n];
} else {
tz = [NSTimeZone localTimeZone];
}
diff --git a/modules/pvr/texture_loader_pvr.cpp b/modules/pvr/texture_loader_pvr.cpp
index 36c0913f62..050dce1aab 100644
--- a/modules/pvr/texture_loader_pvr.cpp
+++ b/modules/pvr/texture_loader_pvr.cpp
@@ -213,10 +213,12 @@ static void _compress_pvrtc4(Image *p_img) {
int ofs, size, w, h;
img->get_mipmap_offset_size_and_dimensions(i, ofs, size, w, h);
Javelin::RgbaBitmap bm(w, h);
+ void *dst = (void *)bm.GetData();
+ copymem(dst, &r[ofs], size);
+ Javelin::ColorRgba<unsigned char> *dp = bm.GetData();
for (int j = 0; j < size / 4; j++) {
- Javelin::ColorRgba<unsigned char> *dp = bm.GetData();
- /* red and Green colors are swapped. */
- new (dp) Javelin::ColorRgba<unsigned char>(r[ofs + 4 * j + 2], r[ofs + 4 * j + 1], r[ofs + 4 * j], r[ofs + 4 * j + 3]);
+ /* red and blue colors are swapped. */
+ SWAP(dp[j].r, dp[j].b);
}
new_img->get_mipmap_offset_size_and_dimensions(i, ofs, size, w, h);
Javelin::PvrTcEncoder::EncodeRgba4Bpp(&wr[ofs], bm);
diff --git a/modules/theora/doc_classes/VideoStreamTheora.xml b/modules/theora/doc_classes/VideoStreamTheora.xml
index 92244a4d28..cb8852d5ef 100644
--- a/modules/theora/doc_classes/VideoStreamTheora.xml
+++ b/modules/theora/doc_classes/VideoStreamTheora.xml
@@ -4,7 +4,8 @@
[VideoStream] resource for Ogg Theora videos.
</brief_description>
<description>
- [VideoStream] resource handling the [url=https://www.theora.org/]Ogg Theora[/url] video format with [code].ogv[/code] extension.
+ [VideoStream] resource handling the [url=https://www.theora.org/]Ogg Theora[/url] video format with [code].ogv[/code] extension. The Theora codec is less efficient than [VideoStreamWebm]'s VP8 and VP9, but it requires less CPU resources to decode. The Theora codec is decoded on the CPU.
+ [b]Note:[/b] While Ogg Theora videos can also have an [code].ogg[/code] extension, you will have to rename the extension to [code].ogv[/code] to use those videos within Godot.
</description>
<tutorials>
</tutorials>
@@ -22,7 +23,7 @@
<argument index="0" name="file" type="String">
</argument>
<description>
- Sets the Ogg Theora video file that this [VideoStreamTheora] resource handles. The [code]file[/code] name should have the [code].o[/code] extension.
+ Sets the Ogg Theora video file that this [VideoStreamTheora] resource handles. The [code]file[/code] name should have the [code].ogv[/code] extension.
</description>
</method>
</methods>
diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp
index 5581ea9318..01c674d086 100644
--- a/modules/visual_script/visual_script_editor.cpp
+++ b/modules/visual_script/visual_script_editor.cpp
@@ -30,8 +30,8 @@
#include "visual_script_editor.h"
+#include "core/class_db.h"
#include "core/input/input.h"
-#include "core/object.h"
#include "core/os/keyboard.h"
#include "core/script_language.h"
#include "core/variant.h"
@@ -1124,8 +1124,8 @@ void VisualScriptEditor::_update_members() {
TreeItem *ti = members->create_item(variables);
ti->set_text(0, E->get());
- Variant var = script->get_variable_default_value(E->get());
- ti->set_suffix(0, "= " + String(var));
+
+ ti->set_suffix(0, "= " + _sanitized_variant_text(E->get()));
ti->set_icon(0, type_icons[script->get_variable_info(E->get()).type]);
ti->set_selectable(0, true);
@@ -1167,6 +1167,18 @@ void VisualScriptEditor::_update_members() {
updating_members = false;
}
+String VisualScriptEditor::_sanitized_variant_text(const StringName &property_name) {
+ Variant var = script->get_variable_default_value(property_name);
+
+ if (script->get_variable_info(property_name).type != Variant::NIL) {
+ Callable::CallError ce;
+ const Variant *converted = &var;
+ var = Variant::construct(script->get_variable_info(property_name).type, &converted, 1, ce, false);
+ }
+
+ return String(var);
+}
+
void VisualScriptEditor::_member_selected() {
if (updating_members) {
return;
@@ -2682,7 +2694,8 @@ void VisualScriptEditor::reload(bool p_soft) {
_update_graph();
}
-void VisualScriptEditor::get_breakpoints(List<int> *p_breakpoints) {
+Array VisualScriptEditor::get_breakpoints() {
+ Array breakpoints;
List<StringName> functions;
script->get_function_list(&functions);
for (List<StringName>::Element *E = functions.front(); E; E = E->next()) {
@@ -2691,10 +2704,11 @@ void VisualScriptEditor::get_breakpoints(List<int> *p_breakpoints) {
for (List<int>::Element *F = nodes.front(); F; F = F->next()) {
Ref<VisualScriptNode> vsn = script->get_node(E->get(), F->get());
if (vsn->is_breakpoint()) {
- p_breakpoints->push_back(F->get() - 1); //subtract 1 because breakpoints in text start from zero
+ breakpoints.push_back(F->get() - 1); //subtract 1 because breakpoints in text start from zero
}
}
}
+ return breakpoints;
}
void VisualScriptEditor::add_callback(const String &p_function, PackedStringArray p_args) {
@@ -4769,8 +4783,8 @@ VisualScriptEditor::VisualScriptEditor() {
graph->set_v_size_flags(Control::SIZE_EXPAND_FILL);
graph->set_anchors_and_margins_preset(Control::PRESET_WIDE);
graph->connect("node_selected", callable_mp(this, &VisualScriptEditor::_node_selected));
- graph->connect("_begin_node_move", callable_mp(this, &VisualScriptEditor::_begin_node_move));
- graph->connect("_end_node_move", callable_mp(this, &VisualScriptEditor::_end_node_move));
+ graph->connect("begin_node_move", callable_mp(this, &VisualScriptEditor::_begin_node_move));
+ graph->connect("end_node_move", callable_mp(this, &VisualScriptEditor::_end_node_move));
graph->connect("delete_nodes_request", callable_mp(this, &VisualScriptEditor::_on_nodes_delete));
graph->connect("duplicate_nodes_request", callable_mp(this, &VisualScriptEditor::_on_nodes_duplicate));
graph->connect("gui_input", callable_mp(this, &VisualScriptEditor::_graph_gui_input));
diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h
index 0c5665cee8..66e435741f 100644
--- a/modules/visual_script/visual_script_editor.h
+++ b/modules/visual_script/visual_script_editor.h
@@ -146,6 +146,7 @@ class VisualScriptEditor : public ScriptEditorBase {
bool updating_members;
void _update_members();
+ String _sanitized_variant_text(const StringName &property_name);
StringName selected;
@@ -312,7 +313,7 @@ public:
virtual void ensure_focus() override;
virtual void tag_saved_version() override;
virtual void reload(bool p_soft) override;
- virtual void get_breakpoints(List<int> *p_breakpoints) override;
+ virtual Array get_breakpoints() override;
virtual void add_callback(const String &p_function, PackedStringArray p_args) override;
virtual void update_settings() override;
virtual bool show_members_overview() override;
diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp
index f13377f3c8..68de686272 100644
--- a/modules/visual_script/visual_script_func_nodes.cpp
+++ b/modules/visual_script/visual_script_func_nodes.cpp
@@ -2088,6 +2088,7 @@ void VisualScriptPropertyGet::_bind_methods() {
BIND_ENUM_CONSTANT(CALL_MODE_SELF);
BIND_ENUM_CONSTANT(CALL_MODE_NODE_PATH);
BIND_ENUM_CONSTANT(CALL_MODE_INSTANCE);
+ BIND_ENUM_CONSTANT(CALL_MODE_BASIC_TYPE);
}
class VisualScriptNodeInstancePropertyGet : public VisualScriptNodeInstance {
diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp
index 1b77ed3168..28122ade99 100644
--- a/modules/visual_script/visual_script_nodes.cpp
+++ b/modules/visual_script/visual_script_nodes.cpp
@@ -1706,8 +1706,10 @@ public:
virtual int step(const Variant **p_inputs, Variant **p_outputs, StartMode p_start_mode, Variant *p_working_mem, Callable::CallError &r_error, String &r_error_str) {
bool valid;
+ // *p_output[0] points to the same place as *p_inputs[2] so we need a temp to store the value before the change in the next line
+ Variant temp = *p_inputs[2];
*p_outputs[0] = *p_inputs[0];
- p_outputs[0]->set(*p_inputs[1], *p_inputs[2], &valid);
+ p_outputs[0]->set(*p_inputs[1], temp, &valid);
if (!valid) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
diff --git a/modules/webm/doc_classes/VideoStreamWebm.xml b/modules/webm/doc_classes/VideoStreamWebm.xml
index dfa04720cf..2edbc08cc8 100644
--- a/modules/webm/doc_classes/VideoStreamWebm.xml
+++ b/modules/webm/doc_classes/VideoStreamWebm.xml
@@ -4,7 +4,8 @@
[VideoStream] resource for WebM videos.
</brief_description>
<description>
- [VideoStream] resource handling the [url=https://www.webmproject.org/]WebM[/url] video format with [code].webm[/code] extension.
+ [VideoStream] resource handling the [url=https://www.webmproject.org/]WebM[/url] video format with [code].webm[/code] extension. Both the VP8 and VP9 codecs are supported. The VP8 and VP9 codecs are more efficient than [VideoStreamTheora], but they require more CPU resources to decode (especially VP9). Both the VP8 and VP9 codecs are decoded on the CPU.
+ [b]Note:[/b] There are known bugs and performance issues with WebM video playback in Godot. If you run into problems, try using the Ogg Theora format instead: [VideoStreamTheora]
</description>
<tutorials>
</tutorials>
diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
index c80b903e39..e21dee8eff 100644
--- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
+++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
@@ -40,7 +40,6 @@
<argument index="0" name="label" type="String">
</argument>
<argument index="1" name="options" type="Dictionary" default="{
-
}">
</argument>
<description>
@@ -82,7 +81,6 @@
<return type="int" enum="Error">
</return>
<argument index="0" name="configuration" type="Dictionary" default="{
-
}">
</argument>
<description>