/* Part of the Fluid Corpus Manipulation Project (http://www.flucoma.org/) Copyright 2017-2019 University of Huddersfield. Licensed under the BSD-3 License. See license.md file in the project root for full license information. This project has received funding from the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899). */ #pragma once #include "SCBufferAdaptor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace fluid { namespace client { template class FluidSCWrapper; namespace impl { template struct AssignBuffer { void operator()(const typename BufferT::type& p, World* w) { if (auto b = static_cast(p.get())) b->assignToRT(w); } }; template struct CleanUpBuffer { void operator()(const typename BufferT::type& p) { if (auto b = static_cast(p.get())) b->cleanUp(); } }; // Iterate over kr/ir inputs via callbacks from params object struct FloatControlsIter { FloatControlsIter(float** vals, index N) : mValues(vals), mSize(N) {} float next() { return mCount >= mSize ? 0 : *mValues[mCount++]; } void reset(float** vals) { mValues = vals; mCount = 0; } index size() const noexcept { return mSize; } index remain() { return mSize - mCount; } private: float** mValues; index mSize; index mCount{0}; }; //////////////////////////////////////////////////////////////////////////////// // Real Time Processor template class RealTime : public SCUnit { using HostVector = FluidTensorView; using ParamSetType = typename Client::ParamSetType; public: static void setup(InterfaceTable* ft, const char* name) { registerUnit(ft, name); ft->fDefineUnitCmd(name, "latency", doLatency); } static void doLatency(Unit* unit, sc_msg_iter*) { float l[]{ static_cast(static_cast(unit)->mClient.latency())}; auto ft = Wrapper::getInterfaceTable(); std::stringstream ss; ss << '/' << Wrapper::getName() << "_latency"; std::cout << ss.str() << std::endl; ft->fSendNodeReply(&unit->mParent->mNode, -1, ss.str().c_str(), 1, l); } RealTime() : mControlsIterator{mInBuf + mSpecialIndex + 1, static_cast(mNumInputs) - mSpecialIndex - 1}, mParams{Wrapper::Client::getParameterDescriptors()}, mClient{Wrapper::setParams(mParams, mWorld->mVerbosity > 0, mWorld, mControlsIterator, true)} {} void init() { assert( !(mClient.audioChannelsOut() > 0 && mClient.controlChannelsOut() > 0) && "Client can't have both audio and control outputs"); // If we don't the number of arguments we expect, the language side code is // probably the wrong version set plugin to no-op, squawk, and bail; if (mControlsIterator.size() != Client::getParameterDescriptors().count()) { mCalcFunc = Wrapper::getInterfaceTable()->fClearUnitOutputs; std::cout << "ERROR: " << Wrapper::getName() << " wrong number of arguments. Expected " << Client::getParameterDescriptors().count() << ", got " << mControlsIterator.size() << ". Your .sc file and binary plugin might be different versions." << std::endl; return; } mClient.sampleRate(fullSampleRate()); mInputConnections.reserve(asUnsigned(mClient.audioChannelsIn())); mOutputConnections.reserve(asUnsigned(mClient.audioChannelsOut())); mAudioInputs.reserve(asUnsigned(mClient.audioChannelsIn())); mOutputs.reserve(asUnsigned( std::max(mClient.audioChannelsOut(), mClient.controlChannelsOut()))); for (index i = 0; i < mClient.audioChannelsIn(); ++i) { mInputConnections.emplace_back(isAudioRateIn(static_cast(i))); mAudioInputs.emplace_back(nullptr, 0, 0); } for (index i = 0; i < mClient.audioChannelsOut(); ++i) { mOutputConnections.emplace_back(true); mOutputs.emplace_back(nullptr, 0, 0); } for (index i = 0; i < mClient.controlChannelsOut(); ++i) { mOutputs.emplace_back(nullptr, 0, 0); } mCalcFunc = make_calc_function(); Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1); } void next(int) { mControlsIterator.reset(mInBuf + mSpecialIndex + 1); // mClient.audioChannelsIn()); Wrapper::setParams( mParams, mWorld->mVerbosity > 0, mWorld, mControlsIterator); // forward on inputs N + audio inputs as params mParams.constrainParameterValues(); const Unit* unit = this; for (index i = 0; i < mClient.audioChannelsIn(); ++i) { if (mInputConnections[asUnsigned(i)]) { mAudioInputs[asUnsigned(i)].reset(IN(i), 0, fullBufferSize()); } } for (index i = 0; i < mClient.audioChannelsOut(); ++i) { assert(i <= std::numeric_limits::max()); if (mOutputConnections[asUnsigned(i)]) mOutputs[asUnsigned(i)].reset(out(static_cast(i)), 0, fullBufferSize()); } for (index i = 0; i < mClient.controlChannelsOut(); ++i) { assert(i <= std::numeric_limits::max()); mOutputs[asUnsigned(i)].reset(out(static_cast(i)), 0, 1); } mClient.process(mAudioInputs, mOutputs, mContext); } private: std::vector mInputConnections; std::vector mOutputConnections; std::vector mAudioInputs; std::vector mOutputs; FloatControlsIter mControlsIterator; FluidContext mContext; protected: ParamSetType mParams; Client mClient; }; //////////////////////////////////////////////////////////////////////////////// /// Non Real Time Processor /// This is also a UGen, but the main action is delegated off to a worker /// thread, via the NRT thread. The RT bit is there to allow us (a) to poll our /// thread and (b) emit a kr progress update template class NonRealTime : public SCUnit { using ParamSetType = typename Client::ParamSetType; public: static void setup(InterfaceTable* ft, const char* name) { registerUnit(ft, name); ft->fDefineUnitCmd(name, "cancel", doCancel); ft->fDefineUnitCmd( name, "queue_enabled", [](struct Unit* unit, struct sc_msg_iter* args) { auto w = static_cast(unit); w->mQueueEnabled = args->geti(0); w->mFifoMsg.Set( w->mWorld, [](FifoMsg* f) { auto w = static_cast(f->mData); w->mClient.setQueueEnabled(w->mQueueEnabled); }, nullptr, w); Wrapper::getInterfaceTable()->fSendMsgFromRT(w->mWorld, w->mFifoMsg); }); ft->fDefineUnitCmd( name, "synchronous", [](struct Unit* unit, struct sc_msg_iter* args) { auto w = static_cast(unit); w->mSynchronous = args->geti(0); w->mFifoMsg.Set( w->mWorld, [](FifoMsg* f) { auto w = static_cast(f->mData); w->mClient.setSynchronous(w->mSynchronous); }, nullptr, w); Wrapper::getInterfaceTable()->fSendMsgFromRT(w->mWorld, w->mFifoMsg); }); } /// Penultimate input is the doneAction, final is blocking mode. Neither are /// params, so we skip them in the controlsIterator NonRealTime() : mControlsIterator{mInBuf, index(mNumInputs) - mSpecialIndex - 2}, mParams{Wrapper::Client::getParameterDescriptors()}, mClient{Wrapper::setParams(mParams, mWorld->mVerbosity > 0, mWorld, mControlsIterator, true)}, mSynchronous{mNumInputs > 2 ? (in0(int(mNumInputs) - 1) > 0) : false} {} ~NonRealTime() { if (mClient.state() == ProcessState::kProcessing) { std::cout << Wrapper::getName() << ": Processing cancelled" << std::endl; Wrapper::getInterfaceTable()->fSendNodeReply(&mParent->mNode, 1, "/done", 0, nullptr); } // processing will be cancelled in ~NRTThreadAdaptor() } /// No option of not using a worker thread for now /// init() sets up the NRT process via the SC NRT thread, and then sets our /// UGen calc function going void init() { 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) { out0(0) = mDone ? 1.0f : static_cast(mClient.progress()); if (0 == pollCounter++ && !mCheckingForDone) { mCheckingForDone = true; mWorld->ft->fDoAsynchronousCommand(mWorld, nullptr, Wrapper::getName(), this, postProcess, exchangeBuffers, tidyUp, destroy, 0, nullptr); } pollCounter %= checkThreadInterval; } /// To be called on NRT thread. Validate parameters and commence processing in /// new thread static void initNRTJob(FifoMsg* f) { auto w = static_cast(f->mData); w->mDone = false; w->mCancelled = false; Result result = validateParameters(w); if (!result.ok()) { std::cout << "ERROR: " << Wrapper::getName() << ": " << result.message().c_str() << std::endl; return; } w->mClient.setSynchronous(w->mSynchronous); w->mClient.enqueue(w->mParams); w->mClient.process(); } /// Check result and report if bad static bool postProcess(World*, void* data) { auto w = static_cast(data); Result r; ProcessState s = w->mClient.checkProgress(r); if ((s == ProcessState::kDone || s == ProcessState::kDoneStillProcessing) || (w->mSynchronous && s == ProcessState::kNoProcess)) // I think this hinges on the fact that // when mSynchrous = true, this call // will always be behind process() on // the command FIFO, so we can assume // that if the state is kNoProcess, it // has run (vs never having run) { // Given that cancellation from the language now always happens by freeing // the synth, this block isn't reached normally. HOwever, if someone // cancels using u_cmd, this is what will fire if (r.status() == Result::Status::kCancelled) { std::cout << Wrapper::getName() << ": Processing cancelled" << std::endl; w->mCancelled = true; return false; } if (!r.ok()) { std::cout << "ERROR: " << Wrapper::getName() << ": " << r.message().c_str() << std::endl; return false; } w->mDone = true; return true; } return false; } /// swap NRT buffers back to RT-land static bool exchangeBuffers(World* world, void* data) { return static_cast(data)->exchangeBuffers(world); } /// Tidy up any temporary buffers 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) { auto w = static_cast(data); if (w->mDone && w->mNumInputs > 2) // don't check for doneAction if UGen has no ins (there should be // 3 minimum -> sig, doneAction, blocking mode) { int doneAction = static_cast( w->in0(int(w->mNumInputs) - 2)); // doneAction is penultimate input; THIS IS THE LAW world->ft->fDoneAction(doneAction, w); return; } w->mCheckingForDone = false; } static void doCancel(Unit* unit, sc_msg_iter*) { static_cast(unit)->mClient.cancel(); } private: static Result validateParameters(NonRealTime* w) { auto results = w->mParams.constrainParameterValues(); for (auto& r : results) { if (!r.ok()) return r; } return {}; } bool exchangeBuffers(World* world) // RT thread { mParams.template forEachParamType(world); // At this point, we can see if we're finished and let the language know (or // it can wait for the doneAction, but that takes extra time) use replyID to // convey status (0 = normal completion, 1 = cancelled) if (mDone) world->ft->fSendNodeReply(&mParent->mNode, 0, "/done", 0, nullptr); if (mCancelled) world->ft->fSendNodeReply(&mParent->mNode, 1, "/done", 0, nullptr); return true; } bool tidyUp(World*) // NRT thread { mParams.template forEachParamType(); return true; } template struct AssignBuffer { void operator()(const typename BufferT::type& p, World* w) { if (auto b = static_cast(p.get())) b->assignToRT(w); } }; template struct CleanUpBuffer { void operator()(const typename BufferT::type& p) { if (auto b = static_cast(p.get())) b->cleanUp(); } }; FloatControlsIter mControlsIterator; FifoMsg mFifoMsg; char* mCompletionMessage = nullptr; void* mReplyAddr = nullptr; const char* mName = nullptr; index checkThreadInterval; index pollCounter{0}; protected: ParamSetType mParams; Client mClient; bool mSynchronous{true}; bool mQueueEnabled{false}; bool mCheckingForDone{false}; // only write to this from RT thread kthx bool mCancelled{false}; }; //////////////////////////////////////////////////////////////////////////////// /// An impossible monstrosty template class NonRealTimeAndRealTime : public RealTime, public NonRealTime { static void setup(InterfaceTable* ft, const char* name) { RealTime::setup(ft, name); NonRealTime::setup(ft, name); } }; //////////////////////////////////////////////////////////////////////////////// // Template Specialisations for NRT/RT template class FluidSCWrapperImpl; template class FluidSCWrapperImpl : public NonRealTime {}; template class FluidSCWrapperImpl : public RealTime {}; //////////////////////////////////////////////////////////////////////////////// // Make base class(es), full of CRTP mixin goodness template using FluidSCWrapperBase = FluidSCWrapperImpl, typename Client::isNonRealTime, typename Client::isRealTime>; } // namespace impl //////////////////////////////////////////////////////////////////////////////// /// The main wrapper template class FluidSCWrapper : public impl::FluidSCWrapperBase { using FloatControlsIter = impl::FloatControlsIter; template struct ParamReader { static const char* oscTagToString(char tag) { switch (tag) { case 'i': return "integer"; break; case 'f': return "float"; break; case 'd': return "double"; break; case 's': return "string"; break; case 'b': return "blob"; break; case 't': return "time tag"; break; default: return "unknown type"; } } static const char* argTypeToString(std::string&) { return "string"; } template static std::enable_if_t::value, const char*> argTypeToString(T&) { return "integer"; } template static std::enable_if_t::value, const char*> argTypeToString(T&) { return "float"; } static const char* argTypeToString(BufferT::type&) { return "buffer"; } static const char* argTypeToString(InputBufferT::type&) { return "buffer"; } template static std::enable_if_t::value,const char*> argTypeToString(P&) { return "shared_object"; //not ideal } static bool argTypeOK(std::string&, char tag) { return tag == 's'; } template static std::enable_if_t::value || std::is_floating_point::value, bool> argTypeOK(T&, char tag) { return tag == 'i' || tag == 'f' || tag == 'd'; } static bool argTypeOK(BufferT::type&, char tag) { return tag == 'i'; } static bool argTypeOK(InputBufferT::type&, char tag) { return tag == 'i'; } template static std::enable_if_t::value,bool> argTypeOK(P&, char tag) { return tag == 's'; } static auto fromArgs(World*, sc_msg_iter* args, std::string, int) { const char* recv = args->gets(""); return std::string(recv ? recv : ""); } static auto fromArgs(World* w, FloatControlsIter& args, std::string, int) { // first is string size, then chars index size = static_cast(args.next()); char* chunk = static_cast(FluidSCWrapper::getInterfaceTable()->fRTAlloc( w, asUnsigned(size + 1))); if (!chunk) { std::cout << "ERROR: " << FluidSCWrapper::getName() << ": RT memory allocation failed\n"; return std::string{""}; } for (index i = 0; i < size; ++i) chunk[i] = static_cast(args.next()); chunk[size] = 0; // terminate string return std::string{chunk}; } template static std::enable_if_t::value, T> fromArgs(World*, FloatControlsIter& args, T, int) { return static_cast(args.next()); } template static std::enable_if_t::value, T> fromArgs(World*, FloatControlsIter& args, T, int) { return args.next(); } template static std::enable_if_t::value, T> fromArgs(World*, sc_msg_iter* args, T, int defVal) { return args->geti(defVal); } template static std::enable_if_t::value, T> fromArgs(World*, sc_msg_iter* args, T, int) { return args->getf(); } static auto fromArgs(World* w, ArgType args, BufferT::type&, int) { typename LongT::type bufnum = static_cast( ParamReader::fromArgs(w, args, typename LongT::type(), -1)); return BufferT::type(bufnum >= 0 ? new SCBufferAdaptor(bufnum, w) : nullptr); } 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); } template static std::enable_if_t::value, P> fromArgs(World* w, ArgType args, P&, int) { return {fromArgs(w, args, std::string{}, 0).c_str()}; } }; // Iterate over arguments via callbacks from params object template struct Setter { static constexpr index argSize = C::getParameterDescriptors().template get().fixedSize; typename T::type operator()(World* w, ArgType args) { // Just return default if there's nothing left to grab if (args.remain() == 0) { std::cout << "WARNING: " << getName() << " received fewer parameters than expected\n"; return C::getParameterDescriptors().template makeValue(); } ParamLiteralConvertor a; using LiteralType = typename ParamLiteralConvertor::LiteralType; for (index i = 0; i < argSize; i++) a[i] = static_cast( ParamReader::fromArgs(w, args, a[0], 0)); return a.value(); } }; template using ArgumentSetter = Setter; 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 index allocSize(typename BufferT::type) { return 1; } template static std::enable_if_t< std::is_integral::value || std::is_floating_point::value, index> allocSize(T) { return 1; } static index allocSize(std::string s) { return asSigned(s.size()) + 1; } // put null char at end when we send static index allocSize(FluidTensor s) { index count = 0; for (auto& str : s) count += (str.size() + 1); return count; } template static index allocSize(FluidTensor s) { return s.size(); } template static std::tuple, index> allocSize(std::tuple&& t) { return allocSizeImpl(std::forward(t), std::index_sequence_for()); }; template static std::tuple, index> allocSizeImpl(std::tuple&& t, std::index_sequence) { index size{0}; std::array res; (void) std::initializer_list{ (res[Is] = size, size += ToFloatArray::allocSize(std::get(t)), 0)...}; return std::make_tuple(res, size); // array of offsets into allocated buffer & // total number of floats to alloc }; static void convert(float* f, typename BufferT::type buf) { f[0] = static_cast(buf.get())->bufnum(); } template static std::enable_if_t::value || std::is_floating_point::value> convert(float* f, T x) { f[0] = static_cast(x); } static void convert(float* f, std::string s) { std::copy(s.begin(), s.end(), f); f[s.size()] = 0; // terminate } 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); } template static void convert(float* f, std::tuple&& t, std::array offsets, std::index_sequence) { (void) std::initializer_list{ (convert(f + offsets[Is], std::get(t)), 0)...}; } }; // 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) { auto ft = getInterfaceTable(); ft->fDefineUnitCmd(getName(), message.name, launchMessage); } }; template static void launchMessage(Unit* u, sc_msg_iter* args) { FluidSCWrapper* x = static_cast(u); using IndexList = typename Client::MessageSetType::template MessageDescriptorAt< N>::IndexList; launchMessageImpl(x, args, IndexList()); } template static void launchMessageImpl(FluidSCWrapper* x, sc_msg_iter* inArgs, std::index_sequence) { 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; msg->name = '/' + Client::getMessageDescriptors().template name(); msg->wrapper = x; ArgTuple& args = msg->args; // type check OSC message std::string tags(inArgs->tags + inArgs->count); bool willContinue = true; bool typesMatch = true; constexpr size_t expectedArgCount = std::tuple_size::value; if(tags.size() > expectedArgCount) { std::cout << "WARNING: " << msg->name << " received more arguments than expected (got " << tags.size() << ", expect " << expectedArgCount << ")\n"; } if(tags.size() < expectedArgCount) { std::cout << "ERROR: " << msg->name << " received fewer arguments than expected (got " << tags.size() << ", expect " << expectedArgCount << ")\n"; willContinue = false; } auto tagsIter = tags.begin(); auto tagsEnd = tags.end(); ForEach(args,[&typesMatch,&tagsIter,&tagsEnd](auto& x){ if(tagsIter == tagsEnd) { typesMatch = false; return; } char t = *(tagsIter++); typesMatch = typesMatch && ParamReader::argTypeOK(x,t); }); willContinue = willContinue && typesMatch; if(!typesMatch) { auto& report = std::cout; report << "ERROR: " << msg->name << " type signature incorrect.\nExpect: ("; size_t i{0}; ForEach(args, [&i](auto& x){ report << ParamReader::argTypeToString(x); if(i < ( expectedArgCount - 1 ) ) { report << " ,"; } i++; }); report << ")\nReceived: ("; i = 0; for(auto t: tags) { report << ParamReader::oscTagToString(t); if( i < ( tags.size() - 1 ) ) { report << ", "; } i++; } report << ")\n"; } if(!willContinue) return; /// (void) std::initializer_list{ (std::get(args) = ParamReader::fromArgs( x->mWorld, inArgs, std::get(args), 0), 0)...}; x->mDone = false; 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 true; }, [](World* world, void* data) // RT thread: response { MessageData* m = static_cast(data); MessageDescriptor::template forEachArg(m->args, world); if(m->result.status() != Result::Status::kError) messageOutput(m->wrapper, m->name, m->result); else { auto ft = getInterfaceTable(); ft->fSendNodeReply(&m->wrapper->mParent->mNode, -1, m->name.c_str(),0, nullptr); } 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 index numArgs = ToFloatArray::allocSize(static_cast(result)); float* values = static_cast( ft->fRTAlloc(x->mWorld, asUnsigned(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); } template static void messageOutput(FluidSCWrapper* x, const std::string& s, MessageResult>& result) { auto ft = getInterfaceTable(); std::array offsets; index numArgs; std::tie(offsets, numArgs) = ToFloatArray::allocSize(static_cast>(result)); float* values = static_cast( ft->fRTAlloc(x->mWorld, asUnsigned(numArgs) * sizeof(float))); ToFloatArray::convert(values, std::tuple(result), offsets, std::index_sequence_for()); ft->fSendNodeReply(&x->mParent->mNode, -1, s.c_str(), static_cast(numArgs), values); } static void doVersion(Unit*, sc_msg_iter*) { std::cout << "Fluid Corpus Manipualtion Toolkit version " << fluidVersion() << std::endl; } public: using Client = C; using ParameterSetType = typename C::ParamSetType; FluidSCWrapper() { impl::FluidSCWrapperBase::init(); } static const char* getName(const char* setName = nullptr) { static const char* name = nullptr; return (name = setName ? setName : name); } static InterfaceTable* getInterfaceTable(InterfaceTable* setTable = nullptr) { static InterfaceTable* ft = nullptr; return (ft = setTable ? setTable : ft); } static void setup(InterfaceTable* ft, const char* name) { getName(name); getInterfaceTable(ft); impl::FluidSCWrapperBase::setup(ft, name); Client::getMessageDescriptors().template iterate(); ft->fDefineUnitCmd(name, "version", doVersion); } static auto& setParams(ParameterSetType& p, bool verbose, World* world, FloatControlsIter& inputs, bool constrain = false) { //TODO: Regain this robustness if possible? // We won't even try and set params if the arguments don't match // if (inputs.size() == C::getParameterDescriptors().count()) // { p.template setParameterValues(verbose, world, inputs); if (constrain) p.constrainParameterValues(); // } // else // { // std::cout << "ERROR: " << getName() // << ": parameter count mismatch. Perhaps your binary plugins " // "and SC sources are different versions" // << std::endl; // } 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 void makeSCWrapper(const char* name, InterfaceTable* ft) { FluidSCWrapper::setup(ft, name); } } // namespace client } // namespace fluid