Skip to content

Commit

Permalink
[var]: add 'set_ex'
Browse files Browse the repository at this point in the history
  • Loading branch information
0x53A committed Dec 4, 2024
1 parent 68e0581 commit e9e59ec
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 5 deletions.
2 changes: 2 additions & 0 deletions godot-core/src/meta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ mod signature;
mod traits;

pub mod error;
pub mod property_update;

pub use args::*;
pub use class_name::ClassName;
pub use godot_convert::{FromGodot, GodotConvert, ToGodot};
pub use property_update::PropertyUpdate;
pub use traits::{ArrayElement, GodotType, PackedArrayElement};

pub(crate) use array_type_info::ArrayTypeInfo;
Expand Down
14 changes: 14 additions & 0 deletions godot-core/src/meta/property_update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pub struct PropertyUpdate<'a, C, T> {
pub new_value: T,
pub field_name: &'a str, // might also be &'a StringName, depending on what's available
pub get_field_mut: fn(&mut C) -> &mut T,
}

impl<C, T> PropertyUpdate<'_, C, T> {
pub fn set(self, obj: &mut C) {
*(self.get_field_mut)(obj) = self.new_value;
}
pub fn set_custom(self, obj: &mut C, value: T) {
*(self.get_field_mut)(obj) = value;
}
}
83 changes: 80 additions & 3 deletions godot-macros/src/class/data_models/field_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@ fn parse_notify(parser: &mut KvParser, key: &str) -> ParseResult<Option<Ident>>
}
}

fn parse_setter_ex(parser: &mut KvParser, key: &str) -> ParseResult<Option<Ident>> {
match parser.handle_any(key) {
// No `notify` argument
None => Ok(None),
Some(value) => match value {
// `notify` without value is an error
None => {
bail!(
parser.span(),
"The correct syntax is 'setter_ex = callback_fn'"
)
}
// `notify = expr`
Some(value) => match value.ident() {
Ok(ident) => Ok(Some(ident)),
Err(_) => bail!(
parser.span(),
"The correct syntax is 'setter_ex = callback_fn'"
),
},
},
}
}

impl FieldVar {
/// Parse a `#[var]` attribute to a `FieldVar` struct.
///
Expand All @@ -62,8 +86,9 @@ impl FieldVar {
let mut getter = GetterSetter::parse(parser, "get")?;
let mut setter = GetterSetter::parse(parser, "set")?;
let notify = parse_notify(parser, "notify")?;
let setter_ex = parse_setter_ex(parser, "set_ex")?;

if getter.is_omitted() && setter.is_omitted() {
if getter.is_omitted() && setter.is_omitted() && setter_ex.is_none() {
getter = GetterSetter::Generated;
setter = GetterSetter::Generated;
}
Expand All @@ -75,6 +100,17 @@ impl FieldVar {
);
}

if setter_ex.is_some() && !setter.is_omitted() {
return bail!(
parser.span(),
"You may not use 'set' and 'set_ex' at the same time, remove one"
);
}

if let Some(ident) = setter_ex {
setter = GetterSetter::Ex(ident);
}

let hint = parser.handle_ident("hint")?;

let hint = if let Some(hint) = hint {
Expand Down Expand Up @@ -120,6 +156,9 @@ pub enum GetterSetter {

/// Getter/setter is handwritten by the user, and here is its identifier.
Custom(Ident),

/// only applicable to setter. A generic setter that takes 'PropertyUpdate<C, T>' is handwritten by the user
Ex(Ident),
}

impl GetterSetter {
Expand Down Expand Up @@ -157,6 +196,12 @@ impl GetterSetter {
GetterSetter::Custom(function_name) => {
Some(GetterSetterImpl::from_custom_impl(function_name))
}
GetterSetter::Ex(function_name) => {
assert!(matches!(kind, GetSet::SetEx(_)));
Some(GetterSetterImpl::from_generated_impl(
class_name, kind, notify, field,
))
}
}
}

Expand All @@ -170,17 +215,18 @@ impl GetterSetter {
}

/// Used to determine whether a [`GetterSetter`] is supposed to be a getter or setter.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum GetSet {
Get,
Set,
SetEx(Ident),
}

impl GetSet {
pub fn prefix(&self) -> &'static str {
match self {
GetSet::Get => "get_",
GetSet::Set => "set_",
GetSet::Set | GetSet::SetEx(_) => "set_",
}
}
}
Expand Down Expand Up @@ -228,6 +274,37 @@ impl GetterSetterImpl {
<#field_type as ::godot::register::property::Var>::set_property(&mut self.#field_name, #field_name);
};

function_body = match notify {
Some(ident) => {
quote! {
let prev_value = self.#field_name;
#function_body_set
if prev_value != self.#field_name {
self.#ident();
}
}
}
None => function_body_set,
};
}
GetSet::SetEx(callback_fn_ident) => {
signature = quote! {
fn #function_name(&mut self, #field_name: <#field_type as ::godot::meta::GodotConvert>::Via)
};

let field_name_string_constant = field_name.to_string();

let function_body_set = quote! {

let new_value = ::godot::meta::FromGodot::from_godot(#field_name);
let property_update = ::godot::meta::PropertyUpdate {
new_value: new_value,
field_name: #field_name_string_constant,
get_field_mut: |c: &mut #class_name|&mut c.#field_name
};
self.#callback_fn_ident(property_update);
};

function_body = match notify {
Some(ident) => {
quote! {
Expand Down
8 changes: 6 additions & 2 deletions godot-macros/src/class/data_models/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

//! Parsing the `var` and `export` attributes on fields.
use crate::class::{Field, FieldVar, Fields, GetSet, GetterSetterImpl, UsageFlags};
use crate::class::{Field, FieldVar, Fields, GetSet, GetterSetter, GetterSetterImpl, UsageFlags};
use proc_macro2::{Ident, TokenStream};
use quote::quote;

Expand Down Expand Up @@ -139,8 +139,12 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
&mut getter_setter_impls,
&mut export_tokens,
);
let setter_kind = match &setter {
GetterSetter::Ex(ident) => GetSet::SetEx(ident.clone()),
_ => GetSet::Set,
};
let setter_name = make_getter_setter(
setter.to_impl(class_name, GetSet::Set, notify, field),
setter.to_impl(class_name, setter_kind, notify, field),
&mut getter_setter_impls,
&mut export_tokens,
);
Expand Down
43 changes: 43 additions & 0 deletions itest/rust/src/object_tests/property_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,3 +486,46 @@ fn test_var_notify() {

class.free();
}

// ---------------------------------------------------------------

#[derive(GodotClass)]
#[class(base=Node, init)]
struct SetExTest {
#[var(set_ex = custom_set)]
a: i32,
#[var(set_ex = custom_set)]
b: i32,

pub call_count: u32,
}

impl SetExTest {
fn custom_set<T>(&mut self, update: godot::meta::property_update::PropertyUpdate<Self, T>) {
// pre-set checks

update.set(self);

// post-set actions
self.call_count += 1;
}
}

#[itest]
fn test_var_set_ex() {
let mut class = NotifyTest::new_alloc();

assert_eq!(class.bind().call_count, 0);

class.call("set_a", &[3.to_variant()]);
assert_eq!(class.bind().a, 3);
assert_eq!(class.bind().call_count, 1);

class.call("set_b", &[5.to_variant()]);
assert_eq!(class.bind().b, 5);
assert_eq!(class.bind().call_count, 2);

class.free();
}

// ---------------------------------------------------------------

0 comments on commit e9e59ec

Please sign in to comment.