-
-
Notifications
You must be signed in to change notification settings - Fork 223
IAudioStreamPlayback::mix parameters could be made safe #1130
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
Comments
Good idea! Like in #1129, we cannot automate this, but we could introduce a new mechanism that allows us to rewrite a custom-defined virtual function. Do you know other APIs that would benefit from this? Maybe we could look at some taking pointers 🤔 Btw @Yarwin typically I use |
This will likely have to wait until v0.4, as I'd prefer not having larger modifications for the soon-to-be-released v0.3, and it's breaking due to signature change. At least unless we rename it, but then that opens another can of works with two coexisting methods. That shouldn't discourage you from experimenting though -- in fact, having a proof-of-concept early in the v0.3 cycle would give us some confidence to break the API in 0.4.0 release. Furthermore, it would be good to batch changes of this kind together -- so if anyone comes across more pointer-based APIs that can be easily made safe, please add a comment here! 🙂 |
I'm planning to collect together a list of functions that can be made safe in the same way as this one. Just from a brief scan of the code there are quite a few. If anyone else has already done this, please let me know. |
@djcsdy I did, added another link to my previous post. |
Thanks! I made a list of functions that take a pointer to a buffer and a length. It should be possible to make all of these safe by wrapping the buffer in
A lot of these are effectively duplicates because of inheritance. I've only inspected the signature of these functions. I still need to verify that the semantics are as described. There are also some functions that provide a pointer to a pointer to a buffer, and a pointer to a length. I haven't listed these as they'll need to be handled differently. I've also excluded functions where the buffer is a *void as these will require more investigation. I'll look into making a PR to make these functions safe. |
Thanks so much, that's very appreciated! 👍 Maybe before jumping straight into implementation, we should check the approach. It's a number of methods that might be worth semi-automating (i.e. listing just the method and parameter names, and letting codegen handle the conversion to safe parameter), but if we find an easy way to write that code manually in a central place, that's probably also OK -- and likely more extensible for any sort of "special" semantics ( |
Agreed, at the moment I am not sure if the best approach is to automate these or implement special cases. I still need to double check the semantics of each of these functions. I think it'll become clearer after I've done that. |
An idea: We would list somewhere in special_cases.rs (or a submodule) the method names to be replaced, and their new signatures: let replaced_virtual_methods = &[
("IAudioStreamPlayback", quote! {
fn mix(&mut self, buffer: &mut [AudioFrame], rate_scale: f32) -> usize;
fn another_method(&self, ...);
}),
...
]; (note that the method name is inferred from the signature). The What remains is the bridge from unsafe raw API to safe user-facing API. This part could also be generated by the compiler, in the form of a mapper trait: trait VirtualMethodMapper {
// This is the original signature, as declared by Godot.
// It is parametrized by T, implementing the user-facing trait.
fn mix<T>(this: &mut T, buffer: *mut AudioFrame, rate_scale: f32, frames: i32) -> i32
where T: IAudioStreamPlayback;
// Here, all methods defined in `replaced_virtual_methods` are listed.
fn another_method<T>(this: &T, ...)
where T: IAudioStreamPlayback;
...
} The resulting trait would land somewhere in enum TheOneMapper {} // uninhabitable
impl VirtualMethodMapper for TheOneMapper {
fn mix<T>(this: &mut T, buffer: *mut AudioFrame, rate_scale: f32, frames: i32) -> i32
where
T: IAudioStreamPlayback
{
// Manual code written by us.
let buffer = unsafe { std::slice::from_raw_parts_mut(buffer, frames as usize) };
// Forward to user-facing trait, convert return type.
this.mix(buffer, rate_scale) as i32
}
// repeat for all trait functions
} godot-rust code on dispatching virtual call: <TheOneMapper as VirtualMethodMapper>::mix(self, buffer, rate_scale, frames) It needs quite a bit of machinery to get going, but it would likely be very flexible. In contrast to fully auto-generated APIs, it would allow to take specific semantics into account, or do signature changes beyond ptr/len -> slice conversion. And possibly this mechanism could be reused elsewhere... Anyway, just some brainstorming 🙂 |
I collected all Godot 4.4 methods that contained at least one pointer (basically I didn't look at semantics, it's really just pointers with any sort of meaning. So as a start, your table of functions specifically working with The entries look like this: MultiplayerPeerExtension::_get_packet
! (p) r_buffer: const uint8_t **
! (p) r_buffer_size: int32_t*
(>) enum::Error
OpenXRAPIExtension::transform_from_pose
! (p) pose: const void*
(>) Transform3D with: (p) parameter
(>) return type
! contains pointer
[required] virtual method must be overridden
[const] &self instead of &mut self access Just to be clear, I don't think an initial contribution should aim at changing all those signatures. On the contrary, we should probably start small with a handful methods and see if such an approach works out. |
CompatibilityOne problem with my previous proposal is that every change from unsafe fn mix(&mut self, buffer: *mut AudioFrame, rate_scale: f32, frames: i32) -> i32; to fn mix(&mut self, buffer: &mut [AudioFrame], rate_scale: f32) -> usize; is breaking. This means that we can only introduce such changes across minor versions, rather than gradually, and it may also be annoying for users. A way forwardAn idea that would allow us to incrementally migrate APIs, and also not run into problems when Godot adds new pointer methods that we don't yet handle:
|
@djcsdy any feedback on my latest proposal? The only action for now is that all pointer-based virtual functions would be renamed to It's a breaking change now in 0.3.0, but should easy the path towards safe functions in 0.3.x and beyond. |
I definitely agree with the renaming as a first step. It'll be a few days at least before I can look at this again in any detail. |
IAudioStreamPlayback::mix
is markedunsafe
because it takes a raw pointer:The first thing I do when implementing this function is to convert
buffer
to a slice:It'd be nice if godot-rust did this conversion for me before calling
mix
, which could then be made safe:Godot calls
mix
from a non-main thread. I am making the assumption that Godot promises not to read or mutatebuffer
untilmix
returns. This seems a safe assumption, because it would be very silly if Godot violated this assumption, but I have not actually checked.The fact that Godot calls
mix
from a non-main thread might also be justification on its own for keepingmix
marked unsafe, but that's a separate issue.The text was updated successfully, but these errors were encountered: