Add fd callback registration for vst3 for linux#9
Conversation
| // under plugin state. | ||
| // In old code, it lived right under view (essentially same as PlugView) | ||
| // so this is kinda the same. | ||
| // Also - do we really want to be bringing back EventHandler? I'm guessing |
There was a problem hiding this comment.
was my biggest concern with adding EventHandler - I wasn't sure if its prior removal implied there was a better approach in mind that just hadn't been added yet.
There was a problem hiding this comment.
It wasn't removed for any explicit reason, it's just that more or less the entire VST3 backend was rewritten and it was never added back. I think it still makes the most sense to have an EventHandler struct like this.
Fundamentally what's necessary is for there to be some object implementing the IEventHandler and ITimerHandler interfaces, and beyond that we have a lot of freedom to split up or combine objects in the backend. For instance, it would be possible to have the PlugView implement those interfaces directly in addition to the IPlugView interface. It would even be possible to get rid of the separate PlugView object and have the main Component object implement all of the view-related interfaces as well.
There was a problem hiding this comment.
Hmm, I'm not sure how to do that (have PlugView implement the interfaces is straightforward, but registering the PlugView has been the tricky part...)
Today we register the handler inside of attached. Within that context, we don't seem to have the ComWrapper<PlugView> we would need in order to register the plugView ("self" only refers to the PlugView, not the ComWrapper<PlugView>). The only place that has the ComWrapper is the Component (via createView - and even in that case, it isn't seemingly saved anywhere within the framework).
Still trying to figure it out, probably missing some simple approach...
This compiles, (within .attach) but segfaults, and seems obviously wrong. Presented only to illustrate my issue:
if let Some(run_loop) = frame.cast::<IRunLoop>() {
let stupid_view = ComWrapper::new(PlugView::new(&self.main_thread_state));
run_loop.registerTimer(stupid_view.as_com_ref::<ITimerHandler>().unwrap().as_ptr(), 16);
if let Some(fd) =
(*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor()
{
run_loop.registerEventHandler(stupid_view.as_com_ref::<IEventHandler>().unwrap().as_ptr(), 16);
}
}And here's the actual interface implementations I added FYI:
impl<P: Plugin> IEventHandlerTrait for PlugView<P> {
unsafe fn onFDIsSet(&self, _fd: FileDescriptor) {
let state = unsafe { &mut *self.main_thread_state.get() };
state.view.as_mut().unwrap().poll();
}
}
There was a problem hiding this comment.
I may have found a way, but it requires saving a raw "self pointer" in PlugView. It seems like this might be a recurring issue with COM objects - not being able to access themselves as COM objects. Requiring the need for a self pointer. Unless I'm missing something (seems like it'd be much easier if all the VST3 hooks also provided the com ptr themselves...).
There was a problem hiding this comment.
Yeah, this is specifically a limitation of the way the bindings are set up in the vst3 crate. The traits all take &self, but there isn't a good way to go from a &self reference to an interface pointer like *mut IEventHandler. I've spent some time thinking about what it would take to make it possible in the vst3 bindings, but for now I think that saving a raw self pointer is probably the best way to go about it.
|
Not exactly clear on how the fd registration stuff works yet, but is it really necessary for MainEventLoop to act as the fd for the purposes of x11 event handlers? Could the fd just be any old Rc that coupler maintains instead and avoid this need to depend on the portlight MainEventLoop (indirectly)? Would also seemingly bypass this boilerplate if that's an option. |
|
FYI, I got this working for CLAP as well in another branch but I'll keep those changes separate for now to keep this PR from getting out of control. |
My eventual plan for this is to introduce an adapter with a generic helper implementation of the impl Plugin for MyPlugin {
// ...
type View = adapter::PluginView<MyView>;
fn view(&mut self, host: ViewHost, parent: &ParentWindow) -> Self::View {
adapter::PluginView::new(parent, MyView::new())
}
// ...
}I definitely don't intend on having every plugin manually implement all of the event loop boilerplate, but I think that can be solved with helpers like this rather than by explicitly tying coupler and portlight together. |
It is necessary to hook up the fd from portlight's X11 connection to the host's event loop in some way or other, because events from that connection need to be able to notify the host to call the plugin's |
|
Thanks for all the great comments, exactly what I was hoping to learn. I've been suddenly busier than usual so haven't gotten a chance to follow up on this but I'm still planning to once able! |
…gin crashes the host on close
|
oookay finally found some time to sit down and make some improvements.... I wanted to try your suggestion of implementing the callbacks directly on PlugView. I've removed EventHandler and done so. The big challenge I ran into was figuring out how to take the PlugView self reference in pub struct PlugView<P: Plugin> {
main_thread_state: Arc<UnsafeCell<MainThreadState<P>>>,
self_ptr: Cell<Option<*mut IPlugView>>,
}Then populating that in the new method (and refactoring new to also handle the various COM shenanigans instead of directly returning a new PlugView impl<P: Plugin> PlugView<P> {
pub fn new(main_thread_state: &Arc<UnsafeCell<MainThreadState<P>>>) -> ComPtr<IPlugView> {
let view = ComWrapper::new(PlugView {
main_thread_state: main_thread_state.clone(),
self_ptr: Cell::new(None),
});
let com_ptr = view.to_com_ptr::<IPlugView>().unwrap();
let raw_ptr = com_ptr.as_ptr();
view.self_ptr.set(Some(raw_ptr));
com_ptr
}
}I opted to call it Then this abomination is needed to actually safely turn that raw ptr into the needed interface. I was only able to figure it out thanks to the other usage of .queryInterface I saw in this repo. macro_rules! query_interface {
($source_ptr:expr, $interface_type:ty) => {{
let mut result_obj: *mut c_void = std::ptr::null_mut();
let unknown_ptr = $source_ptr as *mut FUnknown;
let iid = <$interface_type>::IID;
let result = unsafe {
((*(*unknown_ptr).vtbl).queryInterface)(
unknown_ptr,
iid.as_ptr() as *const TUID,
&mut result_obj,
)
};
if result == kResultOk && !result_obj.is_null() {
Some(result_obj as *mut $interface_type)
} else {
None
}
}};
}
// usage
if let Some(timer_handler_ptr) = query_interface!(ptr, ITimerHandler) {
run_loop.registerTimer(timer_handler_ptr, 16);
...WDYT of this approach? I cleaned up the other todos and stuff, let me know what else should be changed to hopefully make this merge-worthy! |
There are a couple of ways that you can use the machinery in the |
… of manually calling queryInterface
Ah, okay. I should have investigated the vst3 crate more. I was expecting it to be a little tricky to work with the vst3 api but I suppose that weird macro was beyond what I should've expected. I've refactored it to use ComRef and Anything else I should fix? |
| } | ||
| } | ||
|
|
||
| impl<P: Plugin> Class for PlugView<P> { |
There was a problem hiding this comment.
Moving this impl Class into the linux module will cause the build to fail on other platforms since it is now conditional on #[cfg(target_os = "linux")]. You could fix this by instead changing the outer impl Class to:
impl<P: Plugin> Class for PlugView<P> {
#[cfg(not(target_os = "linux"))]
type Interfaces = (IPlugView,);
#[cfg(target_os = "linux")]
type Interfaces = (IEventHandler, ITimerHandler, IPlugView);
}However, to be honest, after seeing both implementations, I think it would be cleaner to just define a separate EventHandler struct holding an Arc<UnsafeCell<MainThreadState<P>>>. I'm sorry to ask you to make that change after you put in the time to get this version working!
| // confirm an event handler actually was registered | ||
| // before trying to unregister (we only register if we can get a | ||
| // file descriptor) | ||
| if (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor().is_some() { |
There was a problem hiding this comment.
It's a relatively minor thing, but I think I would rather store a flag (or maybe an Option<*mut IEventHandler*>) for whether we've registered an event handler and use that to determine whether to unregister it, rather than calling file_descriptor again. My reasoning is that a misbehaving Plugin impl could in principle return different things from subsequent calls to file_descriptor, so the return value of file_descriptor is not necessarily a reliable indicator as to whether we registered one when initializing the view.
89516a4 to
7271934
Compare
Relates to #7
Adds to #8
This is my attempt to make the gain example work on Linux for VST3 only. I AM able to get it running and see the GUI with this.
However, as this is my first PR for this repo, and in fact my first ever Rust PR, I highly expect this to have problems! I'm fine with closing it if it's completely off base, but would definitely appreciate any feedback to help me improve. As a mitigating factor, this is only a PR for another PR branch, not for the main branch. I absolutely do not want to compromise the principals of this project!
My approach was to look at the old code where the fd-based callback registration was in place and working and basically get it wired up within the new structure while making my best guess to try to match the organizing principles of the new code.
Please see the TODOs which call out places where I was especially unsure if it was the right approach. I do not want to litter the codebase with todos, these are only for the purposes of review and helping me remember what areas I was uncertain about. They will be removed.
Changes:
EDIT:
One big question I have about this approach overall is that it seems to push boilerplate into the plugin, as it now has to always store event_loop, and always implement file_descriptor() and poll(). Shouldn't those be things that it's possible for the plugin to avoid having to configure? It seems like it's a way to avoid having coupler depend on portlight though, which certainly has its advantages.
I wonder though, is there a way to avoid the need for that boilerplate (small though it may be) while still keeping portlight and coupler themselves decoupled?