Thanks Christos, that is what I'm leaning to as well right now. I did some more work yesterday, and figured out that it is not hard for the Max object to check the thread it's running in and do a promotion or deferral, so I'm almost at the point where I can allow the user to mark and instance as always-high or always-low. That seems like the "safe" way, and then I may also be able to let the user run with scissors if they want to allow ops in any thread and manage their shared memory manually by wrapping Max's critical_region functions (cross-platform mutexes as far as I can tell).
I'd love to hear any other stories from the trenches on how people have handled this though! On Fri, Sep 25, 2020 at 3:52 AM Christos Vagias <[email protected]> wrote: > Hi Iain, > > Interesting problem and I'd be interested to see any input from others. > I had the same problem in my VST3 scenario: the gui and the dsp run on > separate threads! > > How I tackled this was with 2 separate s7 instances and they exchange > messages. > The messages are stored in the c++ application instance in an > std::queue<std::string> > > The dsp instance has the following c function bound to emit messages > > // the emit accepts a list with symbols,numbers etc. > // the car could be your message type and then the data would follow > s7_pointer s7vst::dsp_emit(s7_scheme* sc, s7_pointer args) { > // the s7 instance holds in *app* the pointer of the relevant > application object > s7vst* that = (s7vst*) s7_c_pointer(s7_name_to_value(sc, "*app*")); > > s7_pointer msg = s7_car(args); > std::string msg_str = s7_format(sc, s7_list(sc, 3, > s7_f(sc), > s7_make_string(sc, "~A\n"), > msg > )); > std::unique_lock lock(that->dsp_msg_mutex); > that->dsp_messages.push(msg_str); > return s7_nil(sc); > } > > > And then, the gui instance has a "recv" function and acts upon any > received messages > > s7_pointer s7vst::gui_recv(s7_scheme* sc, s7_pointer args) { > s7vst* that = (s7vst*) s7_c_pointer(s7_name_to_value(sc, "*app*")); > while (!that->dsp_msg_mutex.try_lock()) { > cerr << "Gui recv: could not lock!"; > std::this_thread::sleep_for(std::chrono::milliseconds(10)); > } > > if (that->dsp_messages.empty()) { > that->dsp_msg_mutex.unlock(); > return s7_nil(sc); > } > s7_pointer msg_vector = s7_make_vector(sc, that->dsp_messages.size()); > size_t pos = 0; > while (!that->dsp_messages.empty()) { > std::string msg = that->dsp_messages.front(); > that->dsp_messages.pop(); > s7_pointer port = s7_open_input_string(sc, msg.c_str()); > s7_pointer obj = s7_read(sc, port); > s7_close_input_port(sc, port); > s7_vector_set(sc, msg_vector, pos, obj); > pos++; > } > > that->dsp_msg_mutex.unlock(); > > return msg_vector; > } > > At least that's a quick & dirty way that I got it working. > Sorry for the long snippets. > > Hope it's of help > > > On Fri, 25 Sep 2020 at 03:41, Iain Duncan <[email protected]> > wrote: > >> Another option I'm thinking of is giving the users a way to expressly >> send messages to one or the other thread within the object (ie by >> requesting promote or defer) and then letting them protect the data >> sensibly themselves. I'm not sure how this is normally done in Scheme >> though. >> >> iain >> >> On Thu, Sep 24, 2020 at 6:34 PM Iain Duncan <[email protected]> >> wrote: >> >>> Hi folks, I'm hoping someone can help me out with a question around S7. >>> >>> In Max/MSP, when setup for live use there are (generally) two threads of >>> operation, with the high-priority/dsp thread able to interrupt the low/GUI. >>> If one doesn't do anything special, this means a max external (such as my >>> Scheme-for-Max) could be receiving messages in both: low for things >>> originating from a GUI action, high from metronomes or midi input. I >>> assume that I should not expect all to be ok if I have one instance of an >>> s7 interpreter, which could be accessed from either thread, and it could >>> get interrupted part way through an eval operation to run another eval in >>> another thread that may access the same data. IE there's no magical thread >>> protection baked into S7 that I don't know about.... >>> >>> I'm wrestling with how to deal with this correctly. One option is to >>> allow users to designate an s4m instance as always-high or always-low, >>> basically saying if you need to mix low and high priority you should treat >>> it like an actor model and have two interpreters that message each other >>> and share data through some non-scheme shared data structure (like max >>> buffers or tables). This might be ok because there is a way for me to >>> insure incoming max messages from any thread are either promoted or >>> demoted. >>> >>> I suppose another option is to get into critical sections, but I can't >>> see how that make sense if we don't want low priority actions to have the >>> chance of locking out high ones. >>> >>> Strangely, I have not had any issues yet. But I presume that just means >>> I've been lucky. Cycling 74 does not (anymore) allow the javascript object >>> to work in both threads, and I'm thinking it must have been around thread >>> stability issues. >>> >>> Any thoughts most welcome! >>> iain >>> >> _______________________________________________ >> Cmdist mailing list >> [email protected] >> https://cm-mail.stanford.edu/mailman/listinfo/cmdist >> >
_______________________________________________ Cmdist mailing list [email protected] https://cm-mail.stanford.edu/mailman/listinfo/cmdist
