-
-
Notifications
You must be signed in to change notification settings - Fork 193
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
Hot reload support #437
Hot reload support #437
Conversation
Not tested yet |
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-437 |
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, great update! 👍
Some feedback:
- Can you elaborate what
create_rust_counterpart
exactly does and maybe add a comment? - Do you think it's possible to test this somehow?
- Please keep all symbols coming from the
godot-ffi
crate qualified withsys::
, this is a deliberate choice to make FFI symbols explicit. So there should be no imports starting withgodot_ffi::
orsys::
. - This might need rebase onto
master
after #436 is merged, which fixes a few things.
Let's say that existing method
Second part does perfectly match what we need in Is |
For now - manually is the most convenient. For future - maybe some unit tests could be added/updated, I'll look later.
Done
Will make separate commits for now and squash everything when all is done. |
99d6c0d
to
9775bf1
Compare
182c7b0
to
de08d73
Compare
daccdd5
to
5916387
Compare
added classes unloading and seems like it works. tested manually against godotengine/godot#82603. thinking about automated tests. |
5916387
to
6a46484
Compare
I think this would need some
Rebasing onto |
d5a60f4
to
876f350
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.
Turns out I was completely wrong, your code was just missing some #[cfg(before_api = "4.2")]
statements for symbols depending on new code.
I fixed this and also rebased onto master. Feel free to integrate my commit into yours, no need to retain my author information or so. I kept it separate for you to see what I changed, but there's no strict need to be like that in the merge.
Added some feedback, mostly smaller bikeshedding 😉 thanks a lot for everything, looks like solid work overall!
use std::{fmt, ptr}; | ||
|
||
static LOADED_CLASSES: Mutex<Option<HashMap<InitLevel, Vec<ClassName>>>> = Mutex::new(None); |
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 know why it's needed, but I do hate Rust's excessive boilerplate with global variables...
Do you know if this can ever be accessed concurrently? Probably only in case of a Godot bug?
It's probably better to be safe and sorry, since this is not a critical path; as such I appreciate also your panicking and descriptive errors on unlock failure further below.
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.
Probably yeah, due to some bug on Godot side (after some major rework on Godot side). Now they are called from one thread (register, unregister). I'm glad you share my position regarding panic in case of theoretically possible multithreaded access from engine side.
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 debugging this! Could you add as a comment:
- The fact that they are only accessed from a single thread at the moment.
- The rationale why we still use mutex + panic:
- "better safe than sorry" part
- hot reloading is very involved anyway, so it's not performance-critical or a hot loop.
godot-core/src/registry.rs
Outdated
} | ||
|
||
out!("All classes for level `{init_level:?}` auto-registered."); | ||
} | ||
|
||
pub fn unregister_classes(init_level: InitLevel) { | ||
let mut loaded_classes = get_loaded_classes_with_mutex(); | ||
let loaded_classes = loaded_classes.get_or_insert_with(Default::default); |
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.
Using HashMap::new
instead of Default::default
is a bit clearer.
godot-core/src/registry.rs
Outdated
let mut loaded_classes = get_loaded_classes_with_mutex(); | ||
let loaded_classes = loaded_classes.get_or_insert_with(Default::default); |
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.
Maybe name loaded_class_map
to differentiate from the further down used loaded_classes
representing a vector.
godot-core/src/registry.rs
Outdated
pub fn unregister_classes(init_level: InitLevel) { | ||
let mut loaded_classes = get_loaded_classes_with_mutex(); | ||
let loaded_classes = loaded_classes.get_or_insert_with(Default::default); | ||
let loaded_classes = loaded_classes.remove(&init_level).unwrap_or_default(); |
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.
Naming both these things loaded_classes
is a bit confusing. As above, I'd name the map loaded_class_map
.
godot-core/src/registry.rs
Outdated
unsafe { | ||
#[allow(clippy::let_unit_value)] | ||
let _: () = interface_fn!(classdb_unregister_extension_class)( | ||
sys::get_library(), | ||
class_name.string_sys(), | ||
); | ||
} |
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.
This could be extracted to a new function, next to register_class_raw
.
godot-core/src/registry.rs
Outdated
@@ -263,6 +366,7 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) { | |||
c.is_editor_plugin = true; | |||
} | |||
} | |||
// c.godot_params.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 needed?
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.
Just in case you missed it 🙂
let _: () = interface_fn!(classdb_register_extension_class)( | ||
sys::get_library(), | ||
class_name.string_sys(), | ||
parent_class_name.string_sys(), | ||
ptr::addr_of!(info.godot_params), | ||
); | ||
#[cfg(since_api = "4.2")] |
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.
Maybe empty line before this to highlight related blocks.
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 this one 🙂
godot-core/src/registry.rs
Outdated
create_rust_part_for_existing_godot_part(T::__godot_init, object) | ||
} | ||
|
||
pub(crate) fn create_custom<T, F>(make_user_instance: F) -> godot_ffi::GDExtensionObjectPtr |
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.
This line shouldn't change -- use sys::
prefix everywhere, not godot_ffi::
.
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.
^
fn create_rust_part_for_existing_godot_part<T, F>( | ||
make_user_instance: F, | ||
base_ptr: sys::GDExtensionObjectPtr, | ||
) -> sys::GDExtensionClassInstancePtr |
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 for your elaboration in comment. I think this method would benefit from a short doc.
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.
^
0ace29f
to
298e2b3
Compare
I've made some fixes after rebase. Commits are left for visibility, but I'll squash them later. About auto-testing of this feature. I've checked test module The problem about this feature is that it is editor-level, but itest is game-level. I see the following approach:
Whether the game is worth the candle? |
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 for the update! There are a few unaddressed comments from an earlier review, I marked them.
Apart from that, looks mostly good, I think you can start squashing to a single commit. There are also a few questions for my understanding that don't warrant a change in the code itself.
use std::{fmt, ptr}; | ||
|
||
static LOADED_CLASSES: Mutex<Option<HashMap<InitLevel, Vec<ClassName>>>> = Mutex::new(None); |
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 debugging this! Could you add as a comment:
- The fact that they are only accessed from a single thread at the moment.
- The rationale why we still use mutex + panic:
- "better safe than sorry" part
- hot reloading is very involved anyway, so it's not performance-critical or a hot loop.
godot-core/src/registry.rs
Outdated
@@ -263,6 +366,7 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) { | |||
c.is_editor_plugin = true; | |||
} | |||
} | |||
// c.godot_params.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.
Just in case you missed it 🙂
let _: () = interface_fn!(classdb_register_extension_class)( | ||
sys::get_library(), | ||
class_name.string_sys(), | ||
parent_class_name.string_sys(), | ||
ptr::addr_of!(info.godot_params), | ||
); | ||
#[cfg(since_api = "4.2")] |
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 this one 🙂
godot-core/src/registry.rs
Outdated
create_rust_part_for_existing_godot_part(T::__godot_init, object) | ||
} | ||
|
||
pub(crate) fn create_custom<T, F>(make_user_instance: F) -> godot_ffi::GDExtensionObjectPtr |
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.
^
fn create_rust_part_for_existing_godot_part<T, F>( | ||
make_user_instance: F, | ||
base_ptr: sys::GDExtensionObjectPtr, | ||
) -> sys::GDExtensionClassInstancePtr |
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.
^
|
||
[build-dependencies] | ||
godot-bindings = { path = "../godot-bindings" } # emit_godot_version_cfg |
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.
This is needed, but unfortunately forces the godot-macro
crate compilation to begin only after godot-bindings
has finished, possibly increasing compile times.
We should probably (in a separate PR, out of scope here) extract the version detection and #[cfg]
generation to its own crate, also in light of #281.
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.
heh, so this feature support is not without downsides...
godot-core/src/registry.rs
Outdated
#[cfg(since_api = "4.2")] | ||
pub unsafe extern "C" fn on_notification<T: cap::GodotNotification>( | ||
instance: sys::GDExtensionClassInstancePtr, | ||
what: i32, | ||
_reversed: sys::GDExtensionBool, |
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 need to change things now, but do you think the user could in the future benefit from having this reversed
flag available inside on_notification
?
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.
it seems, that it is necessary. We use GDExtensionClassCreationInfo2
now and it uses GDExtensionClassNotification2, which has this parameter.
I temporarily downgraded our CI to an older Godot version to unblock open PRs.
I agree that testing this is likely more involved. I don't think this PR should be blocked on that -- if you did some manual tests, that's already a good start. But we could think about how to do it in the future, thanks for your suggestions in that regard. This might be nice, but likely quite a huge effort to implement, our current We could also check if/how godot-cpp has any tests in this regard... |
b72380f
to
51500d9
Compare
Some minor grammar issues in comments, but nothing crucial, so I'll merge. Thanks a lot for your efforts! |
Oh, not again this macOS spurious failure... Really need to fix this or see if there's an issue with Godot. |
thanks for your guidance! I'm glad to take part. |
class_name.string_sys(), | ||
); | ||
} | ||
godot_print!("Class {class_name} unloaded"); |
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.
Sorry if a dumb question (and not sure it's really worth opening a new issue for so I thought I'd do a small comment here)
Is there a way to suppress these lines from printing?
I have an godot project that I use for integration tests for my project (launched via godot cli) and with this new change I now get a big list of nodes at the end of the output which isn't ideal for me.
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.
Make sense. #448
Essentially, that's the Rust version of godotengine/godot-cpp#1200
Targeted to fix #434