Skip to content
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

Implementation of godots GDExtensionScriptInstance #492

Merged
merged 1 commit into from
Dec 5, 2023

Conversation

TitanNano
Copy link
Contributor

I have recently worked on an implementation of GDExtensionScriptInstance and would now like to contribute it to gdext. This is required to be able to properly implement a custom ScriptExtension / ScriptLanguageExtension.

Please let me know your thoughts on the changes, and I welcome every feedback.

@TitanNano TitanNano force-pushed the jovan/impl_script_instance branch from d3bf31a to 5b24d33 Compare November 21, 2023 20:46
@Bromeon
Copy link
Member

Bromeon commented Nov 21, 2023

This is very cool, thanks a lot! 🙂

Regarding the CI, you already found some issues. The classes Script, ScriptLanguage would need to be enabled here.

Did you have a project that uses them? I'm thinking about ways to test this (maybe in an automated way)...? 🤔

@Bromeon Bromeon added feature Adds functionality to the library c: engine Godot classes (nodes, resources, ...) labels Nov 21, 2023
@TitanNano TitanNano force-pushed the jovan/impl_script_instance branch from 5b24d33 to 8ca5b52 Compare November 21, 2023 20:59
@GodotRust
Copy link

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-492

@TitanNano
Copy link
Contributor Author

Did you have a project that uses them? I'm thinking about ways to test this (maybe in an automated way)...? 🤔

@Bromeon I'm using them in https://github.com/titannano/godot-rust-script.

CI tests are also something I have been thinking about. To create a ScriptInstance Godot needs a ScriptExtension and that in turn needs a ScriptLanugageExtension. So I'm not sure how straightforward it would be to test a script instance without implementing a (bare bones) script language first.

💭 Perhaps we could implement the minimally stubbed out interfaces in itest and attach some script to a Godot object… I haven't tried it yet.

Copy link
Member

@lilizoey lilizoey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! im glad someone did this since i remember it being kinda tedious to do last time.

You leak values a lot without much justification, it'd be nice to know why it's fine to leak memory when you do. Whether it is because you're actually passing ownership to godot, or the memory will later be freed by another callback.

Maybe it'd be nice if we made create_script_instance return a smart pointer? one which stores the instance pointer, and is able to check if it has been freed yet. And have that object implement ScriptInstance?

Also you haven't documented any of the public methods/structs, you should do that.

And some tests as @Bromeon mentioned would be good. It should be possible to create a very barebones script "language", maybe one that just checks if a script contains one of a few hardcoded strings to represent the different things we wanna check?

godot-core/src/builtin/meta/mod.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding tests:

💭 Perhaps we could implement the minimally stubbed out interfaces in itest and attach some script to a Godot object… I haven't tried it yet.

That would be great, because it would likely reveal UB and memory leaks during CI. Currently there's no way to check the safety of this PR, since the functionality is never used.

Let us know if you need some support there 🙂

godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/meta/mod.rs Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
@TitanNano
Copy link
Contributor Author

Maybe it'd be nice if we made create_script_instance return a smart pointer? one which stores the instance pointer, and is able to check if it has been freed yet. And have that object implement ScriptInstance?

@lilizoey so create_script_instance creates a GDExtensionScriptInstanceInfo which is immediately passed to Godots script_instance_create which wraps the InstanceInfo with a GDExtensionScriptInstance and returns the pointer to it back to the GDExtension. The GDExtension then has to pass this pointer from the extensions ScriptExtension implemenation to back to the engine. Thereafter, the script instance is out of our hands. Godot only calls the GDExtensionScriptInstanceInfo::free_func once it wants to free the InstanceInfo.

Also you haven't documented any of the public methods/structs, you should do that.

yes, I should do that. Thanks.

@TitanNano
Copy link
Contributor Author

@Bromeon I just noticed that Godot 4.2 will introduce a second API (gdextension_script_instance_create2) for creating script instances. I implemented this PR against Godot 4.1 and wasn't aware of this change. Would you be fine with merging this PR targeting the 4.1 API and looking into the newer API afterward? Or should I try to implement this against the newer API directly? I haven't looked at the new API and am not aware what exactly changed, so I would prefer to finish this and then tackle the API change separately.

@Bromeon
Copy link
Member

Bromeon commented Nov 22, 2023

I implemented this PR against Godot 4.1 and wasn't aware of this change. Would you be fine with merging this PR targeting the 4.1 API and looking into the newer API afterward?

Yep, that's definitely fine! Let's tackle one thing at a time, this is already great work 🙂
As a nice bonus, we could then maybe keep a fallback version of script instances for Godot 4.1, even later.

Right. This is being cleaned up in the script instance free_method_list_func... I see now that this is not ideal. Any suggestion how we could improve it.

In general, I think this is OK and the workflow intended by Godot. The surprise is probably there because this is not obvious from the code when just looking at one of the functions.

As mentioned, I would probably write a few functions that convert Rust domain models to C pointer types and back (so all conversions go through those functions). Maybe they could be named something that highlights the change of ownership, e.g. transfer_xy_to_godot and transfer_xy_from_godot. Or "submit/retrieve", "hand over/take over", "acquire/release", ...

@TitanNano TitanNano force-pushed the jovan/impl_script_instance branch 3 times, most recently from 61ec6c8 to c5aabcc Compare November 25, 2023 00:15
Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot!

I added lots of small formatting comments, but most of them come with suggestions that you can directly commit in the UI, and later squash locally.

Also, how do we want to proceed with tests? You said you're already using this in a project of yours?

godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/meta/mod.rs Show resolved Hide resolved
godot-core/src/builtin/meta/mod.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
@TitanNano TitanNano force-pushed the jovan/impl_script_instance branch 5 times, most recently from 25a66cd to 9026b9d Compare November 26, 2023 22:30
@TitanNano TitanNano force-pushed the jovan/impl_script_instance branch 3 times, most recently from 146011c to 30d9652 Compare November 28, 2023 23:41
@TitanNano
Copy link
Contributor Author

TitanNano commented Nov 28, 2023

I have now implemented itests for the methods which can be called via a Godot API.

While implementing tests, I came into a situation where the engine would crash because the ScriptInstance::refcount_decremented function would panic due to a todo!. It was quite difficult to notice this because no panic information was being printed. I have therefore now wrapped all calls into the trait with a handle_panic.

Tests are failing in CI because of an outdated cache where godot-codegen is not being rebuilt? At least, I think that is what is happening. Besides that, there appears to be a memory leak, and I'm not yet sure where it originated.

EDIT: I have so far narrowed the memory leak down to the property_list test. No other test appears to leak memory.

EDIT: I was able to resolve the memory leaks.

@TitanNano TitanNano force-pushed the jovan/impl_script_instance branch 4 times, most recently from 39de807 to 0f677b0 Compare November 30, 2023 08:42
@TitanNano TitanNano force-pushed the jovan/impl_script_instance branch 4 times, most recently from 66d8a09 to ebdab39 Compare December 2, 2023 00:29
@Bromeon Bromeon force-pushed the jovan/impl_script_instance branch from ebdab39 to 94ca2a5 Compare December 2, 2023 09:24
Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebased onto master (after Godot 4.2 change). Most things look good to me now.

A general note about the public API. The test does this:

#[godot_api]
impl IScriptExtension for TestScript {
    unsafe fn instance_create(&self, _for_object: Gd<Object>) -> *mut c_void {
        create_script_instance(TestScriptInstance::new(self.to_gd().upcast())) as *mut c_void
    }

    fn can_instantiate(&self) -> bool {
        true
    }
}

It would be good if we could somehow abstract the raw FFI types for the user. Remember, sys:: (i.e. the godot-ffi crate) is not part of the public API. I'm aware that not all the things have been properly mapped to public pendants yet, but let's avoid them where possible.

For example, instance_create could directly return a *mut c_void, no? Then your implementation could become simpler (and a bit more explicit with the let script declaration):

#[godot_api]
impl IScriptExtension for TestScript {
    unsafe fn instance_create(&self, _for_object: Gd<Object>) -> *mut c_void {
        let script: Gd<Script> = self.to_gd().upcast();
        create_script_instance(TestScriptInstance::new(script))
    }

    fn can_instantiate(&self) -> bool {
        true
    }
}

(Btw, I think we mark IScriptExtension::instance_create incorrectly as unsafe, but we can fix that later...)

godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
godot-core/src/builtin/script.rs Outdated Show resolved Hide resolved
@TitanNano TitanNano force-pushed the jovan/impl_script_instance branch 2 times, most recently from 158ca24 to 99da879 Compare December 2, 2023 15:26
@TitanNano
Copy link
Contributor Author

TitanNano commented Dec 2, 2023

It would be good if we could somehow abstract the raw FFI types for the user. Remember, sys:: (i.e. the godot-ffi crate) is not part of the public API. I'm aware that not all the things have been properly mapped to public pendants yet, but let's avoid them where possible.

@Bromeon so the last sys type still left in the public API is sys::GDExtensionCallErrorType. How should we deal with that?

@TitanNano TitanNano force-pushed the jovan/impl_script_instance branch from 99da879 to dc8b552 Compare December 2, 2023 15:34
@Bromeon
Copy link
Member

Bromeon commented Dec 2, 2023

so the last sys type still left in the public API is sys::GDExtensionCallErrorType. How should we deal with that?

I think that's OK for now, we still need to map some remaining ones. Maybe you can add a // TODO comment.

Thanks a lot for all the updates! 👍

@TitanNano TitanNano force-pushed the jovan/impl_script_instance branch from dc8b552 to e935213 Compare December 2, 2023 15:57
@Bromeon
Copy link
Member

Bromeon commented Dec 5, 2023

@lilizoey any more comments on this one? Do you think we can merge a first version soon?

@lilizoey
Copy link
Member

lilizoey commented Dec 5, 2023

I think it looks good yeah

@Bromeon Bromeon added this pull request to the merge queue Dec 5, 2023
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Dec 5, 2023
@Bromeon
Copy link
Member

Bromeon commented Dec 5, 2023

@TitanNano Thanks a lot for the great work and your patience during the lengthy review process! 👍

I can fix the remaining CI issues.

@Bromeon Bromeon force-pushed the jovan/impl_script_instance branch from e935213 to 91b6cc3 Compare December 5, 2023 19:31
@Bromeon Bromeon enabled auto-merge December 5, 2023 19:32
@Bromeon Bromeon added this pull request to the merge queue Dec 5, 2023
Merged via the queue into godot-rust:master with commit 6cb6895 Dec 5, 2023
16 checks passed
@TitanNano TitanNano deleted the jovan/impl_script_instance branch December 5, 2023 21:56
TCROC added a commit to Lange-Studios/gdext that referenced this pull request Dec 6, 2023
…cript_instance"

This reverts commit 6cb6895, reversing
changes made to ef8b2b4.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: engine Godot classes (nodes, resources, ...) c: ffi Low-level components and interaction with GDExtension API feature Adds functionality to the library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants