#include "../ports.h" #include "../rtosc.h" #include "../pretty-format.h" #include #include #include #include #include #include /* Compatibility with non-clang compilers */ #ifndef __has_feature # define __has_feature(x) 0 #endif #ifndef __has_extension # define __has_extension __has_feature #endif /* Check for C++11 support */ #if defined(HAVE_CPP11_SUPPORT) # if HAVE_CPP11_SUPPORT # define DISTRHO_PROPER_CPP11_SUPPORT # endif #elif __cplusplus >= 201103L || (defined(__GNUC__) && defined(__GXX_EXPERIMENTAL_CXX0X__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 405) || __has_extension(cxx_noexcept) # define DISTRHO_PROPER_CPP11_SUPPORT # if (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) < 407 && ! defined(__clang__)) || (defined(__clang__) && ! __has_extension(cxx_override_control)) # define override // gcc4.7+ only # define final // gcc4.7+ only # endif #endif using namespace rtosc; static inline void scat(char *dest, const char *src) { while(*dest) dest++; while(*src && *src!=':') *dest++ = *src++; *dest = 0; } RtData::RtData(void) :loc(NULL), loc_size(0), obj(NULL), matches(0), message(NULL) { for(int i=0; i<(int)(sizeof(idx)/sizeof(int)); ++i) idx[i] = 0; } void RtData::push_index(int ind) { for(int i=1; i<(int)(sizeof(idx)/sizeof(int)); ++i) idx[i] = idx[i-1]; idx[0] = ind; } void RtData::pop_index(void) { int n = sizeof(idx)/sizeof(int); for(int i=n-2; i >= 0; --i) idx[i] = idx[i+1]; idx[n-1] = 0; } void RtData::replyArray(const char *path, const char *args, rtosc_arg_t *vals) { (void) path; (void) args; (void) vals; } void RtData::reply(const char *path, const char *args, ...) { va_list va; va_start(va,args); char buffer[1024]; rtosc_vmessage(buffer,1024,path,args,va); reply(buffer); va_end(va); } void RtData::reply(const char *msg) {(void)msg;} void RtData::chain(const char *path, const char *args, ...) { (void) path; (void) args; } void RtData::chain(const char *msg) { (void) msg; } void RtData::chainArray(const char *path, const char *args, rtosc_arg_t *vals) { (void) path; (void) args; (void) vals; }; void RtData::broadcast(const char *path, const char *args, ...) { va_list va; va_start(va,args); char buffer[1024]; rtosc_vmessage(buffer,1024,path,args,va); broadcast(buffer); va_end(va); } void RtData::broadcast(const char *msg) {reply(msg);}; void RtData::broadcastArray(const char *path, const char *args, rtosc_arg_t *vals) { (void) path; (void) args; (void) vals; } void RtData::forward(const char *rational) { (void) rational; } void metaiterator_advance(const char *&title, const char *&value) { if(!title || !*title) { value = NULL; return; } //Try to find "\0=" after title string value = title; while(*value) ++value; if(*++value != '=') value = NULL; else value++; } Port::MetaIterator::MetaIterator(const char *str) :title(str), value(NULL) { metaiterator_advance(title, value); } Port::MetaIterator& Port::MetaIterator::operator++(void) { if(!title || !*title) { title = NULL; return *this; } //search for next parameter start //aka "\0:" unless "\0\0" is seen char prev = 0; while(prev || (*title && *title != ':')) prev = *title++; if(!*title) title = NULL; else ++title; metaiterator_advance(title, value); return *this; } Port::MetaIterator::operator bool(void) const { return title; } Port::MetaContainer::MetaContainer(const char *str_) :str_ptr(str_) {} Port::MetaIterator Port::MetaContainer::begin(void) const { if(str_ptr && *str_ptr == ':') return Port::MetaIterator(str_ptr+1); else return Port::MetaIterator(str_ptr); } Port::MetaIterator Port::MetaContainer::end(void) const { return MetaIterator(NULL); } Port::MetaIterator Port::MetaContainer::find(const char *str) const { for(const auto x : *this) if(!strcmp(x.title, str)) return x; return NULL; } size_t Port::MetaContainer::length(void) const { if(!str_ptr || !*str_ptr) return 0; char prev = 0; const char *itr = str_ptr; while(prev || *itr) prev = *itr++; return 2+(itr-str_ptr); } const char *Port::MetaContainer::operator[](const char *str) const { for(const auto x : *this) if(!strcmp(x.title, str)) return x.value; return NULL; } //Match the arg string or fail inline bool arg_matcher(const char *pattern, const char *args) { //match anything if now arg restriction is present (ie the ':') if(*pattern++ != ':') return true; const char *arg_str = args; bool arg_match = *pattern || *pattern == *arg_str; while(*pattern && *pattern != ':') arg_match &= (*pattern++==*arg_str++); if(*pattern==':') { if(arg_match && !*arg_str) return true; else return arg_matcher(pattern, args); //retry } return arg_match; } inline bool scmp(const char *a, const char *b) { while(*a && *a == *b) a++, b++; return a[0] == b[0]; } typedef std::vector words_t; typedef std::vector svec_t; typedef std::vector cvec_t; typedef std::vector ivec_t; typedef std::vector tuple_t; typedef std::vector tvec_t; namespace rtosc{ class Port_Matcher { public: bool *enump; svec_t fixed; cvec_t arg_spec; ivec_t pos; ivec_t assoc; ivec_t remap; bool rtosc_match_args(const char *pattern, const char *msg) { //match anything if no arg restriction is present //(ie the ':') if(*pattern++ != ':') return true; const char *arg_str = rtosc_argument_string(msg); bool arg_match = *pattern || *pattern == *arg_str; while(*pattern && *pattern != ':') arg_match &= (*pattern++==*arg_str++); if(*pattern==':') { if(arg_match && !*arg_str) return true; else return rtosc_match_args(pattern, msg); //retry } return arg_match; } bool hard_match(int i, const char *msg) { if(strncmp(msg, fixed[i].c_str(), fixed[i].length())) return false; if(arg_spec[i]) return rtosc_match_args(arg_spec[i], msg); else return true; } }; } tvec_t do_hash(const words_t &strs, const ivec_t &pos) { tvec_t tvec; for(auto &s:strs) { tuple_t tuple; tuple.push_back(s.length()); for(const auto &p:pos) if(p < (int)s.size()) tuple.push_back(s[p]); tvec.push_back(std::move(tuple)); } return tvec; } template int count_dups(std::vector &t) { int dups = 0; int N = t.size(); bool mark[t.size()]; memset(mark, 0, N); for(int i=0; i bool has(T &t, Z&z) { for(auto tt:t) if(tt==z) return true; return false; } static int int_max(int a, int b) { return a::max(); while(true) { for(int i=0; i= current_dups) break; current_dups = pos_best_val; pos.push_back(pos_best); } auto hashed = do_hash(strs, pos); int d = count_dups(hashed); //printf("Total Dups: %d\n", d); if(d != 0) pos.clear(); return pos; } static ivec_t do_hash(const words_t &strs, const ivec_t &pos, const ivec_t &assoc) { ivec_t ivec; ivec.reserve(strs.size()); for(auto &s:strs) { int t = s.length(); for(auto p:pos) if(p < (int)s.size()) t += assoc[s[p]]; ivec.push_back(t); } return ivec; } static ivec_t find_assoc(const words_t &strs, const ivec_t &pos) { ivec_t assoc; int current_dups = strs.size(); int N = 127; std::vector useful_chars; for(auto w:strs) for(auto c:w) if(!has(useful_chars, c)) useful_chars.push_back(c); for(int i=0; i::max();; for(int k=0; k<4; ++k) { for(int i:useful_chars) { assoc_best_val = std::numeric_limits::max(); for(int j=0; j<100; ++j) { //printf("."); assoc[i] = j; auto hashed = do_hash(strs, pos, assoc); //for(int i=0; i= current_dups) break; current_dups = assoc_best_val; } auto hashed = do_hash(strs, pos, assoc); //int d = count_dups(hashed); //printf("Total Dups Assoc: %d\n", d); return assoc; } static ivec_t find_remap(words_t &strs, ivec_t &pos, ivec_t &assoc) { ivec_t remap; auto hashed = do_hash(strs, pos, assoc); //for(int i=0; i str, Port_Matcher &pm) { if(str.empty()) return; pm.pos = find_pos(str); if(pm.pos.empty()) { fprintf(stderr, "rtosc: Failed to generate minimal hash\n"); return; } pm.assoc = find_assoc(str, pm.pos); pm.remap = find_remap(str, pm.pos, pm.assoc); } static void generate_minimal_hash(Ports &p, Port_Matcher &pm) { svec_t keys; cvec_t args; bool enump = false; for(unsigned i=0; i 0) { arg = p.ports[i].name+idx; tmp = tmp.substr(0,idx); } keys.push_back(tmp); args.push_back(arg); } pm.fixed = keys; pm.arg_spec = args; generate_minimal_hash(keys, pm); } Ports::Ports(std::initializer_list l) :ports(l), impl(NULL) { refreshMagic(); } Ports::~Ports() { delete []impl->enump; delete impl; } #if !defined(__GNUC__) #define __builtin_expect(a,b) a #endif void Ports::dispatch(const char *m, rtosc::RtData &d, bool base_dispatch) const { if(!strcmp(m, "pointer")) { // rRecur*Cb have already set d.loc to the pointer we need, // so we just return return; } void *obj = d.obj; //handle the first dispatch layer if(base_dispatch) { d.matches = 0; d.message = m; if(m && *m == '/') m++; if(d.loc) d.loc[0] = 0; } //simple case if(!d.loc || !d.loc_size) { for(const Port &port: ports) { if(rtosc_match(port.name,m, NULL)) d.port = &port, port.cb(m,d), d.obj = obj; } } else { //TODO this function is certainly buggy at the moment, some tests //are needed to make it clean //XXX buffer_size is not properly handled yet if(__builtin_expect(d.loc[0] == 0, 0)) { memset(d.loc, 0, d.loc_size); d.loc[0] = '/'; } char *old_end = d.loc; while(*old_end) ++old_end; if(impl->pos.empty()) { //No perfect minimal hash function for(unsigned i=0; ipos) if(p < (int)len) t += impl->assoc[m[p]]; if(t >= (int)impl->remap.size() && !default_handler) return; else if(t >= (int)impl->remap.size() && default_handler) { d.matches++; default_handler(m,d), d.obj = obj; return; } int port_num = impl->remap[t]; //Verify the chosen port is correct if(__builtin_expect(impl->hard_match(port_num, m), 1)) { const Port &port = ports[impl->remap[t]]; if(!port.ports) d.matches++; //Append the path if(impl->enump[port_num]) { const char *msg = m; char *pos = old_end; while(*msg && *msg != '/') *pos++ = *msg++; if(strchr(port.name, '/')) *pos++ = '/'; *pos = '\0'; } else memcpy(old_end, impl->fixed[port_num].c_str(), impl->fixed[port_num].length()+1); d.port = &port; //Apply callback port.cb(m,d), d.obj = obj; //Remove the rest of the path old_end[0] = '\0'; } else if(default_handler) { d.matches++; default_handler(m,d), d.obj = obj; } } } } /* * Returning values from runtime */ //! RtData subclass to capture argument values pretty-printed from //! a runtime object class CapturePretty : public RtData { char* buffer; std::size_t buffersize; int cols_used; void reply(const char *) override { assert(false); } /* void replyArray(const char*, const char *args, rtosc_arg_t *vals) { size_t cur_idx = 0; for(const char* ptr = args; *ptr; ++ptr, ++cur_idx) { assert(cur_idx < max_args); arg_vals[cur_idx].type = *ptr; arg_vals[cur_idx].val = vals[cur_idx]; } // TODO: refactor code, also with Capture ? size_t wrt = rtosc_print_arg_vals(arg_vals, cur_idx, buffer, buffersize, NULL, cols_used); assert(wrt); }*/ void reply(const char *, const char *args, ...) override { va_list va; va_start(va,args); size_t nargs = strlen(args); rtosc_arg_val_t arg_vals[nargs]; rtosc_v2argvals(arg_vals, nargs, args, va); size_t wrt = rtosc_print_arg_vals(arg_vals, nargs, buffer, buffersize, NULL, cols_used); (void) wrt; va_end(va); assert(wrt); } public: //! Return the argument values, pretty-printed const char* value() const { return buffer; } CapturePretty(char* buffer, std::size_t size, int cols_used) : buffer(buffer), buffersize(size), cols_used(cols_used) {} }; /** * @brief Returns a port's value pretty-printed from a runtime object. The * port object must not be known. * * For the parameters, see the overloaded function * @return The argument values, pretty-printed */ static const char* get_value_from_runtime(void* runtime, const Ports& ports, size_t loc_size, char* loc, char* buffer_with_port, std::size_t buffersize, int cols_used) { std::size_t addr_len = strlen(buffer_with_port); // use the port buffer to print the result, but do not overwrite the // port name CapturePretty d(buffer_with_port + addr_len, buffersize - addr_len, cols_used); d.obj = runtime; d.loc_size = loc_size; d.loc = loc; d.matches = 0; // does the message at least fit the arguments? assert(buffersize - addr_len >= 8); // append type memset(buffer_with_port + addr_len, 0, 8); // cover string end and arguments buffer_with_port[addr_len + (4-addr_len%4)] = ','; d.message = buffer_with_port; // buffer_with_port is a message in this call: ports.dispatch(buffer_with_port, d, false); return d.value(); } //! RtData subclass to capture argument values from a runtime object class Capture : public RtData { size_t max_args; rtosc_arg_val_t* arg_vals; int nargs; void chain(const char *path, const char *args, ...) override { nargs = 0; } void chain(const char *msg) override { nargs = 0; } void reply(const char *) override { assert(false); } void replyArray(const char*, const char *args, rtosc_arg_t *vals) override { size_t cur_idx = 0; for(const char* ptr = args; *ptr; ++ptr, ++cur_idx) { assert(cur_idx < max_args); arg_vals[cur_idx].type = *ptr; arg_vals[cur_idx].val = vals[cur_idx]; } nargs = cur_idx; } void reply(const char *, const char *args, ...) override { va_list va; va_start(va,args); nargs = strlen(args); assert((size_t)nargs <= max_args); rtosc_v2argvals(arg_vals, nargs, args, va); va_end(va); } public: //! Return the number of argument values stored int size() const { return nargs; } Capture(std::size_t max_args, rtosc_arg_val_t* arg_vals) : max_args(max_args), arg_vals(arg_vals), nargs(-1) {} }; /** * @brief Returns a port's current value(s) * * This function returns the value(s) of a known port object and stores them as * rtosc_arg_val_t. * @param runtime The runtime object * @param port the port where the value shall be retrieved * @param loc A buffer where dispatch can write down the currently dispatched * path * @param loc_size Size of loc * @param portname_from_base The name of the port, relative to its base * @param buffer_with_port A buffer which already contains the port. * This buffer will be modified and must at least have space for 8 more bytes. * @param buffersize Size of @p buffer_with_port * @param max_args Maximum capacity of @p arg_vals * @param arg_vals Argument buffer for returned argument values * @return The number of argument values stored in @p arg_vals */ static size_t get_value_from_runtime(void* runtime, const Port& port, size_t loc_size, char* loc, const char* portname_from_base, char* buffer_with_port, std::size_t buffersize, std::size_t max_args, rtosc_arg_val_t* arg_vals) { strncpy(buffer_with_port, portname_from_base, buffersize); std::size_t addr_len = strlen(buffer_with_port); Capture d(max_args, arg_vals); d.obj = runtime; d.loc_size = loc_size; d.loc = loc; d.port = &port; d.matches = 0; assert(*loc); // does the message at least fit the arguments? assert(buffersize - addr_len >= 8); // append type memset(buffer_with_port + addr_len, 0, 8); // cover string end and arguments buffer_with_port[addr_len + (4-addr_len%4)] = ','; // TODO? code duplication // buffer_with_port is a message in this call: d.message = buffer_with_port; port.cb(buffer_with_port, d); assert(d.size() >= 0); return d.size(); } /* * default values */ const char* rtosc::get_default_value(const char* port_name, const Ports& ports, void* runtime, const Port* port_hint, int32_t idx, int recursive) { constexpr std::size_t buffersize = 1024; char buffer[buffersize]; char loc[buffersize] = ""; assert(recursive >= 0); // forbid recursing twice char default_annotation[20] = "default"; // if(idx > 0) // snprintf(default_annotation + 7, 13, "[%" PRId32 "]", idx); const char* const dependent_annotation = "default depends"; const char* return_value = nullptr; if(!port_hint) port_hint = ports.apropos(port_name); assert(port_hint); // port must be found const Port::MetaContainer metadata = port_hint->meta(); // Let complex cases depend upon a marker variable // If the runtime is available the exact preset number can be found // This generalizes to envelope types nicely if envelopes have a read // only port which indicates if they're amplitude/frequency/etc const char* dependent = metadata[dependent_annotation]; if(dependent) { char* dependent_port = buffer; *dependent_port = 0; assert(strlen(port_name) + strlen(dependent_port) + 5 < buffersize); strncat(dependent_port, port_name, buffersize - strlen(dependent_port) - 1); strncat(dependent_port, "/../", buffersize - strlen(dependent_port) - 1); strncat(dependent_port, dependent, buffersize - strlen(dependent_port) - 1); dependent_port = Ports::collapsePath(dependent_port); // TODO: collapsePath bug? // Relative paths should not start with a slash after collapsing ... if(*dependent_port == '/') ++dependent_port; const char* dependent_value = runtime ? get_value_from_runtime(runtime, ports, buffersize, loc, dependent_port, buffersize-1, 0) : get_default_value(dependent_port, ports, runtime, NULL, recursive-1); assert(strlen(dependent_value) < 16); // must be an int char* default_variant = buffer; *default_variant = 0; assert(strlen(default_annotation) + 1 + 16 < buffersize); strncat(default_variant, default_annotation, buffersize - strlen(default_variant)); strncat(default_variant, " ", buffersize - strlen(default_variant)); strncat(default_variant, dependent_value, buffersize - strlen(default_variant)); return_value = metadata[default_variant]; } // If return_value is NULL, this can have two meanings: // 1. there was no depedent annotation // => check for a direct (non-dependent) default value // (a non existing direct default value is OK) // 2. there was a dependent annotation, but the dependent value has no // mapping (mapping for default_variant was NULL) // => check for the direct default value, which acts as a default // mapping for all presets; a missing default value indicates an // error in the metadata if(!return_value) { return_value = metadata[default_annotation]; assert(!dependent || return_value); } return return_value; } int rtosc::canonicalize_arg_vals(rtosc_arg_val_t* av, size_t n, const char* port_args, Port::MetaContainer meta) { const char* first = port_args; int errors_found = 0; for( ; *first && (*first == ':' || *first == '[' || *first == ']'); ++first) ; for(size_t i = 0; i < n; ++i, ++first, ++av) { for( ; *first && (*first == '[' || *first == ']'); ++first) ; if(!*first || *first == ':') { // (n-i) arguments left, but we have no recipe to convert them return n-i; } if(av->type == 'S' && *first == 'i') { int val = enum_key(meta, av->val.s); if(val == std::numeric_limits::min()) ++errors_found; else { av->type = 'i'; av->val.i = val; } } } return errors_found; } void rtosc::map_arg_vals(rtosc_arg_val_t* av, size_t n, Port::MetaContainer meta) { char mapbuf[20] = "map "; for(size_t i = 0; i < n; ++i, ++av) { if(av->type == 'i') { snprintf(mapbuf + 4, 16, "%d", av->val.i); const char* val = meta[mapbuf]; if(val) { av->type = 'S'; av->val.s = val; } } } } int rtosc::get_default_value(const char* port_name, const char* port_args, const Ports& ports, void* runtime, const Port* port_hint, int32_t idx, size_t n, rtosc_arg_val_t* res, char* strbuf, size_t strbufsize) { const char* pretty = get_default_value(port_name, ports, runtime, port_hint, idx, 0); int nargs; if(pretty) { nargs = rtosc_count_printed_arg_vals(pretty); assert(nargs > 0); // parse error => error in the metadata? assert((size_t)nargs < n); rtosc_scan_arg_vals(pretty, res, nargs, strbuf, strbufsize); { int errs_found = canonicalize_arg_vals(res, nargs, port_args, port_hint->meta()); if(errs_found) fprintf(stderr, "Could not canonicalize %s\n", pretty); assert(!errs_found); // error in the metadata? } } else nargs = -1; return nargs; } std::string rtosc::get_changed_values(const Ports& ports, void* runtime) { std::string res; constexpr std::size_t buffersize = 1024; char port_buffer[buffersize]; memset(port_buffer, 0, buffersize); // requirement for walk_ports const size_t max_arg_vals = 256; auto on_reach_port = [](const Port* p, const char* port_buffer, const char* port_from_base, const Ports& base, void* data, void* runtime) { assert(runtime); const Port::MetaContainer meta = p->meta(); if((p->name[strlen(p->name)-1] != ':' && !strstr(p->name, "::")) || meta.find("parameter") == meta.end()) { // runtime information can not be retrieved, // thus, it can not be compared with the default value return; } char loc[buffersize] = ""; rtosc_arg_val_t arg_vals_default[max_arg_vals]; rtosc_arg_val_t arg_vals_runtime[max_arg_vals]; char buffer_with_port[buffersize]; char cur_value_pretty[buffersize] = " "; char strbuf[buffersize]; // temporary string buffer for pretty-printing std::string* res = (std::string*)data; assert(strlen(port_buffer) + 1 < buffersize); strncpy(loc, port_buffer, buffersize); // TODO: +-1? strncpy(buffer_with_port, port_from_base, buffersize); const char* portargs = strchr(p->name, ':'); if(!portargs) portargs = p->name + strlen(p->name); #if 0 // debugging stuff if(!strncmp(port_buffer, "/part1/Penabled", 5) && !strncmp(port_buffer+6, "/Penabled", 9)) { printf("runtime: %ld\n", (long int)runtime); } #endif // TODO: p->name: duplicate to p int nargs_default = get_default_value(p->name, portargs, base, runtime, p, -1, max_arg_vals, arg_vals_default, strbuf, buffersize); size_t nargs_runtime = get_value_from_runtime(runtime, *p, buffersize, loc, port_from_base, buffer_with_port, buffersize, max_arg_vals, arg_vals_runtime); if(nargs_default == (int) nargs_runtime) { canonicalize_arg_vals(arg_vals_default, nargs_default, strchr(p->name, ':'), meta); if(!rtosc_arg_vals_eq(arg_vals_default, arg_vals_runtime, nargs_default, nargs_runtime, NULL)) { map_arg_vals(arg_vals_runtime, nargs_runtime, meta); rtosc_print_arg_vals(arg_vals_runtime, nargs_runtime, cur_value_pretty + 1, buffersize - 1, NULL, strlen(port_buffer) + 1); *res += port_buffer; *res += cur_value_pretty; *res += "\n"; } } }; walk_ports(&ports, port_buffer, buffersize, &res, on_reach_port, runtime); if(res.length()) // remove trailing newline res.resize(res.length()-1); return res; } void rtosc::savefile_dispatcher_t::operator()(const char* msg) { *loc = 0; RtData d; d.obj = runtime; d.loc = loc; // we're always dispatching at the base d.loc_size = 1024; ports->dispatch(msg, d, true); } int savefile_dispatcher_t::default_response(size_t nargs, bool first_round, savefile_dispatcher_t::dependency_t dependency) { // default implementation: // no dependencies => round 0, // has dependencies => round 1, // not specified => both rounds return (dependency == not_specified || !(dependency ^ first_round)) ? nargs // argument number is not changed : (int)discard; } int savefile_dispatcher_t::on_dispatch(size_t, char *, size_t, size_t nargs, rtosc_arg_val_t *, bool round2, dependency_t dependency) { return default_response(nargs, round2, dependency); } int rtosc::dispatch_printed_messages(const char* messages, const Ports& ports, void* runtime, savefile_dispatcher_t* dispatcher) { constexpr std::size_t buffersize = 1024; char portname[buffersize], message[buffersize], strbuf[buffersize]; int rd, rd_total = 0; int nargs; int msgs_read = 0; savefile_dispatcher_t dummy_dispatcher; if(!dispatcher) dispatcher = &dummy_dispatcher; dispatcher->ports = &ports; dispatcher->runtime = runtime; // scan all messages twice: // * in the second round, only dispatch those with ports that depend on // other ports // * in the first round, only dispatch all others for(int round = 0; round < 2 && msgs_read >= 0; ++round) { msgs_read = 0; rd_total = 0; const char* msg_ptr = messages; while(*msg_ptr && (msgs_read >= 0)) { nargs = rtosc_count_printed_arg_vals_of_msg(msg_ptr); if(nargs >= 0) { // 16 is usually too much, but it allows the user to add // arguments if necessary size_t maxargs = 16; rtosc_arg_val_t arg_vals[maxargs]; rd = rtosc_scan_message(msg_ptr, portname, buffersize, arg_vals, nargs, strbuf, buffersize); rd_total += rd; const Port* port = ports.apropos(portname); savefile_dispatcher_t::dependency_t dependency = (savefile_dispatcher_t::dependency_t) (port ? !!port->meta()["default depends"] : (int)savefile_dispatcher_t::not_specified); // let the user modify the message and the args // the argument number may have changed, or the user // wants to discard the message or abort the savefile loading nargs = dispatcher->on_dispatch(buffersize, portname, maxargs, nargs, arg_vals, round, dependency); if(nargs == savefile_dispatcher_t::abort) msgs_read = -rd_total-1; // => causes abort else { if(nargs != savefile_dispatcher_t::discard) { rtosc_arg_t vals[nargs]; char argstr[nargs+1]; for(int i = 0; i < nargs; ++i) { vals[i] = arg_vals[i].val; argstr[i] = arg_vals[i].type; } argstr[nargs] = 0; rtosc_amessage(message, buffersize, portname, argstr, vals); (*dispatcher)(message); } } msg_ptr += rd; ++msgs_read; } else if(nargs == std::numeric_limits::min()) { // this means the (rest of the) file is whitespace only // => don't increase msgs_read while(*++msg_ptr) ; } else { // overwrite meaning of msgs_read in order to // inform the user where the read error occurred msgs_read = -rd_total-1; } } } return msgs_read; } std::string rtosc::save_to_file(const Ports &ports, void *runtime, const char *appname, rtosc_version appver) { std::string res; char rtosc_vbuf[12], app_vbuf[12]; { rtosc_version rtoscver = rtosc_current_version(); rtosc_version_print_to_12byte_str(&rtoscver, rtosc_vbuf); rtosc_version_print_to_12byte_str(&appver, app_vbuf); } res += "% RT OSC v"; res += rtosc_vbuf; res += " savefile\n" "% "; res += appname; res += " v"; res += app_vbuf; res += "\n"; res += get_changed_values(ports, runtime); return res; } int rtosc::load_from_file(const char* file_content, const Ports& ports, void* runtime, const char* appname, rtosc_version appver, savefile_dispatcher_t* dispatcher) { char appbuf[128]; int bytes_read = 0; if(dispatcher) { dispatcher->app_curver = appver; dispatcher->rtosc_curver = rtosc_current_version(); } unsigned vma, vmi, vre; int n = 0; sscanf(file_content, "%% RT OSC v%u.%u.%u savefile%n ", &vma, &vmi, &vre, &n); if(n <= 0 || vma > 255 || vmi > 255 || vre > 255) return -bytes_read-1; if(dispatcher) { dispatcher->rtosc_filever.major = vma; dispatcher->rtosc_filever.minor = vmi; dispatcher->rtosc_filever.revision = vre; } file_content += n; bytes_read += n; n = 0; sscanf(file_content, "%% %128s v%u.%u.%u%n ", appbuf, &vma, &vmi, &vre, &n); if(n <= 0 || strcmp(appbuf, appname) || vma > 255 || vmi > 255 || vre > 255) return -bytes_read-1; if(dispatcher) { dispatcher->app_filever.major = vma; dispatcher->app_filever.minor = vmi; dispatcher->app_filever.revision = vre; } file_content += n; bytes_read += n; n = 0; int rval = dispatch_printed_messages(file_content, ports, runtime, dispatcher); return (rval < 0) ? (rval-bytes_read) : rval; } /* * Miscellaneous */ const Port *Ports::operator[](const char *name) const { for(const Port &port:ports) { const char *_needle = name, *_haystack = port.name; while(*_needle && *_needle==*_haystack)_needle++,_haystack++; if(*_needle == 0 && (*_haystack == ':' || *_haystack == '\0')) { return &port; } } return NULL; } static msg_t snip(msg_t m) { while(*m && *m != '/') ++m; return m+1; } const Port *Ports::apropos(const char *path) const { if(path && path[0] == '/') ++path; for(const Port &port: ports) if(strchr(port.name,'/') && rtosc_match_path(port.name,path, NULL)) return (strchr(path,'/')[1]==0) ? &port : port.ports->apropos(snip(path)); //This is the lowest level, now find the best port for(const Port &port: ports) if(*path && (strstr(port.name, path)==port.name || rtosc_match_path(port.name, path, NULL))) return &port; return NULL; } static bool parent_path_p(char *read, char *start) { if(read-start<2) return false; return read[0]=='.' && read[-1]=='.' && read[-2]=='/'; } static void read_path(char *&r, char *start) { while(1) { if(r= p) { //per path chunk either //(1) find a parent ref and inc consuming //(2) find a normal ref and consume //(3) find a normal ref and write through bool ppath = parent_path_p(read_pos, p); if(ppath) { read_path(read_pos, p); consuming++; } else if(consuming) { read_path(read_pos, p); consuming--; } else move_path(read_pos, write_pos, p); } //return last written location, not next to write return write_pos+1; }; void Ports::refreshMagic() { delete impl; impl = new Port_Matcher; generate_minimal_hash(*this, *impl); impl->enump = new bool[ports.size()]; for(int i=0; i<(int)ports.size(); ++i) impl->enump[i] = strchr(ports[i].name, '#'); elms = ports.size(); } ClonePorts::ClonePorts(const Ports &ports_, std::initializer_list c) :Ports({}) { for(auto &to_clone:c) { const Port *clone_port = NULL; for(auto &p:ports_.ports) if(!strcmp(p.name, to_clone.name)) clone_port = &p; if(!clone_port && strcmp("*", to_clone.name)) { fprintf(stderr, "Cannot find a clone port for '%s'\n",to_clone.name); assert(false); } if(clone_port) { ports.push_back({clone_port->name, clone_port->metadata, clone_port->ports, to_clone.cb}); } else { default_handler = to_clone.cb; } } refreshMagic(); } MergePorts::MergePorts(std::initializer_list c) :Ports({}) { //XXX TODO remove duplicates in some sane and documented way //e.g. repeated ports override and remove older ones for(auto *to_clone:c) { assert(to_clone); for(auto &p:to_clone->ports) { bool already_there = false; for(auto &pp:ports) if(!strcmp(pp.name, p.name)) already_there = true; if(!already_there) ports.push_back(p); } } refreshMagic(); } /** * @brief Check if the port @p port is enabled * @param port The port to be checked. Usually of type rRecur* or rSelf. * @param loc The absolute path of @p port * @param loc_size The maximum usable size of @p loc * @param ports The Ports object containing @p port * @param runtime TODO * @return TODO */ bool port_is_enabled(const Port* port, char* loc, size_t loc_size, const Ports& base, void *runtime) { if(port && runtime) { const char* enable_port = port->meta()["enabled by"]; if(enable_port) { /* find out which Ports object to dispatch at (the current one or its child?) */ const char* n = port->name; const char* e = enable_port; for( ; *n && (*n == *e) && *n != '/' && *e != '/'; ++n, ++e) ; bool subport = (*e == '/' && *n == '/'); const char* ask_port_str = subport ? e+1 : enable_port; const Ports& ask_ports = subport ? *base[port->name]->ports : base; assert(!strchr(ask_port_str, '/')); const Port* ask_port = ask_ports[ask_port_str]; assert(ask_port); rtosc_arg_val_t rval; /* concatenate the location string */ if(subport) strncat(loc, "/../", loc_size - strlen(loc) - 1); strncat(loc, enable_port, loc_size - strlen(loc) - 1); char* collapsed_loc = Ports::collapsePath(loc); loc_size -= (collapsed_loc - loc); // TODO: collapse, use .. only in one case /* receive the "enabled" property */ char buf[loc_size]; #ifdef NEW_CODE strncpy(buf, collapsed_loc, loc_size); #else // TODO: try to use portname_from_base, since Ports might // also be of type a#N/b const char* last_slash = strrchr(collapsed_loc, '/'); strncpy(buf, last_slash ? last_slash + 1 : collapsed_loc, loc_size); #endif get_value_from_runtime(runtime, *ask_port, loc_size, collapsed_loc, ask_port_str, buf, 0, 1, &rval); assert(rval.type == 'T' || rval.type == 'F'); return rval.val.T == 'T'; } else // Port has no "enabled" property, so it is always enabled return true; } else // no runtime provided, so run statically through all subports return true; } // TODO: copy the changes into walk_ports_2 void rtosc::walk_ports(const Ports *base, char *name_buffer, size_t buffer_size, void *data, port_walker_t walker, void* runtime) { auto walk_ports_recurse = [](const Port& p, char* name_buffer, size_t buffer_size, const Ports& base, void* data, port_walker_t walker, void* runtime, const char* old_end) { // TODO: all/most of these checks must also be done for the // first, non-recursive call bool enabled = true; if(runtime) { enabled = (p.meta().find("no walk") == p.meta().end()); if(enabled) { // get child runtime and check if it's NULL RtData r; r.obj = runtime; r.port = &p; char buf[1024]; strncpy(buf, old_end, 1024); strncat(buf, "pointer", 1024 - strlen(buf) - 1); assert(1024 - strlen(buf) >= 8); strncpy(buf + strlen(buf) + 1, ",", 2); p.cb(buf, r); runtime = r.obj; // callback has stored the child pointer here // if there is runtime information, but the pointer is NULL, // the port is not enabled enabled = (bool) runtime; if(enabled) { // check if the port is disabled by a switch enabled = port_is_enabled(&p, name_buffer, buffer_size, base, runtime); } } } if(enabled) rtosc::walk_ports(p.ports, name_buffer, buffer_size, data, walker, runtime); }; //only walk valid ports if(!base) return; assert(name_buffer); //XXX buffer_size is not properly handled yet if(name_buffer[0] == 0) name_buffer[0] = '/'; char *old_end = name_buffer; while(*old_end) ++old_end; if(port_is_enabled((*base)["self:"], name_buffer, buffer_size, *base, runtime)) for(const Port &p: *base) { //if(strchr(p.name, '/')) {//it is another tree if(p.ports) {//it is another tree if(strchr(p.name,'#')) { const char *name = p.name; char *pos = old_end; while(*name != '#') *pos++ = *name++; const unsigned max = atoi(name+1); for(unsigned i=0; iatoi(m.title+4) ? atoi(m.title+4) : min; return min; } static int enum_max(Port::MetaContainer meta) { int max = 0; for(auto m:meta) if(strstr(m.title, "map ")) max = atoi(m.title+4); for(auto m:meta) if(strstr(m.title, "map ")) max = max::min(); for(auto m:meta) if(strstr(m.title, "map ")) if(!strcmp(m.value, value)) { result = atoi(m.title+4); break; } return result; } static ostream &add_options(ostream &o, Port::MetaContainer meta) { string sym_names = "xyzabcdefghijklmnopqrstuvw"; int sym_idx = 0; bool has_options = false; for(auto m:meta) if(strstr(m.title, "map ")) has_options = true; for(auto m:meta) if(strcmp(m.title, "documentation") && strcmp(m.title, "parameter") && strcmp(m.title, "max") && strcmp(m.title, "min")) printf("m.title = <%s>\n", m.title); if(!has_options) return o; o << " \n"; for(auto m:meta) { if(strstr(m.title, "map ")) { o << " " << m.value << "\n"; } } o << " \n"; return o; } static ostream &dump_t_f_port(ostream &o, string name, string doc) { o << " \n"; o << " Enable " << doc << "\n"; o << " \n"; o << " \n"; o << " \n"; o << " Disable " << doc << "\n"; o << " \n"; o << " \n"; o << " \n"; o << " Get state of " << doc << "\n"; o << " \n"; o << " \n"; o << " Value of " << doc << "\n"; o << " "; o << " \n"; o << " \n"; o << " Value of " << doc << "\n"; o << " "; o << " \n"; return o; } static ostream &dump_any_port(ostream &o, string name, string doc) { o << " \n"; o << " " << doc << "\n"; o << " \n"; return o; } static ostream &dump_generic_port(ostream &o, string name, string doc, string type) { const char *t = type.c_str(); string arg_names = "xyzabcdefghijklmnopqrstuvw"; //start out with argument separator if(*t++ != ':') return o; //now real arguments (assume [] don't exist) string args; while(*t && *t != ':') args += *t++; o << " \n"; o << " " << doc << "\n"; assert(args.length()\n"; o << " \n"; if(*t == ':') return dump_generic_port(o, name, doc, t); else return o; } void dump_ports_cb(const rtosc::Port *p, const char *name,const char*, const Ports&,void *v, void*) { std::ostream &o = *(std::ostream*)v; auto meta = p->meta(); const char *args = strchr(p->name, ':'); auto mparameter = meta.find("parameter"); auto mdoc = meta.find("documentation"); string doc; if(mdoc != p->meta().end()) doc = mdoc.value; if(meta.find("internal") != meta.end()) { doc += "[INTERNAL]"; } if(mparameter != p->meta().end()) { char type = 0; if(args) { if(strchr(args, 'f')) type = 'f'; else if(strchr(args, 'i')) type = 'i'; else if(strchr(args, 'c')) type = 'c'; else if(strchr(args, 'T')) type = 't'; else if(strchr(args, 's')) type = 's'; } if(!type) { fprintf(stderr, "rtosc port dumper: Cannot handle '%s'\n", name); fprintf(stderr, " args = <%s>\n", args); return; } if(type == 't') { dump_t_f_port(o, name, doc); return; } o << " \n"; o << " Set Value of " << doc << "\n"; if(meta.find("min") != meta.end() && meta.find("max") != meta.end() && type != 'c') { o << " \n"; o << " \n"; o << " "; } else if(meta.find("enumerated") != meta.end()) { o << " \n"; o << " \n"; add_options(o, meta); o << " \n"; o << " \n"; } else { o << " \n"; } o << " \n"; o << " \n"; o << " Get Value of " << doc << "\n"; o << " \n"; o << " \n"; o << " Value of " << doc << "\n"; if(meta.find("min") != meta.end() && meta.find("max") != meta.end() && type != 'c') { o << " \n"; o << " \n"; o << " \n"; } else if(meta.find("enumerated") != meta.end()) { o << " \n"; o << " \n"; add_options(o, meta); o << " \n"; o << " \n"; } else { o << " \n"; } o << " \n"; } else if(mdoc != meta.end() && (!args || args == std::string(""))) { dump_any_port(o, name, doc); } else if(mdoc != meta.end() && args) { dump_generic_port(o, name, doc, args); } else if(mdoc != meta.end()) { fprintf(stderr, "Skipping \"%s\"\n", name); if(args) { fprintf(stderr, " type = %s\n", args); } } else fprintf(stderr, "Skipping [UNDOCUMENTED] \"%s\"\n", name); } std::ostream &rtosc::operator<<(std::ostream &o, rtosc::OscDocFormatter &formatter) { o << "\n"; o << "\n"; o << " \n"; o << " " << formatter.prog_name << "\n"; o << " " << formatter.uri << "\n"; o << " " << formatter.doc_origin << "\n"; o << " " << formatter.author_first; o << "" << formatter.author_last << "\n"; o << " \n"; char buffer[1024]; memset(buffer, 0, sizeof(buffer)); walk_ports2(formatter.p, buffer, 1024, &o, dump_ports_cb); o << "\n"; return o; }