diff --git a/core/math/aabb.cpp b/core/math/aabb.cpp index 7d1d7c5648b3..9b27305e7ec7 100644 --- a/core/math/aabb.cpp +++ b/core/math/aabb.cpp @@ -445,5 +445,8 @@ Variant AABB::intersects_ray_bind(const Vector3 &p_from, const Vector3 &p_dir) c } AABB::operator String() const { - return "[P: " + position.operator String() + ", S: " + size + "]"; + return String::concat( + "[P: ", position.operator String(), + ", S: ", size, + "]"); } diff --git a/core/math/basis.cpp b/core/math/basis.cpp index e5f3431eef84..d5f708d660be 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -715,9 +715,11 @@ bool Basis::operator!=(const Basis &p_matrix) const { } Basis::operator String() const { - return "[X: " + get_column(0).operator String() + - ", Y: " + get_column(1).operator String() + - ", Z: " + get_column(2).operator String() + "]"; + return String::concat( + "[X: ", get_column(0).operator String(), + ", Y: ", get_column(1).operator String(), + ", Z: ", get_column(2).operator String(), + "]"); } Quaternion Basis::get_quaternion() const { diff --git a/core/math/color.cpp b/core/math/color.cpp index 4c31d8149d8a..2647ce58db2a 100644 --- a/core/math/color.cpp +++ b/core/math/color.cpp @@ -487,7 +487,13 @@ Color Color::from_rgba8(int64_t p_r8, int64_t p_g8, int64_t p_b8, int64_t p_a8) } Color::operator String() const { - return "(" + String::num(r, 4) + ", " + String::num(g, 4) + ", " + String::num(b, 4) + ", " + String::num(a, 4) + ")"; + return String::concat( + "(", + String::num(r, 4), ", ", + String::num(g, 4), ", ", + String::num(b, 4), ", ", + String::num(a, 4), + ")"); } Color Color::operator+(const Color &p_color) const { diff --git a/core/math/face3.cpp b/core/math/face3.cpp index 1dff0ee4a6f0..5e08a760ca7c 100644 --- a/core/math/face3.cpp +++ b/core/math/face3.cpp @@ -201,7 +201,10 @@ bool Face3::intersects_aabb(const AABB &p_aabb) const { } Face3::operator String() const { - return String() + vertex[0] + ", " + vertex[1] + ", " + vertex[2]; + return String::concat( + vertex[0], ", ", + vertex[1], ", ", + vertex[2]); } void Face3::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { diff --git a/core/math/plane.cpp b/core/math/plane.cpp index 6b9bcea08131..6d5d667c9af8 100644 --- a/core/math/plane.cpp +++ b/core/math/plane.cpp @@ -177,5 +177,8 @@ bool Plane::is_finite() const { } Plane::operator String() const { - return "[N: " + normal.operator String() + ", D: " + String::num_real(d, false) + "]"; + return String::concat( + "[N: ", normal.operator String(), + ", D: ", String::num_real(d, false), + "]"); } diff --git a/core/math/projection.cpp b/core/math/projection.cpp index 20638826a6e5..95dbcffd85d9 100644 --- a/core/math/projection.cpp +++ b/core/math/projection.cpp @@ -912,10 +912,12 @@ void Projection::set_light_atlas_rect(const Rect2 &p_rect) { } Projection::operator String() const { - return "[X: " + columns[0].operator String() + - ", Y: " + columns[1].operator String() + - ", Z: " + columns[2].operator String() + - ", W: " + columns[3].operator String() + "]"; + return String::concat( + "[X: ", columns[0].operator String(), + ", Y: ", columns[1].operator String(), + ", Z: ", columns[2].operator String(), + ", W: ", columns[3].operator String(), + "]"); } real_t Projection::get_aspect() const { diff --git a/core/math/quaternion.cpp b/core/math/quaternion.cpp index 08eac14b76d6..b675e2b6ac94 100644 --- a/core/math/quaternion.cpp +++ b/core/math/quaternion.cpp @@ -277,7 +277,13 @@ Quaternion Quaternion::spherical_cubic_interpolate_in_time(const Quaternion &p_b } Quaternion::operator String() const { - return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ", " + String::num_real(w, false) + ")"; + return String::concat( + "(", + String::num_real(x, false), ", ", + String::num_real(y, false), ", ", + String::num_real(z, false), ", ", + String::num_real(w, false), + ")"); } Vector3 Quaternion::get_axis() const { @@ -294,7 +300,7 @@ real_t Quaternion::get_angle() const { Quaternion::Quaternion(const Vector3 &p_axis, real_t p_angle) { #ifdef MATH_CHECKS - ERR_FAIL_COND_MSG(!p_axis.is_normalized(), "The axis Vector3 " + p_axis.operator String() + " must be normalized."); + ERR_FAIL_COND_MSG(!p_axis.is_normalized(), String::concat("The axis Vector3 ", p_axis.operator String(), " must be normalized.")); #endif real_t d = p_axis.length(); if (d == 0) { diff --git a/core/math/rect2.cpp b/core/math/rect2.cpp index 7f77b0786c2c..75824e75f551 100644 --- a/core/math/rect2.cpp +++ b/core/math/rect2.cpp @@ -283,7 +283,10 @@ bool Rect2::intersects_transformed(const Transform2D &p_xform, const Rect2 &p_re } Rect2::operator String() const { - return "[P: " + position.operator String() + ", S: " + size.operator String() + "]"; + return String::concat( + "[P: ", position.operator String(), + ", S: ", size.operator String(), + "]"); } Rect2::operator Rect2i() const { diff --git a/core/math/rect2i.cpp b/core/math/rect2i.cpp index 807fe0707a75..42a39a7ad558 100644 --- a/core/math/rect2i.cpp +++ b/core/math/rect2i.cpp @@ -34,7 +34,10 @@ #include "core/string/ustring.h" Rect2i::operator String() const { - return "[P: " + position.operator String() + ", S: " + size + "]"; + return String::concat( + "[P: ", position.operator String(), + ", S: ", size, + "]"); } Rect2i::operator Rect2() const { diff --git a/core/math/transform_2d.cpp b/core/math/transform_2d.cpp index f6525fe5caf1..914cc88362ab 100644 --- a/core/math/transform_2d.cpp +++ b/core/math/transform_2d.cpp @@ -308,7 +308,9 @@ Transform2D Transform2D::operator/(real_t p_val) const { } Transform2D::operator String() const { - return "[X: " + columns[0].operator String() + - ", Y: " + columns[1].operator String() + - ", O: " + columns[2].operator String() + "]"; + return String::concat( + "[X: ", columns[0].operator String(), + ", Y: ", columns[1].operator String(), + ", O: ", columns[2].operator String(), + "]"); } diff --git a/core/math/transform_3d.cpp b/core/math/transform_3d.cpp index d4673851a3df..a3257fd4fceb 100644 --- a/core/math/transform_3d.cpp +++ b/core/math/transform_3d.cpp @@ -219,10 +219,12 @@ Transform3D Transform3D::operator/(real_t p_val) const { } Transform3D::operator String() const { - return "[X: " + basis.get_column(0).operator String() + - ", Y: " + basis.get_column(1).operator String() + - ", Z: " + basis.get_column(2).operator String() + - ", O: " + origin.operator String() + "]"; + return String::concat( + "[X: ", basis.get_column(0).operator String(), + ", Y: ", basis.get_column(1).operator String(), + ", Z: ", basis.get_column(2).operator String(), + ", O: ", origin.operator String(), + "]"); } Transform3D::Transform3D(const Basis &p_basis, const Vector3 &p_origin) : diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp index 0590ee8a373b..6199b85c882c 100644 --- a/core/math/vector2.cpp +++ b/core/math/vector2.cpp @@ -203,7 +203,11 @@ bool Vector2::is_finite() const { } Vector2::operator String() const { - return "(" + String::num_real(x, true) + ", " + String::num_real(y, true) + ")"; + return String::concat( + "(", + String::num_real(x, true), ", ", + String::num_real(y, true), + ")"); } Vector2::operator Vector2i() const { diff --git a/core/math/vector2i.cpp b/core/math/vector2i.cpp index 790f56473492..7e903058a869 100644 --- a/core/math/vector2i.cpp +++ b/core/math/vector2i.cpp @@ -135,7 +135,11 @@ bool Vector2i::operator!=(const Vector2i &p_vec2) const { } Vector2i::operator String() const { - return "(" + itos(x) + ", " + itos(y) + ")"; + return String::concat( + "(", + itos(x), ", ", + itos(y), + ")"); } Vector2i::operator Vector2() const { diff --git a/core/math/vector3.cpp b/core/math/vector3.cpp index e18ac3b01147..2d31e9b9ceeb 100644 --- a/core/math/vector3.cpp +++ b/core/math/vector3.cpp @@ -165,7 +165,12 @@ bool Vector3::is_finite() const { } Vector3::operator String() const { - return "(" + String::num_real(x, true) + ", " + String::num_real(y, true) + ", " + String::num_real(z, true) + ")"; + return String::concat( + "(", + String::num_real(x, true), ", ", + String::num_real(y, true), ", ", + String::num_real(z, true), + ")"); } Vector3::operator Vector3i() const { diff --git a/core/math/vector3i.cpp b/core/math/vector3i.cpp index 93f9d15ac1bd..0ae80385d10f 100644 --- a/core/math/vector3i.cpp +++ b/core/math/vector3i.cpp @@ -70,7 +70,12 @@ Vector3i Vector3i::snappedi(int32_t p_step) const { } Vector3i::operator String() const { - return "(" + itos(x) + ", " + itos(y) + ", " + itos(z) + ")"; + return String::concat( + "(", + itos(x), ", ", + itos(y), ", ", + itos(z), + ")"); } Vector3i::operator Vector3() const { diff --git a/core/math/vector4.cpp b/core/math/vector4.cpp index 8ac2c4bf1f17..a6fc0a679dc2 100644 --- a/core/math/vector4.cpp +++ b/core/math/vector4.cpp @@ -213,7 +213,13 @@ Vector4 Vector4::clampf(real_t p_min, real_t p_max) const { } Vector4::operator String() const { - return "(" + String::num_real(x, true) + ", " + String::num_real(y, true) + ", " + String::num_real(z, true) + ", " + String::num_real(w, true) + ")"; + return String::concat( + "(", + String::num_real(x, true), ", ", + String::num_real(y, true), ", ", + String::num_real(z, true), ", ", + String::num_real(w, true), + ")"); } static_assert(sizeof(Vector4) == 4 * sizeof(real_t)); diff --git a/core/math/vector4i.cpp b/core/math/vector4i.cpp index afa77a4988dc..b2153ac9b71f 100644 --- a/core/math/vector4i.cpp +++ b/core/math/vector4i.cpp @@ -90,7 +90,13 @@ Vector4i Vector4i::snappedi(int32_t p_step) const { } Vector4i::operator String() const { - return "(" + itos(x) + ", " + itos(y) + ", " + itos(z) + ", " + itos(w) + ")"; + return String::concat( + "(", + itos(x), ", ", + itos(y), ", ", + itos(z), ", ", + itos(w), + ")"); } Vector4i::operator Vector4() const { diff --git a/core/string/ustring.h b/core/string/ustring.h index b8ebeb99d7cb..7b7ddb266285 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -594,6 +594,23 @@ class String { // Use `is_valid_ascii_identifier()` instead. Kept for compatibility. bool is_valid_identifier() const { return is_valid_ascii_identifier(); } + template + void extend(Args... args); + + template + static String concat(Args... args) { + String string; + string.extend(args...); + return string; + } + + template + static String concat(String &&string, Args... args) { + // Optimized case of the concat call, where we can consume the first argument to avoid re-allocation. + string.extend(args...); + return std::move(string); + } + /** * The constructors must not depend on other overloads */ @@ -654,6 +671,50 @@ String operator+(const char *p_chr, const String &p_str); String operator+(const wchar_t *p_chr, const String &p_str); String operator+(char32_t p_chr, const String &p_str); +_FORCE_INLINE_ char32_t *_insert_string(const StrRange &string, char32_t *dst) { + const char32_t *src = dst; + for (const char32_t *end = dst + string.len; src < end; ++src, ++dst) { + *dst = *src; + } + return dst; +} + +_FORCE_INLINE_ char32_t *_insert_string(const StrRange &string, char32_t *dst) { + memcpy(dst, string.c_str, string.len * sizeof(char32_t)); + dst += string.len; + return dst; +} + +inline StrRange _to_str_range(const String &string) { + return StrRange(string); +} +template >> +StrRange _to_str_range(const StrRange &string) { + return string; +} +template >> +StrRange _to_str_range(const Char (&string)[len]) { + return StrRange::from_c_str(string); +} +template >> +StrRange _to_str_range(const Char &chr) { + return StrRange(&chr, 1); +} + +template +void _extend_string_ranges(String string, Args... args) { + int start = string.size(); + string.resize((args.len + ...) + 1); + char32_t *dst = string.ptrw() + start; + ((dst = _insert_string(args, dst)), ...); + *dst = 0; +} + +template +void String::extend(Args... args) { + _extend_string_ranges(_to_str_range(args)...); +} + String itos(int64_t p_val); String uitos(uint64_t p_val); String rtos(double p_val); diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index f092905a3ab0..34a16d2bd44f 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -1875,10 +1875,10 @@ String Variant::stringify(int recursion_count) const { } case RID: { const ::RID &s = *reinterpret_cast(_data._mem); - return "RID(" + itos(s.get_id()) + ")"; + return String::concat("RID(", itos(s.get_id()), ")"); } default: { - return "<" + get_type_name(type) + ">"; + return String::concat("<", get_type_name(type), ">"); } } }