summaryrefslogtreecommitdiff
path: root/bench
diff options
context:
space:
mode:
authorBenji Dial <benji@benjidial.net>2023-05-29 16:36:19 -0400
committerBenji Dial <benji@benjidial.net>2023-05-29 16:36:19 -0400
commit97c79ff771d4993e322d0d6c44f265180797b2eb (patch)
tree5513cf25721cf21c06efd913ed2f82b980e3cb24 /bench
parent338549f9cd49fa0f3001826c6605663fa6dd019b (diff)
downloadlib94-97c79ff771d4993e322d0d6c44f265180797b2eb.tar.gz
a whole lot more
Diffstat (limited to 'bench')
-rw-r--r--bench/bench_window.cpp324
-rw-r--r--bench/bench_window.hpp112
-rw-r--r--bench/core_widget.cpp162
-rw-r--r--bench/core_widget.hpp46
-rw-r--r--bench/main.cpp31
-rw-r--r--bench/main.hpp16
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