Skip to content

Commit

Permalink
Merge branch 'feature/dictionary'
Browse files Browse the repository at this point in the history
  • Loading branch information
Bromeon committed Jan 28, 2023
2 parents 4ebdc5e + 7eae6b6 commit 188aa69
Show file tree
Hide file tree
Showing 9 changed files with 792 additions and 2 deletions.
1 change: 1 addition & 0 deletions godot-core/src/builtin/arrays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ impl_builtin_traits! {
for Array {
Default => array_construct_default;
Drop => array_destroy;
PartialEq => array_operator_equal;
}
}

Expand Down
355 changes: 355 additions & 0 deletions godot-core/src/builtin/dictionary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,355 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use godot_ffi as sys;

use crate::builtin::{inner, FromVariant, ToVariant, Variant, VariantConversionError};
use crate::obj::Share;
use std::collections::{HashMap, HashSet};
use std::fmt;
use sys::types::*;
use sys::{ffi_methods, interface_fn, GodotFfi};

use super::Array;

/// Godot's `Dictionary` type.
///
/// The keys and values of the array are all `Variant`, so they can all be of different types.
/// Variants are designed to be generally cheap to clone.
///
/// # Thread safety
///
/// The same principles apply as for [`Array`]. Consult its documentation for details.
#[repr(C)]
pub struct Dictionary {
opaque: OpaqueDictionary,
}

impl Dictionary {
fn from_opaque(opaque: OpaqueDictionary) -> Self {
Self { opaque }
}

/// Constructs an empty `Dictionary`.
pub fn new() -> Self {
Self::default()
}

/// Removes all key-value pairs from the dictionary. Equivalent to `clear` in Godot.
pub fn clear(&mut self) {
self.as_inner().clear()
}

/// Returns a deep copy of the dictionary. All nested arrays and dictionaries are duplicated and
/// will not be shared with the original dictionary. Note that any `Object`-derived elements will
/// still be shallow copied.
///
/// To create a shallow copy, use [`duplicate_shallow()`] instead. To create a new reference to
/// the same array data, use [`share()`].
///
/// Equivalent to `dictionary.duplicate(true)` in Godot.
pub fn duplicate_deep(&self) -> Self {
self.as_inner().duplicate(true)
}

/// Returns a shallow copy of the dictionary. All dictionary keys and values are copied, but
/// any reference types (such as `Array`, `Dictionary` and `Object`) will still refer to the
/// same value.
///
/// To create a deep copy, use [`duplicate_deep()`] instead. To create a new reference to the
/// same dictionary data, use [`share()`].
///
/// Equivalent to `dictionary.duplicate(false)` in Godot.
pub fn duplicate_shallow(&self) -> Self {
self.as_inner().duplicate(false)
}

/// Removes a key from the map, and returns the value associated with
/// the key if the key was in the dictionary.
pub fn remove<K: ToVariant>(&mut self, key: K) -> Option<Variant> {
let key = key.to_variant();
let old_value = self.get(key.clone());
self.as_inner().erase(key);
old_value
}

/// Returns the first key whose associated value is `value`, if one exists.
///
/// Unlike in Godot, this will return `None` if the key does not exist
/// and `Some(nil)` the key is `null`.
pub fn find_key_by_value<V: ToVariant>(&self, value: V) -> Option<Variant> {
let key = self.as_inner().find_key(value.to_variant());

if !key.is_nil() || self.contains_key(key.clone()) {
Some(key)
} else {
None
}
}

/// Returns the value at the key in the dictionary, if there is
/// one.
///
/// Unlike `get` in Godot, this will return `None` if there is
/// no value with the given key.
pub fn get<K: ToVariant>(&self, key: K) -> Option<Variant> {
let key = key.to_variant();
if !self.contains_key(key.clone()) {
return None;
}

Some(self.get_or_nil(key))
}

/// Returns the value at the key in the dictionary, or nil otherwise. This
/// method does not let you differentiate `NIL` values stored as values from
/// absent keys. If you need that, use `get()`.
///
/// This is equivalent to `get` in Godot.
pub fn get_or_nil<K: ToVariant>(&self, key: K) -> Variant {
self.as_inner().get(key.to_variant(), Variant::nil())
}

/// Returns `true` if the dictionary contains the given key.
///
/// This is equivalent to `has` in Godot.
pub fn contains_key<K: ToVariant>(&self, key: K) -> bool {
let key = key.to_variant();
self.as_inner().has(key)
}

/// Returns `true` if the dictionary contains all the given keys.
///
/// This is equivalent to `has_all` in Godot.
pub fn contains_all_keys(&self, keys: Array) -> bool {
self.as_inner().has_all(keys)
}

/// Returns a 32-bit integer hash value representing the dictionary and its contents.
pub fn hash(&self) -> u32 {
self.as_inner().hash().try_into().unwrap()
}

/// Creates a new `Array` containing all the keys currently in the dictionary.
pub fn keys(&self) -> Array {
self.as_inner().keys()
}

/// Creates a new `Array` containing all the values currently in the dictionary.
pub fn values(&self) -> Array {
self.as_inner().values()
}

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

/// Copies all keys and values from other into self.
///
/// If overwrite is true, it will overwrite pre-existing keys. Otherwise
/// it will not.
///
/// This is equivalent to `merge` in Godot.
pub fn extend_dictionary(&mut self, other: Self, overwrite: bool) {
self.as_inner().merge(other, overwrite)
}

/// Returns the number of entries in the dictionary.
///
/// This is equivalent to `size` in Godot.
pub fn len(&self) -> usize {
self.as_inner().size().try_into().unwrap()
}

/// Get the pointer corresponding to the given key in the dictionary,
/// if there exists no value at the given key then a new one is created
/// and initialized to a nil variant.
fn get_ptr_mut<K: ToVariant>(&mut self, key: K) -> *mut Variant {
let key = key.to_variant();
unsafe {
let ptr =
(interface_fn!(dictionary_operator_index))(self.sys_mut(), key.var_sys_const());
assert!(!ptr.is_null());
ptr as *mut Variant
}
}

/// Insert a value at the given key, returning the value
/// that previously was at that key if there was one.
pub fn insert<K: ToVariant, V: ToVariant>(&mut self, key: K, value: V) -> Option<Variant> {
let key = key.to_variant();
let old_value = self.get(key.clone());
self.set(key, value);
old_value
}

/// Set a key to a given value.
pub fn set<K: ToVariant, V: ToVariant>(&mut self, key: K, value: V) {
let key = key.to_variant();
unsafe {
*self.get_ptr_mut(key) = value.to_variant();
}
}

#[doc(hidden)]
pub fn as_inner(&self) -> inner::InnerDictionary {
inner::InnerDictionary::from_outer(self)
}
}

/// Creates a Dictionary from the given iterator I over a (&K, &V) key-value pair.
/// Each key and value are converted to a Variant.
impl<'a, 'b, K, V, I> From<I> for Dictionary
where
I: IntoIterator<Item = (&'a K, &'b V)>,
K: ToVariant + 'a,
V: ToVariant + 'b,
{
fn from(iterable: I) -> Self {
iterable
.into_iter()
.map(|(key, value)| (key.to_variant(), value.to_variant()))
.collect()
}
}

/// Convert this dictionary to a strongly typed rust `HashMap`. If the conversion
/// fails for any key or value, an error is returned.
///
/// Will be replaced by a proper iteration implementation.
impl<K: FromVariant + Eq + std::hash::Hash, V: FromVariant> TryFrom<&Dictionary> for HashMap<K, V> {
type Error = VariantConversionError;

fn try_from(dictionary: &Dictionary) -> Result<Self, Self::Error> {
// TODO: try to panic or something if modified while iterating
// Though probably better to fix when implementing iteration proper
dictionary
.keys()
.iter_shared()
.zip(dictionary.values().iter_shared())
.map(|(key, value)| Ok((K::try_from_variant(&key)?, V::try_from_variant(&value)?)))
.collect()
}
}

/// Convert the keys of this dictionary to a strongly typed rust `HashSet`. If the
/// conversion fails for any key, an error is returned.
impl<K: FromVariant + Eq + std::hash::Hash> TryFrom<&Dictionary> for HashSet<K> {
type Error = VariantConversionError;

fn try_from(dictionary: &Dictionary) -> Result<Self, Self::Error> {
// TODO: try to panic or something if modified while iterating
// Though probably better to fix when implementing iteration proper
dictionary
.keys()
.iter_shared()
.map(|key| K::try_from_variant(&key))
.collect()
}
}

/// Inserts all key-values from the iterator into the dictionary,
/// replacing values with existing keys with new values returned
/// from the iterator.
impl<K: ToVariant, V: ToVariant> Extend<(K, V)> for Dictionary {
fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
for (k, v) in iter.into_iter() {
self.set(k.to_variant(), v.to_variant())
}
}
}

impl<K: ToVariant, V: ToVariant> FromIterator<(K, V)> for Dictionary {
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
let mut dict = Dictionary::new();
dict.extend(iter);
dict
}
}

impl_builtin_traits! {
for Dictionary {
Default => dictionary_construct_default;
Drop => dictionary_destroy;
PartialEq => dictionary_operator_equal;
}
}

impl GodotFfi for Dictionary {
ffi_methods! {
type sys::GDExtensionTypePtr = *mut Opaque;
fn from_sys;
fn sys;
fn write_sys;
}

unsafe fn from_sys_init(init_fn: impl FnOnce(sys::GDExtensionTypePtr)) -> Self {
// Can't use uninitialized pointer -- Dictionary CoW implementation in C++ expects that on
// assignment, the target CoW pointer is either initialized or nullptr

let mut result = Self::default();
init_fn(result.sys_mut());
result
}
}

impl fmt::Debug for Dictionary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.to_variant().stringify())
}
}

/// Creates a new reference to the data in this dictionary. Changes to the original dictionary will be
/// reflected in the copy and vice versa.
///
/// To create a (mostly) independent copy instead, see [`Dictionary::duplicate_shallow()`] and
/// [`Dictionary::duplicate_deep()`].
impl Share for Dictionary {
fn share(&self) -> Self {
unsafe {
Self::from_sys_init(|self_ptr| {
let ctor = sys::builtin_fn!(dictionary_construct_copy);
let args = [self.sys_const()];
ctor(self_ptr, args.as_ptr());
})
}
}
}

/// Creates a new dictionary with the given keys and values, the syntax mirrors
/// Godot's dictionary creation syntax.
///
/// Any value can be used as a key, but to use an expression you need to surround it
/// in `()` or `{}`.
///
/// Example
/// ```no_run
/// use godot::builtin::dict;
///
/// let key = "my_key";
/// let d = dict! {
/// "key1": 10,
/// "another": Variant::nil(),
/// key: true,
/// (1 + 2): "final",
/// }
/// ```
#[macro_export]
macro_rules! dict {
($($key:tt: $value:expr),* $(,)?) => {
{
let mut d = $crate::builtin::Dictionary::new();
$(
// `cargo check` complains that `(1 + 2): true` has unused parens, even though it's not
// possible to omit the parens.
#[allow(unused_parens)]
d.set($key, $value);
)*
d
}
};
}
4 changes: 4 additions & 0 deletions godot-core/src/builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ mod vector_macros;

mod arrays;
mod color;
mod dictionary;
mod node_path;
mod others;
mod packed_array;
Expand All @@ -52,8 +53,11 @@ mod vector4i;

pub mod meta;

pub use crate::dict;

pub use arrays::*;
pub use color::*;
pub use dictionary::*;
pub use node_path::*;
pub use others::*;
pub use packed_array::*;
Expand Down
1 change: 0 additions & 1 deletion godot-core/src/builtin/others.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ impl_builtin_stub!(Projection, OpaqueProjection);
impl_builtin_stub!(Rid, OpaqueRid);
impl_builtin_stub!(Callable, OpaqueCallable);
impl_builtin_stub!(Signal, OpaqueSignal);
impl_builtin_stub!(Dictionary, OpaqueDictionary);

#[repr(C)]
struct InnerRect {
Expand Down
Loading

0 comments on commit 188aa69

Please sign in to comment.