Skip to content

Commit 04dc3e3

Browse files
bors[bot]Bromeon
andauthored
Merge #274
274: Optimize array random access r=Bromeon a=Bromeon As of godotengine/godot#66185, the FFI methods * `array_operator_index` * `array_operator_index_const` no longer print an error on out-of-bounds access. As before, they return a null pointer in that case. This means we can reduce the number of FFI calls from 2 to 1, and directly check the returned pointer instead of doing a bounds check in advance. We can also make the `ptr[_mut]_unchecked` safe, and just let them return a null pointer. Co-authored-by: Jan Haller <[email protected]>
2 parents 60f75bf + 65ce2c3 commit 04dc3e3

File tree

21 files changed

+126
-70
lines changed

21 files changed

+126
-70
lines changed

godot-core/src/builtin/array.rs

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,17 @@ impl<T: VariantMetadata> Array<T> {
7878
}
7979

8080
/// Returns the number of elements in the array. Equivalent of `size()` in Godot.
81+
///
82+
/// Retrieving the size incurs an FFI call. If you know the size hasn't changed, you may consider storing
83+
/// it in a variable. For loops, prefer iterators.
8184
pub fn len(&self) -> usize {
8285
to_usize(self.as_inner().size())
8386
}
8487

8588
/// Returns `true` if the array is empty.
89+
///
90+
/// Checking for emptiness incurs an FFI call. If you know the size hasn't changed, you may consider storing
91+
/// it in a variable. For loops, prefer iterators.
8692
pub fn is_empty(&self) -> bool {
8793
self.as_inner().is_empty()
8894
}
@@ -150,10 +156,24 @@ impl<T: VariantMetadata> Array<T> {
150156
///
151157
/// If `index` is out of bounds.
152158
fn ptr(&self, index: usize) -> *const Variant {
153-
self.check_bounds(index);
159+
let ptr = self.ptr_or_null(index);
160+
assert!(
161+
!ptr.is_null(),
162+
"Array index {index} out of bounds (len {len})",
163+
len = self.len(),
164+
);
165+
ptr
166+
}
167+
168+
/// Returns a pointer to the element at the given index, or null if out of bounds.
169+
fn ptr_or_null(&self, index: usize) -> *const Variant {
170+
// SAFETY: array_operator_index_const returns null for invalid indexes.
171+
let variant_ptr = unsafe {
172+
let index = to_i64(index);
173+
interface_fn!(array_operator_index_const)(self.sys(), index)
174+
};
154175

155-
// SAFETY: We just checked that the index is not out of bounds.
156-
unsafe { self.ptr_unchecked(index) }
176+
Variant::ptr_from_sys(variant_ptr)
157177
}
158178

159179
/// Returns a mutable pointer to the element at the given index.
@@ -162,29 +182,23 @@ impl<T: VariantMetadata> Array<T> {
162182
///
163183
/// If `index` is out of bounds.
164184
fn ptr_mut(&self, index: usize) -> *mut Variant {
165-
self.check_bounds(index);
166-
167-
// SAFETY: We just checked that the index is not out of bounds.
168-
unsafe { self.ptr_mut_unchecked(index) }
185+
let ptr = self.ptr_mut_or_null(index);
186+
assert!(
187+
!ptr.is_null(),
188+
"Array index {index} out of bounds (len {len})",
189+
len = self.len(),
190+
);
191+
ptr
169192
}
170193

171-
/// Returns a pointer to the element at the given index.
172-
///
173-
/// # Safety
174-
///
175-
/// Calling this with an out-of-bounds index is undefined behavior.
176-
unsafe fn ptr_unchecked(&self, index: usize) -> *const Variant {
177-
let variant_ptr = interface_fn!(array_operator_index_const)(self.sys(), to_i64(index));
178-
Variant::ptr_from_sys(variant_ptr)
179-
}
194+
/// Returns a pointer to the element at the given index, or null if out of bounds.
195+
fn ptr_mut_or_null(&self, index: usize) -> *mut Variant {
196+
// SAFETY: array_operator_index returns null for invalid indexes.
197+
let variant_ptr = unsafe {
198+
let index = to_i64(index);
199+
interface_fn!(array_operator_index)(self.sys(), index)
200+
};
180201

181-
/// Returns a mutable pointer to the element at the given index.
182-
///
183-
/// # Safety
184-
///
185-
/// Calling this with an out-of-bounds index is undefined behavior.
186-
unsafe fn ptr_mut_unchecked(&self, index: usize) -> *mut Variant {
187-
let variant_ptr = interface_fn!(array_operator_index)(self.sys(), to_i64(index));
188202
Variant::ptr_from_sys_mut(variant_ptr)
189203
}
190204

@@ -365,6 +379,7 @@ impl<T: VariantMetadata + FromVariant> Array<T> {
365379
///
366380
/// If `index` is out of bounds.
367381
pub fn get(&self, index: usize) -> T {
382+
// Panics on out-of-bounds
368383
let ptr = self.ptr(index);
369384

370385
// SAFETY: `ptr()` just verified that the index is not out of bounds.
@@ -582,17 +597,17 @@ unsafe impl<T: VariantMetadata> GodotFfi for Array<T> {
582597
fn move_return_ptr;
583598
}
584599

585-
unsafe fn from_arg_ptr(ptr: sys::GDExtensionTypePtr, _call_type: sys::PtrcallType) -> Self {
586-
let array = Self::from_sys(ptr);
587-
std::mem::forget(array.share());
588-
array
589-
}
590-
591600
unsafe fn from_sys_init_default(init_fn: impl FnOnce(sys::GDExtensionTypePtr)) -> Self {
592601
let mut result = Self::default();
593602
init_fn(result.sys_mut());
594603
result
595604
}
605+
606+
unsafe fn from_arg_ptr(ptr: sys::GDExtensionTypePtr, _call_type: sys::PtrcallType) -> Self {
607+
let array = Self::from_sys(ptr);
608+
std::mem::forget(array.share());
609+
array
610+
}
596611
}
597612

598613
impl<T: VariantMetadata> fmt::Debug for Array<T> {
@@ -702,9 +717,11 @@ impl<T: VariantMetadata + ToVariant> From<&[T]> for Array<T> {
702717
return array;
703718
}
704719
array.resize(len);
705-
let ptr = array.ptr_mut(0);
720+
721+
let ptr = array.ptr_mut_or_null(0);
706722
for (i, element) in slice.iter().enumerate() {
707723
// SAFETY: The array contains exactly `len` elements, stored contiguously in memory.
724+
// Also, the pointer is non-null, as we checked for emptiness above.
708725
unsafe {
709726
*ptr.offset(to_isize(i)) = element.to_variant();
710727
}
@@ -767,10 +784,11 @@ impl<'a, T: VariantMetadata + FromVariant> Iterator for Iter<'a, T> {
767784
if self.next_idx < self.array.len() {
768785
let idx = self.next_idx;
769786
self.next_idx += 1;
770-
// Using `ptr_unchecked` rather than going through `get()` so we can avoid a second
771-
// bounds check.
772-
// SAFETY: We just checked that the index is not out of bounds.
773-
let variant = unsafe { &*self.array.ptr_unchecked(idx) };
787+
788+
let element_ptr = self.array.ptr_or_null(idx);
789+
790+
// SAFETY: We just checked that the index is not out of bounds, so the pointer won't be null.
791+
let variant = unsafe { &*element_ptr };
774792
let element = T::from_variant(variant);
775793
Some(element)
776794
} else {

godot-core/src/builtin/basis.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ unsafe impl GodotFfi for Basis {
579579

580580
/// The ordering used to interpret a set of euler angles as extrinsic
581581
/// rotations.
582-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
582+
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
583583
#[repr(C)]
584584
pub enum EulerOrder {
585585
XYZ = 0,

godot-core/src/builtin/color.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@ use sys::{ffi_methods, GodotFfi};
1515
/// Channel values are _typically_ in the range of 0 to 1, but this is not a requirement, and
1616
/// values outside this range are explicitly allowed for e.g. High Dynamic Range (HDR).
1717
#[repr(C)]
18-
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
18+
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
1919
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2020
pub struct Color {
2121
/// The color's red component.
2222
pub r: f32,
23+
2324
/// The color's green component.
2425
pub g: f32,
26+
2527
/// The color's blue component.
2628
pub b: f32,
29+
2730
/// The color's alpha component. A value of 0 means that the color is fully transparent. A
2831
/// value of 1 means that the color is fully opaque.
2932
pub a: f32,
@@ -318,12 +321,14 @@ unsafe impl GodotFfi for Color {
318321
ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
319322
}
320323

321-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
324+
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
322325
pub enum ColorChannelOrder {
323326
/// RGBA channel order. Godot's default.
324327
Rgba,
328+
325329
/// ABGR channel order. Reverse of the default RGBA order.
326330
Abgr,
331+
327332
/// ARGB channel order. More compatible with DirectX.
328333
Argb,
329334
}

godot-core/src/builtin/dictionary.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ impl Dictionary {
192192
/// _Godot equivalent: `dict[key] = value`_
193193
pub fn set<K: ToVariant, V: ToVariant>(&mut self, key: K, value: V) {
194194
let key = key.to_variant();
195+
196+
// SAFETY: always returns a valid pointer to a value in the dictionary; either pre-existing or newly inserted.
195197
unsafe {
196198
*self.get_ptr_mut(key) = value.to_variant();
197199
}
@@ -226,12 +228,16 @@ impl Dictionary {
226228

227229
/// Get the pointer corresponding to the given key in the dictionary.
228230
///
229-
/// If there exists no value at the given key, a `NIL` variant will be created.
231+
/// If there exists no value at the given key, a `NIL` variant will be inserted for that key.
230232
fn get_ptr_mut<K: ToVariant>(&mut self, key: K) -> *mut Variant {
231233
let key = key.to_variant();
234+
235+
// SAFETY: accessing an unknown key _mutably_ creates that entry in the dictionary, with value `NIL`.
232236
let ptr = unsafe {
233237
interface_fn!(dictionary_operator_index)(self.sys_mut(), key.var_sys_const())
234238
};
239+
240+
// Never a null pointer, since entry either existed already or was inserted above.
235241
Variant::ptr_from_sys_mut(ptr)
236242
}
237243
}
@@ -256,17 +262,17 @@ unsafe impl GodotFfi for Dictionary {
256262
fn move_return_ptr;
257263
}
258264

259-
unsafe fn from_arg_ptr(ptr: sys::GDExtensionTypePtr, _call_type: sys::PtrcallType) -> Self {
260-
let dictionary = Self::from_sys(ptr);
261-
std::mem::forget(dictionary.share());
262-
dictionary
263-
}
264-
265265
unsafe fn from_sys_init_default(init_fn: impl FnOnce(sys::GDExtensionTypePtr)) -> Self {
266266
let mut result = Self::default();
267267
init_fn(result.sys_mut());
268268
result
269269
}
270+
271+
unsafe fn from_arg_ptr(ptr: sys::GDExtensionTypePtr, _call_type: sys::PtrcallType) -> Self {
272+
let dictionary = Self::from_sys(ptr);
273+
std::mem::forget(dictionary.share());
274+
dictionary
275+
}
270276
}
271277

272278
impl_builtin_traits! {

godot-core/src/builtin/meta/class_name.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::obj::GodotClass;
1313

1414
/// Utility to construct class names known at compile time.
1515
/// Cannot be a function since the backing string must be retained.
16-
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
16+
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
1717
pub struct ClassName {
1818
backing: StringName,
1919
}

godot-core/src/builtin/projection.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ unsafe impl GodotFfi for Projection {
494494
}
495495

496496
/// A projections clipping plane.
497-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
497+
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
498498
#[repr(C)]
499499
pub enum ProjectionPlane {
500500
Near = 0,
@@ -507,7 +507,7 @@ pub enum ProjectionPlane {
507507

508508
/// The eye to create a projection for, when creating a projection adjusted
509509
/// for head-mounted displays.
510-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
510+
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
511511
#[repr(C)]
512512
pub enum ProjectionEye {
513513
Left = 1,

godot-core/src/builtin/rect2i.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use super::{Rect2, RectSide, Vector2i};
2020
pub struct Rect2i {
2121
/// The position of the rectangle.
2222
pub position: Vector2i,
23+
2324
/// The size of the rectangle.
2425
pub size: Vector2i,
2526
}

godot-core/src/builtin/rid.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ use sys::{ffi_methods, GodotFfi};
3030
// eligible for it, it is also guaranteed to have it. Meaning the layout of this type is identical to `u64`.
3131
// See: https://doc.rust-lang.org/nomicon/ffi.html#the-nullable-pointer-optimization
3232
// Cannot use `#[repr(C)]` as it does not use the nullable pointer optimization.
33-
#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)]
33+
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
3434
pub enum Rid {
3535
/// A valid RID may refer to some resource, but is not guaranteed to do so.
3636
Valid(NonZeroU64),
37+
3738
/// An invalid RID will never refer to a resource. Internally it is represented as a 0.
3839
Invalid,
3940
}

godot-core/src/builtin/variant/mod.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,16 +200,13 @@ impl Variant {
200200
sys::to_const_ptr(self.var_sys())
201201
}
202202

203+
/// Converts to variant pointer; can be a null pointer.
203204
pub(crate) fn ptr_from_sys(variant_ptr: sys::GDExtensionVariantPtr) -> *const Variant {
204-
assert!(!variant_ptr.is_null(), "ptr_from_sys: null variant pointer");
205205
variant_ptr as *const Variant
206206
}
207207

208+
/// Converts to variant mut pointer; can be a null pointer.
208209
pub(crate) fn ptr_from_sys_mut(variant_ptr: sys::GDExtensionVariantPtr) -> *mut Variant {
209-
assert!(
210-
!variant_ptr.is_null(),
211-
"ptr_from_sys_mut: null variant pointer"
212-
);
213210
variant_ptr as *mut Variant
214211
}
215212
}

godot-core/src/builtin/variant/variant_traits.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub trait ToVariant {
4949

5050
// ----------------------------------------------------------------------------------------------------------------------------------------------
5151

52-
#[derive(Debug, Eq, PartialEq)]
52+
#[derive(Eq, PartialEq, Debug)]
5353
pub struct VariantConversionError;
5454
/*pub enum VariantConversionError {
5555
/// Variant type does not match expected type

godot-core/src/builtin/vector2.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ use super::{real, RAffine2, RVec2};
2626
/// vectors; use the gdext library with the `double-precision` feature in that case.
2727
///
2828
/// See [`Vector2i`] for its integer counterpart.
29-
#[derive(Debug, Default, Clone, Copy, PartialEq)]
29+
#[derive(Default, Copy, Clone, PartialEq, Debug)]
3030
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3131
#[repr(C)]
3232
pub struct Vector2 {
3333
/// The vector's X component.
3434
pub x: real,
35+
3536
/// The vector's Y component.
3637
pub y: real,
3738
}
@@ -318,11 +319,12 @@ unsafe impl GodotFfi for Vector2 {
318319
}
319320

320321
/// Enumerates the axes in a [`Vector2`].
321-
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
322+
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
322323
#[repr(i32)]
323324
pub enum Vector2Axis {
324325
/// The X axis.
325326
X,
327+
326328
/// The Y axis.
327329
Y,
328330
}

godot-core/src/builtin/vector2i.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ use super::IVec2;
2323
/// required. Note that the values are limited to 32 bits, and unlike [`Vector2`] this cannot be
2424
/// configured with an engine build option. Use `i64` or [`PackedInt64Array`] if 64-bit values are
2525
/// needed.
26-
#[derive(Debug, Default, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
26+
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
2727
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2828
#[repr(C)]
2929
pub struct Vector2i {
3030
/// The vector's X component.
3131
pub x: i32,
32+
3233
/// The vector's Y component.
3334
pub y: i32,
3435
}
@@ -99,11 +100,12 @@ unsafe impl GodotFfi for Vector2i {
99100
}
100101

101102
/// Enumerates the axes in a [`Vector2i`].
102-
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
103+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
103104
#[repr(i32)]
104105
pub enum Vector2iAxis {
105106
/// The X axis.
106107
X,
108+
107109
/// The Y axis.
108110
Y,
109111
}

0 commit comments

Comments
 (0)