diff --git a/include/FluidSCWrapper.hpp b/include/FluidSCWrapper.hpp index f0de16b..8c5b09b 100644 --- a/include/FluidSCWrapper.hpp +++ b/include/FluidSCWrapper.hpp @@ -8,11 +8,15 @@ #include +#include +#include #include #include #include #include +#include + namespace fluid { namespace client { @@ -114,9 +118,9 @@ public: mOutputConnections.emplace_back(true); mOutputs.emplace_back(nullptr, 0, 0); } - + for (int i = 0; i < static_cast(mClient.controlChannelsOut()); ++i) { mOutputs.emplace_back(nullptr, 0, 0); } - + mCalcFunc = make_calc_function(); Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1); } @@ -161,6 +165,7 @@ template class NonRealTime: public SCUnit { using ParamSetType = typename Client::ParamSetType; + public: static void setup(InterfaceTable *ft, const char *name) @@ -184,26 +189,35 @@ public: mClient.setSynchronous(false); mFifoMsg.Set(mWorld, initNRTJob, nullptr, this); mWorld->ft->fSendMsgFromRT(mWorld,mFifoMsg); + + //we want to poll thread roughly every 20ms + checkThreadInterval = static_cast(0.02 / controlDur()); set_calc_function(); }; /// The calc function. Checks to see if we've cancelled, spits out progress, launches tidy up when complete void poll(int) { - if(!mClient.done()) - { +// if(!mClient.done()) +// { out0(0) = static_cast(mClient.progress()); - } - else { +// } +// else { + if(0 == pollCounter++) + { + mWorld->ft->fDoAsynchronousCommand(mWorld, nullptr, Wrapper::getName(), this, + postProcess, exchangeBuffers, tidyUp, destroy, + 0, nullptr); + } + + pollCounter %= checkThreadInterval; + // if(mClient.state() == kDone) // mDone = true; - mCalcFunc = mWorld->ft->fClearUnitOutputs; +// mCalcFunc = mWorld->ft->fClearUnitOutputs; // if(!mDone) - mWorld->ft->fDoAsynchronousCommand(mWorld, nullptr, Wrapper::getName(), this, - postProcess, exchangeBuffers, tidyUp, destroy, - 0, nullptr); - - } + +// } } /// To be called on NRT thread. Validate parameters and commence processing in new thread @@ -228,22 +242,25 @@ public: { auto w = static_cast(data); Result r; - w->mClient.checkProgress(r); - if(r.status() == Result::Status::kCancelled) - { - std::cout << Wrapper::getName() << ": Processing cancelled \n"; - return false; - } + ProcessState s = w->mClient.checkProgress(r); - if(!r.ok()) + if(s==ProcessState::kDone || s==ProcessState::kDoneStillProcessing) { - std::cout << "ERROR: " << Wrapper::getName() << ": " << r.message().c_str() << '\n'; - return false; + if(r.status() == Result::Status::kCancelled) + { + std::cout << Wrapper::getName() << ": Processing cancelled \n"; + return false; + } + + if(!r.ok()) + { + std::cout << "ERROR: " << Wrapper::getName() << ": " << r.message().c_str() << '\n'; + return false; + } + + return true; } - -// w->mDone = true; - - return true; + return false; } /// swap NRT buffers back to RT-land @@ -252,13 +269,13 @@ public: static bool tidyUp(World *world, void *data) { return static_cast(data)->tidyUp(world); } /// Now we're actually properly done, call the UGen's done action (possibly destroying this instance) - static void destroy(World *world, void *data) + static void destroy(World*, void*) { - auto w = static_cast(data); +// auto w = static_cast(data); // if(w->mDone) // { - int doneAction = static_cast(w->in0(static_cast(w->mNumInputs - 1))); - world->ft->fDoneAction(doneAction,w); +// int doneAction = static_cast(w->in0(static_cast(w->mNumInputs - 1))); +// world->ft->fDoneAction(doneAction,w); // } } @@ -315,6 +332,8 @@ private: char * mCompletionMessage = nullptr; void * mReplyAddr = nullptr; const char *mName = nullptr; + size_t checkThreadInterval; + size_t pollCounter{0}; protected: ParamSetType mParams; Client mClient; @@ -367,24 +386,31 @@ using FluidSCWrapperBase = FluidSCWrapperImpl, is template class FluidSCWrapper : public impl::FluidSCWrapperBase { + using FloatControlsIter = impl::FloatControlsIter; template struct ParamReader { - + + static auto fromArgs(World *, sc_msg_iter* args, std::string, int) + { + return std::string(args->gets("")); + } + + static auto fromArgs(World *, FloatControlsIter& args, LongT::type, int) { return args.next(); } static auto fromArgs(World *, FloatControlsIter& args, FloatT::type, int) { return args.next(); } static auto fromArgs(World *, sc_msg_iter* args, LongT::type, int defVal) { return args->geti(defVal); } static auto fromArgs(World *, sc_msg_iter* args, FloatT::type, int) { return args->getf(); } - static auto fromArgs(World *w, ArgType args, BufferT::type, int) + static auto fromArgs(World *w, ArgType args, BufferT::type&, int) { typename LongT::type bufnum = static_cast(fromArgs(w, args, LongT::type(), -1)); return BufferT::type(bufnum >= 0 ? new SCBufferAdaptor(bufnum, w) : nullptr); } - static auto fromArgs(World *w, ArgType args, InputBufferT::type, int) + static auto fromArgs(World *w, ArgType args, InputBufferT::type&, int) { typename LongT::type bufnum = static_cast(fromArgs(w, args, LongT::type(), -1)); return InputBufferT::type(bufnum >= 0 ? new SCBufferAdaptor(bufnum, w) : nullptr); @@ -417,35 +443,155 @@ class FluidSCWrapper : public impl::FluidSCWrapperBase template using ControlSetter = Setter; + //CryingEmoji.png: SC API hides all the useful functions for sending + //replies back to the language with things like, uh, strings and stuff. + //We have Node_SendReply, which assumes you are sending an array of float, + //and must be called only from the RT thread. Thanks. + //So, we do in reverse what the SendReply Ugen does, and parse + //an array of floats as characters in the language. VomitEmoji.png + + struct ToFloatArray + { + static size_t allocSize(SCBufferAdaptor*){ return 1; } + static size_t allocSize(double){ return 1; } + static size_t allocSize(float){ return 1; } + static size_t allocSize(intptr_t){ return 1; } + static size_t allocSize(std::string s){ return s.size(); } + static size_t allocSize(FluidTensor s) + { + size_t count = 0; + for(auto& str: s) count += (str.size() + 1); + return count; + } + template + static size_t allocSize(FluidTensor s) { return s.size() ; } + + + static void convert(float* f, SCBufferAdaptor* buf) { f[0] = buf->bufnum(); } + static void convert(float* f, double d) { f[0] = static_cast(d); } + static void convert(float* f, intptr_t i) { f[0] = i; } + static void convert(float* f, std::string s) { std::copy(s.begin(), s.end(), f); } + static void convert(float* f, FluidTensor s) + { + for(auto& str: s) + { + std::copy(str.begin(), str.end(), f); + f += str.size(); + *f++ = 0; + } + } + template + static void convert(float* f, FluidTensor s) + { + static_assert(std::is_convertible::value,"Can't convert this to float output"); + std::copy(s.begin(), s.end(), f); + } + }; + + + //So, to handle a message to a plugin we will need to + // (1) Launch the invovation of the message on the SC NRT Queue using FIFO Message + // (2) Run the actual function (maybe asynchronously, in our own thread) + // (3) Launch an asynchronous command to send the reply back (in Stage 3) + + template + struct MessageDispatch + { + static constexpr size_t Message = N; + FluidSCWrapper* wrapper; + ArgTuple args; + Ret result; + std::string name; + }; + //Sets up a single /u_cmd template struct SetupMessage { void operator()(const T& message) { -// class_addmethod(getClass(), (method)invokeMessage, message.name,A_GIMME, 0); auto ft = getInterfaceTable(); - ft->fDefineUnitCmd(message.name, invokeMessage); + ft->fDefineUnitCmd(getName(), message.name, launchMessage); } }; template - static void invokeMessage(FluidSCWrapper* x,sc_msg_iter* args) + static void launchMessage(Unit* u,sc_msg_iter* args) { + FluidSCWrapper* x = static_cast(u); using IndexList = typename Client::MessageSetType::template MessageDescriptorAt::IndexList; - invokeMessageImpl(x,s,ac,av,IndexList()); + launchMessageImpl(x,args,IndexList()); } template - static void invokeMessageImp(FluidSCWrapper* x,sc_msg_iter* inArgs,std::index_sequence) + static void launchMessageImpl(FluidSCWrapper* x,sc_msg_iter* inArgs,std::index_sequence) { - using ArgTuple = typename Client::MessageSetType::template MessageDescriptorAt::ArgumentTypes; - ArgTuple args; - (void)std::initializer_list{(std::get(args) = (ParamReader::fromArgs(x->mWorld,inArgs,std::get(args)),0))...}; + using MessageDescriptor = typename Client::MessageSetType::template MessageDescriptorAt; + using ArgTuple = typename MessageDescriptor::ArgumentTypes; + using ReturnType = typename MessageDescriptor::ReturnType; + using IndexList = typename MessageDescriptor::IndexList; + using MessageData = MessageDispatch; + auto ft = getInterfaceTable(); + void* msgptr = ft->fRTAlloc(x->mWorld,sizeof(MessageData)); + MessageData* msg = new(msgptr) MessageData; + ArgTuple& args = msg->args; + (void)std::initializer_list{(std::get(args) = ParamReader::fromArgs(x->mWorld,inArgs,std::get(args),0),0)...}; + + msg->name = '/' + Client::getMessageDescriptors().template name(); + msg->wrapper = x; + ft->fDoAsynchronousCommand(x->mWorld, nullptr, getName(), msg, + [](World*, void* data) //NRT thread: invocation + { + MessageData* m = static_cast(data); + m->result = ReturnType{invokeImpl(m->wrapper, m->args, IndexList{})}; + + + + if(!m->result.ok()) + { + printResult(m->wrapper, m->result); + return false; + } + return true; + }, + [](World*, void* data) //RT thread: response + { + MessageData* m = static_cast(data); + messageOutput(m->wrapper,m->name,m->result); + return true; + } + , nullptr, //NRT Thread: No-op + [](World* w, void* data) //RT thread: clean up + { + getInterfaceTable()->fRTFree(w,data); + }, + 0, nullptr); } + template //Call from NRT + static decltype(auto) invokeImpl(FluidSCWrapper* x, ArgsTuple& args, std::index_sequence) + { + return x->mClient.template invoke(x->mClient,std::get(args)...); + } + template //call from RT + static void messageOutput(FluidSCWrapper* x, const std::string& s, MessageResult& result) + { + auto ft = getInterfaceTable(); + //allocate return values + size_t numArgs = ToFloatArray::allocSize(static_cast(result)); + float* values = static_cast(ft->fRTAlloc(x->mWorld,numArgs * sizeof(float))); + //copy return data + ToFloatArray::convert(values,static_cast(result)); + ft->fSendNodeReply(&x->mParent->mNode, -1, s.c_str(), static_cast(numArgs), values); + } + + static void messageOutput(FluidSCWrapper* x, const std::string& s, MessageResult&) + { + auto ft = getInterfaceTable(); + ft->fSendNodeReply(&x->mParent->mNode, -1, s.c_str(), 0, nullptr); + } public: @@ -474,6 +620,7 @@ public: getName(name); getInterfaceTable(ft); impl::FluidSCWrapperBase::setup(ft, name); + Client::getMessageDescriptors().template iterate(); } static auto& setParams(ParameterSetType& p, bool verbose, World* world, FloatControlsIter& inputs, bool constrain = false) @@ -489,8 +636,38 @@ public: } return p; } + + static void printResult(FluidSCWrapper* x, Result& r) + { + if (!x) return; + + switch (r.status()) + { + case Result::Status::kWarning: + { + if(x->mWorld->mVerbosity > 0) + std::cout << "WARNING: " << r.message().c_str() << '\n'; + break; + } + case Result::Status::kError: + { + std::cout << "ERROR: " << r.message().c_str() << '\n'; + break; + } + case Result::Status::kCancelled: + { + std::cout << "Task cancelled\n" << '\n'; + break; + } + default: { + } + } + } }; + + + template class Client> void makeSCWrapper(const char *name, InterfaceTable *ft) {