-
-
Notifications
You must be signed in to change notification settings - Fork 218
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
Make ScriptInstance.call
and company re-entrant
#554
Comments
I believe the script instance should either adopt That said, script recursion should not require going back through the engine and should be contained in the script runtime. EDIT: I missed that |
I'm not sure I agree with that. If I have an unknown object of type |
@Mercerenies All right, I was more thinking of direct recursion. But what you are describing is definitely an issue. Unfortunately, it is a general issue of gdext. If I'm not mistaken, even with |
No, You can look at an integration test to get a feel. Here you have the chain gdext/itest/rust/src/object_tests/reentrant_test.rs Lines 27 to 44 in e3644a0
|
@Bromeon sorry I didn't make my example super clear. So yes, the new Something like this: CustomScriptLanguage::method(&mut self) -> Engine::api_method() -> CustomScriptLanguage::get_type(&self) Or a class calling a GDScript that calls back into the class due to some circular references: impl CustomNode {
pub fn method(&mut self) {
self.ref_gdscript_child_node.call("some_method", &[]);
}
pub fn on_child_signal(&self) {...}
} func some_method():
self.child_signal.emit() |
This discussion leads me to this idea of performing a "downgrade" of let base = self.base_mut();
let self_gd = base.to_godot().cast::<Self>();
let new_self = self_gd.bind(); |
@Mercerenies what are your thoughts on making the entire |
Can we do that? To be honest, I would've written that idea off as impossible, but I suppose if everything really is just clever interior mutability, then we could probably get away with it. If I also feel like we have too much direct access to |
Experimental branch where
I don't really follow. I would say |
Using Furthermore, it doesn't follow our API design anywhere else -- |
Let me put it another way. Our struct ScriptInstanceData<T: ScriptInstance> {
inner: RefCell<T>,
gd_instance_ptr: *mut sys::GDExtensionScriptInstanceInfo,
} And then whenever a method is called on that, we do |
@Bromeon I think this is a fair point. We would then still have to provide a way to make Do you have a suggestion how to approach this instead?
@Mercerenies I don't entirely agree with this. Exposing just a cell like |
Another option where |
You can make this work with impl CustomNode {
pub fn method(&mut self) {
let child_node = self.ref_gdscript_child_node.clone();
let guard = self.base_mut();
child_node.call("some_method", &[]);
std::mem::drop(guard);
}
pub fn on_child_signal(&self) {...}
} |
@lilizoey Yeah, that's the sort of care that I would like to be possible for people like me implementing a scripting language. Most godot-rust users won't need or care about re-entrancy, so they shouldn't pay the cost of noisy syntax. But it should be possible for those of us who need it. And now, thanks to your work, it is. I hope a similar solution is viable for |
@Bromeon regrading your comment in #647 (comment): |
@TitanNano Thanks a lot for looking into it and getting a PoC running! 👍 Since you've worked with this for a while, you probably know some stuff that I don't and might clarify some things. Which is a considerable downgrade in user-friendlyness, in multiple ways:
So, a few questions:
Basically, we should not expose |
Most functions do not execute arbitrary logic defined by script authors. So
Pin is required by GdCell so if we can get it out of the public interface it won't be needed anymore.
We perhaps could introduce a custom wrapper for Alternatively, we would have to somehow become part of the instantiation of the (I haven't tested any of this, just some thoughts) |
Is this really much simpler of a solution than just making every method take |
That sounds interesting, and nicer than a Could something like
Not sure what exactly you mean here -- but if you think it's more complex, we can gladly explore the other avenue first 🙂
Also fair point -- it's a bit hard for me to judge how often one could live with the "basic Thanks a lot for the all the input, it's appreciated! |
@lilizoey i agree but it was dismissed in #554 (comment) |
In short, yes. The owner of the script instance is just a I will see if I can get a rough draft of this concept implemented. |
At the time I didn't know that the alternative would be so much more complex, to be honest 😁 So I'm no longer generally against To summarize different suggestions: Everywhere
Custom type
Something with
Thanks for looking into it @TitanNano. Feel free to keep the PoC small, no need to map the entire |
@Bromeon here is the latest POC based on what we discussed: https://github.com/TitanNano/gdext/compare/jovan/script_instance_gd_cell...TitanNano:gdext:jovan/script_instance_mut_wrapper?expand=1 One draw back of this solution is that owner APIs are only available to |
I think it'd be possible to more closely mirror pub trait ScriptInstance {
fn init(owner: Owner<Object>) -> Self;
fn owner_field(&self) -> &Owner<Object>;
fn owner(&self) -> OwnerRef<Object> {
let gd = self.owner_field().to_gd();
OwnerRef::new(gd, self)
}
fn owner_mut(&mut self) -> OwnerMut<Object> {
let owner_gd = self.owner_field().to_gd();
let storage = self.owner_field().get_storage_somehow();
let guard = storage.get_inaccessible(self);
OwnerMut::new(owner_gd, guard)
}
// The rest of the methods
} Could maybe even split it into two traits, and have a |
The API for Now, to implement a custom script extension, the user needs to know:
Which looks like a very steep learning curve. Sure, there is quite a bit of inherent complexity in the way how script extensions work, but maybe we should also check if/how other bindings implement those and if we could simplify something without abandoning safety (probably not, but worth checking). Additionally, if we build a parallel I'm also a bit worried since there are likely already additional smart pointer types necessary for the multi-threaded case, and a proliferation of those will leave us with gdnative-level complexity that is near-impossible to understand for casual game developers. Again, sometimes complexity is inevitable, and maybe |
as-is, we cannot directly reuse the existing logic from |
I think that could work, but only if there's enough common code. If we end up needing traits with 2 separate impls, then there's not much code sharing. Also this would then be more an implementation detail, not the user facing API. But if the latter has conceptual + naming parallels between |
A Problem I see with this solution is that you now have taken over the type instantiation of what ever implements the trait which will be quite inconvenient for who ever wants to use You also cannot get the script instance storage via the script owner object unless we start maintaining a mapping between godot objects and their script instances. The engine does not expose any way to access script instances. |
I don't see how |
I updated this with a commit that implements a POC for using |
That should probably work yeah, it's a lot less effort than trying to mirror Needing to add an extra trait in One thing to consider (which we could do another time too) that i just thought of is: In fn instance_create(&self) -> impl ScriptInstance; Since then we can just call I think this is a bigger issue actually with how we generate bindings currently. Since we just declare any method that takes or returns a pointer |
If there are no further comments on the topic, I will now start cleaning up the POC and create an PR. Are there any opinions how the |
Related to #501, though
ScriptInstance
is a bit of a special case.Godot actually passes
ScriptInstance
around asvoid*
for some reason, so on the Rust side we implement a trait calledScriptInstance
whose methods take a&mut self
. In particular, the method of most concern isScriptInstance::call
This method documentation makes the following note.
Which makes any attempts to implement recursion in a Rust-side scripting language a non-starter. I think we can make this method re-entrant, and I'd like to start the discussion on how to do that.
Currently, we implement the GDExtension
ScriptInterface
type asScriptInstanceData<T: ScriptInstance>
, which contains aRefCell<T>
. When we need to invoke a trait function likeScriptInstance::call
, we take theScriptInstanceData<T>
and doThat is, we borrow the contents of the
RefCell<T>
,instance.inner
, for the duration of the call. That makes it impossible to invokecall
again on the same instance recursively from Godot until the original call exits.Proposed Solution 1
My first thought is this.
ScriptInstance
is never used as a trait object, so it needn't be object-safe. So one solution is to change the signature of all of theScriptInstance
methods fromto
Then the implementor of
call
can borrow mutably, decide what it needs to do, and then release the borrow if it's going to make a potentially-recursive call. This just puts the control (and responsibility) of borrowing correctly in the hands of the implementor. I'm not entirely sure how I feel about that, asinstance: RefCell<Self>
is a much more obtuse API than&mut self
, but it definitely would get the job done.Proposed Solution 2
We may be able to apply the
GodotCell
trick toScriptInstance
. I don't claim to fully understand the trickery exhibited by this new cell type, but it looks like it hinges on the fact that theself
in these calls is actually owned by aGd<Self>
. In our case withScriptInstance
, theself
we have is always owned by aScriptInstanceData<Self>
, so a similar trick may be viable.The text was updated successfully, but these errors were encountered: