-
-
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
Add implementation of Callable #231
Conversation
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.
Thanks a lot for this initial implementation! Also great that you elaborate why some parts are missing as part of the docs.
It's a bit a pity that Godot allows all these half-initialized callables with implicit different null states, but I don't think there's much we can/should do about it in Rust.
We should probably have a
new
constructor, but what should it be? should it just create an empty callable, i.e just doSelf::default
, or should it callfrom_object_method
? maybe a function like:
If there's no clear answer what the intuitive behavior for new
is, that's usually a good indicator that we shouldn't have such a constructor. It's better to have named constructors with clear semantics.
Add
length
andis_empty
toStringName
.is_empty
is used inCallable::method_name
, and it just felt weird to me to haveis_empty
but notlength
.
Nice addition! In the future, could you split such changes in a separate commit? No need to change anything now, it's a minor detail 🙂
godot-core/src/builtin/callable.rs
Outdated
/// Calls the method represented by this callable. Arguments passed should match the method's | ||
/// signature. If called with more arguments than expected by the method, the extra arguments will be | ||
/// ignored. If called with fewer arguments than expected it will crash. If called with arguments of the | ||
/// wrong type then an error will be printed and the call fails. | ||
/// | ||
/// _Godot equivalent: `callv`_ |
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.
Please start docs with a brief description that is not longer than one line. This one will be displayed in the overview/title of the section.
/// Returns the name of the method represented by this callable. If the callable is a lambda function, | ||
/// returns the function's name. |
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.
If the callable is a lambda function, returns the function's name.
What does that mean? Lambdas have no name, at least not from the user perspective.
Since we have Option
return type, would it be possible to return None
in cases where the function name is not meaningful for the user?
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.
lambdas can in fact have names in GDScript, which is kinda counter-intuitive since that's not what lambda means. Here is an example of a lambda with a name:
func _ready():
var foo = func bleh():
pass
print(foo)
this will print
bleh(lambda)
Though i realize now that the godot docs are actually wrong and trying to use get_method
on a lambda does not in fact return the function's name but rather just 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.
godotengine/godot#73052 this is a known bug apparently, not sure how we should handle that in our case
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.
Ah, I see! Regarding the bug, it's probably OK to rely on it working, hopefully it will be fixed at some point. We could maybe add a mention in the doc.
godot-core/src/builtin/callable.rs
Outdated
/// Returns the object on which this callable is called. | ||
/// | ||
/// _Godot equivalent: `get_object`_ |
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.
Should mention in which cases None
is returned.
|
||
/// Returns a callable referencing a method from this object named `method_name`. | ||
pub fn callable<S: Into<StringName>>(&self, method_name: S) -> Callable { | ||
Callable::from_object_method(self.share(), method_name) | ||
} |
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 see the convenience of this method; though over time, as we're adding more and more interactions between Gd
and other types, we might want to decide where those should live (and I'd tend to "outside of Gd
").
It's fine for now, but this may still change in the future.
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, it's convenient for now but we should have a proper discussion of what the API for using callable should be.
godot-core/src/builtin/callable.rs
Outdated
/// Returns true if this callable is a standard callable. This method is the opposite of [`is_custom`]. | ||
/// Returns `false` if this callable is a lambda function. | ||
/// | ||
/// _Godot equivalent: `is_standard`_ | ||
pub fn is_standard(&self) -> bool { | ||
self.as_inner().is_standard() | ||
} |
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.
Should we remove either is_standard
/is_custom
, so that the provided getter methods are actually orthogonal to each other?
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.
Im not sure, i feel like a Callable if implemented in rust would effectively be a kind of enum, something like
enum Callable {
Standard(Gd<Object>, StringName),
Custom(Box<dyn CustomCallable>)
}
in which case it is fairly common to have an is_x
for every case.
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, but at the moment it's more like
enum Callable {
Standard(Gd<Object>, StringName),
Invalid(Gd<Object>, StringName), // method does not exist
Custom(Box<dyn CustomCallable>),
Null,
}
But even that is not accurate, because standard == !custom
according to Godot docs.
It's just that the mental model is already quite complex with multiple states, and having redundant methods doesn't exactly help.
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 guess we could remove is_standard
then, since callables are generally assumed to be a standard callable, and custom callables are the exception.
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.
Sounds good to me!
Maybe we can still mention the relation of standard/custom inside is_custom
, or provide a doc alias...
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.
we should probably provide doc aliases for all functions that diverge from the godot name tbh, but yeah a doc alias in this case at least.
/// Returns the 32-bit hash value of this callable's object. | ||
/// | ||
/// _Godot equivalent: `hash`_ | ||
pub fn hash(&self) -> u32 { | ||
self.as_inner().hash().try_into().unwrap() | ||
} |
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.
Is this guaranteed not to panic? The values returned by Godot are unsigned?
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.
according to Array::hash
at least yeah. i think i checked this myself at some point too.
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 if this ever breaks/changes then it's likely gonna break for every call to hash
because of how hashes generally work. so we'd notice in for instance the callable_hash
itest.
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.
Thanks! A few more comments, then maybe squash the "feedback" commit 🙂
godot-core/src/builtin/callable.rs
Outdated
/// A `Callable` represents a function in Godot. | ||
/// | ||
/// Usually a callable is a reference to an `Object` and a method name, this is a standard callable. See | ||
/// [`Callable::is_standard`]. But can also be a custom callable, which is usually created from `bind`, | ||
/// `unbind`, or a GDScript lambda. See [`Callable::is_custom`]. | ||
/// | ||
/// Currently it is impossible to use `bind` and `unbind` in GDExtension, see [godot-cpp#802]. | ||
/// | ||
/// [godot-cpp#802]: https://github.com/godotengine/godot-cpp/issues/802 |
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.
Callable::is_standard
is no longer here 😬
godot-core/src/builtin/callable.rs
Outdated
/// Create a callable for the method named `method` in the specified `object`. | ||
/// | ||
/// _Godot equivalent: `Callable(Object object, StringName method)`_ | ||
pub fn from_object_method<T, S>(object: Gd<T>, method: S) -> Self |
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.
method_name
would be clearer as parameter name, especially since the type is generic.
Doc could then just be somthing like
/// Create a callable for the method `object::method_name`.
godot-core/src/builtin/callable.rs
Outdated
/// Calls the method represented by this callable. | ||
/// | ||
/// Arguments passed should match the method's signature. If called with more arguments than expected by | ||
/// the method, the extra arguments will be ignored. If called with fewer arguments than expected it will | ||
/// crash. If called with arguments of the wrong type then an error will be printed and the call fails. | ||
/// | ||
/// _Godot equivalent: `callv`_ | ||
pub fn callv(&self, arguments: VariantArray) -> Variant { | ||
self.as_inner().callv(arguments) | ||
} |
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.
Error conditions are a bit vague:
- "it will crash" -- UB or what?
- "the call fails" -- panic? NIL returned?
It's also not clear what happens when the callable is null (empty) or invalid (bad method name).
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'll check
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.
Could maybe be worth adding as tests too. These things tend to change in Godot patch versions 😁
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 ones that can be tested without crashing our integration tests entirely are tested now. also we can't check that an error is printed in godot.
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.
That's more than enough, thanks a lot! 👍
godot-core/src/builtin/callable.rs
Outdated
// Increment refcount because we're getting a reference, and `InnerCallable::get_object` doesn't | ||
// increment the refcount. | ||
self.as_inner().get_object().map(|object| { | ||
<<Object as GodotClass>::Mem as crate::obj::mem::Memory>::maybe_inc_ref(&object); |
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 don't think both expansions (as GodotClass
, as Memory
) are necessary -- we have statements like
<engine::Object as GodotClass>::Declarer::scoped_mut(...)
in other places 🙂
/// Returns the ID of this callable's object, see also [`Gd::instance_id`]. | ||
/// | ||
/// Returns `None` when this callable doesn't have any target to call a method on. | ||
/// | ||
/// _Godot equivalent: `get_object_id`_ |
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 None
when the state is invalid?
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.
no
godot-core/src/builtin/callable.rs
Outdated
// Equality depends on equality of the object/custom object of the callable's target. | ||
// Which may be partial, so we cannot be sure that equality of Callable is total. | ||
PartialEq => callable_operator_equal; |
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.
Is it though?
We have a blanket
impl<T: GodotClass> Eq for Gd<T> {}
impl<T: GodotClass> PartialEq for Gd<T> {
/// ⚠️ Returns whether two `Gd` pointers point to the same object.
///
/// # Panics
/// When `self` or `other` is dead.
fn eq(&self, other: &Self) -> bool {
// Panics when one is dead
self.instance_id() == other.instance_id()
}
}
So equality does not depend on contents of T
.
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 it is actually a total equality yeah, but custom callables in godot are free to implement equality however they want. from what i can tell it is always either just a pointer equality or object + method equality (and if references to two custom callables are the same pointer then they are always equal). but im not sure if godot guarantees that all custom callables in godot will be implemented to have a total equality.
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 see, but the current comment says it depends on the object (which doesn't make clear that they are free to implement however they want).
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.
fair, i wrote object/custom object
where custom object
was meant to refer to custom callable objects. but i can be more clear about that
itest/rust/src/callable_test.rs
Outdated
assert!(obj.callable("foo").is_valid()); | ||
assert!(Callable::default().is_null()); | ||
assert!(!Callable::default().is_custom()); |
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.
Could you also check all 3 methods for these 2 cases?
I.e. we would have 3x3 assert statements in total.
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.
im also adding .object().is_some()/is_none()
bb48d74
to
7cb77f2
Compare
7cb77f2
to
7c786cd
Compare
bors r+ |
Build succeeded: |
233: Refactor `GodotString`, `NodePath`, and `StringName` r=lilizoey a=lilizoey Creates a new module in `builtin` that contains all the string-related modules. Make all the string-files look more similar where it made sense. And add things that only existed in one of the three but not the others, such as: `Hash` impl (now using godot's hashing), new-constructor, conversions. Added pass-by-value From impls in addition to pass-by-reference From impls, so we can do just ```rs let node_path: NodePath = string.into(); ``` instead of needing to do ```rs let node_path: NodePath = NodePath::from(&string); ``` Moves `String`-specific stuff into `builtin/string/mod.rs` and renamed `string.rs` to `godot_string.rs`. And adds a `VariantMetadata` impl for `String`. Adds some more tests to test all the types a bit more extensively. Since this is gonna conflict with #231 (as i added some stuff to StringName there) i wanna wait with merging this until that is merged. but otherwise the PR is ready to be merged. Co-authored-by: Lili Zoey <lili.andersen@nrk.no>
Add an initial implementation of callable, mostly just wrapping
InnerCallable
.bind
and its relatives will crash if you attempt to use them. i experimented with a purely rust-based solution but it wouldn't work when passed to gdscript and if we're only using rust then we might as well use closures.call
andcall_deferred
will also crash when called.Add a
Gd::callable
method to more easily makeCallables
from aGd<T>
, it just defers toCallable::from_object_method
.New?
We should probably have a
new
constructor, but what should it be? should it just create an empty callable, i.e just doSelf::default
, or should it callfrom_object_method
? maybe a function like:Other
Add vararg functions for builtin inner classes. initially i planned to use these for
call
andcall_deferred
, but it seems like these functions dont work at the moment. i believe for the same reason as #169Add
length
andis_empty
toStringName
.is_empty
is used inCallable::method_name
, and it just felt weird to me to haveis_empty
but notlength
.