Trampoline support for assist and inletinfo
Opened this issue · 3 comments
Hi, I'd like to start out by saying how much I appreciate this project. It's great to be able to build Max externals in rust.
I spent a while figuring out how to implement the assist
and inletinfo
callbacks. I got it working in my project, but I'm not proficient enough at rust macros or even rust in general to add support in a pull request, at least not yet.
I thought I'd write out here what I've figured out in case someone else has the bandwidth and the knowhow to set up trampoline macros for these.
Here's what I have that is working for me:
type AssistMethod<T> = unsafe extern "C" fn(
&mut median::wrapper::Wrapper<
median::max_sys::object,
median::wrapper::MaxWrapperInternal<T>,
T,
>,
*mut ::std::ffi::c_void,
::std::os::raw::c_long,
::std::os::raw::c_long,
*mut ::std::ffi::c_char,
);
type InletInfoMethod<T> = unsafe extern "C" fn(
&mut median::wrapper::Wrapper<
median::max_sys::object,
median::wrapper::MaxWrapperInternal<T>,
T,
>,
*mut ::std::ffi::c_void,
::std::os::raw::c_long,
*mut ::std::ffi::c_char,
);
// in impl MaxObjWrapped<MaxExtern> for MaxExtern {
fn class_setup(c: &mut Class<MaxObjWrapper<Self>>) {
unsafe {
let inletinfos = ::std::ffi::CString::new("inletinfo").unwrap();
median::max_sys::class_addmethod(
c.inner(),
// I call this `itramp` to disambiguate between a real `tramp`
Some(std::mem::transmute::<InletInfoMethod<MaxExtern>, median::method::MaxMethod>(Self::inletinfo_itramp)),
inletinfos.as_ptr(),
median::max_sys::e_max_atomtypes::A_CANT,
0,
);
let assists = ::std::ffi::CString::new("assist").unwrap();
median::max_sys::class_addmethod(
c.inner(),
Some(std::mem::transmute::<AssistMethod<MaxExtern>, median::method::MaxMethod>(Self::assist_itramp)),
assists.as_ptr(),
median::max_sys::e_max_atomtypes::A_CANT,
0,
);
}
// in impl MaxExtern {
pub extern "C" fn assist_itramp(wrapper: &mut median::wrapper::Wrapper< median::max_sys::object, median::wrapper::MaxWrapperInternal<MaxExtern>, MaxExtern>, _b: *mut ::std::ffi::c_void, io: ::std::os::raw::c_long, index: ::std::os::raw::c_long, t: *mut ::std::ffi::c_char) {
let s = wrapper.wrapped_mut();
let s = wrapper.wrapped_mut();
let inlet_0 = "Messages in or (bang) encode attributes and send bytes to first outlet";
let description = match io {
// Inlets
1 => match s.mode {
Mode::Note => match index {
0 => inlet_0,
1 => "(number) set frequency",
2 => "(number) set amplitude",
3 => "(number) set waveform",
_ => "unknown inlet",
},
// etc...
},
// Outlets
2 => {
match s.mode {
Mode::Parse => "(inactive)",
_ => match index {
0 => "(number) raw bytes of encoded attributes",
1 => "(bang) when all bytes from a message have been sent",
_ => "unknown outlet"
},
}
},
_ => "unknown",
};
if let Ok(c_description) = std::ffi::CString::new(description) {
unsafe {
// see https://cycling74.com/sdk/max-sdk-8.0.3/chapter_enhancements.html#chapter_enhancements_assistance
median::max_sys::strncpy_zero(t, c_description.as_ptr(), 512);
}
}
}
pub extern "C" fn inletinfo_itramp(_wrapper: &mut median::wrapper::Wrapper<median::max_sys::object, median::wrapper::MaxWrapperInternal<MaxExtern>, MaxExtern>, _b: *mut ::std::ffi::c_void, index: ::std::os::raw::c_long, t: *mut ::std::ffi::c_char) {
// Only the first outlet is "hot". All others simply set attributes.
if index > 0 {
unsafe {
*t = 1;
}
}
}
Note that there's a ton of unsafe code that would be nice to abstract away in to the trampoline method.
I think it would be nice if these could be written like this:
#[assist]
pub fn assist(&self, io: usize, index: usize) -> Option<&std::ffi::CStr> {
let description = ...;
Some(std::ffi::CString::new(description))
}
#[inletinfo]
pub fn inletinfo(&self, index: usize) -> bool {
// Only the first outlet is "hot". All others simply set attributes.
// return true for "hot" outlets.
index == 0
}
@amiel interestingly enough, I have a branch that already exposes assist
and I was thinking about doing inletinfo
as well.. I talked a bit with @isabelgk and we were thinking that it might make sense to update the API to allow for specifying a name when you create an inlet or outlet and just let the wrapper code supply that when assist
is called.