Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable sorting for walk_ports #60

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ maketestcpp(headerlib)
maketestcpp(test-walker)
maketestcpp(walk-ports)
maketestcpp(path-search)
maketestcpp(port-cmp)

if(PERF_TEST)
maketestcpp(performance)
Expand Down
14 changes: 14 additions & 0 deletions include/rtosc/ports.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
*********************/
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down
92 changes: 70 additions & 22 deletions src/cpp/ports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,28 @@ MergePorts::MergePorts(std::initializer_list<const rtosc::Ports*> 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.
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
};

/**
Expand All @@ -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)
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
};
Expand All @@ -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)
Expand All @@ -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<const Port*> 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);
}
}

Expand Down Expand Up @@ -1585,7 +1633,7 @@ std::ostream &rtosc::operator<<(std::ostream &o, rtosc::OscDocFormatter &formatt
o << " </meta>\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 << "</osc_unit>\n";
return o;
}
Expand Down
2 changes: 1 addition & 1 deletion src/cpp/savefile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
47 changes: 47 additions & 0 deletions test/port-cmp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include <rtosc/ports.h>

#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();
}
39 changes: 37 additions & 2 deletions test/walk-ports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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();
}