Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add StringName::transient_ord() #586

Merged
merged 5 commits into from
Jan 31, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions godot-codegen/src/models/domain.rs
Original file line number Diff line number Diff line change
@@ -331,18 +331,6 @@ pub trait Function: fmt::Display {
}
}

#[deprecated]
struct FnSignature<'a> {
function_name: &'a str,
surrounding_class: Option<&'a TyName>, // None if global function
is_private: bool,
is_virtual: bool,
is_vararg: bool,
qualifier: FnQualifier,
params: Vec<FnParam>,
return_value: FnReturn,
}

// ----------------------------------------------------------------------------------------------------------------------------------------------

pub struct UtilityFunction {
1 change: 0 additions & 1 deletion godot-codegen/src/models/domain_mapping.rs
Original file line number Diff line number Diff line change
@@ -343,7 +343,6 @@ impl BuiltinMethod {
.as_deref()
.map(JsonMethodReturn::from_type_no_meta);

// use BuiltinMethod, but adopt values from above FnSignature expr
Some(Self {
common: FunctionCommon {
// Fill in these fields
11 changes: 6 additions & 5 deletions godot-core/src/builtin/callable.rs
Original file line number Diff line number Diff line change
@@ -271,15 +271,16 @@ impl Callable {

impl_builtin_traits! {
for Callable {
// Currently no Default::default() to encourage explicit valid initialization.
//Default => callable_construct_default;
// Default is absent by design, to encourage explicit valid initialization.

Clone => callable_construct_copy;
Drop => callable_destroy;

// Equality for custom callables depend on the equality implementation of that custom callable.
// So we cannot implement `Eq` here and be confident equality will be total for all future custom callables.
// Godot does not define a less-than operator, so there is no `PartialOrd`.
PartialEq => callable_operator_equal;
Clone => callable_construct_copy;
Drop => callable_destroy;
// Godot does not define a less-than operator, so there is no `PartialOrd`.
// Hash could be added, but without Eq it's not that useful; wait for actual use cases.
}
}

3 changes: 3 additions & 0 deletions godot-core/src/builtin/dictionary.rs
Original file line number Diff line number Diff line change
@@ -297,6 +297,8 @@ impl_builtin_traits! {
Default => dictionary_construct_default;
Drop => dictionary_destroy;
PartialEq => dictionary_operator_equal;
// No < operator for dictionaries.
// Hash could be added, but without Eq it's not that useful.
}
}

@@ -305,6 +307,7 @@ impl fmt::Debug for Dictionary {
write!(f, "{:?}", self.to_variant().stringify())
}
}

impl fmt::Display for Dictionary {
/// Formats `Dictionary` to match Godot's string representation.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 changes: 35 additions & 20 deletions godot-core/src/builtin/mod.rs
Original file line number Diff line number Diff line change
@@ -36,26 +36,35 @@
// Re-export macros.
pub use crate::{array, dict, real, reals, varray};

pub use aabb::*;
pub use array_inner::{Array, VariantArray};
pub use basis::*;
pub use callable::*;
pub use color::*;
pub use dictionary_inner::Dictionary;
pub use packed_array::*;
pub use plane::*;
pub use projection::*;
pub use quaternion::*;
pub use real_inner::*;
pub use rect2::*;
pub use rect2i::*;
pub use rid::*;
pub use signal::*;
pub use string::*;
pub use transform2d::*;
pub use transform3d::*;
pub use variant::*;
pub use vectors::*;
#[doc(hidden)]
pub mod __prelude_reexport {
use super::*;

pub use aabb::*;
pub use array_inner::{Array, VariantArray};
pub use basis::*;
pub use callable::*;
pub use color::*;
pub use dictionary_inner::Dictionary;
pub use packed_array::*;
pub use plane::*;
pub use projection::*;
pub use quaternion::*;
pub use real_inner::*;
pub use rect2::*;
pub use rect2i::*;
pub use rid::*;
pub use signal::*;
pub use string::{GString, NodePath, StringName};
pub use transform2d::*;
pub use transform3d::*;
pub use variant::*;
pub use vectors::*;

pub use crate::{array, dict, real, reals, varray};
}

pub use __prelude_reexport::*;

/// Meta-information about variant types, properties and class names.
pub mod meta;
@@ -73,6 +82,11 @@ pub mod dictionary {
pub use super::dictionary_inner::{Iter, Keys, TypedIter, TypedKeys};
}

/// Specialized types related to Godot's various string implementations.
pub mod strings {
pub use super::string::TransientStringNameOrd;
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Implementation

@@ -145,6 +159,7 @@ pub enum RectSide {
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// #[test] utils for serde

#[cfg(all(test, feature = "serde"))]
pub(crate) mod test_utils {
3 changes: 3 additions & 0 deletions godot-core/src/builtin/plane.rs
Original file line number Diff line number Diff line change
@@ -299,6 +299,9 @@ impl std::fmt::Display for Plane {
}
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Tests

#[cfg(test)]
mod test {
use crate::assert_eq_approx;
144 changes: 77 additions & 67 deletions godot-core/src/builtin/projection.rs
Original file line number Diff line number Diff line change
@@ -17,18 +17,15 @@ use std::ops::Mul;
use super::meta::impl_godot_as_self;
use super::{Aabb, Rect2, Vector3};

/// A 4x4 matrix used for 3D projective transformations. It can represent
/// transformations such as translation, rotation, scaling, shearing, and
/// perspective division. It consists of four Vector4 columns.
/// A 4x4 matrix used for 3D projective transformations.
///
/// For purely linear transformations (translation, rotation, and scale), it is
/// recommended to use Transform3D, as it is more performant and has a lower
/// memory footprint.
/// `Projection` can represent transformations such as translation, rotation, scaling, shearing, and perspective division.
/// It consists of four [`Vector4`] columns. It is used internally as `Camera3D`'s projection matrix.
///
/// Used internally as Camera3D's projection matrix.
/// For purely linear transformations (translation, rotation, and scale), it is recommended to use [`Transform3D`], as that is
/// more performant and has a lower memory footprint.
///
/// Note: The current implementation largely makes calls to godot for its
/// methods and as such are not as performant as other types.
/// This builtin comes with two related types [`ProjectionEye`] and [`ProjectionPlane`], that are type-safe pendants to Godot's integers.
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
@@ -40,14 +37,10 @@ pub struct Projection {
impl Projection {
/// A Projection with no transformation defined. When applied to other data
/// structures, no transformation is performed.
///
/// _Godot equivalent: Projection.IDENTITY_
pub const IDENTITY: Self = Self::from_diagonal(1.0, 1.0, 1.0, 1.0);

/// A Projection with all values initialized to 0. When applied to other
/// data structures, they will be zeroed.
///
/// _Godot equivalent: Projection.ZERO_
pub const ZERO: Self = Self::from_diagonal(0.0, 0.0, 0.0, 0.0);

/// Create a new projection from a list of column vectors.
@@ -67,7 +60,7 @@ impl Projection {

/// Create a matrix from four column vectors.
///
/// _Godot equivalent: Projection(Vector4 x_axis, Vector4 y_axis, Vector4 z_axis, Vector4 w_axis)_
/// _Godot equivalent: `Projection(Vector4 x_axis, Vector4 y_axis, Vector4 z_axis, Vector4 w_axis)`_
pub const fn from_cols(x: Vector4, y: Vector4, z: Vector4, w: Vector4) -> Self {
Self { cols: [x, y, z, w] }
}
@@ -76,7 +69,7 @@ impl Projection {
/// -1 to 1 to one that ranges from 0 to 1, and flips the projected
/// positions vertically, according to flip_y.
///
/// _Godot equivalent: Projection.create_depth_correction()_
/// _Godot equivalent: `Projection.create_depth_correction()`_
pub fn create_depth_correction(flip_y: bool) -> Self {
Self::from_cols(
Vector4::new(1.0, 0.0, 0.0, 0.0),
@@ -89,7 +82,7 @@ impl Projection {
/// Creates a new Projection that scales a given projection to fit around
/// a given AABB in projection space.
///
/// _Godot equivalent: Projection.create_fit_aabb()_
/// _Godot equivalent: `Projection.create_fit_aabb()`_
pub fn create_fit_aabb(aabb: Aabb) -> Self {
let translate_unscaled = -2.0 * aabb.position - aabb.size; // -(start+end)

@@ -108,7 +101,7 @@ impl Projection {
/// display with the given X:Y aspect ratio, distance between eyes, display
/// width, distance to lens, oversampling factor, and depth clipping planes.
///
/// _Godot equivalent: Projection.create_for_hmd()_
/// _Godot equivalent: `Projection.create_for_hmd()`_
#[allow(clippy::too_many_arguments)]
pub fn create_for_hmd(
eye: ProjectionEye,
@@ -137,7 +130,7 @@ impl Projection {
/// Creates a new Projection that projects positions in a frustum with the
/// given clipping planes.
///
/// _Godot equivalent: Projection.create_frustum()_
/// _Godot equivalent: `Projection.create_frustum()`_
pub fn create_frustum(
left: real,
right: real,
@@ -171,7 +164,7 @@ impl Projection {
/// `flip_fov` determines whether the projection's field of view is flipped
/// over its diagonal.
///
/// _Godot equivalent: Projection.create_frustum_aspect()_
/// _Godot equivalent: `Projection.create_frustum_aspect()`_
pub fn create_frustum_aspect(
size: real,
aspect: real,
@@ -204,7 +197,7 @@ impl Projection {

/// Creates a new Projection that projects positions into the given Rect2.
///
/// _Godot equivalent: Projection.create_light_atlas_rect()_
/// _Godot equivalent: `Projection.create_light_atlas_rect()`_
pub fn create_light_atlas_rect(rect: Rect2) -> Self {
Self::from_cols(
Vector4::new(rect.size.x, 0.0, 0.0, 0.0),
@@ -217,7 +210,7 @@ impl Projection {
/// Creates a new Projection that projects positions using an orthogonal
/// projection with the given clipping planes.
///
/// _Godot equivalent: Projection.create_orthogonal()_
/// _Godot equivalent: `Projection.create_orthogonal()`_
pub fn create_orthogonal(
left: real,
right: real,
@@ -235,7 +228,7 @@ impl Projection {
/// `flip_fov` determines whether the projection's field of view is flipped
/// over its diagonal.
///
/// _Godot equivalent: Projection.create_orthogonal_aspect()_
/// _Godot equivalent: `Projection.create_orthogonal_aspect()`_
pub fn create_orthogonal_aspect(
size: real,
aspect: real,
@@ -261,7 +254,7 @@ impl Projection {
/// `flip_fov` determines whether the projection's field of view is flipped
/// over its diagonal.
///
/// _Godot equivalent: Projection.create_perspective()_
/// _Godot equivalent: `Projection.create_perspective()`_
pub fn create_perspective(
fov_y: real,
aspect: real,
@@ -286,7 +279,7 @@ impl Projection {
/// `flip_fov` determines whether the projection's field of view is flipped
/// over its diagonal.
///
/// _Godot equivalent: Projection.create_perspective_hmd()_
/// _Godot equivalent: `Projection.create_perspective_hmd()`_
#[allow(clippy::too_many_arguments)]
pub fn create_perspective_hmd(
fov_y: real,
@@ -326,116 +319,105 @@ impl Projection {
ret
}

/// Return the determinant of the matrix.
/// Returns the vertical field of view of a projection (in degrees) which
/// has the given horizontal field of view (in degrees) and aspect ratio.
///
/// _Godot equivalent: Projection.determinant()_
/// _Godot equivalent: `Projection.get_fovy()`_
#[doc(alias = "get_fovy")]
pub fn create_fovy(fov_x: real, aspect: real) -> real {
let half_angle_fov_x = f64::to_radians(fov_x.as_f64() * 0.5);
let vertical_transform = f64::atan(aspect.as_f64() * f64::tan(half_angle_fov_x));
let full_angle_fov_y = f64::to_degrees(vertical_transform * 2.0);

real::from_f64(full_angle_fov_y)
}

/// Return the determinant of the matrix.
pub fn determinant(&self) -> real {
self.glam(|mat| mat.determinant())
}

/// Returns a copy of this Projection with the signs of the values of the Y
/// column flipped.
///
/// _Godot equivalent: Projection.flipped_y()_
/// Returns a copy of this projection, with the signs of the values of the Y column flipped.
pub fn flipped_y(self) -> Self {
let [x, y, z, w] = self.cols;
Self::from_cols(x, -y, z, w)
}

/// Returns the X:Y aspect ratio of this Projection's viewport.
///
/// _Godot equivalent: Projection.get_aspect()_
pub fn aspect(&self) -> real {
real::from_f64(self.as_inner().get_aspect())
}

/// Returns the dimensions of the far clipping plane of the projection,
/// divided by two.
///
/// _Godot equivalent: Projection.get_far_plane_half_extents()_
pub fn far_plane_half_extents(&self) -> Vector2 {
self.as_inner().get_far_plane_half_extents()
}

/// Returns the horizontal field of view of the projection (in degrees).
///
/// _Godot equivalent: Projection.get_fov()_
/// _Godot equivalent: `Projection.get_fov()`_
pub fn fov(&self) -> real {
real::from_f64(self.as_inner().get_fov())
}

/// Returns the vertical field of view of a projection (in degrees) which
/// has the given horizontal field of view (in degrees) and aspect ratio.
///
/// _Godot equivalent: Projection.get_fovy()_
#[doc(alias = "get_fovy")]
pub fn create_fovy(fov_x: real, aspect: real) -> real {
let half_angle_fov_x = f64::to_radians(fov_x.as_f64() * 0.5);
let vertical_transform = f64::atan(aspect.as_f64() * f64::tan(half_angle_fov_x));
let full_angle_fov_y = f64::to_degrees(vertical_transform * 2.0);

real::from_f64(full_angle_fov_y)
}

/// Returns the factor by which the visible level of detail is scaled by
/// this Projection.
///
/// _Godot equivalent: Projection.get_lod_multiplier()_
/// _Godot equivalent: `Projection.get_lod_multiplier()`_
pub fn lod_multiplier(&self) -> real {
real::from_f64(self.as_inner().get_lod_multiplier())
}

/// Returns the number of pixels with the given pixel width displayed per
/// meter, after this Projection is applied.
///
/// _Godot equivalent: Projection.get_pixels_per_meter()_
pub fn pixels_per_meter(&self, pixel_width: i64) -> i64 {
/// _Godot equivalent: `Projection.get_pixels_per_meter()`_
pub fn get_pixels_per_meter(&self, pixel_width: i64) -> i64 {
self.as_inner().get_pixels_per_meter(pixel_width)
}

/// Returns the clipping plane of this Projection whose index is given by
/// plane.
///
/// _Godot equivalent: Projection.get_projection_plane()_
pub fn projection_plane(&self, plane: ProjectionPlane) -> Plane {
/// _Godot equivalent: `Projection.get_projection_plane()`_
pub fn get_projection_plane(&self, plane: ProjectionPlane) -> Plane {
self.as_inner().get_projection_plane(plane as i64)
}

/// Returns the dimensions of the viewport plane that this Projection
/// projects positions onto, divided by two.
///
/// _Godot equivalent: Projection.get_viewport_half_extents()_
/// _Godot equivalent: `Projection.get_viewport_half_extents()`_
pub fn viewport_half_extents(&self) -> Vector2 {
self.as_inner().get_viewport_half_extents()
}

/// Returns the distance for this Projection beyond which positions are
/// clipped.
///
/// _Godot equivalent: Projection.get_z_far()_
/// _Godot equivalent: `Projection.get_z_far()`_
pub fn z_far(&self) -> real {
real::from_f64(self.as_inner().get_z_far())
}

/// Returns the distance for this Projection before which positions are
/// clipped.
///
/// _Godot equivalent: Projection.get_z_near()_
/// _Godot equivalent: `Projection.get_z_near()`_
pub fn z_near(&self) -> real {
real::from_f64(self.as_inner().get_z_near())
}

/// Returns a Projection that performs the inverse of this Projection's
/// projective transformation.
///
/// _Godot equivalent: Projection.inverse()_
pub fn inverse(self) -> Self {
self.glam(|mat| mat.inverse())
}

/// Returns `true` if this Projection performs an orthogonal projection.
///
/// _Godot equivalent: Projection.is_orthogonal()_
/// _Godot equivalent: `Projection.is_orthogonal()`_
pub fn is_orthogonal(&self) -> bool {
self.cols[3].w == 1.0

@@ -450,7 +432,7 @@ impl Projection {
/// Returns a Projection with the X and Y values from the given [`Vector2`]
/// added to the first and second values of the final column respectively.
///
/// _Godot equivalent: Projection.jitter_offseted()_
/// _Godot equivalent: `Projection.jitter_offseted()`_
#[must_use]
pub fn jitter_offset(&self, offset: Vector2) -> Self {
Self::from_cols(
@@ -466,7 +448,7 @@ impl Projection {
///
/// Note: The original Projection must be a perspective projection.
///
/// _Godot equivalent: Projection.perspective_znear_adjusted()_
/// _Godot equivalent: `Projection.perspective_znear_adjusted()`_
pub fn perspective_znear_adjusted(&self, new_znear: real) -> Self {
self.as_inner()
.perspective_znear_adjusted(new_znear.as_f64())
@@ -545,8 +527,7 @@ impl GlamConv for Projection {
type Glam = RMat4;
}

// SAFETY:
// This type is represented as `Self` in Godot, so `*mut Self` is sound.
// SAFETY: This type is represented as `Self` in Godot, so `*mut Self` is sound.
unsafe impl GodotFfi for Projection {
fn variant_type() -> sys::VariantType {
sys::VariantType::Projection
@@ -557,7 +538,9 @@ unsafe impl GodotFfi for Projection {

impl_godot_as_self!(Projection);

/// A projections clipping plane.
/// A projection's clipping plane.
///
/// See [Godot docs about `Projection` constants](https://docs.godotengine.org/en/stable/classes/class_projection.html#constants).
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[repr(C)]
pub enum ProjectionPlane {
@@ -569,33 +552,60 @@ pub enum ProjectionPlane {
Bottom = 5,
}

/// The eye to create a projection for, when creating a projection adjusted
/// for head-mounted displays.
impl ProjectionPlane {
/// Convert from one of GDScript's `Projection.PLANE_*` integer constants.
pub fn try_from_ord(ord: i64) -> Option<Self> {
match ord {
0 => Some(Self::Near),
1 => Some(Self::Far),
2 => Some(Self::Left),
3 => Some(Self::Top),
4 => Some(Self::Right),
5 => Some(Self::Bottom),
_ => None,
}
}
}

/// The eye to create a projection for, when creating a projection adjusted for head-mounted displays.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[repr(C)]
pub enum ProjectionEye {
Left = 1,
Right = 2,
}

impl ProjectionEye {
/// Convert from numbers `1` and `2`.
pub fn try_from_ord(ord: i64) -> Option<Self> {
match ord {
1 => Some(Self::Left),
2 => Some(Self::Right),
_ => None,
}
}
}

impl std::fmt::Display for Projection {
/// Formats `Projection` to match Godot's string representation.
///
/// # Example
/// ```
/// use godot::prelude::*;
/// # use godot::prelude::*;
/// let proj = Projection::new([
/// Vector4::new(1.0, 2.5, 1.0, 0.5),
/// Vector4::new(0.0, 1.5, 2.0, 0.5),
/// Vector4::new(0.0, 0.0, 3.0, 2.5),
/// Vector4::new(3.0, 1.0, 4.0, 1.5),
/// ]);
///
/// const FMT_RESULT: &str = r"
/// 1, 0, 0, 3
/// 2.5, 1.5, 0, 1
/// 1, 2, 3, 4
/// 0.5, 0.5, 2.5, 1.5
/// ";
///
/// assert_eq!(format!("{}", proj), FMT_RESULT);
/// ```
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3 changes: 0 additions & 3 deletions godot-core/src/builtin/string/gstring.rs
Original file line number Diff line number Diff line change
@@ -17,9 +17,6 @@ use crate::builtin::meta::impl_godot_as_self;
use super::string_chars::validate_unicode_scalar_sequence;
use super::{NodePath, StringName};

#[deprecated = "Renamed to `GString`, will soon be removed."]
pub type GodotString = GString;

/// Godot's reference counted string type.
///
/// This is the Rust binding of GDScript's `String` type. It represents the native string class used within the Godot engine,
4 changes: 2 additions & 2 deletions godot-core/src/builtin/string/mod.rs
Original file line number Diff line number Diff line change
@@ -14,8 +14,8 @@ mod string_chars;
mod string_name;

pub use gstring::*;
pub use node_path::*;
pub use string_name::*;
pub use node_path::NodePath;
pub use string_name::{StringName, TransientStringNameOrd};

use super::meta::{ConvertError, FromGodot, GodotConvert, ToGodot};

1 change: 1 addition & 0 deletions godot-core/src/builtin/string/node_path.rs
Original file line number Diff line number Diff line change
@@ -82,6 +82,7 @@ impl_builtin_traits! {
Clone => node_path_construct_copy;
Drop => node_path_destroy;
Eq => node_path_operator_equal;
// NodePath provides no < operator.
Hash;
}
}
80 changes: 78 additions & 2 deletions godot-core/src/builtin/string/string_name.rs
Original file line number Diff line number Diff line change
@@ -18,6 +18,14 @@ use crate::builtin::{GString, NodePath};
///
/// StringNames are immutable strings designed for representing unique names. StringName ensures that only
/// one instance of a given name exists.
///
/// # Ordering
///
/// In Godot, `StringName`s are **not** ordered lexicographically, and the ordering relation is **not** stable across multiple runs of your
/// application. Therefore, this type does not implement `PartialOrd` and `Ord`, as it would be very easy to introduce bugs by accidentally
/// relying on lexicographical ordering.
///
/// Instead, we provide [`transient_ord()`][Self::transient_ord] for ordering relations.
#[repr(C)]
pub struct StringName {
opaque: sys::types::OpaqueStringName,
@@ -90,6 +98,19 @@ impl StringName {
.expect("Godot hashes are uint32_t")
}

/// O(1), non-lexicographic, non-stable ordering relation.
///
/// The result of the comparison is **not** lexicographic and **not** stable across multiple runs of your application.
///
/// However, it is very fast. It doesn't depend on the length of the strings, but on the memory location of string names.
/// This can still be useful if you need to establish an ordering relation, but are not interested in the actual order of the strings
/// (example: binary search).
///
/// For lexicographical ordering, convert to `GString` (significantly slower).
pub fn transient_ord(&self) -> TransientStringNameOrd<'_> {
TransientStringNameOrd(self)
}

ffi_methods! {
type sys::GDExtensionStringNamePtr = *mut Opaque;

@@ -152,8 +173,8 @@ impl_builtin_traits! {
Clone => string_name_construct_copy;
Drop => string_name_destroy;
Eq => string_name_operator_equal;
// currently broken: https://github.com/godotengine/godot/issues/76218
// Ord => string_name_operator_less;
// Do not provide PartialOrd or Ord. Even though Godot provides a `operator <`, it is non-lexicographic and non-deterministic
// (based on pointers). See transient_ord() method.
Hash;
}
}
@@ -248,6 +269,61 @@ impl From<NodePath> for StringName {
}
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Ordering

/// Type that implements `Ord` for `StringNames`.
///
/// See [`StringName::transient_ord()`].
pub struct TransientStringNameOrd<'a>(&'a StringName);

impl<'a> PartialEq for TransientStringNameOrd<'a> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}

impl<'a> Eq for TransientStringNameOrd<'a> {}

impl<'a> PartialOrd for TransientStringNameOrd<'a> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

// implement Ord like above
impl<'a> Ord for TransientStringNameOrd<'a> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// SAFETY: builtin operator provided by Godot.
let op_less = |lhs, rhs| unsafe {
let mut result = false;
sys::builtin_call! {
string_name_operator_less(lhs, rhs, result.sys_mut())
}
result
};

let self_ptr = self.0.sys();
let other_ptr = other.0.sys();

if op_less(self_ptr, other_ptr) {
std::cmp::Ordering::Less
} else if op_less(other_ptr, self_ptr) {
std::cmp::Ordering::Greater
} else if self.eq(other) {
std::cmp::Ordering::Equal
} else {
panic!(
"Godot provides inconsistent StringName ordering for \"{}\" and \"{}\"",
self.0, other.0
);
}
}
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// serde support

#[cfg(feature = "serde")]
mod serialize {
use super::*;
20 changes: 10 additions & 10 deletions godot-core/src/builtin/variant/mod.rs
Original file line number Diff line number Diff line change
@@ -306,6 +306,16 @@ unsafe impl GodotFfi for Variant {

impl_godot_as_self!(Variant);

impl Default for Variant {
fn default() -> Self {
unsafe {
Self::from_var_sys_init(|variant_ptr| {
interface_fn!(variant_new_nil)(variant_ptr);
})
}
}
}

impl Clone for Variant {
fn clone(&self) -> Self {
unsafe {
@@ -324,16 +334,6 @@ impl Drop for Variant {
}
}

impl Default for Variant {
fn default() -> Self {
unsafe {
Self::from_var_sys_init(|variant_ptr| {
interface_fn!(variant_new_nil)(variant_ptr);
})
}
}
}

// Variant is not Eq because it can contain floats and other types composed of floats.
impl PartialEq for Variant {
fn eq(&self, other: &Self) -> bool {
6 changes: 0 additions & 6 deletions godot/src/lib.rs
Original file line number Diff line number Diff line change
@@ -206,12 +206,6 @@ pub mod register {
pub use godot_macros::{godot_api, Export, FromGodot, GodotClass, GodotConvert, ToGodot, Var};
}

/// Renamed to [`register`] module.
#[deprecated = "Renamed to `register`."]
pub mod bind {
pub use super::register::*;
}

/// Testing facilities (unstable).
#[doc(hidden)]
pub mod test {
3 changes: 1 addition & 2 deletions godot/src/prelude.rs
Original file line number Diff line number Diff line change
@@ -10,10 +10,9 @@ pub use super::register::property::{Export, TypeStringHint, Var};
// Re-export macros.
pub use super::register::{godot_api, Export, FromGodot, GodotClass, GodotConvert, ToGodot, Var};

pub use super::builtin::__prelude_reexport::*;
pub use super::builtin::math::FloatExt as _;
pub use super::builtin::meta::{FromGodot, ToGodot};
pub use super::builtin::*;
pub use super::builtin::{array, dict, varray};

pub use super::engine::{
load, try_load, utilities, AudioStreamPlayer, Camera2D, Camera3D, GFile, IAudioStreamPlayer,
7 changes: 2 additions & 5 deletions itest/rust/src/builtin_tests/containers/callable_test.rs
Original file line number Diff line number Diff line change
@@ -146,6 +146,7 @@ impl CallableRefcountTest {
#[cfg(since_api = "4.2")]
mod custom_callable {
use super::*;
use crate::framework::assert_eq_self;
use godot::builtin::Dictionary;
use std::fmt;
use std::hash::Hash;
@@ -220,11 +221,7 @@ mod custom_callable {
let b = Callable::from_custom(Adder::new_tracked(3, bt.clone()));
let c = Callable::from_custom(Adder::new_tracked(4, ct.clone()));

// clippy complains with assert_eq!(a, a) and it can't be suppressed with the suggested #[allow(clippy::eq_op)], hence this ceremony.
#[allow(clippy::eq_op)]
if a != a {
panic!("a != a; left: {a:?}, right: {a:?}");
}
assert_eq_self!(a);
assert_eq!(
eq_count(&at),
0,
3 changes: 2 additions & 1 deletion itest/rust/src/builtin_tests/geometry/rect2i_test.rs
Original file line number Diff line number Diff line change
@@ -8,7 +8,8 @@
use std::fmt::Debug;

use crate::framework::itest;
use godot::prelude::{inner::InnerRect2i, *};
use godot::builtin::inner::InnerRect2i;
use godot::builtin::{Rect2i, RectSide, Vector2i};

#[itest]
fn rect2i_equiv_unary() {
6 changes: 4 additions & 2 deletions itest/rust/src/builtin_tests/geometry/transform2d_test.rs
Original file line number Diff line number Diff line change
@@ -7,7 +7,9 @@

use crate::framework::itest;

use godot::prelude::{inner::InnerTransform2D, *};
use godot::builtin::inner::InnerTransform2D;
use godot::builtin::meta::ToGodot;
use godot::builtin::{real, RealConv, Rect2, Transform2D, VariantOperator, Vector2};
use godot::private::class_macros::assert_eq_approx;

const TEST_TRANSFORM: Transform2D = Transform2D::from_cols(
@@ -23,7 +25,7 @@ fn transform2d_equiv() {
let vec = Vector2::new(1.0, 2.0);

#[rustfmt::skip]
let mappings_transform = [
let mappings_transform = [
("affine_inverse", inner.affine_inverse(), outer.affine_inverse() ),
("orthonormalized", inner.orthonormalized(), outer.orthonormalized() ),
("rotated", inner.rotated(1.0), outer.rotated(1.0) ),
4 changes: 3 additions & 1 deletion itest/rust/src/builtin_tests/geometry/transform3d_test.rs
Original file line number Diff line number Diff line change
@@ -7,7 +7,9 @@

use crate::framework::itest;

use godot::prelude::{inner::InnerTransform3D, *};
use godot::builtin::inner::InnerTransform3D;
use godot::builtin::meta::ToGodot;
use godot::builtin::{Aabb, Basis, Plane, Transform3D, VariantOperator, Vector3};
use godot::private::class_macros::assert_eq_approx;

const TEST_TRANSFORM: Transform3D = Transform3D::new(
30 changes: 21 additions & 9 deletions itest/rust/src/builtin_tests/string/string_name_test.rs
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

use std::collections::HashSet;

use crate::framework::itest;
use crate::framework::{assert_eq_self, itest};
use godot::builtin::{GString, NodePath, StringName};

#[itest]
@@ -58,17 +58,29 @@ fn string_name_equality() {
assert_ne!(string, different);
}

// TODO: add back in when ordering StringNames is fixed
#[itest(skip)]
fn string_name_ordering() {
let _low = StringName::from("Alpha");
let _high = StringName::from("Beta");
/*
#[itest]
#[allow(clippy::eq_op)]
fn string_name_transient_ord() {
// We can't deterministically know the ordering, so this test only ensures consistency between different operators.
let low = StringName::from("Alpha");
let high = StringName::from("Beta");

let mut low = low.transient_ord();
let mut high = high.transient_ord();

if low > high {
std::mem::swap(&mut low, &mut high);
}

assert!(low < high);
assert!(low <= high);
assert!(high > low);
assert!(high > low); // implied.
assert!(high >= low);
*/

// Check PartialEq/Eq relation.
assert_eq_self!(low);
assert_eq_self!(high);
assert!(low != high);
}

#[itest]
16 changes: 16 additions & 0 deletions itest/rust/src/framework/mod.rs
Original file line number Diff line number Diff line change
@@ -137,3 +137,19 @@ pub fn suppress_godot_print(mut f: impl FnMut()) {
pub fn runs_release() -> bool {
!Os::singleton().is_debug_build()
}

/// Workaround for tests of the form `assert!(a == a)`.
///
/// We can't always use `assert_eq!(a, a)` because of lacking `Debug` impl.
///
/// Clippy however complains, yet the suggested `#[allow(clippy::eq_op)]` cannot be used to suppress the Clippy warning (likely a bug).
#[macro_export]
macro_rules! assert_eq_self {
($a:expr) => {{
if !($a == $a) {
panic!("assertion failed: `(a == a)`");
}
}};
}

pub use crate::assert_eq_self;
5 changes: 2 additions & 3 deletions itest/rust/src/object_tests/object_test.rs
Original file line number Diff line number Diff line change
@@ -8,15 +8,14 @@
use std::cell::{Cell, RefCell};
use std::rc::Rc;

use godot::builtin::meta::GodotType;
use godot::builtin::meta::{FromGodot, ToGodot};
use godot::builtin::{GString, StringName, Variant, Vector3};
use godot::engine::{
file_access, Area2D, Camera3D, Engine, FileAccess, IRefCounted, Node, Node3D, Object,
RefCounted,
};
use godot::obj;
use godot::obj::{Base, Gd, Inherits, InstanceId, NewAlloc, NewGd, RawGd};
use godot::prelude::meta::GodotType;
use godot::register::{godot_api, GodotClass};
use godot::sys::{self, interface_fn, GodotFfi};

@@ -529,7 +528,7 @@ fn object_engine_upcast() {

fn ref_instance_id(obj: &Object) -> InstanceId {
// SAFETY: raw FFI call since we can't access get_instance_id() of a raw Object anymore, and call() needs &mut.
use obj::EngineClass as _;
use godot::obj::EngineClass as _;

let obj_ptr = obj.as_object_ptr();

7 changes: 4 additions & 3 deletions itest/rust/src/register_tests/option_ffi_test.rs
Original file line number Diff line number Diff line change
@@ -5,9 +5,10 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use godot::obj::{NewAlloc, NewGd, RawGd};
use godot::prelude::meta::GodotType;
use godot::prelude::{godot_api, Gd, GodotClass, Node, Object, RefCounted};
use godot::builtin::meta::GodotType;
use godot::engine::{Node, Object, RefCounted};
use godot::obj::{Gd, NewAlloc, NewGd, RawGd};
use godot::register::{godot_api, GodotClass};
use godot::sys::GodotFfi;

use crate::framework::itest;