Skip to content

Commit c112d06

Browse files
bors[bot]BogayBromeon
authored
Merge #841
841: Add `get`/`set` to `property` attribute to specify custom getter/setter r=Bromeon a=Bogay Fix #547 Co-authored-by: bogay <[email protected]> Co-authored-by: Jan Haller <[email protected]>
2 parents 1f3b19d + 768020f commit c112d06

File tree

6 files changed

+628
-71
lines changed

6 files changed

+628
-71
lines changed

gdnative-core/src/export/property.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! Property registration.
2+
use std::marker::PhantomData;
23

34
use accessor::{Getter, RawGetter, RawSetter, Setter};
45
use invalid_accessor::{InvalidGetter, InvalidSetter};
@@ -322,6 +323,115 @@ impl PropertyUsage {
322323
}
323324
}
324325

326+
/// Placeholder type for exported properties with no backing field.
327+
///
328+
/// This is the go-to type whenever you want to expose a getter/setter to GDScript, which
329+
/// does not directly map to a field in your struct. Instead of adding a useless field
330+
/// of the corresponding type (which needs initialization, extra space, etc.), you can use
331+
/// an instance of this type as a placeholder.
332+
///
333+
/// `Property` is a zero-sized type (ZST) which has exactly one value: `Property::default()`.
334+
/// It implements most of the basic traits, which allows its enclosing struct to remain
335+
/// composable and derive those traits itself.
336+
///
337+
/// ## When to use `Property<T>` instead of `T`
338+
///
339+
/// The following table shows which combinations of `#[property]` attributes and field types are allowed.
340+
/// In this context, `get` and `set` behave symmetrically, so only one of the combinations is listed.
341+
/// Furthermore, `get_ref` can be used in place of `get`, when it appears with a path.
342+
///
343+
/// Field type ➡ <br> Attributes ⬇ | bare `T` | `Property<T>`
344+
/// ------------------------------------------|-------------------------------|-----------------------------
345+
/// `#[property]` | ✔️ default get + set | ❌️
346+
/// `#[property(get, set)]` _(same as above)_ | ✔️ default get + set | ❌️
347+
/// `#[property(get)]` | ✔️ default get (no set) | ❌️
348+
/// `#[property(get="path")]` | ⚠️ custom get (no set) | ✔️ custom get (no set)
349+
/// `#[property(get="path", set)]` | ✔️ custom get, default set | ❌️
350+
/// `#[property(get="path", set="path")]` | ⚠️ custom get + set | ✔️ custom get + set
351+
///
352+
/// "⚠️" means that this attribute combination is allowed for bare `T`, but you should consider
353+
/// using `Property<T>`.
354+
///
355+
/// Since there is no default `get` or `set` in these cases, godot-rust will never access the field
356+
/// directly. In other words, you are not really exporting _that field_, but linking its name and type
357+
/// (but not its value) to the specified get/set methods.
358+
///
359+
/// To decide when to use which:
360+
/// * If you access your field as-is on the Rust side, use bare `T`.<br>
361+
/// With a `Property<T>` field on the other hand, you would need to _additionally_ add a `T` backing field.
362+
/// * If you don't need a backing field, use `Property<T>`.<br>
363+
/// This is the case whenever you compute a result dynamically, or map values between Rust and GDScript
364+
/// representations.
365+
///
366+
/// ## Examples
367+
///
368+
/// Read/write accessible:
369+
/// ```no_run
370+
/// # use gdnative::prelude::*;
371+
/// #[derive(NativeClass)]
372+
/// # #[no_constructor]
373+
/// struct MyObject {
374+
/// #[property]
375+
/// color: Color,
376+
/// }
377+
/// ```
378+
///
379+
/// Read-only:
380+
/// ```no_run
381+
/// # use gdnative::prelude::*;
382+
/// #[derive(NativeClass)]
383+
/// # #[no_constructor]
384+
/// struct MyObject {
385+
/// #[property(get)]
386+
/// hitpoints: f32,
387+
/// }
388+
/// ```
389+
///
390+
/// Read-write, with validating setter:
391+
/// ```no_run
392+
/// # use gdnative::prelude::*;
393+
/// # fn validate(s: &String) -> bool { true }
394+
/// #[derive(NativeClass)]
395+
/// # #[no_constructor]
396+
/// struct MyObject {
397+
/// #[property(get, set = "Self::set_name")]
398+
/// player_name: String,
399+
/// }
400+
///
401+
/// #[methods]
402+
/// impl MyObject {
403+
/// fn set_name(&mut self, _owner: TRef<Reference>, name: String) {
404+
/// if validate(&name) {
405+
/// self.player_name = name;
406+
/// }
407+
/// }
408+
/// }
409+
/// ```
410+
///
411+
/// Write-only, no backing field, custom setter:
412+
/// ```no_run
413+
/// # use gdnative::prelude::*;
414+
/// #[derive(NativeClass)]
415+
/// # #[no_constructor]
416+
/// struct MyObject {
417+
/// #[property(set = "Self::set_password")]
418+
/// password: Property<String>,
419+
/// }
420+
///
421+
/// #[methods]
422+
/// impl MyObject {
423+
/// fn set_password(&mut self, _owner: TRef<Reference>, password: String) {
424+
/// // securely hash and store password
425+
/// }
426+
/// }
427+
/// ```
428+
429+
// Note: traits are mostly implemented to enable deriving the same traits on the enclosing struct.
430+
#[derive(Copy, Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq, Hash)]
431+
pub struct Property<T> {
432+
_marker: PhantomData<T>,
433+
}
434+
325435
mod impl_export {
326436
use super::*;
327437

gdnative-derive/src/lib.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,21 @@ pub fn profiled(meta: TokenStream, input: TokenStream) -> TokenStream {
218218
/// Call hook methods with `self` and `owner` before and/or after the generated property
219219
/// accessors.
220220
///
221+
/// - `get` / `get_ref` / `set`
222+
///
223+
/// Configure getter/setter for property. All of them can accept a path to specify a custom
224+
/// property accessor. For example, `#[property(get = "Self::my_getter")]` will use
225+
/// `Self::my_getter` as the getter.
226+
///
227+
/// The difference of `get` and `get_ref` is that `get` will register the getter with
228+
/// `with_getter` function, which means your getter should return an owned value `T`, but
229+
/// `get_ref` use `with_ref_getter` to register getter. In this case, your custom getter
230+
/// should return a shared reference `&T`.
231+
///
232+
/// Situations with custom getters/setters and no backing fields require the use of the
233+
/// type [`Property<T>`][gdnative::export::Property]. Consult its documentation for
234+
/// a deeper elaboration of property exporting.
235+
///
221236
/// - `no_editor`
222237
///
223238
/// Hides the property from the editor. Does not prevent it from being sent over network or saved in storage.
@@ -379,19 +394,21 @@ pub fn derive_native_class(input: TokenStream) -> TokenStream {
379394
let derive_input = syn::parse_macro_input!(input as DeriveInput);
380395

381396
// Implement NativeClass for the input
382-
native_script::derive_native_class(&derive_input).map_or_else(
397+
let derived = native_script::derive_native_class(&derive_input).map_or_else(
383398
|err| {
384399
// Silence the other errors that happen because NativeClass is not implemented
385400
let empty_nativeclass = native_script::impl_empty_nativeclass(&derive_input);
386401
let err = err.to_compile_error();
387402

388-
TokenStream::from(quote! {
403+
quote! {
389404
#empty_nativeclass
390405
#err
391-
})
406+
}
392407
},
393408
std::convert::identity,
394-
)
409+
);
410+
411+
TokenStream::from(derived)
395412
}
396413

397414
#[proc_macro_derive(ToVariant, attributes(variant))]

0 commit comments

Comments
 (0)