Skip to content
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

Stream callback defined as 'static #151

Open
chris-zen opened this issue Jan 9, 2017 · 5 comments
Open

Stream callback defined as 'static #151

chris-zen opened this issue Jan 9, 2017 · 5 comments

Comments

@chris-zen
Copy link

I was wondering if there is an important reason for the callback to be defined as 'static at:

https://github.com/RustAudio/rust-portaudio/blob/master/src/stream.rs#L1294

This implies that I can not wrap the logic for audio handling in an struct and use self within the callback (even if Send is implemented for it). It is not possible neither to pass any kind of reference to an struct that doesn't have static lifetime, which forces to create the audio handling struct just near the callback definition inside the same function.

For example, the following code is wrong:

fn run<'a>(handler: &'a mut Handler) -> Result<(), portaudio::error::Error> {
    // ...
    let callback = move |portaudio::OutputStreamCallbackArgs { buffer, .. }| {
        for sample in buffer.chunks_mut(CHANNELS as usize) {
            let (s0, s1) = handler.process();
            sample[0] = s0 as f32;
            sample[1] = s1 as f32;
        }

        portaudio::Continue
    };

    let mut stream = try!(pa_ctx.open_non_blocking_stream(settings, callback));
    try!(stream.start());

    // ...
}

And fails with:

error[E0477]: the type `[closure@src/main.rs:46:20: 55:6 handler:&'a mut Handler]` does not fulfill the required lifetime
  --> src/main.rs:58:34
   |
58 |     let mut stream = try!(pa_ctx.open_non_blocking_stream(settings, callback));
   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: type must outlive the static lifetime

The previous code only compiles when the Handler is instantiated inside run.

@mitchmindtree
Copy link
Member

I believe it is necessary for the compiler in order to allow the callback to be moved into the inner closure which gets run on a separate thread by portaudio. Currently, there's no other way for rustc to know that the contents of the callback live long enough, "long enough" being whatever the lifetime of the portaudio stream's audio thread may be.

We may be able to improve this by assuming that a Stream's audio thread never lives longer than the Stream itself and enforcing that any data or references contained within the callback must live at least as long as the Stream. This would require unsafe, but should be OK as long as we can provide a way of guaranteeing that the portaudio stream's audio thread never lives longer than a Stream, perhaps by adding a lifetime to Stream and bounding the callback by this lifetime.

I'm afraid I don't have the time to look into this further myself. In the meantime, you should be able to get around this by wrapping your Handler in a std::sync::Arc<> instead of trying to use it in the callback via &'a mut. You would likely need something similar to this whether or not the 'static bound exists, as it looks like your Handler is accessed via both the main thread and the portaudio Stream's callback thread?

Sorry I don't have time to dig further, I hope this helps at least!

@chris-zen
Copy link
Author

chris-zen commented Jan 10, 2017

@mitchmindtree thank you very much for your elaborated response, it has been very helpful.

It makes sense to me that the callback lifetime is limited by the lifetime of the Stream as far as the Drop implementation for it takes care of stopping and releasing the corresponding portaudio stream state. This would allow to implement the callback in an struct I guess. If I have a chance I will explore it.

You were right, and by using Arc<Mutex<Handler>> I was able to call the mutable process() method on the Handler, so at least I can put all the processing logic under an struct (even if the callback has been defined outside it).

@crsaracco
Copy link
Member

crsaracco commented Sep 18, 2017

@chris-zen:

Not sure if you're still having troubles since this issue was opened a while ago, but I recently got this working with Trait types; see this repo at cc5c7e11dd. Of course the example is kind of weird since I'm effectively creating silence by adding a sine with its negative, but you get the idea.

I'm also messing around with a producer/consumer model and passing a chan::Receiver into run(), and it seems like it might be working out decently.

@quatrezoneilles
Copy link

There is a simple trick that will give an audio stream a non-static lifetime, which I got from @Timmmm.
You add a lifetime parameter to the struct that wraps the stream, and you enforce that lifetime (and placate the compiler) by adding a ph: PhantomData<&'a ()> field to to the struct. The rest is pretty much mechanical. I've done it for coreaudio-rs since it seemed to be a minimal amount of work and I'm submitting a PR.

@Timmmm
Copy link

Timmmm commented Sep 19, 2017

Note that I have temporarily abandoned soundio-rs because I couldn't figure out all the borrowing and lifetime stuff. The PhantomData is useful but I didn't realise it doesn't solve the 'non-borrowing lifetime' problem as also suffered by the SDL guys. More info here and here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants