Skip to content

Commit

Permalink
Implement basic Array functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
ttencate committed Jan 25, 2023
1 parent 9907d1c commit 8c1407f
Show file tree
Hide file tree
Showing 6 changed files with 614 additions and 8 deletions.
374 changes: 368 additions & 6 deletions godot-core/src/builtin/arrays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

use godot_ffi as sys;

use crate::builtin::{inner, FromVariant, Variant};
use crate::builtin::{inner, FromVariant, ToVariant, Variant, VariantConversionError};
use std::fmt;
use std::marker::PhantomData;
use sys::types::*;
use sys::{ffi_methods, interface_fn, GodotFfi};
Expand Down Expand Up @@ -44,14 +45,317 @@ impl_builtin_froms!(PackedStringArray; Array => packed_string_array_from_array);
impl_builtin_froms!(PackedVector2Array; Array => packed_vector2_array_from_array);
impl_builtin_froms!(PackedVector3Array; Array => packed_vector3_array_from_array);

/// Godot's `Array` type.
///
/// This is a variant array, meaning it contains `Variant`s which may be of different types even
/// within the same array.
///
/// Unlike GDScript, all indices and sizes are unsigned, so negative indices are not supported.
impl Array {
pub fn get(&self, index: i64) -> Option<Variant> {
/// Constructs an empty `Array`.
pub fn new() -> Self {
Self::default()
}

/// Returns the number of elements in the array. Equivalent of `size()` in Godot.
pub fn len(&self) -> usize {
self.as_inner().size().try_into().unwrap()
}

/// Returns `true` if the array is empty.
pub fn is_empty(&self) -> bool {
self.as_inner().is_empty()
}

/// Returns a 32-bit integer hash value representing the array and its contents.
///
/// Note: Arrays with equal content will always produce identical hash values. However, the
/// reverse is not true. Returning identical hash values does not imply the arrays are equal,
/// because different arrays can have identical hash values due to hash collisions.
pub fn hash(&self) -> u32 {
// The GDExtension interface only deals in `i64`, but the engine's own `hash()` function
// actually returns `uint32_t`.
self.as_inner().hash().try_into().unwrap()
}

/// Converts this array to a strongly typed Rust vector. If the conversion from `Variant` fails
/// for any element, an error is returned.
pub fn try_to_vec<T: FromVariant>(&self) -> Result<Vec<T>, VariantConversionError> {
let len = self.len();
let mut vec = Vec::with_capacity(len);
if len == 0 {
// Cannot get pointer to first element in this case.
return Ok(vec);
}
// SAFETY: Arrays are stored contiguously in memory, so we can use pointer arithmetic
// instead of going through `array_operator_index_const` for every index.
unsafe {
let ptr = (interface_fn!(array_operator_index))(self.sys(), index) as *mut Variant;
if ptr.is_null() {
return None;
let ptr = (interface_fn!(array_operator_index_const))(self.sys(), 0) as *const Variant;
assert!(!ptr.is_null());
for i in 0..len {
let element = T::try_from_variant(&*ptr.offset(i.try_into().unwrap()))?;
vec.push(element);
}
Some((*ptr).clone())
}
Ok(vec)
}

/// Clears the array, removing all elements.
pub fn clear(&mut self) {
self.as_inner().clear();
}

/// Resizes the array to contain a different number of elements. If the new size is smaller,
/// elements are removed from the end. If the new size is larger, new elements are set to
/// [`Variant::nil()`].
pub fn resize(&mut self, size: usize) {
self.as_inner().resize(size.try_into().unwrap());
}

// TODO: find out why this segfaults (even on an empty array, regardless of deep = true/false)
// /// Returns a deep copy of the array. All nested arrays and dictionaries are duplicated and
// /// will not be shared with the original array. Note that any `Object`-derived elements will
// /// still be shallow copied.
// ///
// /// To create a shallow copy, use `clone()` instead.
// pub fn duplicate_deep(&self) -> Self {
// self.as_inner().duplicate(true)
// }

/// Returns the value at the specified index as a `Variant`. To convert to a specific type, use
/// the available conversion methods on `Variant`, such as [`Variant::try_to`] or
/// [`Variant::to`].
///
/// Panics if `index` is out of bounds.
pub fn get(&self, index: usize) -> Variant {
self.check_bounds(index);
// SAFETY: We just verified that the index is not out of bounds.
unsafe {
let ptr = (interface_fn!(array_operator_index_const))(
self.sys(), index.try_into().unwrap()) as *const Variant;
assert!(!ptr.is_null(), "index within bounds but pointer is null");
(*ptr).clone()
}
}

/// Returns the value at the specified index as a `Variant`. To convert to a specific type, use
/// the available conversion methods on `Variant`, such as [`Variant::try_to`] or
/// [`Variant::to`].
///
/// Panics if `index` is out of bounds.
///
/// # Safety
///
/// The returned reference is invalidated if the same array is mutated through another
/// reference.
pub unsafe fn get_mut(&mut self, index: usize) -> &mut Variant {
self.check_bounds(index);
// SAFETY: We just checked that the index is not out of bounds.
unsafe {
let ptr = (interface_fn!(array_operator_index))(
self.sys(), index.try_into().unwrap()) as *mut Variant;
assert!(!ptr.is_null(), "index within bounds but pointer is null");
&mut *ptr
}
}

/// Returns the first element in the array, or `None` if the array is empty. Equivalent of
/// `front()` in GDScript.
pub fn first(&self) -> Option<Variant> {
(!self.is_empty()).then(|| self.as_inner().front())
}

/// Returns the last element in the array, or `None` if the array is empty. Equivalent of
/// `back()` in GDScript.
pub fn last(&self) -> Option<Variant> {
(!self.is_empty()).then(|| self.as_inner().back())
}

/// Finds the index of an existing value in a sorted array using binary search. Equivalent of
/// `bsearch` in GDScript.
///
/// If the value is not present in the array, returns the insertion index that would maintain
/// sorting order.
///
/// Calling `binary_search` on an unsorted array results in unspecified behavior.
pub fn binary_search(&self, value: Variant) -> usize {
self.as_inner().bsearch(value, true).try_into().unwrap()
}

/// Returns the number of times a value is in the array.
pub fn count(&self, value: Variant) -> usize {
self.as_inner().count(value).try_into().unwrap()
}

/// Returns `true` if the array contains the given value. Equivalent of `has` in GDScript.
pub fn contains(&self, value: Variant) -> bool {
self.as_inner().has(value)
}

/// Searches the array for the first occurrence of a value and returns its index, or `None` if
/// not found. Starts searching at index `from`; pass `None` to search the entire array.
pub fn find(&self, value: Variant, from: Option<usize>) -> Option<usize> {
let from = from.unwrap_or(0).try_into().unwrap();
let index = self.as_inner().find(value, from);
if index >= 0 {
Some(index.try_into().unwrap())
} else {
None
}
}

/// Searches the array backwards for the last occurrence of a value and returns its index, or
/// `None` if not found. Starts searching at index `from`; pass `None` to search the entire
/// array.
pub fn rfind(&self, value: Variant, from: Option<usize>) -> Option<usize> {
let from = from
.map(|from| from.try_into().unwrap())
.unwrap_or(-1);
let index = self.as_inner().rfind(value, from);
// It's not documented, but `rfind` returns -1 if not found.
if index >= 0 {
Some(index.try_into().unwrap())
} else {
None
}
}

/// Returns the minimum value contained in the array if all elements are of comparable types.
/// If the elements can't be compared or the array is empty, `None` is returned.
pub fn min(&self) -> Option<Variant> {
let min = self.as_inner().min();
(!min.is_nil()).then(|| min)
}

/// Returns the maximum value contained in the array if all elements are of comparable types.
/// If the elements can't be compared or the array is empty, `None` is returned.
pub fn max(&self) -> Option<Variant> {
let max = self.as_inner().max();
(!max.is_nil()).then(|| max)
}

/// Returns a random element from the array, or `None` if it is empty.
pub fn pick_random(&self) -> Option<Variant> {
(!self.is_empty()).then(|| self.as_inner().pick_random())
}

/// Sets the value at the specified index as a `Variant`. To convert a specific type (which
/// implements `ToVariant`) to a variant, call [`ToVariant::to_variant`] on it.
///
/// Panics if `index` is out of bounds.
pub fn set(&mut self, index: usize, value: Variant) {
self.check_bounds(index);
// SAFETY: We just checked that the index is not out of bounds.
unsafe {
let ptr = (interface_fn!(array_operator_index))(
self.sys(), index.try_into().unwrap()) as *mut Variant;
assert!(!ptr.is_null(), "index within bounds but pointer is null");
*ptr = value;
}
}

/// Appends an element to the end of the array. Equivalent of `append` and `push_back` in
/// GDScript.
pub fn push(&mut self, value: Variant) {
self.as_inner().push_back(value);
}

/// Adds an element at the beginning of the array. See also `push`.
///
/// Note: On large arrays, this method is much slower than `push` as it will move all the
/// array's elements. The larger the array, the slower `push_front` will be.
pub fn push_front(&mut self, value: Variant) {
self.as_inner().push_front(value);
}

/// Removes and returns the last element of the array. Returns `None` if the array is empty.
/// Equivalent of `pop_back` in GDScript.
pub fn pop(&mut self) -> Option<Variant> {
(!self.is_empty()).then(|| self.as_inner().pop_back())
}

/// Removes and returns the first element of the array. Returns `None` if the array is empty.
///
/// Note: On large arrays, this method is much slower than `pop` as it will move all the
/// array's elements. The larger the array, the slower `pop_front` will be.
pub fn pop_front(&mut self) -> Option<Variant> {
(!self.is_empty()).then(|| self.as_inner().pop_front())
}

/// Inserts a new element at a given index in the array. The index must be valid, or at the end
/// of the array (`index == len()`).
///
/// Note: On large arrays, this method is much slower than `push` as it will move all the
/// array's elements after the inserted element. The larger the array, the slower `insert` will
/// be.
pub fn insert(&mut self, index: usize, value: Variant) {
let len = self.len();
if index > len {
panic!("Array insertion index {} is out of bounds: length is {}", index, len);
}
self.as_inner().insert(index.try_into().unwrap(), value);
}

/// Removes and returns the element at the specified index. Equivalent of `pop_at` in GDScript.
///
/// On large arrays, this method is much slower than `pop_back` as it will move all the array's
/// elements after the removed element. The larger the array, the slower `remove` will be.
///
/// Panics if `index` is out of bounds.
pub fn remove(&mut self, index: usize) -> Variant {
self.check_bounds(index);
self.as_inner().pop_at(index.try_into().unwrap())
}

/// Removes the first occurrence of a value from the array. If the value does not exist in the
/// array, nothing happens. To remove an element by index, use `remove` instead.
///
/// On large arrays, this method is much slower than `pop_back` as it will move all the array's
/// elements after the removed element. The larger the array, the slower `remove` will be.
pub fn erase(&mut self, value: Variant) {
self.as_inner().erase(value);
}

/// Assigns the given value to all elements in the array. This can be used together with
/// `resize` to create an array with a given size and initialized elements.
pub fn fill(&mut self, value: Variant) {
self.as_inner().fill(value);
}

/// Appends another array at the end of this array. Equivalent of `append_array` in GDScript.
pub fn extend_array(&mut self, other: Array) {
self.as_inner().append_array(other);
}

/// Reverses the order of the elements in the array.
pub fn reverse(&mut self) {
self.as_inner().reverse();
}

/// Sorts the array.
///
/// Note: The sorting algorithm used is not
/// [stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability). This means that values
/// considered equal may have their order changed when using `sort_unstable`.
pub fn sort_unstable(&mut self) {
self.as_inner().sort();
}

/// Shuffles the array such that the items will have a random order. This method uses the
/// global random number generator common to methods such as `randi`. Call `randomize` to
/// ensure that a new seed will be used each time if you want non-reproducible shuffling.
pub fn shuffle(&mut self) {
self.as_inner().shuffle();
}

/// Wraps negative indices to count from the end of the array, because the GDExtension API
/// doesn't do this.
///
/// Panics if `index` is out of bounds.
fn check_bounds(&self, index: usize) {
let len = self.len();
if index >= len {
panic!("Array index {} is out of bounds: length is {}", index, len);
}
}

Expand All @@ -62,6 +366,64 @@ impl Array {
}
}

/// Creates an `Array` from the given Rust array. Each element is converted to a `Variant`.
impl<T: ToVariant, const N: usize> From<&[T; N]> for Array {
fn from(arr: &[T; N]) -> Self {
Self::from(&arr[..])
}
}

/// Creates an `Array` from the given slice. Each element is converted to a `Variant`.
impl<T: ToVariant> From<&[T]> for Array {
fn from(slice: &[T]) -> Self {
let mut array = Self::new();
let len = slice.len().try_into().unwrap();
if len == 0 {
return array;
}
array.resize(len);
// SAFETY: The array contains exactly `len` elements, stored contiguously in memory.
unsafe {
let ptr = (interface_fn!(array_operator_index))(array.sys(), 0) as *mut Variant;
assert!(!ptr.is_null());
for (i, element) in slice.into_iter().enumerate() {
*ptr.offset(i.try_into().unwrap()) = element.to_variant();
}
}
array
}
}

/// Creates an `Array` from an iterator. Each element is converted to a `Variant`.
impl<T: ToVariant> FromIterator<T> for Array {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut array = Array::new();
array.extend(iter);
array
}
}

/// Extends an `Array` with the contents of an iterator. Each element is converted to a `Variant`.
impl<T: ToVariant> Extend<T> for Array {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
// Unfortunately the GDExtension API does not offer the equivalent of `Vec::reserve`.
// Otherwise we could use it to pre-allocate based on `iter.size_hint()`.
//
// A faster implementation using `resize()` and direct pointer writes might still be
// possible.
for item in iter.into_iter() {
self.push(item.to_variant());
}
}
}

impl fmt::Debug for Array {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Going through `Variant` because there doesn't seem to be a direct way.
write!(f, "{:?}", self.to_variant().stringify())
}
}

impl_builtin_traits! {
for Array {
Default => array_construct_default;
Expand Down
Loading

0 comments on commit 8c1407f

Please sign in to comment.