-
-
Notifications
You must be signed in to change notification settings - Fork 249
Make base_mut
not clone Gd
and work in init and predelete
#1324
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-1324 |
b28af45
to
2683a6d
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.
Thanks a lot, nice work 👍
I am still worried a bit about two competing mechanisms to access the base object during initialization. While it does solve your problem, how do we explain this from a UX perspective?
If I change your code to the following, it still works:
#[godot_api]
impl IArrayMesh for InitArrayMeshTest {
fn init(base: Base<ArrayMesh>) -> Self {
base.to_init_gd()
.add_surface_from_arrays(mesh::PrimitiveType::TRIANGLES, &make_mesh_arrays());
Self { base }
}
}
So yes, we can save a refcount here, but at the cost of multiple ways to do the same thing.
When should someone use to_init_gd()
, and when base_mut()
+ base()
?
What about WithBaseField::to_gd()
? Should we consider making that behave like current to_init_gd()
(not sure if possible)? I wonder if there are scenarios where someone needs to access the base object before inserting it into the Self
struct... 🤔
I don't really like the This PR is closer to how |
But what about cases where Silly example: struct EntityDatabase {}
impl EntityDatabase {
fn register(obj: Gd<RefCounted>);
}
#[godot_api]
impl IRefCounted for MyEntity {
fn init(base: Base<RefCounted>) -> Self {
EntityDatabase::register(base.to_init_gd());
Self { base }
}
} |
2683a6d
to
87a9b2f
Compare
I think this can store instance id. Even if we use |
This is the case for every I don't see how this is an argument against the scenario here.
That would downgrade type-safety. Imagine the registry doesn't have Also, it's broken: fn init(base: Base<ArrayMesh>) -> Self {
let mut sf = Self { base };
sf.base_mut()
.add_surface_from_arrays(mesh::PrimitiveType::TRIANGLES, &make_mesh_arrays());
let id = sf.base().instance_id();
let _clone: Gd<ArrayMesh> = Gd::from_instance_id(id); // <--
sf
} The above code causes a double-panic and aborts the process. Notably, the same code works with fn init(base: Base<ArrayMesh>) -> Self {
let id = base.to_init_gd().instance_id();
let _clone: Gd<ArrayMesh> = Gd::from_instance_id(id);
let mut sf = Self { base };
sf.base_mut()
.add_surface_from_arrays(mesh::PrimitiveType::TRIANGLES, &make_mesh_arrays());
sf
} |
The above problem is something I have run into constantly during #1273. The core conflict is: as soon as you hand out an object to the user, you need to assume they use it the same way how objects generally work. If random operations (like instance ID, cloning, etc) break down, that is unintuitive and will cause frustration. I'm hesitant to sacrifice soundness and logical consistency in the name of performance, especially in safe APIs. Some things I considered:
Ultimately we have to choices: (a) make things safe to use, at slight cost. (b) optimize performance but sacrifice correctness. I chose (a), also because this cost is only relevant for If you find that (b) is really worth it, we can consider a special API for it -- but I don't see an easy way to make But for me to understand the background: You mentioned multiple times that godot-cpp allows this to be faster, but do you actually need to avoid this 1 refcount increment so much, that it's worth all this effort? Is it the bottleneck? Don't other parts like Godot/GDExtension object initialization overhead take up a much larger amount of time? |
Godot doesn't allow incrementing ref count in init otherwise it will be freed before actually referenced. So I think we should never create a strong I think overhead of cloning a RefCounted is trivail, but it also pushes a callable to the message queue. |
I think we've fallen into the discussion about RefCounted initialization again, but in my opinion, using |
I demonstrated a problem that we have with Or do you mean it allows using the What would you think of a solution based on current |
In the demonstration users can statically hold a strong ref to achieve similar things like
I suspect this might also cause the object to be released after the reference count is decremented. |
Fully agree, I haven't gotten around to investigate that yet. My hope is that there's some sort of approach here, too, but I may be too optimistic 😇 That said, @Yarwin will bring this issue up with the GDExtension team and see if there are better ways to deal with
But they're only unsound if there is no prior refcount increment, which is what
The challenge is that it's very hard to identify which operations exactly break. Cloning is one of the fundamental operations of |
IMO fundamentally this is Godot problem, not ours – the whole problem comes from requirement of performing some work in the constructor (init), while constructor itself just wants to get default instance and call it a day – and said default instance can be used for multitude of things (default values in inspector, information for placeholders etc – stuff for which we don't want to call any side effects). Doing anything else than returning default instance is and will be inherently cursed no matter what hack/approach we would use. We should have gdscript-like Yeah, I've been repeating myself with it, but this whole issue is just silly.
IMO this is not a huge deal either – Godot uses callables internally for a lot of stuff and they are doing fine. Custom Callables are reasonably fast either. I don't think it is good design, but it might be the best 🤷. As for issue itself – not increasing refcount is fine, using |
All of Godot's source code implicitly uses base class pointer in constructor and destructor, why would |
Godot's source code is written in a different language, has a different audience with different goals and different design guidelines than user-facing extensions. There is a ton of things that people do in C++ that isn't acceptable in Rust, like raw alloc/free, unchecked spans, etc.
This discussion is starting to go in circles, but I'll gladly repeat it: 🙂
In other words: Important I consider "doesn't break in random places" an acceptance criterion for new features.
Also I doubt that this is such a big issue in practice. Not only does TLDR: #1273 is good enough for now. We can think about further improvements, but compromising correctness should be an absolute last resort. |
21ff544
to
cc9c70c
Compare
I think we can handle some breakages better such as object being released in init and unreferencing dead RefCounted. (though I don't really think they are related to my solution, they existed already). |
cc9c70c
to
a644d65
Compare
Don't call
self.to_gd
inbase_mut
to make it work in init and predelete.BaseMut
has no runtime inaccessible guard during initialization because the instance binding is not set yet, but Rust compiler's borrow checking forbase
andbase_mut
are still enforced.