Skip to content

Commit 6ae8ffd

Browse files
bors[bot]lilizoey
andauthored
Merge #92
92: Add basic dictionary impl r=Bromeon a=sayaks Add basic dictionary impl with: - `From` for `HashMap` and `HashSet` (with true as the values) - `FromIterator` for `(K,V)` - methods from godot, or a rusty substitute - `dict!` macro to easily make dictionaries godot-style missing: - iteration - `get_mut` (and other `_mut` functions) - Entry-api (if possible) - `key = value` syntax for `dict!` macro The added `PartialEq => array_operator_equal` impl in `Array` is there to simplify a test, but can be removed. But i think it's gonna be added eventually anyway. I'm also not entirely sure about the safety of using dictionaries, my guess is that it's the same as for array though. Co-authored-by: Lili Zoey <[email protected]>
2 parents 4ebdc5e + 2c2f924 commit 6ae8ffd

File tree

9 files changed

+792
-2
lines changed

9 files changed

+792
-2
lines changed

godot-core/src/builtin/arrays.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,7 @@ impl_builtin_traits! {
559559
for Array {
560560
Default => array_construct_default;
561561
Drop => array_destroy;
562+
PartialEq => array_operator_equal;
562563
}
563564
}
564565

godot-core/src/builtin/dictionary.rs

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use godot_ffi as sys;
8+
9+
use crate::builtin::{inner, FromVariant, ToVariant, Variant, VariantConversionError};
10+
use crate::obj::Share;
11+
use std::collections::{HashMap, HashSet};
12+
use std::fmt;
13+
use sys::types::*;
14+
use sys::{ffi_methods, interface_fn, GodotFfi};
15+
16+
use super::Array;
17+
18+
/// Godot's `Dictionary` type.
19+
///
20+
/// The keys and values of the array are all `Variant`, so they can all be of different types.
21+
/// Variants are designed to be generally cheap to clone.
22+
///
23+
/// # Thread safety
24+
///
25+
/// The same principles apply as for [`Array`]. Consult its documentation for details.
26+
#[repr(C)]
27+
pub struct Dictionary {
28+
opaque: OpaqueDictionary,
29+
}
30+
31+
impl Dictionary {
32+
fn from_opaque(opaque: OpaqueDictionary) -> Self {
33+
Self { opaque }
34+
}
35+
36+
/// Constructs an empty `Dictionary`.
37+
pub fn new() -> Self {
38+
Self::default()
39+
}
40+
41+
/// Removes all key-value pairs from the dictionary. Equivalent to `clear` in Godot.
42+
pub fn clear(&mut self) {
43+
self.as_inner().clear()
44+
}
45+
46+
/// Returns a deep copy of the dictionary. All nested arrays and dictionaries are duplicated and
47+
/// will not be shared with the original dictionary. Note that any `Object`-derived elements will
48+
/// still be shallow copied.
49+
///
50+
/// To create a shallow copy, use [`duplicate_shallow()`] instead. To create a new reference to
51+
/// the same array data, use [`share()`].
52+
///
53+
/// Equivalent to `dictionary.duplicate(true)` in Godot.
54+
pub fn duplicate_deep(&self) -> Self {
55+
self.as_inner().duplicate(true)
56+
}
57+
58+
/// Returns a shallow copy of the dictionary. All dictionary keys and values are copied, but
59+
/// any reference types (such as `Array`, `Dictionary` and `Object`) will still refer to the
60+
/// same value.
61+
///
62+
/// To create a deep copy, use [`duplicate_deep()`] instead. To create a new reference to the
63+
/// same dictionary data, use [`share()`].
64+
///
65+
/// Equivalent to `dictionary.duplicate(false)` in Godot.
66+
pub fn duplicate_shallow(&self) -> Self {
67+
self.as_inner().duplicate(false)
68+
}
69+
70+
/// Removes a key from the map, and returns the value associated with
71+
/// the key if the key was in the dictionary.
72+
pub fn remove<K: ToVariant>(&mut self, key: K) -> Option<Variant> {
73+
let key = key.to_variant();
74+
let old_value = self.get(key.clone());
75+
self.as_inner().erase(key);
76+
old_value
77+
}
78+
79+
/// Returns the first key whose associated value is `value`, if one exists.
80+
///
81+
/// Unlike in Godot, this will return `None` if the key does not exist
82+
/// and `Some(nil)` the key is `null`.
83+
pub fn find_key_by_value<V: ToVariant>(&self, value: V) -> Option<Variant> {
84+
let key = self.as_inner().find_key(value.to_variant());
85+
86+
if !key.is_nil() || self.contains_key(key.clone()) {
87+
Some(key)
88+
} else {
89+
None
90+
}
91+
}
92+
93+
/// Returns the value at the key in the dictionary, if there is
94+
/// one.
95+
///
96+
/// Unlike `get` in Godot, this will return `None` if there is
97+
/// no value with the given key.
98+
pub fn get<K: ToVariant>(&self, key: K) -> Option<Variant> {
99+
let key = key.to_variant();
100+
if !self.contains_key(key.clone()) {
101+
return None;
102+
}
103+
104+
Some(self.get_or_nil(key))
105+
}
106+
107+
/// Returns the value at the key in the dictionary, or nil otherwise. This
108+
/// method does not let you differentiate `NIL` values stored as values from
109+
/// absent keys. If you need that, use `get()`.
110+
///
111+
/// This is equivalent to `get` in Godot.
112+
pub fn get_or_nil<K: ToVariant>(&self, key: K) -> Variant {
113+
self.as_inner().get(key.to_variant(), Variant::nil())
114+
}
115+
116+
/// Returns `true` if the dictionary contains the given key.
117+
///
118+
/// This is equivalent to `has` in Godot.
119+
pub fn contains_key<K: ToVariant>(&self, key: K) -> bool {
120+
let key = key.to_variant();
121+
self.as_inner().has(key)
122+
}
123+
124+
/// Returns `true` if the dictionary contains all the given keys.
125+
///
126+
/// This is equivalent to `has_all` in Godot.
127+
pub fn contains_all_keys(&self, keys: Array) -> bool {
128+
self.as_inner().has_all(keys)
129+
}
130+
131+
/// Returns a 32-bit integer hash value representing the dictionary and its contents.
132+
pub fn hash(&self) -> u32 {
133+
self.as_inner().hash().try_into().unwrap()
134+
}
135+
136+
/// Creates a new `Array` containing all the keys currently in the dictionary.
137+
pub fn keys(&self) -> Array {
138+
self.as_inner().keys()
139+
}
140+
141+
/// Creates a new `Array` containing all the values currently in the dictionary.
142+
pub fn values(&self) -> Array {
143+
self.as_inner().values()
144+
}
145+
146+
/// Returns true if the dictionary is empty.
147+
pub fn is_empty(&self) -> bool {
148+
self.as_inner().is_empty()
149+
}
150+
151+
/// Copies all keys and values from other into self.
152+
///
153+
/// If overwrite is true, it will overwrite pre-existing keys. Otherwise
154+
/// it will not.
155+
///
156+
/// This is equivalent to `merge` in Godot.
157+
pub fn extend_dictionary(&mut self, other: Self, overwrite: bool) {
158+
self.as_inner().merge(other, overwrite)
159+
}
160+
161+
/// Returns the number of entries in the dictionary.
162+
///
163+
/// This is equivalent to `size` in Godot.
164+
pub fn len(&self) -> usize {
165+
self.as_inner().size().try_into().unwrap()
166+
}
167+
168+
/// Get the pointer corresponding to the given key in the dictionary,
169+
/// if there exists no value at the given key then a new one is created
170+
/// and initialized to a nil variant.
171+
fn get_ptr_mut<K: ToVariant>(&mut self, key: K) -> *mut Variant {
172+
let key = key.to_variant();
173+
unsafe {
174+
let ptr =
175+
(interface_fn!(dictionary_operator_index))(self.sys_mut(), key.var_sys_const());
176+
assert!(!ptr.is_null());
177+
ptr as *mut Variant
178+
}
179+
}
180+
181+
/// Insert a value at the given key, returning the value
182+
/// that previously was at that key if there was one.
183+
pub fn insert<K: ToVariant, V: ToVariant>(&mut self, key: K, value: V) -> Option<Variant> {
184+
let key = key.to_variant();
185+
let old_value = self.get(key.clone());
186+
self.set(key, value);
187+
old_value
188+
}
189+
190+
/// Set a key to a given value.
191+
pub fn set<K: ToVariant, V: ToVariant>(&mut self, key: K, value: V) {
192+
let key = key.to_variant();
193+
unsafe {
194+
*self.get_ptr_mut(key) = value.to_variant();
195+
}
196+
}
197+
198+
#[doc(hidden)]
199+
pub fn as_inner(&self) -> inner::InnerDictionary {
200+
inner::InnerDictionary::from_outer(self)
201+
}
202+
}
203+
204+
/// Creates a Dictionary from the given iterator I over a (&K, &V) key-value pair.
205+
/// Each key and value are converted to a Variant.
206+
impl<'a, 'b, K, V, I> From<I> for Dictionary
207+
where
208+
I: IntoIterator<Item = (&'a K, &'b V)>,
209+
K: ToVariant + 'a,
210+
V: ToVariant + 'b,
211+
{
212+
fn from(iterable: I) -> Self {
213+
iterable
214+
.into_iter()
215+
.map(|(key, value)| (key.to_variant(), value.to_variant()))
216+
.collect()
217+
}
218+
}
219+
220+
/// Convert this dictionary to a strongly typed rust `HashMap`. If the conversion
221+
/// fails for any key or value, an error is returned.
222+
///
223+
/// Will be replaced by a proper iteration implementation.
224+
impl<K: FromVariant + Eq + std::hash::Hash, V: FromVariant> TryFrom<&Dictionary> for HashMap<K, V> {
225+
type Error = VariantConversionError;
226+
227+
fn try_from(dictionary: &Dictionary) -> Result<Self, Self::Error> {
228+
// TODO: try to panic or something if modified while iterating
229+
// Though probably better to fix when implementing iteration proper
230+
dictionary
231+
.keys()
232+
.iter_shared()
233+
.zip(dictionary.values().iter_shared())
234+
.map(|(key, value)| Ok((K::try_from_variant(&key)?, V::try_from_variant(&value)?)))
235+
.collect()
236+
}
237+
}
238+
239+
/// Convert the keys of this dictionary to a strongly typed rust `HashSet`. If the
240+
/// conversion fails for any key, an error is returned.
241+
impl<K: FromVariant + Eq + std::hash::Hash> TryFrom<&Dictionary> for HashSet<K> {
242+
type Error = VariantConversionError;
243+
244+
fn try_from(dictionary: &Dictionary) -> Result<Self, Self::Error> {
245+
// TODO: try to panic or something if modified while iterating
246+
// Though probably better to fix when implementing iteration proper
247+
dictionary
248+
.keys()
249+
.iter_shared()
250+
.map(|key| K::try_from_variant(&key))
251+
.collect()
252+
}
253+
}
254+
255+
/// Inserts all key-values from the iterator into the dictionary,
256+
/// replacing values with existing keys with new values returned
257+
/// from the iterator.
258+
impl<K: ToVariant, V: ToVariant> Extend<(K, V)> for Dictionary {
259+
fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
260+
for (k, v) in iter.into_iter() {
261+
self.set(k.to_variant(), v.to_variant())
262+
}
263+
}
264+
}
265+
266+
impl<K: ToVariant, V: ToVariant> FromIterator<(K, V)> for Dictionary {
267+
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
268+
let mut dict = Dictionary::new();
269+
dict.extend(iter);
270+
dict
271+
}
272+
}
273+
274+
impl_builtin_traits! {
275+
for Dictionary {
276+
Default => dictionary_construct_default;
277+
Drop => dictionary_destroy;
278+
PartialEq => dictionary_operator_equal;
279+
}
280+
}
281+
282+
impl GodotFfi for Dictionary {
283+
ffi_methods! {
284+
type sys::GDExtensionTypePtr = *mut Opaque;
285+
fn from_sys;
286+
fn sys;
287+
fn write_sys;
288+
}
289+
290+
unsafe fn from_sys_init(init_fn: impl FnOnce(sys::GDExtensionTypePtr)) -> Self {
291+
// Can't use uninitialized pointer -- Dictionary CoW implementation in C++ expects that on
292+
// assignment, the target CoW pointer is either initialized or nullptr
293+
294+
let mut result = Self::default();
295+
init_fn(result.sys_mut());
296+
result
297+
}
298+
}
299+
300+
impl fmt::Debug for Dictionary {
301+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302+
write!(f, "{:?}", self.to_variant().stringify())
303+
}
304+
}
305+
306+
/// Creates a new reference to the data in this dictionary. Changes to the original dictionary will be
307+
/// reflected in the copy and vice versa.
308+
///
309+
/// To create a (mostly) independent copy instead, see [`Dictionary::duplicate_shallow()`] and
310+
/// [`Dictionary::duplicate_deep()`].
311+
impl Share for Dictionary {
312+
fn share(&self) -> Self {
313+
unsafe {
314+
Self::from_sys_init(|self_ptr| {
315+
let ctor = sys::builtin_fn!(dictionary_construct_copy);
316+
let args = [self.sys_const()];
317+
ctor(self_ptr, args.as_ptr());
318+
})
319+
}
320+
}
321+
}
322+
323+
/// Creates a new dictionary with the given keys and values, the syntax mirrors
324+
/// Godot's dictionary creation syntax.
325+
///
326+
/// Any value can be used as a key, but to use an expression you need to surround it
327+
/// in `()` or `{}`.
328+
///
329+
/// Example
330+
/// ```no_run
331+
/// use godot::builtin::dict;
332+
///
333+
/// let key = "my_key";
334+
/// let d = dict! {
335+
/// "key1": 10,
336+
/// "another": Variant::nil(),
337+
/// key: true,
338+
/// (1 + 2): "final",
339+
/// }
340+
/// ```
341+
#[macro_export]
342+
macro_rules! dict {
343+
($($key:tt: $value:expr),* $(,)?) => {
344+
{
345+
let mut d = $crate::builtin::Dictionary::new();
346+
$(
347+
// `cargo check` complains that `(1 + 2): true` has unused parens, even though it's not
348+
// possible to omit the parens.
349+
#[allow(unused_parens)]
350+
d.set($key, $value);
351+
)*
352+
d
353+
}
354+
};
355+
}

godot-core/src/builtin/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ mod vector_macros;
3737

3838
mod arrays;
3939
mod color;
40+
mod dictionary;
4041
mod node_path;
4142
mod others;
4243
mod packed_array;
@@ -52,8 +53,11 @@ mod vector4i;
5253

5354
pub mod meta;
5455

56+
pub use crate::dict;
57+
5558
pub use arrays::*;
5659
pub use color::*;
60+
pub use dictionary::*;
5761
pub use node_path::*;
5862
pub use others::*;
5963
pub use packed_array::*;

godot-core/src/builtin/others.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ impl_builtin_stub!(Projection, OpaqueProjection);
2525
impl_builtin_stub!(Rid, OpaqueRid);
2626
impl_builtin_stub!(Callable, OpaqueCallable);
2727
impl_builtin_stub!(Signal, OpaqueSignal);
28-
impl_builtin_stub!(Dictionary, OpaqueDictionary);
2928

3029
#[repr(C)]
3130
struct InnerRect {

0 commit comments

Comments
 (0)