From b84e4be3c79c92ecece72c9a6a70de3be74f6c65 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Sun, 23 Jan 2022 11:00:20 +0100 Subject: [PATCH] Enable sorting for walk_ports --- CMakeLists.txt | 1 + include/rtosc/ports.h | 14 +++++++ src/cpp/ports.cpp | 92 ++++++++++++++++++++++++++++++++----------- src/cpp/savefile.cpp | 2 +- test/port-cmp.cpp | 47 ++++++++++++++++++++++ test/walk-ports.cpp | 39 +++++++++++++++++- 6 files changed, 170 insertions(+), 25 deletions(-) create mode 100644 test/port-cmp.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 90ace24..ae4120c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,7 @@ maketestcpp(headerlib) maketestcpp(test-walker) maketestcpp(walk-ports) maketestcpp(path-search) +maketestcpp(port-cmp) if(PERF_TEST) maketestcpp(performance) diff --git a/include/rtosc/ports.h b/include/rtosc/ports.h index 8122fa0..b60aeb8 100644 --- a/include/rtosc/ports.h +++ b/include/rtosc/ports.h @@ -284,6 +284,17 @@ int canonicalize_arg_vals(rtosc_arg_val_t* av, size_t n, void map_arg_vals(rtosc_arg_val_t* av, size_t n, Port::MetaContainer meta); +/** + * Portname comparison function. + * Behaves like strcmp(), but 2-way-comparison and ignoring match patterns. + * This means two ports with same name and different match patterns are equal. + * @return true iff p1 < p2 + */ +bool port_is_less(const char* p1, const char* p2); +inline bool port_is_less(const Port* p1, const Port* p2) { + return port_is_less(p1->name, p2->name); +} + /********************* * Port walking code * *********************/ @@ -311,6 +322,8 @@ typedef void(*port_walker_t)(const Port*,const char*,const char*, * reset to zero over the full length! * @param buffer_size Size of name_buffer * @param data Data that should be available in the callback + * @param sorted Whether the callbacks shall be sorted alphabetically. + * Ports with the same name before a colon are kept stable. * @param walker Callback function * @param expand_bundles Whether walking over bundles without subports * invokes walking over each of the bundle's port @@ -323,6 +336,7 @@ void walk_ports(const Ports *base, size_t buffer_size, void *data, port_walker_t walker, + bool sorted = false, bool expand_bundles = true, void *runtime = NULL, bool ranges = false); diff --git a/src/cpp/ports.cpp b/src/cpp/ports.cpp index 09b7958..c41f7d9 100644 --- a/src/cpp/ports.cpp +++ b/src/cpp/ports.cpp @@ -893,6 +893,28 @@ MergePorts::MergePorts(std::initializer_list c) refreshMagic(); } +bool rtosc::port_is_less(const char* p1, const char* p2) +{ + for(; *p1 && *p2; ++p1, ++p2) + { + if(*p1 == *p2) + { + if(*p1 == ':') + return false; + } + else + { + if(*p1 == ':') + return true; + else if(*p2 == ':') + return false; + else // different, and none is ':' or ' ' + return *p1 < *p2; + } + } + return (*p1 == ':' || *p2 == ':') ? false : *p1 < *p2; +} + /** * @brief Check if the port @p port is enabled * @param port The port to be checked. Usually of type rRecur* or rSelf. @@ -982,6 +1004,7 @@ bool port_is_enabled(const Port* port, char* loc, size_t loc_size, static void walk_ports_recurse(const Port& p, char* name_buffer, size_t buffer_size, const Ports& base, void* data, port_walker_t walker, + bool sorted, void* runtime, const char* old_end, bool expand_bundles, bool ranges) { @@ -1030,7 +1053,7 @@ static void walk_ports_recurse(const Port& p, char* name_buffer, } if(enabled) rtosc::walk_ports(p.ports, name_buffer, buffer_size, - data, walker, expand_bundles, runtime, ranges); + data, walker, sorted, expand_bundles, runtime, ranges); }; /** @@ -1053,6 +1076,7 @@ char pointer example: static void walk_ports_recurse0(const Port& p, char* name_buffer, size_t buffer_size, const Ports* base, void* data, port_walker_t walker, + bool sorted, void* runtime, char* const old_end, char* write_head, bool expand_bundles, const char* read_head, bool ranges) @@ -1082,6 +1106,7 @@ static void walk_ports_recurse0(const Port& p, char* name_buffer, int written = sprintf(write_head,"[0,%d]/", max-1); //Recurse walk_ports_recurse0(p, name_buffer, buffer_size, base, data, walker, + sorted, runtime, old_end, write_head + written, expand_bundles, read_head, ranges); } @@ -1090,6 +1115,7 @@ static void walk_ports_recurse0(const Port& p, char* name_buffer, int written = sprintf(write_head,"%d/",i); //Recurse walk_ports_recurse0(p, name_buffer, buffer_size, base, data, walker, + sorted, runtime, old_end, write_head + written, expand_bundles, read_head, ranges); } @@ -1102,7 +1128,9 @@ static void walk_ports_recurse0(const Port& p, char* name_buffer, *write_head = 0; //Recurse walk_ports_recurse(p, name_buffer, buffer_size, - *base, data, walker, runtime, old_end, + *base, data, walker, + sorted, + runtime, old_end, expand_bundles, ranges); } }; @@ -1112,6 +1140,7 @@ void rtosc::walk_ports(const Ports *base, size_t buffer_size, void *data, port_walker_t walker, + bool sorted, bool expand_bundles, void* runtime, bool ranges) @@ -1129,30 +1158,49 @@ void rtosc::walk_ports(const Ports *base, 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 + { + auto handle_subport = [name_buffer, buffer_size, base, data, walker, sorted, + runtime, old_end, expand_bundles, ranges](const Port& p) + { + //if(strchr(p.name, '/')) {//it is another tree + if(p.ports) {//it is another tree - walk_ports_recurse0(p, name_buffer, buffer_size, - base, data, walker, runtime, old_end, old_end, - expand_bundles, p.name, ranges); + walk_ports_recurse0(p, name_buffer, buffer_size, + base, data, walker, sorted, + runtime, old_end, old_end, + expand_bundles, p.name, ranges); - } else { - if(strchr(p.name,'#')) { - bundle_foreach(p, p.name, old_end, name_buffer, *base, - data, runtime, walker, expand_bundles, true, ranges); } else { - //Append the path - scat(name_buffer, p.name); - - //Apply walker function - walker(&p, name_buffer, old_end, *base, data, runtime); + if(strchr(p.name,'#')) { + bundle_foreach(p, p.name, old_end, name_buffer, *base, + data, runtime, walker, expand_bundles, true, ranges); + } else { + //Append the path + scat(name_buffer, p.name); + + //Apply walker function + walker(&p, name_buffer, old_end, *base, data, runtime); + } } - } - //Remove the rest of the path - char *tmp = old_end; - while(*tmp) *tmp++=0; + //Remove the rest of the path + char *tmp = old_end; + while(*tmp) *tmp++=0; + }; + + if(sorted) + { + std::vector subports_sorted; + subports_sorted.reserve(base->size()); + for(const Port& p : *base) subports_sorted.push_back(&p); + auto my_port_is_less = [] (const Port* port1, const Port* port2) -> bool + { + return port_is_less(port1, port2); + }; + std::stable_sort(subports_sorted.begin(), subports_sorted.end(), my_port_is_less); + for(const Port* p: subports_sorted) handle_subport(*p); + } + else for(const Port &p: *base) handle_subport(p); } } @@ -1585,7 +1633,7 @@ std::ostream &rtosc::operator<<(std::ostream &o, rtosc::OscDocFormatter &formatt o << " \n"; char buffer[1024]; memset(buffer, 0, sizeof(buffer)); - walk_ports(formatter.p, buffer, 1024, &o, dump_ports_cb, false, nullptr, true); + walk_ports(formatter.p, buffer, 1024, &o, dump_ports_cb, false, false, nullptr, true); o << "\n"; return o; } diff --git a/src/cpp/savefile.cpp b/src/cpp/savefile.cpp index f059baa..a4c4cf5 100644 --- a/src/cpp/savefile.cpp +++ b/src/cpp/savefile.cpp @@ -276,7 +276,7 @@ std::string get_changed_values(const Ports& ports, void* runtime) }; walk_ports(&ports, port_buffer, buffersize, &data, on_reach_port, false, - runtime); + false, runtime); if(data.res.length()) // remove trailing newline data.res.resize(data.res.length()-1); diff --git a/test/port-cmp.cpp b/test/port-cmp.cpp new file mode 100644 index 0000000..8062bad --- /dev/null +++ b/test/port-cmp.cpp @@ -0,0 +1,47 @@ +#include + +#include "common.h" + +using namespace rtosc; + +int main() +{ + assert_true (port_is_less("xa", "xb"), "ports without pattern 1", __LINE__); + assert_false(port_is_less("xb", "xa"), "ports without pattern 2", __LINE__); + assert_false(port_is_less("xa", "xa"), "ports without pattern 3", __LINE__); + assert_true (port_is_less("x" , "xa"), "ports without pattern 4", __LINE__); + assert_false(port_is_less("xa", "x" ), "ports without pattern 5", __LINE__); + assert_true (port_is_less("x" , "y" ), "ports without pattern 6", __LINE__); + assert_false(port_is_less("y" , "x" ), "ports without pattern 7", __LINE__); + + assert_true (port_is_less("ax:", "ay:"), "ports with pattern 1", __LINE__); + assert_false(port_is_less("ay:", "ax:"), "ports with pattern 2", __LINE__); + assert_false(port_is_less("ay:", "ay:"), "ports with pattern 3", __LINE__); + assert_true (port_is_less("a:", "ax:"), "ports with pattern 4", __LINE__); + assert_false(port_is_less("ax:", "a:"), "ports with pattern 5", __LINE__); + assert_false(port_is_less("a:" , "a:"), "ports with pattern 6", __LINE__); + assert_false(port_is_less("a:i", "a:f"), "ports with pattern 7", __LINE__); + assert_false(port_is_less("a:f", "a:i"), "ports with pattern 8", __LINE__); + + // same length + assert_true (port_is_less("ax:", "ay" ), "mixes ports 1", __LINE__); + assert_false(port_is_less("ay:", "ax" ), "mixes ports 2", __LINE__); + assert_true (port_is_less("ax" , "ay:"), "mixes ports 3", __LINE__); + assert_false(port_is_less("ay" , "ax:"), "mixes ports 4", __LINE__); + assert_false(port_is_less("ax:", "ax" ), "mixes ports 5", __LINE__); + assert_false(port_is_less("ax" , "ax:"), "mixes ports 6", __LINE__); + + // different length + assert_false(port_is_less("ax:", "a" ), "mixes ports 7", __LINE__); + assert_true (port_is_less("a:" , "ax:"), "mixes ports 8", __LINE__); + assert_true (port_is_less("a:" , "ax:"), "mixes ports 9", __LINE__); + assert_false(port_is_less("ax:", "a" ), "mixes ports 10", __LINE__); + assert_true (port_is_less("a:i", "ax:"), "mixes ports 11", __LINE__); + assert_false(port_is_less("ax:", "a:i"), "mixes ports 12", __LINE__); + assert_false(port_is_less("a:i", "a:ii"), "mixes ports 13", __LINE__); + assert_false(port_is_less("a:ii","a:i" ), "mixes ports 14", __LINE__); + + // more practical tests in walk-ports tests + + return test_summary(); +} diff --git a/test/walk-ports.cpp b/test/walk-ports.cpp index 23ad002..34bc1c8 100644 --- a/test/walk-ports.cpp +++ b/test/walk-ports.cpp @@ -56,13 +56,22 @@ void append_str(const rtosc::Port*, const char *name, const char*, *res += ";"; } +void append_str_and_args(const rtosc::Port* p, const char *, const char*, + const rtosc::Ports&, void *resVoid, void*) +{ + std::string* res = (std::string*)resVoid; + *res += p->name; + *res += ";"; +} + void check_all_subports(const rtosc::Ports& root, const char* exp, - const char* testcase, int line) + const char* testcase, int line, + bool app_args = false, bool sorted = false) { char buffer[1024]; memset(buffer, 0, sizeof(buffer)); std::string res; - rtosc::walk_ports(&root, buffer, 1024, &res, append_str); + rtosc::walk_ports(&root, buffer, 1024, &res, app_args ? append_str_and_args : append_str, sorted); assert_str_eq(exp, res.c_str(), testcase, line); } @@ -91,6 +100,32 @@ int main() check_all_subports(multiple_ports, "/c/d/e;/a/x;/a/y;/c/d/;/a/;/b;/b2;", "walk_ports with multiple common prefixes", __LINE__); + const rtosc::Ports sort_ports = { + {"c:ii", 0, 0, null_fn}, + {"c:i", 0, 0, null_fn}, + {"c:", 0, 0, null_fn}, + {"c", 0, 0, null_fn}, + {"ccc", 0, 0, null_fn}, + {"b", 0, 0, null_fn}, + {"a", 0, 0, null_fn}, + }; + const rtosc::Ports sort_ports_reversed = { + {"ccc", 0, 0, null_fn}, + {"c", 0, 0, null_fn}, + {"c:", 0, 0, null_fn}, + {"c:i", 0, 0, null_fn}, + {"c:ii", 0, 0, null_fn}, + {"b", 0, 0, null_fn}, + {"a", 0, 0, null_fn}, + }; + + check_all_subports(sort_ports, "c:ii;c:i;c:;c;ccc;b;a;", + "walk_ports unsorted", __LINE__, true, false); + check_all_subports(sort_ports, "a;b;c:ii;c:i;c:;c;ccc;", + "walk_ports sorted 1", __LINE__, true, true); + check_all_subports(sort_ports_reversed, "a;b;c;c:;c:i;c:ii;ccc;", + "walk_ports sorted 2", __LINE__, true, true); + return test_summary(); }