-
Notifications
You must be signed in to change notification settings - Fork 333
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
Performance and safety improvements #429
base: master
Are you sure you want to change the base?
Changes from 3 commits
4761ab1
b5906e2
1f5d92c
29460e0
86b1dc7
c7e74d5
93a0ff5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,6 +41,7 @@ namespace matplot { | |
class MATPLOT_EXPORTS backend_interface { | ||
/// Virtual functions you can override to create any backend | ||
public: | ||
virtual ~backend_interface() noexcept = default; | ||
/// \brief True if backend is in interactive mode | ||
/// One backends might support both interactive and | ||
/// non-interactive mode. | ||
|
@@ -213,7 +214,31 @@ namespace matplot { | |
/// This is useful when tracing the gnuplot commands | ||
/// and when generating a gnuplot file. | ||
virtual void include_comment(const std::string &text); | ||
}; | ||
}; // class backend_interface | ||
|
||
/// Captures (backend) version information. | ||
struct Version { | ||
int major{0}, minor{0}, patch{0}; | ||
constexpr bool operator==(const Version &o) const { | ||
return major == o.major && minor == o.minor && patch == o.patch; | ||
} | ||
constexpr bool operator!=(const Version &other) const { return !(*this == other); } | ||
constexpr bool operator<(const Version &other) const { | ||
if (major < other.major) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it the same as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. Looking at the rest of the code and considering There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is gone, sorry my bad. |
||
return true; | ||
else if (major == other.major) { | ||
if (minor < other.minor) | ||
return true; | ||
else if (minor == other.minor) | ||
return patch < other.patch; | ||
} | ||
return false; | ||
} | ||
constexpr bool operator>(const Version &other) const { return other < *this; } | ||
constexpr bool operator<=(const Version &other) const { return !(other < *this); } | ||
constexpr bool operator>=(const Version &other) const { return !(other > *this); } | ||
constexpr operator bool() const { return *this != Version{0,0,0}; } | ||
}; // struct Version | ||
} // namespace backend | ||
|
||
} // namespace matplot | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ | |
#include <matplot/util/common.h> | ||
#include <matplot/util/popen.h> | ||
#include <iostream> | ||
#include <regex> | ||
#include <charconv> | ||
#include <thread> | ||
#include <cstring> | ||
#include <cstdlib> | ||
|
@@ -317,13 +317,28 @@ namespace matplot::backend { | |
} | ||
} | ||
|
||
/// returns the next word in text after prefix terminated with white space. | ||
static std::string_view word_after(std::string_view text, std::string_view prefix) | ||
{ | ||
auto res = text.substr(0,0); | ||
if (auto b = text.find(prefix); b != std::string_view::npos) { | ||
b += prefix.length(); | ||
while (b < text.length() && std::isspace(text[b])) | ||
++b; // skip white space before word | ||
auto e = b; | ||
while (e < text.length() && !std::isspace(text[e])) | ||
++e; // scan until white space or end | ||
res = text.substr(b, e-b); | ||
} | ||
return res; | ||
} | ||
|
||
std::string gnuplot::default_terminal_type() { | ||
static std::string terminal_type; | ||
const bool dont_know_term_type = terminal_type.empty(); | ||
if (dont_know_term_type) { | ||
terminal_type = run_and_get_output("gnuplot -e \"show terminal\" 2>&1"); | ||
terminal_type = std::regex_replace(terminal_type, | ||
std::regex("[^]*terminal type is ([^ ]+)[^]*"), "$1"); | ||
terminal_type = word_after(terminal_type, "terminal type is "); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Heh... Great! :) |
||
const bool still_dont_know_term_type = terminal_type.empty(); | ||
if (still_dont_know_term_type) { | ||
terminal_type = "qt"; | ||
|
@@ -334,50 +349,24 @@ namespace matplot::backend { | |
|
||
bool gnuplot::terminal_is_available(std::string_view term) { | ||
std::string msg = run_and_get_output("gnuplot -e \"set terminal " + | ||
std::string(term.data()) + "\" 2>&1"); | ||
std::string{term} + "\" 2>&1"); | ||
return msg.empty(); | ||
} | ||
|
||
std::tuple<int, int, int> gnuplot::gnuplot_version() { | ||
static std::tuple<int, int, int> version{0, 0, 0}; | ||
const bool dont_know_gnuplot_version_yet = | ||
version == std::tuple<int, int, int>({0, 0, 0}); | ||
if (dont_know_gnuplot_version_yet) { | ||
std::string version_str = | ||
run_and_get_output("gnuplot --version 2>&1"); | ||
std::string version_major = std::regex_replace( | ||
version_str, | ||
std::regex("[^]*gnuplot (\\d+)\\.\\d+ patchlevel \\d+ *"), | ||
"$1"); | ||
std::string version_minor = std::regex_replace( | ||
version_str, | ||
std::regex("[^]*gnuplot \\d+\\.(\\d+) patchlevel \\d+ *"), | ||
"$1"); | ||
std::string version_patch = std::regex_replace( | ||
version_str, | ||
std::regex("[^]*gnuplot \\d+\\.\\d+ patchlevel (\\d+) *"), | ||
"$1"); | ||
try { | ||
std::get<0>(version) = std::stoi(version_major); | ||
} catch (...) { | ||
std::get<0>(version) = 0; | ||
} | ||
try { | ||
std::get<1>(version) = std::stoi(version_minor); | ||
} catch (...) { | ||
std::get<1>(version) = 0; | ||
} | ||
try { | ||
std::get<2>(version) = std::stoi(version_patch); | ||
} catch (...) { | ||
std::get<2>(version) = 0; | ||
} | ||
const bool still_dont_know_gnuplot_version = | ||
version == std::tuple<int, int, int>({0, 0, 0}); | ||
if (still_dont_know_gnuplot_version) { | ||
// assume it's 5.2.6 by convention | ||
version = std::tuple<int, int, int>({5, 2, 6}); | ||
Version gnuplot::gnuplot_version() { | ||
static auto version = Version{}; | ||
if (!version) { // unknown version | ||
const auto version_str = run_and_get_output("gnuplot --version 2>&1"); | ||
const auto major = word_after(version_str, "gnuplot"); | ||
const auto minor = word_after(major, "."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This pattern is a little confusing when the function is called repeatedly. Let's say the output is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, the name |
||
const auto patch = word_after(version_str, "patchlevel"); | ||
if (!major.empty() && !minor.empty() && !patch.empty()) { | ||
std::from_chars(major.data(), major.data()+major.length(), version.major); | ||
std::from_chars(minor.data(), minor.data()+minor.length(), version.minor); | ||
std::from_chars(patch.data(), patch.data()+patch.length(), version.patch); | ||
} | ||
if (!version) // still unknown | ||
version = {5, 2, 6}; // assume by convention | ||
} | ||
return version; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,7 +47,7 @@ namespace matplot::backend { | |
/// Identify the default terminal type in the system | ||
static std::string default_terminal_type(); | ||
static bool terminal_is_available(std::string_view); | ||
static std::tuple<int, int, int> gnuplot_version(); | ||
static Version gnuplot_version(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So converting the output to a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, I have reverted |
||
static bool terminal_has_title_option(const std::string &t); | ||
static bool terminal_has_size_option(const std::string &t); | ||
static bool terminal_has_position_option(const std::string &t); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -426,7 +426,7 @@ namespace matplot { | |
run_command("set polar"); | ||
} | ||
auto set_or_unset_axis = [this](class axis_type &ax, | ||
std::string axis_name, | ||
const std::string &axis_name, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If that was your concern about breaking the API, both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No concerns here: that is just a lambda expression - internal stuff. |
||
bool minor_ticks = false) { | ||
// cb is the only axis we don't unset if tics are empty | ||
// r-axis labels should still be handled even if axis is invisible since we use the grid | ||
|
@@ -694,8 +694,7 @@ namespace matplot { | |
// Gnuplot version needs to be 5.2.6+ for keyentry | ||
bool ok = true; | ||
if (parent_->backend_->consumes_gnuplot_commands()) { | ||
if (backend::gnuplot::gnuplot_version() < | ||
std::tuple<int, int, int>{5, 2, 6}) { | ||
if (backend::gnuplot::gnuplot_version() < backend::Version{5, 2, 6}) { | ||
ok = false; | ||
} | ||
} | ||
|
@@ -916,9 +915,8 @@ namespace matplot { | |
static bool msg_shown_once = false; | ||
// Gnuplot version needs to be 5.2.6+ for keyentry | ||
if (parent_->backend_->consumes_gnuplot_commands()) { | ||
std::tuple<int, int, int> v = | ||
backend::gnuplot::gnuplot_version(); | ||
if (v < std::tuple<int, int, int>{5, 2, 6}) { | ||
const auto v = backend::gnuplot::gnuplot_version(); | ||
if (v < backend::Version{5, 2, 6}) { | ||
if (!msg_shown_once) { | ||
std::cerr | ||
<< "You need gnuplot 5.2.6+ to include legends" | ||
|
@@ -2745,9 +2743,9 @@ namespace matplot { | |
} | ||
|
||
std::vector<function_line_handle> | ||
axes_type::fplot(std::vector<function_line::function_type> equations, | ||
std::array<double, 2> x_range, | ||
std::vector<std::string> line_specs) { | ||
axes_type::fplot(const std::vector<function_line::function_type> &equations, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. All these API changes are OK. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's OK if the thing is recompiled, but I am not sure at ABI level, and I do not know if anyone cares. |
||
const std::array<double, 2> &x_range, | ||
const std::vector<std::string> &line_specs) { | ||
axes_silencer temp_silencer_{this}; | ||
std::vector<function_line_handle> res; | ||
auto it_line_specs = line_specs.begin(); | ||
|
@@ -2764,9 +2762,9 @@ namespace matplot { | |
} | ||
|
||
std::vector<function_line_handle> | ||
axes_type::fplot(std::vector<function_line::function_type> equations, | ||
std::vector<double> x_range, | ||
std::vector<std::string> line_specs) { | ||
axes_type::fplot(const std::vector<function_line::function_type>& equations, | ||
const std::vector<double>& x_range, | ||
const std::vector<std::string>& line_specs) { | ||
return this->fplot(equations, to_array<2>(x_range), line_specs); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -634,8 +634,7 @@ namespace matplot { | |
// In gnuplot 5.5 we have the wall function to set the axes color | ||
// with a rectangle workaround, which does not work well for 3d. | ||
static const auto v = backend::gnuplot::gnuplot_version(); | ||
const bool has_wall_option = | ||
std::get<0>(v) > 5 || (std::get<0>(v) == 5 && std::get<1>(v) >= 5); | ||
Comment on lines
-637
to
-638
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this was my trigger :-) |
||
const bool has_wall_option = (v >= backend::Version{5,5,0}); | ||
// So we only plot the default background if it's not 3d or version is | ||
// higher than 5.5. Otherwise, gnuplot won't be able to set the axes | ||
// colors. | ||
|
@@ -895,4 +894,4 @@ namespace matplot { | |
|
||
bool figure_type::should_close() { return backend_->should_close(); } | ||
|
||
} // namespace matplot | ||
} // namespace matplot |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs MATPLOT_EXPORTS if it's part of the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would also be good to maintain the snake_case convention.
version
or maybegnuplot::version
since there's no other backend.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make it snake case, sorry.
I could also move Version to gnuplot, I just thought the class is more general than just gnuplot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Outside
gnuplot
is also OK. I was thinking about the name-grabbing, but that's also OK. We won't needmatplot::version
for anything else.