#pragma once #include "ArgsFromClient.hpp" #include "ArgsToClient.hpp" #include "CopyReplyAddress.hpp" #include namespace fluid { namespace client { template struct FluidSCMessaging{ static auto getInterfaceTable(){ return FluidSCWrapper::getInterfaceTable(); } static auto getName(){ return FluidSCWrapper::getName(); } using Params = typename Client::ParamSetType; using ParamValues = typename Params::ValueTuple; template struct MessageDispatchCmd { using Descriptor = typename Client::MessageSetType::template MessageDescriptorAt; using ArgTuple = typename Descriptor::ArgumentTypes; using ReturnType = typename Descriptor::ReturnType; using IndexList = typename Descriptor::IndexList; static constexpr size_t Message = N; index id; ArgTuple args; ReturnType result; std::string name; IndexList argIndices; void* replyAddr{nullptr}; }; private: static bool is_vowel(const char p_char) { constexpr char vowels[] = { 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U' }; return std::find(std::begin(vowels), std::end(vowels), p_char) != std::end(vowels); } static std::string remove_vowel(std::string st) { auto to_erase = std::remove_if(st.begin(), st.end(), is_vowel); st.erase(to_erase, st.end()); return st; } public: template struct SetupMessageCmd { void operator()(const T& message) { static std::string messageName = std::string{getName()} + '/' + message.name; if(messageName.size() >= 32u) messageName = remove_vowel(messageName); auto ft = getInterfaceTable(); if(!ft->fDefinePlugInCmd(messageName.c_str(), doMessage,(void*)messageName.c_str())) std::cout << "ERROR: failed to register command \"" << messageName << "\"\n"; } }; template static auto constexpr filterOneOptional(const T&) { return std::make_tuple(T{}); } template static auto constexpr filterOneOptional(const Optional&) { return std::make_tuple(); } template static auto constexpr filterOptional(std::tuple) { return std::tuple_cat(filterOneOptional(Ts{})...); } template static Optional validateMessageArgs(Message* msg, sc_msg_iter* inArgs) { //we can be sure that optional args always follow mandatory ones, as this is enforced at compile time in flucoma-core using ArgTuple = decltype(msg->args); using MandatoryArgsTuple = decltype(filterOptional(msg->args)); std::string tags(inArgs->tags + inArgs->count);//evidently this needs commenting: construct string at pointer offset by tag count, to pick up args bool willContinue = true; bool typesMatch = true; auto& args = msg->args; static constexpr size_t expectedArgCount = std::tuple_size::value; /// TODO this squawks if we have a completion message, so maybe we can check if extra arg is a 'b' and squawk if not? // 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(); size_t argCount = 0; ForEach(args,[&typesMatch,&tagsIter,&tagsEnd,firstTag=tags.begin(),&argCount](auto& arg){ if(tagsIter == tagsEnd) { if(std::distance(firstTag,tagsIter) < asSigned(expectedArgCount)) typesMatch = false; return; } char t = *(tagsIter++); typesMatch = typesMatch && ParamReader::argTypeOK(arg,t); argCount++; }); 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){ std::cout << ParamReader::argTypeToString(x); if(i < (std::tuple_size::value - 1 ) ) { std::cout << " ,"; } i++; }); report << ")\nReceived: ("; i = 0; for(auto t: tags) { report << ParamReader::oscTagToString(t); if( i < ( tags.size() - 1 ) ) { report << ", "; } i++; } report << ")\n"; } return willContinue ? Optional(argCount) : Optional(); } static void refreshParams(Params& p, MessageResult& r) { p.fromTuple(ParamValues(r)); } template static void refreshParams(Params&,MessageResult&){} template static void doMessage(World* inWorld, void* inUserData, struct sc_msg_iter* args, void* replyAddr) { using MessageData = MessageDispatchCmd; auto msg = new MessageData(); msg->id = args->geti(); msg->replyAddr = copyReplyAddress(replyAddr); ///TODO make this step contingent on verbosity or something, in the name of effieciency auto tagCount = validateMessageArgs(msg, args); if(!tagCount.has_value()) { delete msg; return; } msg->name = std::string{'/'} + (const char*)(inUserData); ForEach(msg-> args,[inWorld,&args,tagCount,n=0](auto& thisarg)mutable { if(n++ < asSigned(tagCount.value())) thisarg = ParamReader::fromArgs(inWorld, *args,thisarg,0); }); size_t completionMsgSize{args ? args->getbsize() : 0}; assert(completionMsgSize <= std::numeric_limits::max()); char* completionMsgData = nullptr; if (completionMsgSize) { completionMsgData = (char*)getInterfaceTable()->fRTAlloc(inWorld, completionMsgSize); args->getb(completionMsgData, completionMsgSize); } auto ft = getInterfaceTable(); ft->fDoAsynchronousCommand(inWorld, replyAddr, getName(), msg, [](World* world, void* data) // NRT thread: invocation { MessageData* m = static_cast(data); using ReturnType = typename MessageData::ReturnType; if(auto ptr = FluidSCWrapper::get(m->id).lock()) { m->result = ReturnType{invokeImpl(ptr->mClient, m->args,m->argIndices)}; if (!m->result.ok()) FluidSCWrapper::printResult(world, m->result); else refreshParams(ptr->mParams, m->result); } else FluidSCWrapper::printNotFound(m->id); return true; }, [](World* world, void* data) // RT thread: buffer swap (and possible completion messages) { MessageData* m = static_cast(data); MessageData::Descriptor::template forEachArg(m->args, world); return true; }, [](World*, void* data) // NRT Thread: Send reply { MessageData* m = static_cast(data); if(m->result.status() != Result::Status::kError) messageOutput(m->name, m->id, m->result, m->replyAddr); return false; }, [](World*, void* data) // RT thread: clean up { MessageData* m = static_cast(data); delete m; }, static_cast(completionMsgSize), completionMsgData); if(completionMsgSize) ft->fRTFree(inWorld, completionMsgData); } template // Call from NRT static decltype(auto) invokeImpl(Client& x, ArgsTuple& args, std::index_sequence) { return x.template invoke(x, std::get(args)...); } template // call from RT static void messageOutput(const std::string& s, index id, MessageResult& result, void* replyAddr) { index numTags = ToOSCTypes::numTags(static_cast(result)); if(numTags > 2048) { std::cout << "ERROR: Message response too big to send (" << asUnsigned(numTags) * sizeof(float) << " bytes)." << std::endl; return; } small_scpacket packet; packet.adds(s.c_str()); packet.maketags(static_cast(numTags) + 2); packet.addtag(','); packet.addtag('i'); ToOSCTypes::getTag(packet, static_cast(result)); packet.addi(static_cast(id)); ToOSCTypes::convert(packet, static_cast(result)); if(replyAddr) SendReply(replyAddr,packet.data(),static_cast(packet.size())); } static void messageOutput(const std::string& s,index id, MessageResult&, void* replyAddr) { small_scpacket packet; packet.adds(s.c_str()); packet.maketags(2); packet.addtag(','); packet.addtag('i'); packet.addi(static_cast(id)); if(replyAddr) SendReply(replyAddr,packet.data(),static_cast(packet.size())); } template static void messageOutput(const std::string& s, index id, MessageResult>& result, void* replyAddr) { using T = std::tuple; index numTags = ToOSCTypes::numTags(static_cast(result)); if(numTags > 2048) { std::cout << "ERROR: Message response too big to send (" << asUnsigned(numTags) * sizeof(float) << " bytes)." << std::endl; return; } small_scpacket packet; packet.adds(s.c_str()); packet.maketags(static_cast(numTags + 2)); packet.addtag(','); packet.addtag('i'); ToOSCTypes::getTag(packet,static_cast(result)); packet.addi(static_cast(id)); ToOSCTypes::convert(packet, static_cast(result)); if(replyAddr) SendReply(replyAddr,packet.data(),static_cast(packet.size())); } }; } }