-
Notifications
You must be signed in to change notification settings - Fork 476
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 free The 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. |
@@ -799,6 +811,7 @@ extern "C" fn c_music_finished_hook() { | |||
pub struct Music<'a> { | |||
pub raw: *mut mixer::Mix_Music, | |||
pub owned: bool, | |||
pub owned_data: Option<Box<[u8]>>, |
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 should not be pub (other fields too but unrelated to this PR, if you want you can make them private too), also I am unsure if you can keep the Box here without invalidating other pointers to its data when Music is moved in memory, I would prefer Option<NonNull<[u8]>>
here (that needs manual dealloc using Box::from_raw).
pub fn from_static_bytes(buf: &'static [u8]) -> Result<Music<'static>, String> { | ||
let rw = | ||
unsafe { sys::SDL_RWFromConstMem(buf.as_ptr() as *const c_void, buf.len() as c_int) }; | ||
if rw.is_null() { | ||
return Err(get_error()); | ||
} | ||
let raw = unsafe { mixer::Mix_LoadMUS_RW(rw, 0) }; | ||
if raw.is_null() { | ||
Err(get_error()) | ||
} else { | ||
Ok(Music { | ||
raw, | ||
owned: true, | ||
owned_data: None, | ||
_marker: PhantomData, | ||
}) | ||
} | ||
} |
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 very similar to from_owned_bytes
, can you maybe factor out a function that does the unsafe stuff?
I am not sure I understand how to implement your changes. Does this look like what you want: |
Adds support for loading music and sound files directly from memory regardless of their file format.
I removed the static lifetime restriction from
Music
and transformed it to similar function asChunk
's counterpart. This should be fine?Fixes: #1482
How should the crate versions be updated, bump both
sdl2
andsdl2-sys
to0.38.0
?