-
Notifications
You must be signed in to change notification settings - Fork 472
Add better support for loading SDL2 mixer sounds and music from memory #1483
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
base: master
Are you sure you want to change the base?
Conversation
|
Okay, I can already pretty much say that the |
|
I added different variants for different scenarios when
|
|
Leave the sdl2 and sdl2-sys version as-is (0.37), they will be bumped just before a release on crates.io.
|
|
Good point. |
|
Looks good, but now there is a breaking change because Could you re-add #[deprecated(since="0.38.0", note="use `from_bytes` instead")]
pub fn from_static_bytes(buf: &'static [u8]) -> Result<Music<'static>, String> {
Self::from_bytes(buf)
}And finally, can you add a small line to the changelog? Technically it doesn't break anything, but you did implement a more generic |
|
I don't think it is possible unless you implement some hard to use function callback, or have the function block for the duration of the music. An example to show it is unsound would look something like this: let bytes = vec![0,1,2,3]; // etc...
let mus = Music::from_bytes(&bytes);
mus.play(-1);
core::mem::forget(mus); // do not run drop
core::mem::forget(bytes);
// sleep for some time like 1 second to give it some time to read from the already dropped Vec |
I think you're right on the unsoundness, but that happens for all |
|
The newly added examples use include_bytes!() which just gets bytes from a file at compile time that becomes &'static [u8] at runtime (embedded in binary). This does not need from_bytes and works fine with from_static_bytes. The need for from_bytes would be for things like receiving audio files from the network or some other way where they cannot be embedded in the binary or read from a file. |
|
This goes beyond just this PR, this whole API is unsound. Any I haven't tried, but I'm 90% sure this is a use-after-free: let buffer: Vec<u8> = load_some_music_bytes(); // some music here as compressed mp3 for example
let music: Music = Music::from_bytes(&buffer);
music.play(-1);
drop(music);
drop(buffer);
// use after freeThe reason is that the buffer is read from SDL2 but not copied like Chunks are. So if the buffer drops, SDL_Mixer is reading free'd data. Since there is nothing tying a music playback to a lifetime, we can drop both the music and the buffer while it is still being used. We can do this with anything that generates a |
There is an exception, a function like |
|
But your solution, even if it works, is really hacky and unergonomic for Rust. A real solution would be to have a Either way, the current Mixer's Music API does not make sense and needs an overhaul. |
|
If I understand this correctly, if stopping is delegated to let mus1_playback = mus1.play(-1);
mus2.play(-1);
drop(mus1_playback);The The idea of trying to force the API to be safe without manual calls to freeing loaded music seems challenging. Would not this issue be solved temporarily for now if the API offered only |
It would need some logic to prevent stopping the second one when the first one gets dropped, but otherwise yes.
good idea, but don't even add |
|
I actually meant that
For this, I am not sure how that kind of logic would be possible unless you can internally ask SDL2 if it is already playing some identifiable music data since the two music instances do not know anything about each other internally. |
|
I am not sure I understand how to implement your changes. Does this look like what you want: |
|
Yes, almost. Lines 893 and 894 both create intermediate (immutable) references of which at least one (as far as I know) is unsound, it basically does |
|
Looks better? |
antonilol
left a comment
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.
Looks good to me, I did not check for cargo clippy warnings/errors though, reviewed on my phone.
antonilol
left a comment
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 don't understand the point of using Have you thoroughly tested it? |
Box uniquely owns a heap allocation, here there only ever is a Box before giving a pointer to SDL, or after Mix_FreeMusic. NonNull is just a non null pointer with no uniqueness guarantee.
Don't know if this question is directed at me, but I have done code review and the example runs and plays the sound correctly. |
|
Ok so I tested it and because
I don't understand your explanation and to me it's still exactly the same behavior as a box. First of all Then if we look at when a
|
Right, at those places the uniqueness guarantee for Box can be upheld.
Box contains a Unique<T> (note that this type is unstable so doc may not be entirely accurate), which has stronger guarantees than NonNull (but the same layout). Box has even stronger guarantees.
|
This is regarding the "rust" ownership. This is exactly our case: we own that piece of memory, SDL only borrows it from our point of view. SDL can't change its location or even modify it without us ordering it to. If it did, re-transforming it into a Box to destroy it afterwards would be completely unsound.
This is straight up false. A
Using unsafe when unnecessary is not an advantage. This is just extra steps of unsafe and the potential to have more safety bugs, just for the sake of it. So I stand by it, replace it by a Box and remove the unsafe at creation+deletion. |
Not uniquely if SDL holds a pointer too, moving the Box (into the newly created The unique guarantee that Box asserts is too strong in this case, so I suggested to use
Transforming back to a Box is fine as long as SDL does not read from pointers derived from that memory anymore, which does not happen,
|
|
You can clearly see that the underlying pointer does not move when moving a box from a variable to a struct: https://play.rust-lang.org/?version=stable&mode=release&edition=2024&gist=32866dca6fdba1b96a1429c3b90b7565 If the pointer referencing it did move, thousands of programs in the wild would break MIRI probably reports this as an error as the "worst case" scenario as in "this Box can still be changed by the user while a pointer still refers to it", but a pointer on the heap will NOT change because a Using Pin is indeed stupid because this doesn't bring anything in our case, but this would be kind of a marker to show that the |
|
MIRI probably complains because of this, from the docs of
The compiler may skip allocating memory if it decides there is no need for the value to ever exist on the heap. It can be quite clever about it, for example here it knows that the value is only ever observed after being moved to the stack. Leaving the value in the However, I can't find or come up with an example where the compiler makes such an assumption when passing a pointer to a C function (see the analogous case here). In general, if the docs say it must be unique otherwise we're in UB land, it's wise to do the semantically correct thing, as I've had a case where UB manifested only with LTO enabled, and it was not a fun time. Edit: fixed links |
Pointer addresses match, but clearly not the provenance (for more info see https://doc.rust-lang.org/std/ptr/index.html#provenance), as miri is able to flag access through one of the pointers as invalid, miri keeps track of both address and provenance. |
This is the whole problem with UB, even if the program works now with the UB, it might break with different compiler flags, on a different platform, later in a different compiler version, etc. |
Adds support for loading music and sound files directly from memory regardless of their file format.
I removed the static lifetime restriction from
Musicand transformed it to similar function asChunk's counterpart. This should be fine?Fixes: #1482
How should the crate versions be updated, bump both
sdl2andsdl2-systo0.38.0?