Cycling74/median

Trampoline support for assist and inletinfo

Opened this issue · 3 comments

amiel commented

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
         }
x37v commented

@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.

amiel commented

@x37v that would be amazing! Thanks!

x37v commented

checkout #13