-
-
Notifications
You must be signed in to change notification settings - Fork 268
TypedSignal: document different ways of connecting signals
#1443
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
base: master
Are you sure you want to change the base?
Conversation
|
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1443 |
Bromeon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your contribution!
Regarding the example snippets: you have 75% of the code hidden with #, while only 25% is visible.
- The question is, is it clear enough for a reader without the hidden code? The fact that a variable is
OnEditorfor example is absolutely not obvious. If not in code, there should be a comment. - But at the same time we want to keep things concise -- it's hard to strike a good balance here.
- Can maybe the overall design be simplified? Definitely omit any useless code like comments in hidden parts...
| /// # Availability | ||
| /// For typed signals to be available, you need: | ||
| /// - A `#[godot_api] impl MyClass {}` block. | ||
| /// - This must be an inherent impl, the `I*`` trait `impl`` won't be enough. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double backtick doesn't render properly, see https://godot-rust.github.io/docs/gdext/pr-1443/godot/register/struct.TypedSignal.html
(You can test this by running cargo doc locally).
| /// Example usage: | ||
| /// ``` | ||
| /// # use godot::prelude::*; | ||
| /// # #[derive(GodotClass)] | ||
| /// # #[class(base=Node,init)] | ||
| /// # pub struct Player { | ||
| /// # health_ui: OnEditor<Gd<HealthUI>>, | ||
| /// # base: Base<Node>, | ||
| /// # } | ||
| /// # #[derive(GodotClass)] | ||
| /// # #[class(base=Node2D,init)] | ||
| /// # pub struct HealthUI { | ||
| /// # base: Base<Node2D>, | ||
| /// # } | ||
| /// # #[godot_api] | ||
| /// # impl Player { | ||
| /// # #[signal] | ||
| /// # fn health_changed(health: i32); | ||
| /// # } | ||
| /// # impl Player { | ||
| /// # fn change_health_anim(&mut self, health: i32) { | ||
| /// # // if health > 0 cure anim, if health < 0 hit anim | ||
| /// # } | ||
| /// # } | ||
| /// # impl HealthUI { | ||
| /// # fn on_health_changed(&mut self, health: i32) { | ||
| /// # // Update healthbar | ||
| /// # } | ||
| /// # } | ||
| /// #[godot_api] | ||
| /// impl INode for Player { | ||
| /// fn ready(&mut self) { | ||
| /// self.signals() // Connect to self | ||
| /// .health_changed() | ||
| /// .connect_self(Self::change_health_anim); | ||
| /// self.signals() // Connect to other object | ||
| /// .health_changed() | ||
| /// .connect_self(|s, amount| s.health_ui.bind_mut().on_health_changed(amount)); | ||
| /// } | ||
| /// } | ||
| /// ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some general remarks, before I do line-by-line bikeshedding:
- Examples should come at the end, after other important information (here 2 bullet points).
- They should start with an
# Exampleor# Examplesh1 section. - Comments in code must all start with uppercase (unless referring to a lowercase symbol) and end in period.
- Use
no_runin the opening code blocks, because we can't run any engine code during doctests. Here it still works because no code is actually run, but it's still better to skip that entirely. - Please follow formatting of existing code, e.g.
#[class(init, base=Control)]with space instead of#[class(base=Control,init)].
Before doing those changes, we should however agree on an approach on structuring examples, see main thread discussion 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the no-run check atleast check that it compiles?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, no_run checks if it compiles: https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#attributes
| /// self.button // Connect from other to self | ||
| /// .signals() | ||
| /// .pressed() | ||
| /// .connect_other(&self.to_gd(), Self::execute_tool); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// .connect_other(&self.to_gd(), Self::execute_tool); | |
| /// .connect_other(self, Self::execute_tool); |
I believe this works, or &*self otherwise 🤔
See also API docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know about this! This is why these docs are useful! :3 I had so many runtime borrow problems because I was passing self.to_gd()!
I think rendering the whole code takes too much space, so maybe, the approach of warning that the variables are OnReady is the best one? While also showing the signatures of the functions? |
|
Another thing, do you think the examples are okay? I tried to keep them relevant to gamedev, but not so complicated that they detract from the explanation of the function. I tried to see how I used the signals API myself, for inspiration |
|
I'm not able to import classes in the doctest with |
…ilability paragraph from book
99a5e84 to
193e3f0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of my feedback wasn't addressed yet, I added a commit since it's faster than back-and-forth 🙂
For the future, please run rustfmt on nightly before pushing.
But there's still the basic question what you want to show. Now you call the same API multiple times, which is counter to your original suggestion to showcase different ways of connecting signals.
| /// fn ready(&mut self) { | ||
| /// self.signals() // Connect to self | ||
| /// .health_changed() | ||
| /// .connect_self(Self::change_health_anim); | ||
| /// self.signals() // Connect to other object (health_ui is OnEditor<Gd<HealthUI>>) | ||
| /// .health_changed() | ||
| /// .connect_self(|s, amount| s.health_ui.bind_mut().on_health_changed(amount)); | ||
| /// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is confusing:
Connect to other object
That's not true, you still connect to this object. You just manually delegate in to the other UI. Which raises more questions than it answers:
- Why not connect a signal directly to the UI?
- If not, why not address this in the same handler as
change_health_anim? - How are these two different from a connect perspective?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this example is more complex than it needs to be. If the point is to demonstrate that one can use both function references and closures, then I'd use the same connection, with two different syntaxes. That's much clearer and immediately demonstrates the equivalence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, this makes sense, you would you connect_other in this case, mhh
EDIT: no okay, using connect_other gives to double borrow errors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you can't use connect_other on self, that's why connect_self exists.
What I meant is: show the exact same connect_self call twice:
- with a function reference
- with a closure
This makes clear that it's possible to use different syntaxes.
| /// fn ready(&mut self) { | ||
| /// self.button // Connect from other to self | ||
| /// .signals() | ||
| /// .pressed() | ||
| /// .connect_other(self, Self::execute_tool); | ||
| /// self.button // Connect from other to other (other_tool is OnEditor<Gd<OtherTool>>) | ||
| /// .signals() | ||
| /// .pressed() | ||
| /// .connect_other(&*self.other_tool, |tool| tool.set_tool_enabled(false)); | ||
| /// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also here, it's twice the same way of connecting signals.
I thought the idea was to showcase different APIs? Since we're having the problem that examples are getting too long, we definitely don't have to show the same usage more than once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that we are connecting first from the button to Self, and the second example is from the button to OtherTool, which is just another object. I can reduce it to just a single example if you think the distinction doesn't matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok fair, in this case it can make sense to showcase both 👍
193e3f0 to
cedb206
Compare
|
As I said on the discord, I wanted to show how to use connect_self to connect to both self and another object, and to use connect_other to connect to self and to another object. And I showed different ways of using the API (Self::method, OtherObject::method, closure) while doing so so that they are also shown to be possible. If it is too confusing mixing the two concepts I understand |
If you don't have nightly rust, |
|
I thought of a simplified version of the examples, in line with the other ones already in the API use godot::{classes::Button, prelude::*};
#[derive(GodotClass)]
#[class(base=Node,init)]
pub struct ExampleClass {
button: OnEditor<Gd<Button>>,
other_button: OnEditor<Gd<Button>>,
base: Base<Node>,
}
#[godot_api]
impl ExampleClass {
#[signal]
fn signal();
fn method(&mut self) {}
fn method_with_args(&mut self, value: bool) {}
}
impl ExampleClass {
fn example(&mut self) {
// This would be the only part shown in the API
self.signals().signal().connect_self(Self::method);
self.signals()
.signal()
.connect_self(|s| s.method_with_args(true));
self.button
.signals()
.pressed()
.connect_other(self, Self::method);
self.button
.signals()
.toggled()
.connect_other(&*self.other_button, |other_button, toggled| {
other_button.set_pressed(!toggled)
});
// To here
}
}
`` |
|
Removing the context such as how An idea to clear this up would be to not add an example on individual methods, but have a new section Please also read my review feedback:
It has already taken me a lot of time to review this, and I even corrected one commit myself because these changes weren't applied. I'd appreciate if I didn't have to repeat myself. Thanks 🙂 |
I'm sorry, i swear I had pushed two commits applying the changes and trying to fix ci. I don't see those edits in your formatting commit |
TypedSignal: document different ways of connecting signals
Hello, I created the doctests I was talking about in the discord for the connect functions in TypedSignal :) I also added the availability warning for the signals api that is in the book but not in the API afaik? if im wrong, i'll erase it