Cleanup some stuff mainly related to script instance - #741
Conversation
|
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-741 |
21faba3 to
ab284cf
Compare
|
Thanks a lot! I'd probably merge this after #736, as there are some possibly conflicting changes. It's nice that even though there are a lot of changes, the diff is still visible in most places! If you plan further refactorings (like moving modules to files), it could make sense to do that in a separate PR 🙂 |
|
yeah i was considering moving modules to files and maybe moving around some functions to different places, but i figured it'd be best to avoid doing things that would confuse the diff to make it more easy to read |
ab284cf to
f5a4c0c
Compare
Bromeon
left a comment
There was a problem hiding this comment.
Great idea with the constants and the conversion functions!
| // SAFETY: `name` and `return_value` were created from the appropriate method calls, and have not been freed before this. | ||
| unsafe { | ||
| let _name = StringName::from_owned_string_sys(name); | ||
| PropertyInfo::free_owned_property_sys(return_value); | ||
| } | ||
|
|
||
| // SAFETY: These pointers were both created from a call to `as_mut_ptr` on a slice. Additionally these pointer will not be accessed | ||
| // again after this function call. | ||
| let (arguments_slice, default_arguments_slice) = unsafe { | ||
| ( | ||
| std::slice::from_raw_parts_mut(arguments, u32_into_usize(argument_count)), | ||
| std::slice::from_raw_parts_mut( | ||
| default_arguments, | ||
| u32_into_usize(default_argument_count), | ||
| ), | ||
| ) | ||
| }; | ||
|
|
||
| // SAFETY: We have exclusive ownership of these slices, and they were originally created from a call to `Box::leak`. | ||
| let (arguments, default_arguments) = unsafe { | ||
| ( | ||
| Box::from_raw(arguments_slice), | ||
| Box::from_raw(default_arguments_slice), | ||
| ) | ||
| }; | ||
|
|
||
| for info in arguments.iter() { | ||
| // SAFETY: These infos were originally created from a call to `PropertyInfo::into_owned_property_sys`, and this method | ||
| // will not be called again on this pointer. | ||
| unsafe { PropertyInfo::free_owned_property_sys(*info) } | ||
| } | ||
|
|
||
| for variant in default_arguments.iter() { | ||
| // SAFETY: These pointers were originally created from a call to `Variant::into_owned_var_sys`, and this method will not be | ||
| // called again on this pointer. | ||
| let _variant = unsafe { Variant::from_owned_var_sys(*variant) }; |
There was a problem hiding this comment.
I find this a bit hard to read... On one hand the multi-line tuples, on the other all the separate unsafe blocks which cover the entire area of this code. It's only 10 lines of actual logic, but the initialization is so sophisticated that the data flow is not very obvious anymore. For example, if arguments_slice were used within a block, it would be immediately clear that it's only used once and not further down the function.
Some unsafe could possibly be collapsed (e.g. slice and box initialization). Imo it's fine if a SAFETY block explains more than one thing, or if multiple explanations are within an unsafe block (next to their respective operations).
I was also considering that instead of alternating a1 b1 a2 b2 statements, we could use a1 a2 b1 b2 -- i.e. do all the arguments stuff first, then all the default_arguments stuff. This keeps data dependencies more local, however it spreads out similar unsafe operations, so it might be worse. Not sure if it's needed at all after above changes.
All in all it's not a big deal, just my first impression 🙂
| } | ||
|
|
||
| impl<T: ScriptInstance> ScriptInstanceData<T> { | ||
| /// Convert a `StringName` sys pointer to a reference with unbounded lifetime. |
There was a problem hiding this comment.
I think "StringName" is wrong here.
| /// Helper struct to store the lengths of lists so they can be properly freed. | ||
| pub struct PtrlistContainer<T> { | ||
| list_lengths: Mutex<HashMap<*mut T, u32>>, |
There was a problem hiding this comment.
Nice abstraction!
Re name, "list" and "container" are somewhat redundant, or it's not really clear how they relate. Maybe we should somehow express that this stores the lengths? BoundedPtrList or something like that?
You could also mention that you use "list" and not "array" because these refer to "property/method lists" (in Godot terms).
| unsafe fn transfer_ptr_list_from_godot<T>(ptr: *const T, list_length: usize) -> Vec<T> { | ||
| Vec::from_raw_parts(sys::force_mut_ptr(ptr), list_length, list_length) | ||
| } | ||
| (ptr as *const T, len) |
There was a problem hiding this comment.
sys::to_const_ptr...
or can you call as_ptr() instead of as_mut_ptr() directly? Does the hashmap need mut pointers?
There was a problem hiding this comment.
we do need to call as_mut_ptr() because otherwise we get a pointer derived from a &[T] reference which we cannot later free again since it doesn't have write permissions.
The issue really is just that c++ const pointers can be freed, whereas in rust a pointer derived from a shared reference cannot. So there really isn't a great way to map c++ const pointers to rust in this case, but the functions in question do take const pointers so that's what we need to use.
There was a problem hiding this comment.
I see, thanks for elaborating! Then you could use sys::to_const_ptr(ptr) in the return tuple, to avoid as.
| pub const fn bool_into_sys(value: bool) -> sys::GDExtensionBool { | ||
| value as sys::GDExtensionBool | ||
| } |
There was a problem hiding this comment.
I'd use bool_to_sys. Even if it takes by value, being Copy means the original isn't consumed. And the shorter these functions are, the better...
Same for other similar ones.
f5a4c0c to
c05b8f5
Compare
Bromeon
left a comment
There was a problem hiding this comment.
Thanks! Nice discovery of cast_mut and cast_const 👍
I think a few safety statements could be slightly more concise, e.g.
// SAFETY: `arguments` was returned from a call to `as_mut_ptr` on a mutable slice, with length equal to `default_argument_count`. This
// length has not changed since it was created so `from_raw_parts_mut` is safe to call on it.could be the following:
// SAFETY:
// - `from_raw_parts_mut`: `arguments` comes from `as_mut_ptr()` on a mutable slice of length `default_argument_count`.
// - `Box::from_raw`: ...(with line breaks if needed, I don't see it well in GitHub).
| // The slice was returned from a call to `Box::leak`, and we have ownership of the value behind this pointer, so we `Box::from_raw` | ||
| // is safe to call on it. |
There was a problem hiding this comment.
| // The slice was returned from a call to `Box::leak`, and we have ownership of the value behind this pointer, so we `Box::from_raw` | |
| // is safe to call on it. | |
| // The slice was returned from a call to `Box::leak`, and we have ownership of the value behind this pointer, so `Box::from_raw` | |
| // is safe to call on it. |
(maybe obsolete with main comment)
| // The slice was returned from a call to `Box::leak`, and we have ownership of the value behind this pointer, so we `Box::from_raw` | ||
| // is safe to call on it. |
There was a problem hiding this comment.
| // The slice was returned from a call to `Box::leak`, and we have ownership of the value behind this pointer, so we `Box::from_raw` | |
| // is safe to call on it. | |
| // The slice was returned from a call to `Box::leak`, and we have ownership of the value behind this pointer, so `Box::from_raw` | |
| // is safe to call on it. |
Sure though i dont entirely see the benefit of collapsing the two unsafe blocks into one if we're just gonna separately document each call anyway? |
bdaee68 to
19fafc3
Compare
Yeah, it's a bit unfortunate that In situations where that happens, another option would be: // SAFETY: see inside.
unsafe {
// SAFETY: ...
let a = do_stuff();
let b = do_other_stuff();
// SAFETY: ...
do_more(a, b);
}but that might also be confusing. Every approach has some downsides... I didn't find a perfect one, so if you find one that works well enough, great 🙂 If the tuples are aggregated on a single line (using variables), that's also more readable than a multiline-tuple expression. |
|
One way to at least be consistent is via the |
Cleanup script instance borrowing
19fafc3 to
221030a
Compare
One problem is that our unsafe surface is huge due to the nature of our project being a binding, and there are different "danger levels". For example, we don't need to repeatedly justify calling a Godot C function, because we make sure it's mapped correctly with bindgen and the header file, and we check version compatibility at startup. With the preconditions checked, they might even be safe functions, but On the other hand, we do have to justify why reinterpreting pointers, assuming buffer lengths, etc. are sound, so Making |
|
I think the PR looks good, could you maybe group some of the commits? E.g. the two "add/use |
Well there are ways to counteract some of these issues. For instance a token system can work, where we check the preconditions once, and return a token of some kind. Then this token allows the user to safely call functions since it can only be created when the preconditions have been checked. But i dont know if such a rewrite is worth it. Worth considering though in some cases maybe. Maybe we can make |
Add into/free owned_sys methods for MethodList Remove helper functions that were just used in one place and which didnt really do much to improve readability
Add `#[deny(unsafe_op_in_unsafe_fn)]` to `script_instance_info` Use `sys::conv` bools Final touches
221030a to
686b59d
Compare
Mainly:
into/from_owned_sysfunctions instead of storing entire property lists and such in a hashmapScriptInstanceDatato be methodssys::convmodule for common conversionsbool -> GDExtensionBoolconversion functionGDExtensionBoolu32 -> usize(this fails at compile time instead of runtime and should have no overhead, since i do not believe we (will) support any targets whereu32<=usize)#[deny(unsafe_op_in_unsafe_fn)]to all the ffi-functions in script instance so that we're sure all unsafe function calls are explicitly tracked and called outAlso @TitanNano since you made a lot of this originally