Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support OnEditor<T>/Required<T>for #[export] similar to OnReady<T> #892

Open
Yarwin opened this issue Sep 13, 2024 · 6 comments
Open

Support OnEditor<T>/Required<T>for #[export] similar to OnReady<T> #892

Yarwin opened this issue Sep 13, 2024 · 6 comments
Labels
c: register Register classes, functions and other symbols to GDScript feature Adds functionality to the library

Comments

@Yarwin
Copy link
Contributor

Yarwin commented Sep 13, 2024

Currently the pattern for structs with Gd references to other nodes looks like:

#[derive(GodotClass)]
#[class(init, base=Node)]
pub struct SomeNode {
    #[export]
    required_node_a: Option<Gd<Node>>,
    #[export]
     required_node_b: Option<Gd<Node>>,
    #[export]
    optional_node: Option<Gd<Node>>,
    ...
}

#[godot_api]
impl INode for SomeNode {
    fn ready(&mut self) {
        let Some(required_node_a) = self.required_node_a.as_ref()  else { panic!("required node must be set!")};
        let Some(required_node_b) = self.required_node_b.as_ref()  else { panic!("required node must be set!")};
        if let Some(optional) = self.optional_node.as_ref() {
                godot_print!("optional node has been set!");
       }
    }
}

Thus forcing user to abuse the .unwrap().

Similar case in gdscript can be represented as:

extends Node

@export var required_node_a: Node
@export var required_node_b: Node
@export var optional_node: Node

func _ready() -> void:
	print(required_node_a.name)  # errors if required_node_a is not present
	print(required_node_b.name)  # errors if required_node_b is not present
	if optional_node:
		print(optional_node.name)

It would be great to create abstraction similar to OnReady<Gd> for required exports. The inner workings would be similar to OnReady, while the exported property itself could be represented by simple Required<Gd<Node>>.

@Bromeon Bromeon added feature Adds functionality to the library c: register Register classes, functions and other symbols to GDScript labels Sep 13, 2024
@Bromeon
Copy link
Member

Bromeon commented Sep 13, 2024

Agree with this; I noticed the need for something like that and even started experimenting. One thing where I wasn't sure, and it's not obvious from your code either:

Should the initialization happen lazily or during ready? Because the latter would just be a special-cased OnReady<T> which also allows Export, no? Whereas the former could panic on Deref/DerefMut.

@Yarwin
Copy link
Contributor Author

Yarwin commented Sep 13, 2024

Personally I would prefer having it in ready – not supplying the required property is a logic error and user should be notified such. In broader context – I'm unsure if it wouldn't cause issues with various editor tools and plugins 🤷.

@Bromeon
Copy link
Member

Bromeon commented Sep 13, 2024

Is it guaranteed that #[export] fields are always initialized before ready()?
Also thinking of hot-reloading scenarios etc... 🤔

@Yarwin
Copy link
Contributor Author

Yarwin commented Sep 22, 2024

I did some digging and it seems that lazy Deref/DerefMut might be better option 🤔. I can move on to implementation if nobody has any objections

@Bromeon
Copy link
Member

Bromeon commented Sep 22, 2024

So the only reason for OnEditor to exist would be to avoid the explicit .as_ref().unwrap()?

I'm not saying that's not worth it -- I think these common cases should be as ergonomic as possible -- but I'd like to be clear about the goals 🙂 also then we can find the best possible name.

Furthermore, would this only exist for Gd<T> or also fields of other types?

@Yarwin
Copy link
Contributor Author

Yarwin commented Sep 23, 2024

So the only reason for OnEditor to exist would be to avoid the explicit .as_ref().unwrap()?

Yep. Moreover it would be handy if it could be used for Resources and Refcounted/Objects (almost half of my potential use cases are related to this).

Furthermore, would this only exist for Gd or also fields of other types?

For any T:ffi that implements GodotNullableFfi, 1:1 to option.

I have second thoughts though; it doesn't need to be a part of the core to work (can be external package instead), doesn't point to godot's abstraction directly (read - hard to discover) and implementing such wrapper for Option is trivial (shouldn't take more than 5 to 10 minutes? I pasted my wrapper below anyway).

here is my current wrapper, definitively naive (literally an Option<T> that deferences to T) albeit works.

pub struct Required<T> {
    inner: Option<T>
}

impl<T> Default for Required<T> {
    fn default() -> Self {
        Required { inner: None }
    }
}

impl<T> std::ops::Deref for Required<T>
{
    type Target = T;
    fn deref(&self) -> &Self::Target {
        match &self.inner {
            None => panic!(),
            Some(v) => v
        }
    }
}

impl<T> std::ops::DerefMut for Required<T>
{
    fn deref_mut(&mut self) -> &mut Self::Target {
        match &mut self.inner {
            None => panic!(),
            Some(v) => v
        }
    }
}


impl<T: GodotConvert> GodotConvert for Required<T>
    where
        Option<T::Via>: GodotType,
{
    type Via = Option<T::Via>;
}

impl<T: ToGodot> ToGodot for Required<T>
    where
        Option<T::Via>: GodotType,
{
    fn to_godot(&self) -> Self::Via {
        self.inner.to_godot()
    }

    fn into_godot(self) -> Self::Via {
        self.inner.into_godot()
    }
    fn to_variant(&self) -> Variant {
        self.inner.to_variant()
    }

}

impl<T: FromGodot> FromGodot for Required<T>
    where
        Option<T::Via>: GodotType,
{
    fn try_from_godot(via: Self::Via) -> Result<Self, ConvertError> {
        match Option::<T>::try_from_godot(via) {
            Ok(val) => Ok( Required { inner: val }),
            Err(e) => Err(e)
        }
    }

    fn from_godot(via: Self::Via) -> Self {
        Required { inner: Option::<T>::from_godot(via) }
    }

    fn try_from_variant(variant: &Variant) -> Result<Self, ConvertError> {
        Ok(Required {inner: Option::<T>::try_from_variant(variant)?})
    }

    fn from_variant(variant: &Variant) -> Self {
        return Required {inner: Option::<T>::from_variant(variant)}
    }
}

impl<T> Var for Required<T>
    where
        T: Var + FromGodot,
        Option<T>: GodotConvert<Via = Option<T::Via>>,
        Required<T>: GodotConvert<Via = Option<T::Via>>
{
    fn get_property(&self) -> Self::Via {
        Option::<T>::get_property(&self.inner)
    }

    fn set_property(&mut self, value: Self::Via) {
        Option::<T>::set_property(&mut self.inner, value as Self::Via);
    }
}

impl<T> Export for Required<T>
    where
        T: Export + GodotType,
        T::Ffi: GodotNullableFfi,
        Option<T>: Var,
        Required<T>: Var
{
    fn default_export_info() -> PropertyHintInfo {
        Option::<T>::default_export_info()
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: register Register classes, functions and other symbols to GDScript feature Adds functionality to the library
Projects
None yet
Development

No branches or pull requests

2 participants