Skip to content

Make ScriptInstance.call and company re-entrant #554

@Mercerenies

Description

@Mercerenies

Related to #501, though ScriptInstance is a bit of a special case.

Godot actually passes ScriptInstance around as void* for some reason, so on the Rust side we implement a trait called ScriptInstance whose methods take a &mut self. In particular, the method of most concern is ScriptInstance::call

fn call(
    &mut self,
    method: StringName,
    args: &[&Variant]
) -> Result<Variant, u32>

This method documentation makes the following note.

It’s important that the script does not cause a second call to this function while executing a method call. This would result in a panic.

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 as ScriptInstanceData<T: ScriptInstance>, which contains a RefCell<T>. When we need to invoke a trait function like ScriptInstance::call, we take the ScriptInstanceData<T> and do

borrow_instance_mut(instance).call(method.clone(), args)

That is, we borrow the contents of the RefCell<T>, instance.inner, for the duration of the call. That makes it impossible to invoke call 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 the ScriptInstance methods from

fn call(&mut self, ...) -> ...

to

fn call(instance: RefCell<Self>, ...) -> ...

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, as instance: 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 to ScriptInstance. 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 the self in these calls is actually owned by a Gd<Self>. In our case with ScriptInstance, the self we have is always owned by a ScriptInstanceData<Self>, so a similar trick may be viable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    c: script-instanceScript-instance APIsfeatureAdds functionality to the library

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions