-
-
Notifications
You must be signed in to change notification settings - Fork 24
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
Higher-level API #3
Comments
Sorry for the long delay in responding. It does indeed expose a lot of detail. I am not currently very familiar with the Patches are of course welcome, if anyone wanted to submit a proposed implementation. fyi, long term I intend to enquire whether the PA project itself would like to take over ownership and maintenance of these PA related crates of mine. Even longer term, let's hope PA itself fully converts to Rust, which would certainly allow use of |
I have just published version 2.0. The biggest change is support for closures as callbacks across most of the API. I have looked into Futures stuff a little; but not yet enough to determine definitely whether it is suitable for use here. My biggest concern currently is how execution of callbacks would work with PA's event loop ("mainloop") construct. I'll put more work into looking at this shortly, now that the callback closure stuff is out of the way (mostly, the mainloop API is the primary bit left undone). |
Hi. I would like to add some info to this issue. I think the "closures as callbacks" does not fit when running the main loop using the callback style. What is "running the main loop using the callback style"?Here is how pacat.c run the main loop: /* Code omitted... */
/* This is called whenever the context status changes */
static void context_state_callback(pa_context *c, void *userdata) {
pa_assert(c);
switch (pa_context_get_state(c)) {
/* Take action based on context state... */
}
/* Code omitted... */
}
/* Code omitted... */
int main(int argc, char *argv[]) {
pa_mainloop* m = NULL;
/* Code omitted... */
/* Set up a new main loop */
if (!(m = pa_mainloop_new())) {
pa_log(_("pa_mainloop_new() failed."));
goto quit;
}
/* Code omitted... */
/* Create a new connection context */
if (!(context = pa_context_new_with_proplist(mainloop_api, NULL, proplist))) {
/* Log error and quit... */
}
pa_context_set_state_callback(context, context_state_callback, NULL);
/* Connect the context */
if (pa_context_connect(context, server, 0, NULL) < 0) {
/* Log error and quit... */
}
/* Run the main loop */
if (pa_mainloop_run(m, &ret) < 0) {
/* Log error and quit... */
}
/* Code omitted... */
} Basically it performs major tasks in callbacks. It then registers those callbacks, and lets the main loop run by itself. What is the problem?I tried to follow the pattern above using use libpulse_binding::{
context::{self, Context},
mainloop::standard::Mainloop,
proplist::Proplist,
};
use std::{cell::RefCell, rc::Rc};
fn main() {
// Set up a new main loop
let mut mainloop = Mainloop::new().unwrap();
// Create an empty proplist
let proplist = Proplist::new().unwrap();
// Create a new connection context
let context = Rc::new(RefCell::new(
Context::new_with_proplist(&mainloop, "Try Pulse", &proplist).unwrap(),
));
context
.borrow_mut()
.set_state_callback(Some(context_state_callback(Rc::clone(&context))));
// Connect the context
context
.borrow_mut()
.connect(None, context::flags::NOFLAGS, None)
.unwrap();
// Run the main loop
mainloop.run().unwrap();
}
/* Return a closure that will be called whenever the context status changes */
fn context_state_callback(context: Rc<RefCell<Context>>) -> Box<dyn FnMut()> {
Box::new(move || {
match context.borrow().get_state() {
// Take action based on context state...
_ => (),
}
})
} When running it with backtrace enabled, I got
The key stacks here are number 16 and 27. The problem is that we have already borrowed I think this problem cannot be solved using only safe rust. Currently, to workaround it, one way is avoiding callback, like the example shown in the |
@dianhenglau, yes, I'm aware that there are some limitations with the design, forcing use of small bits of unsafe user code, and simple user mistakes not being caught at compile time. There is no easy solution.
I'm not sure what you're asking here - why you've quoted what appears to be a question...Or if you're asking a question at all... For the standard mainloop, the mainloop and callbacks are all executed within the user thread. For the threaded mainloop, they are executed in a separate thread dedicated to the mainloop.
Yes....
Yep. You've got to hack around the borrow checker in this situation. The only way to do things without any unsafe user code would be to create wrappers such as alternatives to Example pointer based solution, if it helps: use libpulse_binding::{
context::{self, Context},
mainloop::standard::Mainloop,
proplist::Proplist,
};
use std::{cell::RefCell, rc::Rc};
fn main() {
// Set up a new main loop
let mut mainloop = Mainloop::new().unwrap();
// Create an empty proplist
let proplist = Proplist::new().unwrap();
// Create a new connection context
let context = Rc::new(RefCell::new(
Context::new_with_proplist(&mainloop, "Try Pulse", &proplist).unwrap(),
));
context
.borrow_mut()
.set_state_callback(Some(context_state_callback(Rc::clone(&context))));
// Connect the context
context
.borrow_mut()
.connect(None, context::flags::NOFLAGS, None)
.unwrap();
// Run the main loop
mainloop.run().unwrap();
}
/* Return a closure that will be called whenever the context status changes */
fn context_state_callback(context: Rc<RefCell<Context>>) -> Box<dyn FnMut()> {
Box::new(move || {
let state = unsafe { &*context.as_ptr() }.get_state();
match state {
// Take action based on context state...
_ => (),
}
})
}
It's been a while since I looked at this, so I can't comment off hand exactly how much better off we'd be with futures in terms of avoiding the problems, but getting futures integrated remains a key goal. Unfortunately it is very much non-trivial to wire up to the PulseAudio API correctly, hence why I've not gotten it done thus far. |
I am currently looking into potentially writing an async/await wrapper for libpulse-binding. After a small amount of initial experimentation, doing so seems to be viable. Certain parts are easier than others to wrap, with e.g. simply waiting for the result of a callback being more or less trivial, and making the mainloop's poll non-blocking being somewhat complex |
Also, for a non-async high level API, there is rust-pulsectl, though the maintenance state of that crate is shaky at best. |
I've pushed some of my work to my repository at https://github.com/agraven/libpulse-binding-async |
I was already considering once to write a pulseaudio binding, but more high-level. I still feel this binding exposes a lot of details of dealing with the threading model of pulseaudio. I was wondering if there would be a way to utilize futures, but I am not particularly proficient with those yet.
Especially the callback stuff still requires one to pass C pointers around: https://docs.rs/libpulse-binding/1.0.3/libpulse_binding/context/type.ContextNotifyCb.html
The text was updated successfully, but these errors were encountered: