-
-
Notifications
You must be signed in to change notification settings - Fork 217
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
Comments
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 |
Personally I would prefer having it in |
Is it guaranteed that |
I did some digging and it seems that lazy |
So the only reason for 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 |
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).
For any 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 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()
}
} |
Just a quick note here: for me it's not about avoiding writing out As @Yarwin mentions above, I have some cases where some an exported item is legitimately optional, and so is well modeled by:
But I'm also making heavy use of a pattern where I'm using Resources to configure nodes, and the resource is required for the node to function correctly. I think an approved/recommended way of doing this would be nice, whether that means including a wrapper type, an export attribute, or even just mentioning Yarvin's recipe above in the "Recipes" section of the book. Thanks Bromeon, loving gdext! |
note: The declaration of mentioned recipe changed slightly, I'll update it later 😅
More usecases can be found in: godotengine/godot-proposals#162
@bcolloran the main issues are discoverability, consistency with Gdscript's user API and avoiding cognitive overload. I think I can made PR with said recipe for a book and then we'll see how wildly it's used? |
Thanks @Yarwin! |
bit late, but I did another round to check if this feature is worth adding to the core; so far I was investigating if it can be supplemented with #[derive(GodotClass)]
#[class(init, base=Node)]
struct MyCustomNode {}
#[derive(GodotClass)]
#[class(init, base=Node)]
struct ExampleExporter {
#[export]
#[init(val=MyCustomNode::new_alloc())]
some_node_export: Gd<MyCustomNode>,
base: Base<Node>
}
#[godot_api]
impl INode for ExampleExporter {
fn ready(&mut self) {
godot_print!("{}", self.some_node_export);
}
} The main, glaring issue is insane amount of warnings – Changing the value emits yet another confusing error: I was unable to enforce null on given property – even if it is nil (set via scene tree, inspector or tool script) it is being changed to default value on runtime (which is fine!). Additionally trying to set null on
```bash
Cannot get path of node as it is not in a scene tree.
core/string/node_path.cpp:263 - Condition "!p_np.is_absolute()" is true. Returning: NodePath()
Node not found: "" (relative to "/root/@EditorNode@16886/@panel@13/@VBoxContainer@14/DockHSplitLeftL/DockHSplitLeftR/DockHSplitMain/@VBoxContainer@25/DockVSplitCenter/@VSplitContainer@52/@VBoxContainer@53/@PanelContainer@98/MainScreen/@CanvasItemEditor@9272/@VSplitContainer@9094/@HSplitContainer@9096/@HSplitContainer@9098/@control@9099/@SubViewportContainer@9100/@SubViewport@9101/Node/ExampleExporter2").
gdext/godot-core/src/private.rs:335 - godot-rust function call failed: ExampleExporter::set_some_node_export()
Reason: parameter #0 (godot_core::obj::gd::Gd) conversion
Source: `Gd` cannot be null: null
```
Every property with The only way to avoid these problems is creating Gd::null or something like that, which is too horrifying to consider 😬 Theoretically it could be used for instantiating default, non-null RefCounted types. Following snippet: #[derive(GodotClass)]
#[class(init, base=Resource)]
struct MyCustomResource {
#[export]
some_value: i64
}
#[derive(GodotClass)]
#[class(init, base=Node)]
struct ExampleExporter {
#[export]
#[init(val=MyCustomResource::new_gd())]
some_resource_export: Gd<MyCustomResource>,
base: Base<Node>
} Works fine and memory leaks are not an issue anymore, but it still emits staggering amount of warnings 😒. So, the questions are: |
Thank you for looking into this more @Yarwin !
For whatever it's worth:
|
It should be removed in v0.3, I agree. People mostly use it because
I think yes. There seems to be quite some demand for a non-optional, ergonomic, exportable object type. The question is whether this has some use outside of objects 🤔 I think it might, although we would then go a bit into "custom territory" -- we could have a specific value for a type that is seen as "uninitialized" and has to be set by the user. Examples:
Obviously we'd need to let the user customize this, but it should be easy with constructors: #[init(val = OnEditor::uninit(0))]
hp: OnEditor<i32>,
// or even:
#[init(editor_uninit = 0)]
hp: OnEditor<i32>, Just brainstorming...
True, supporting Also here, it might technically be possible to provide both via constructors Lazy init does have the specialty that if it's never accessed, a missing initialization won't be detected. That can be both a bug or a feature. It can also add some non-determinism depending on where it is initialized, but generally that should be OK and also performance-wise acceptable (most field values are reasonably lightweight). So, should we start with lazy initialization? I.e. panic on Then there's also the name... I find What I like about |
Currently the pattern for structs with Gd references to other nodes looks like:
Thus forcing user to abuse the
.unwrap()
.Similar case in gdscript can be represented as:
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>>
.The text was updated successfully, but these errors were encountered: