diff options
author | Benji Dial <benji@benjidial.net> | 2023-05-29 16:36:19 -0400 |
---|---|---|
committer | Benji Dial <benji@benjidial.net> | 2023-05-29 16:36:19 -0400 |
commit | 97c79ff771d4993e322d0d6c44f265180797b2eb (patch) | |
tree | 5513cf25721cf21c06efd913ed2f82b980e3cb24 /bench | |
parent | 338549f9cd49fa0f3001826c6605663fa6dd019b (diff) | |
download | lib94-97c79ff771d4993e322d0d6c44f265180797b2eb.tar.gz |
a whole lot more
Diffstat (limited to 'bench')
-rw-r--r-- | bench/bench_window.cpp | 324 | ||||
-rw-r--r-- | bench/bench_window.hpp | 112 | ||||
-rw-r--r-- | bench/core_widget.cpp | 162 | ||||
-rw-r--r-- | bench/core_widget.hpp | 46 | ||||
-rw-r--r-- | bench/main.cpp | 31 | ||||
-rw-r--r-- | bench/main.hpp | 16 |
6 files changed, 691 insertions, 0 deletions
diff --git a/bench/bench_window.cpp b/bench/bench_window.cpp new file mode 100644 index 0000000..bcdc12d --- /dev/null +++ b/bench/bench_window.cpp @@ -0,0 +1,324 @@ +#include <fstream> + +#include "bench_window.hpp" +#include "main.hpp" + +bench_window::bench_window() + : runner_stop_now(false), + runner_stop_on_death(true), + runner_stop_on_win(true), + runner_update_ui(true), + control_box(Gtk::Orientation::VERTICAL), + new_round_button("new round"), + single_step_button("single step"), + start_button("start"), + stop_button("stop"), + draw_each_step_toggle("draw each step"), + pause_on_death_toggle("pause on death"), + pause_on_win_toggle("pause on win"), + add_warrior_button("add warrior"), + remove_warrior_button("remove warrior"), + runner({}), + runner_active(false) { + + warrior_list_store = Gtk::TreeStore::create(warrior_list_columns); + instructions_store = Gtk::TreeStore::create(instructions_columns); + + warrior_list_view.set_model(warrior_list_store); + instructions_view.set_model(instructions_store); + + warrior_list_view.get_selection()->set_mode(Gtk::SelectionMode::SINGLE); + instructions_view.get_selection()->set_mode(Gtk::SelectionMode::NONE); + + warrior_list_view.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &bench_window::update_buttons)); + + warrior_list_view.append_column("name", warrior_list_columns.warrior_name); + warrior_list_view.append_column("#p", warrior_list_columns.processes); + warrior_list_view.append_column("pc", warrior_list_columns.next_pc); + + instructions_view.append_column("", instructions_columns.address); + instructions_view.append_column("", instructions_columns.instruction); + + for (lib94::number_t i = 0; i < LIB94_CORE_SIZE; ++i) { + auto row = instructions_store->append(); + (*row)[instructions_columns.address] = i; + } + + update_ui(); + + draw_each_step_toggle.activate(); + pause_on_death_toggle.activate(); + pause_on_win_toggle.activate(); + + new_round_button.signal_clicked().connect(sigc::mem_fun(*this, &bench_window::on_click_new_round)); + single_step_button.signal_clicked().connect(sigc::mem_fun(*this, &bench_window::on_click_single_step)); + start_button.signal_clicked().connect(sigc::mem_fun(*this, &bench_window::on_click_start)); + stop_button.signal_clicked().connect(sigc::mem_fun(*this, &bench_window::on_click_stop)); + add_warrior_button.signal_clicked().connect(sigc::mem_fun(*this, &bench_window::on_click_add_warrior)); + remove_warrior_button.signal_clicked().connect(sigc::mem_fun(*this, &bench_window::on_click_remove_warrior)); + + draw_each_step_toggle.signal_toggled().connect(sigc::mem_fun(*this, &bench_window::on_toggle_draw_each_step)); + pause_on_death_toggle.signal_toggled().connect(sigc::mem_fun(*this, &bench_window::on_toggle_pause_on_death)); + pause_on_win_toggle.signal_toggled().connect(sigc::mem_fun(*this, &bench_window::on_toggle_pause_on_win)); + + control_box.append(new_round_button); + control_box.append(single_step_button); + control_box.append(start_button); + control_box.append(stop_button); + control_box.append(draw_each_step_toggle); + control_box.append(pause_on_death_toggle); + control_box.append(pause_on_win_toggle); + control_box.append(add_warrior_button); + control_box.append(remove_warrior_button); + + warrior_list_scroll.set_child(warrior_list_view); + warrior_list_scroll.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC); + warrior_list_scroll.set_vexpand(); + + control_box.append(warrior_list_scroll); + + control_box.append(core_rate_label); + control_box.append(core_render_label); + + instructions_scroll.set_child(instructions_view); + instructions_scroll.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC); + + control_box.set_size_request(200, -1); + core.set_expand(); + instructions_scroll.set_size_request(200, -1); + + main_box.append(control_box); + main_box.append(core); + main_box.append(instructions_scroll); + + set_child(main_box); + set_title("lib94 bench"); + + runner_update_ui_dispatcher.connect(sigc::mem_fun(*this, &bench_window::update_ui)); + runner_stopping_dispatcher.connect(sigc::mem_fun(*this, &bench_window::on_runner_stopping)); +} + +void bench_window::add_modified_for_instruction_view(std::set<lib94::number_t> the_set) { + for (lib94::number_t n : the_set) + modified_addresses_for_instructions_view.insert(n); +} + +const lib94::warrior *bench_window::do_step() { + auto time = std::chrono::system_clock::now(); + last_core_step_distance = time - last_core_step_start; + last_core_step_start = time; + + const lib94::warrior *result = lib94::single_step(); + + core.mut.lock(); + core.age_all(); + core.add_new_writes(lib94::get_written_addresses()); + core.add_new_reads(lib94::get_read_addresses()); + core.add_new_executions(lib94::get_executed_addresses()); + core.mut.unlock(); + + add_modified_for_instruction_view(lib94::get_written_addresses()); + add_modified_for_instruction_view(lib94::get_read_addresses()); + add_modified_for_instruction_view(lib94::get_executed_addresses()); + + lib94::clear_address_sets(); + return result; +} + +void bench_window::runner_main() { + std::chrono::system_clock::time_point last_step = std::chrono::system_clock::now(); + + core_mutex.lock(); + while (!runner_stop_now) { + bool death = do_step() != 0; + + if (runner_stop_on_death && death) + break; + if (runner_stop_on_win && death && lib94::alive_warrior_count() == 1) + break; + if (runner_update_ui) + runner_update_ui_dispatcher.emit(); + + core_mutex.unlock(); + std::this_thread::sleep_until(last_step + time_between_steps); + last_step = std::chrono::system_clock::now(); + core_mutex.lock(); + } + + runner_stopping_dispatcher.emit(); + core_mutex.unlock(); +} + +void bench_window::on_click_single_step() { + do_step(); + update_ui(); +} + +void bench_window::on_click_new_round() { + lib94::clear_core({ + .op = lib94::DAT, + .mod = lib94::F, + .amode = lib94::DIRECT, + .bmode = lib94::DIRECT, + .anumber = 0, + .bnumber = 0 + }); + + lib94::init_round(warriors.data(), warriors.size()); + + core.mut.lock(); + core.age_scale = std::pow(2.0 / 3.0, 1.0 / (float)warriors.size()); + core.clear_all(); + core.mut.unlock(); + + modified_addresses_for_instructions_view.clear(); + for (lib94::number_t i = 0; i < LIB94_CORE_SIZE; ++i) + instructions_store->children()[i][instructions_columns.instruction] + = lib94::instruction_to_string(lib94::get_instruction(i)); + + update_ui(); +} + +void bench_window::on_click_start() { + runner_active = true; + update_ui(); + runner_stop_now = false; + runner = std::thread(sigc::mem_fun(*this, &bench_window::runner_main)); +} + +void bench_window::on_click_stop() { + runner_stop_now = true; +} + +void bench_window::on_toggle_draw_each_step() { + runner_update_ui = draw_each_step_toggle.get_active(); +} + +void bench_window::on_toggle_pause_on_death() { + runner_stop_on_death = pause_on_death_toggle.get_active(); +} + +void bench_window::on_toggle_pause_on_win() { + runner_stop_on_win = pause_on_win_toggle.get_active(); +} + +void bench_window::on_add_warrior_dialog_response(int response_id, Gtk::FileChooserDialog *dialog) { + if (response_id == Gtk::ResponseType::OK) { + + Glib::RefPtr<Gio::File> file = dialog->get_file(); + char *contents; + gsize length; + file->load_contents(contents, length); + + auto w = lib94::compile_warrior(std::string(contents, length)); + + delete contents; + + if (std::holds_alternative<lib94::warrior *>(w)) { + warriors.push_back(std::get<lib94::warrior *>(w)); + on_click_new_round(); + } + + else { + Gtk::MessageDialog *md = new Gtk::MessageDialog(std::string("Failed to compile: ") + std::get<std::string>(w)); + md->set_transient_for(*this); + md->set_modal(); + md->signal_response().connect([md](int) {delete md;}); + md->show(); + } + } + + delete dialog; +} + +void bench_window::on_click_add_warrior() { + Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog("select a warrior"); + + dialog->set_transient_for(*this); + dialog->set_modal(); + dialog->signal_response().connect(sigc::bind(sigc::mem_fun(*this, &bench_window::on_add_warrior_dialog_response), dialog)); + dialog->add_button("add", Gtk::ResponseType::OK); + dialog->add_button("cancel", Gtk::ResponseType::CANCEL); + + Glib::RefPtr<Gtk::FileFilter> text_filter = Gtk::FileFilter::create(); + text_filter->add_mime_type("text/plain"); + text_filter->set_name("text files"); + dialog->add_filter(text_filter); + + Glib::RefPtr<Gtk::FileFilter> any_filter = Gtk::FileFilter::create(); + any_filter->add_pattern("*"); + any_filter->set_name("all files"); + dialog->add_filter(any_filter); + + dialog->show(); +} + +void bench_window::on_click_remove_warrior() { + auto iter = warrior_list_view.get_selection()->get_selected(); + if (iter) { + + while (!(*iter)[warrior_list_columns.warrior]) + iter = iter->parent(); + + const lib94::warrior *w = (*iter)[warrior_list_columns.warrior]; + + for (auto i = warriors.begin(); i != warriors.end(); ++i) + if (*i == w) { + warriors.erase(i); + break; + } + + on_click_new_round(); + + delete w; + + } +} + +void bench_window::update_buttons() { + new_round_button.set_sensitive(!runner_active && warriors.size() > 0); + single_step_button.set_sensitive(!runner_active && lib94::alive_warrior_count() > 0); + start_button.set_sensitive(!runner_active && lib94::alive_warrior_count() > 0); + stop_button.set_sensitive(runner_active); + add_warrior_button.set_sensitive(!runner_active); + remove_warrior_button.set_sensitive(!runner_active && warrior_list_view.get_selection()->get_selected()); +} + +void bench_window::update_ui() { + if (runner_active) + core_mutex.lock(); + + update_buttons(); + + core_render_label.set_text("core render: " + ns_to_string(core.last_draw_time)); + core_rate_label.set_text("core rate: " + hz_to_string(1000000000.0 / (double)last_core_step_distance.count())); + + core.queue_draw(); + + warrior_list_store->clear(); + + for (const lib94::warrior *w : warriors) { + auto w_row = warrior_list_store->append(); + (*w_row)[warrior_list_columns.warrior] = w; + (*w_row)[warrior_list_columns.warrior_name] = w->name; + auto procs = lib94::get_processes(w); + (*w_row)[warrior_list_columns.processes] = procs.size(); + (*w_row)[warrior_list_columns.next_pc] = procs.size() ? std::to_string(procs[0]) : ""; + } + + //warrior_list_view.expand_all(); + + for (lib94::number_t i : modified_addresses_for_instructions_view) + instructions_store->children()[i][instructions_columns.instruction] + = lib94::instruction_to_string(lib94::get_instruction(i)); + modified_addresses_for_instructions_view.clear(); + + if (runner_active) + core_mutex.unlock(); +} + +void bench_window::on_runner_stopping() { + runner.join(); + runner_active = false; + update_ui(); +} diff --git a/bench/bench_window.hpp b/bench/bench_window.hpp new file mode 100644 index 0000000..86b2f1c --- /dev/null +++ b/bench/bench_window.hpp @@ -0,0 +1,112 @@ +#ifndef LIB94_BENCH_BENCH_WINDOW_HPP +#define LIB94_BENCH_BENCH_WINDOW_HPP + +#include <gtkmm.h> +#include <thread> + +#include "core_widget.hpp" + +class bench_window : public Gtk::Window { +public: + bench_window(); + + Glib::Dispatcher runner_update_ui_dispatcher; + Glib::Dispatcher runner_stopping_dispatcher; + + bool runner_stop_now; + bool runner_stop_on_death; + bool runner_stop_on_win; + bool runner_update_ui; + + core_widget core; + +private: + class warrior_list_columns_record : public Gtk::TreeModelColumnRecord { + public: + Gtk::TreeModelColumn<Glib::ustring> warrior_name; + Gtk::TreeModelColumn<size_t> processes; + Gtk::TreeModelColumn<Glib::ustring> next_pc; + Gtk::TreeModelColumn<const lib94::warrior *> warrior; + + warrior_list_columns_record() { + add(warrior_name); + add(processes); + add(next_pc); + add(warrior); + } + }; + + class instructions_columns_record : public Gtk::TreeModelColumnRecord { + public: + Gtk::TreeModelColumn<lib94::number_t> address; + Gtk::TreeModelColumn<Glib::ustring> instruction; + + instructions_columns_record() { + add(address); + add(instruction); + } + }; + + Gtk::ScrolledWindow warrior_list_scroll; + Gtk::ScrolledWindow instructions_scroll; + + warrior_list_columns_record warrior_list_columns; + instructions_columns_record instructions_columns; + + Glib::RefPtr<Gtk::TreeStore> warrior_list_store; + Glib::RefPtr<Gtk::TreeStore> instructions_store; + + Gtk::TreeView warrior_list_view; + Gtk::TreeView instructions_view; + + std::set<lib94::number_t> modified_addresses_for_instructions_view; + + void add_modified_for_instruction_view(std::set<lib94::number_t> the_set); + + Gtk::Box main_box; + Gtk::Box control_box; + + Gtk::Button new_round_button; + Gtk::Button single_step_button; + Gtk::Button start_button; + Gtk::Button stop_button; + + Gtk::CheckButton draw_each_step_toggle; + Gtk::CheckButton pause_on_death_toggle; + Gtk::CheckButton pause_on_win_toggle; + + Gtk::Button add_warrior_button; + Gtk::Button remove_warrior_button; + + std::chrono::system_clock::time_point last_core_step_start; + std::chrono::nanoseconds last_core_step_distance; + Gtk::Label core_rate_label; + Gtk::Label core_render_label; + + const lib94::warrior *do_step(); + void runner_main(); + + void on_click_new_round(); + void on_click_single_step(); + void on_click_start(); + void on_click_stop(); + + void on_toggle_draw_each_step(); + void on_toggle_pause_on_death(); + void on_toggle_pause_on_win(); + + void on_click_add_warrior(); + void on_click_remove_warrior(); + + std::thread runner; + bool runner_active; + + void update_buttons(); + void update_ui(); + + void on_runner_stopping(); + + void on_add_warrior_dialog_response(int response_id, Gtk::FileChooserDialog *dialog); +}; + +#endif diff --git a/bench/core_widget.cpp b/bench/core_widget.cpp new file mode 100644 index 0000000..b687a01 --- /dev/null +++ b/bench/core_widget.cpp @@ -0,0 +1,162 @@ +#include "core_widget.hpp" + +core_widget::core_widget() { + clear_all(); +} + +void core_widget::clear_all() { + for (int i = 0; i < LIB94_CORE_SIZE; ++i) { + + if (write_values[i] != 0) + to_draw_write.insert(i); + write_values[i] = 0; + + if (read_values[i] != 0) + to_draw_read.insert(i); + read_values[i] = 0; + + if (execute_values[i] != 0) + to_draw_execute.insert(i); + execute_values[i] = 0; + + } +} + +void core_widget::age_all() { + for (int i = 0; i < LIB94_CORE_SIZE; ++i) { + + if (write_values[i] != 0) { + to_draw_write.insert(i); + write_values[i] = (uint8_t)((float)write_values[i] * age_scale); + } + + if (read_values[i] != 0) { + to_draw_read.insert(i); + read_values[i] = (uint8_t)((float)read_values[i] * age_scale); + } + + if (execute_values[i] != 0) { + to_draw_execute.insert(i); + execute_values[i] = (uint8_t)((float)execute_values[i] * age_scale); + } + + } +} + +void core_widget::add_new_writes(const std::set<lib94::number_t> &writes) { + for (lib94::number_t n : writes) { + write_values[n] = 255; + to_draw_write.insert(n); + } +} + +void core_widget::add_new_reads(const std::set<lib94::number_t> &reads) { + for (lib94::number_t n : reads) { + read_values[n] = 255; + to_draw_read.insert(n); + } +} + +void core_widget::add_new_executions(const std::set<lib94::number_t> &executions) { + for (lib94::number_t n : executions) { + execute_values[n] = 255; + to_draw_execute.insert(n); + } +} + +void core_widget::measure_vfunc(Gtk::Orientation, int for_size, int &minimum, int &natural, int &minimum_baseline, int &natural_baseline) const { + minimum = (LIB94_CORE_SIZE - 1) / for_size + 1; + natural = (LIB94_CORE_SIZE - 1) / for_size + 1; + minimum_baseline = -1; + natural_baseline = -1; +} + +void core_widget::draw(int width, int height) { + mut.lock(); + + if (width == last_width && height == last_height) { + + uint8_t *buffer = (uint8_t *)pixbuf->property_pixels().get_value(); + + for (lib94::number_t i : to_draw_write) { + int cy = i / cpr * scale + ypad; + int cx = i % cpr * scale + xpad; + for (int dy = 0; dy < scale; ++dy) + for (int dx = 0; dx < scale; ++dx) + buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4] = write_values[i]; + } + + for (lib94::number_t i : to_draw_read) { + int cy = i / cpr * scale + ypad; + int cx = i % cpr * scale + xpad; + for (int dy = 0; dy < scale; ++dy) + for (int dx = 0; dx < scale; ++dx) + buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4 + 2] = read_values[i]; + } + + for (lib94::number_t i : to_draw_execute) { + int cy = i / cpr * scale + ypad; + int cx = i % cpr * scale + xpad; + for (int dy = 0; dy < scale; ++dy) + for (int dx = 0; dx < scale; ++dx) + buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4 + 1] = execute_values[i]; + } + + } + + else { + + pixbuf = Gdk::Pixbuf::create(Gdk::Colorspace::RGB, true, 8, width, height); + last_width = width; + last_height = height; + + scale = 1; + while (true) { + ++scale; + if ((width / scale) * (height / scale) < LIB94_CORE_SIZE) { + --scale; + break; + } + } + + cpr = width / scale; + xpad = (width % scale) / 2; + ypad = (height % scale) / 2; + + uint8_t *buffer = (uint8_t *)pixbuf->property_pixels().get_value(); + + for (lib94::number_t i = 0; i < LIB94_CORE_SIZE; ++i) { + int cy = i / cpr * scale + ypad; + int cx = i % cpr * scale + xpad; + for (int dy = 0; dy < scale; ++dy) + for (int dx = 0; dx < scale; ++dx) { + buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4] = write_values[i]; + buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4 + 2] = read_values[i]; + buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4 + 1] = execute_values[i]; + buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4 + 3] = 255; + } + } + + } + + to_draw_write.clear(); + to_draw_read.clear(); + to_draw_execute.clear(); + + mut.unlock(); +} + +void core_widget::snapshot_vfunc(const Glib::RefPtr<Gtk::Snapshot> &snapshot) { + auto start_time = std::chrono::system_clock::now(); + + Gtk::Allocation allocation = get_allocation(); + Gdk::Rectangle allocation_rect(0, 0, allocation.get_width(), allocation.get_height()); + + draw(allocation.get_width(), allocation.get_height()); + + auto texture = Gdk::Texture::create_for_pixbuf(pixbuf); + snapshot->append_texture(texture, allocation_rect); + + auto end_time = std::chrono::system_clock::now(); + last_draw_time = end_time - start_time; +} diff --git a/bench/core_widget.hpp b/bench/core_widget.hpp new file mode 100644 index 0000000..78797c9 --- /dev/null +++ b/bench/core_widget.hpp @@ -0,0 +1,46 @@ +#ifndef LIB94_BENCH_CORE_WIDGET_HPP +#define LIB94_BENCH_CORE_WIDGET_HPP + +#include <lib94/lib94.hpp> +#include <gtkmm.h> + +class core_widget : public Gtk::Widget { +public: + core_widget(); + void clear_all(); + void age_all(); + void add_new_writes(const std::set<lib94::number_t> &writes); + void add_new_reads(const std::set<lib94::number_t> &reads); + void add_new_executions(const std::set<lib94::number_t> &executions); + + void measure_vfunc(Gtk::Orientation orientation, int for_size, int &minimum, int &natural, int &minimum_baseline, int &natural_baseline) const override; + void snapshot_vfunc(const Glib::RefPtr<Gtk::Snapshot> &snapshot); + + float age_scale; + + std::chrono::nanoseconds last_draw_time; + + std::mutex mut; + +private: + uint8_t write_values[LIB94_CORE_SIZE]; + uint8_t read_values[LIB94_CORE_SIZE]; + uint8_t execute_values[LIB94_CORE_SIZE]; + + std::set<lib94::number_t> to_draw_write; + std::set<lib94::number_t> to_draw_read; + std::set<lib94::number_t> to_draw_execute; + + Glib::RefPtr<Gdk::Pixbuf> pixbuf; + + int last_width; + int last_height; + int scale; + int cpr; + int xpad; + int ypad; + + void draw(int width, int height); +}; + +#endif diff --git a/bench/main.cpp b/bench/main.cpp new file mode 100644 index 0000000..9663650 --- /dev/null +++ b/bench/main.cpp @@ -0,0 +1,31 @@ +#include "bench_window.hpp" +#include "main.hpp" + +std::vector<lib94::warrior *> warriors; +std::chrono::milliseconds time_between_steps(50); +std::mutex core_mutex; + +int main(int argc, char **argv) { + auto app = Gtk::Application::create("net.benjidial.lib94.bench"); + return app->make_window_and_run<bench_window>(argc, argv); +} + +std::string ns_to_string(std::chrono::nanoseconds dur) { + if (dur.count() >= 10000000000) + return std::to_string((dur.count() + 500000000) / 1000000000) + "s"; + if (dur.count() >= 10000000) + return std::to_string((dur.count() + 500000) / 1000000) + "ms"; + if (dur.count() >= 10000) + return std::to_string((dur.count() + 500) / 1000) + "μs"; + return std::to_string(dur.count()) + "ns"; +} + +std::string hz_to_string(double rate) { + if (rate >= 10000000.0) + return std::to_string((int)std::round(rate / 1000000.0)) + "MHz"; + if (rate >= 10000.0) + return std::to_string((int)std::round(rate / 1000.0)) + "kHz"; + if (rate >= 10.0) + return std::to_string((int)std::round(rate)) + "Hz"; + return std::to_string((int)std::round(rate * 1000.0)) + "mHz"; +} diff --git a/bench/main.hpp b/bench/main.hpp new file mode 100644 index 0000000..ee0e1e3 --- /dev/null +++ b/bench/main.hpp @@ -0,0 +1,16 @@ +#ifndef LIB94_BENCH_MAIN_HPP +#define LIB94_BENCH_MAIN_HPP + +#include <lib94/lib94.hpp> +#include <chrono> +#include <vector> +#include <mutex> + +extern std::vector<lib94::warrior *> warriors; +extern std::chrono::milliseconds time_between_steps; +extern std::mutex core_mutex; + +std::string ns_to_string(std::chrono::nanoseconds dur); +std::string hz_to_string(double rate); + +#endif |