summaryrefslogtreecommitdiff
path: root/src/icecc.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/icecc.cc')
-rw-r--r--src/icecc.cc357
1 files changed, 357 insertions, 0 deletions
diff --git a/src/icecc.cc b/src/icecc.cc
new file mode 100644
index 0000000..5e99981
--- /dev/null
+++ b/src/icecc.cc
@@ -0,0 +1,357 @@
+#include "common.hh"
+
+#include <algorithm>
+#include <math.h>
+
+#include "animation.hh"
+#include "args.hh"
+#include "blissful_monitor.hh"
+#include "cairo.hh"
+#include "fake_monitor.hh"
+#include "icecc.hh"
+#include "monitor.hh"
+#include "pango.hh"
+
+namespace {
+
+class IceccMonMonImpl : public IceccMonMon, virtual Monitor::Observer {
+public:
+ IceccMonMonImpl(std::shared_ptr<PollLooper> const& looper, unsigned columns)
+ : IceccMonMon(looper), connected_(false), max_jobs_(0), jobs_(0),
+ requests_(0), force_columns_(columns) {
+ }
+
+ void connect(Args const* args) override {
+ std::unique_ptr<Monitor> monitor;
+#if FAKE_MONITOR
+ monitor = FakeMonitor::create(looper_);
+#else
+ monitor = Monitor::create(looper_);
+#endif
+ monitor_ = BlissfulMonitor::create(looper_, std::move(monitor));
+ monitor_->add_observer(this);
+ monitor_->connect(args->arg("network", ""),
+ args->arg("scheduler", ""));
+ }
+
+protected:
+ void width_changed() override {
+ job_pattern_.reset();
+ }
+
+#if FAKE_MONITOR
+ void do_toggle_fakes() override {
+ monitor_->toggle_fakes();
+ }
+#endif
+
+private:
+ class MachineAnimation;
+
+ struct Machine : virtual Animator::AnimationObserver {
+ uint32_t id;
+ Monitor::Machine data;
+ unsigned jobs;
+ unsigned requests;
+ double x;
+ std::shared_ptr<MachineAnimation> animation;
+
+ explicit Machine(uint32_t id)
+ : id(id), jobs(0), requests(0), x(0.0) {
+ }
+
+ Machine(Machine const&) = delete;
+ Machine& operator=(Machine const&) = delete;
+
+ ~Machine() override {
+ assert(!animation);
+ }
+
+ bool operator<(Machine const& machine) const {
+ if (data.name < machine.data.name) return true;
+ if (data.name > machine.data.name) return false;
+ return id < machine.id;
+ }
+
+ void ticked(Animator*, Animation*, double value) override {
+ x = value;
+ }
+
+ void stopped(Animator*, Animation*) override {
+ x = static_cast<double>(jobs) / data.max_jobs;
+ animation.reset();
+ }
+ };
+
+ class MachineAnimation : public Animation {
+ public:
+ explicit MachineAnimation(Machine* machine)
+ : machine_(machine), last_(0.0) {
+ }
+
+ bool tick(double duration, double* value) override {
+ auto target = static_cast<double>(machine_->jobs)
+ / machine_->data.max_jobs;
+ if (duration == 0.0) {
+ last_ = duration;
+ *value = machine_->x;
+ return machine_->x != target;
+ } else {
+ double diff = target - machine_->x;
+ double max = (duration - last_) * .5;
+ last_ = duration;
+ if (fabs(diff) > max) {
+ diff = diff < 0.0 ? -max : max;
+ *value = machine_->x + diff;
+ return true;
+ } else {
+ *value = target;
+ return false;
+ }
+ }
+ }
+
+ private:
+ Machine* machine_;
+ double last_;
+ };
+
+
+ void state(Monitor*, Monitor::State state) override {
+ switch (state) {
+ case Monitor::SEARCHING:
+ connected_ = false;
+ stop_all_animations();
+ machines_.clear();
+ max_jobs_ = 0;
+ jobs_ = 0;
+ requests_ = 0;
+ if (!wnd_) return;
+ draw();
+ break;
+ case Monitor::CONNECTED:
+ connected_ = true;
+ draw();
+ break;
+ }
+ }
+
+ void stop_all_animations() override {
+ if (!animator_) return;
+ for (auto& machine : machines_) {
+ if (machine->animation) animator_->stop(machine->animation.get());
+ }
+ }
+
+ void internal_quit() override {
+ monitor_->disconnect();
+ }
+
+ void update(Machine* machine) {
+ auto old = machine->data.max_jobs;
+ machine->data = monitor_->machine(machine->id);
+ if (old < machine->data.max_jobs) {
+ max_jobs_ += machine->data.max_jobs - old;
+ if (machine->jobs) animate(machine);
+ } else if (old > machine->data.max_jobs) {
+ max_jobs_ -= old - machine->data.max_jobs;
+ if (machine->jobs) animate(machine);
+ }
+ }
+
+ void draw_content(cairo_t* cairo, PangoLayout* layout, uint16_t w, uint16_t h)
+ override {
+ auto const pad_x = std::min(9.0, w / 20.0), pad_y = pad_x;
+ auto const margin_x = std::min(7.5, w / 20.0), margin_y = margin_x;
+ auto y = pad_y;
+ auto const columns = force_columns_ ? force_columns_ : std::max(1, w / h);
+ auto const box_width = (w - pad_x) / columns - pad_x;
+ auto const box_height = box_height_ + margin_y * 2;
+ if (!job_pattern_) {
+ job_pattern_.reset(cairo_pattern_create_linear(0.0, 0.0, box_width, 0.0));
+ cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 0.000000,
+ 0.000000, 0.000000, 0.000000);
+ cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 0.594324,
+ 0.729412, 0.000000, 0.000000);
+ cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 0.809683,
+ 1.000000, 0.545098, 0.196078);
+ cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 0.899833,
+ 0.972549, 0.937255, 0.074510);
+ cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 1.000000,
+ 0.976471, 0.968627, 0.831373);
+ }
+ pango_layout_set_width(layout,
+ (box_width - margin_x * 2) * PANGO_SCALE);
+ unsigned col = 0;
+ for (auto const& machine : machines_) {
+ pango_layout_set_text(layout, machine->data.name.data(),
+ machine->data.name.size());
+ auto x = pad_x + col * (box_width + pad_x);
+ rounded_path(cairo, x, y, box_width, box_height);
+ cairo_set_source_rgba(cairo, 0.1, 0.1, 0.1, 0.7);
+
+ if (machine->x > 0.0) {
+ cairo_fill_preserve(cairo);
+ auto old = cairo::unique_path(cairo_copy_path(cairo));
+ cairo_new_path(cairo);
+ cairo_rectangle(cairo, x, y, machine->x * box_width, box_height);
+ cairo_clip(cairo);
+ cairo_append_path(cairo, old.get());
+ cairo_matrix_t matrix;
+ cairo_matrix_init_translate(&matrix, -x, 0);
+ cairo_pattern_set_matrix(job_pattern_.get(), &matrix);
+ cairo_set_source(cairo, job_pattern_.get());
+ cairo_fill(cairo);
+ cairo_reset_clip(cairo);
+ } else {
+ cairo_fill(cairo);
+ }
+ if (machine->requests > 0) {
+ auto radius = box_height / 10.0;
+ cairo_set_line_width(cairo, 1.5);
+ cairo_new_path(cairo);
+ cairo_move_to(cairo, x + box_width - radius, y);
+ cairo_rel_line_to(cairo,
+ -(machine->requests * (box_width - radius * 2))
+ / requests_, 0);
+ cairo_set_source_rgb(cairo, 0, 0.6, 0);
+ cairo_stroke(cairo);
+ }
+
+ cairo_move_to(cairo, x + margin_x, y + margin_y);
+ pango_cairo_layout_path(cairo, layout);
+ cairo_set_source_rgb(cairo, 0.0, 0.0, 0.0);
+ cairo_set_line_cap(cairo, CAIRO_LINE_CAP_ROUND);
+ cairo_set_line_width(cairo, 2.0);
+ cairo_stroke(cairo);
+ cairo_set_source_rgb(cairo, 1.0, 1.0, 1.0);
+ cairo_move_to(cairo, x + margin_x, y + margin_y);
+ pango_cairo_show_layout(cairo, layout);
+ if (++col == columns) {
+ col = 0;
+ y += box_height + pad_y * 2;
+ if (y >= h) break;
+ }
+ }
+ }
+
+ void added_machine(Monitor*, uint32_t id) override {
+ auto machine = std::make_unique<Machine>(id);
+ update(machine.get());
+ machines_.emplace(
+ std::lower_bound(machines_.begin(), machines_.end(), machine,
+ compare_machine),
+ std::move(machine));
+ draw();
+ }
+
+ static bool compare_machine(std::unique_ptr<Machine> const& m1,
+ std::unique_ptr<Machine> const& m2) {
+ return *m1 < *m2;
+ }
+
+ void updated_machine(Monitor*, uint32_t id) override {
+ for (auto& machine : machines_) {
+ if (machine->id == id) {
+ auto old = machine->data.name;
+ update(machine.get());
+ if (machine->data.name != old) {
+ // TODO: Perhaps remove and insert instead?
+ std::sort(machines_.begin(), machines_.end(), compare_machine);
+ }
+ draw();
+ return;
+ }
+ }
+ assert(false);
+ }
+
+ void removed_machine(Monitor*, uint32_t id) override {
+ for (auto it = machines_.begin(); it != machines_.end(); ++it) {
+ if ((*it)->id == id) {
+ max_jobs_ -= (*it)->data.max_jobs;
+ requests_ -= (*it)->requests;
+ jobs_ -= (*it)->jobs;
+ if (animator_) animator_->stop((*it)->animation.get());
+ machines_.erase(it);
+ draw();
+ return;
+ }
+ }
+ assert(false);
+ }
+
+ void added_job(Monitor*, uint32_t source, uint32_t target) override {
+ if (source == target) {
+ for (auto& machine : machines_) {
+ if (machine->id == source) {
+ ++machine->jobs;
+ animate(machine.get());
+ }
+ }
+ } else {
+ for (auto& machine : machines_) {
+ if (machine->id == source) {
+ ++machine->requests;
+ }
+ if (machine->id == target) {
+ ++machine->jobs;
+ animate(machine.get());
+ }
+ }
+ ++requests_;
+ }
+ ++jobs_;
+ draw();
+ }
+
+ void removed_job(Monitor*, uint32_t source, uint32_t target) override {
+ if (source == target) {
+ for (auto& machine : machines_) {
+ if (machine->id == source) {
+ --machine->jobs;
+ animate(machine.get());
+ }
+ }
+ } else {
+ for (auto& machine : machines_) {
+ if (machine->id == source) {
+ --machine->requests;
+ }
+ if (machine->id == target) {
+ --machine->jobs;
+ animate(machine.get());
+ }
+ }
+ --requests_;
+ }
+ --jobs_;
+ draw();
+ }
+
+ void animate(Machine* machine) {
+ if (!animator_) return;
+ if (machine->animation) return;
+ machine->animation = std::make_unique<MachineAnimation>(machine);
+ animator_->start(machine->animation, machine);
+ }
+
+ std::unique_ptr<Monitor> monitor_;
+ bool connected_;
+ std::vector<std::unique_ptr<Machine>> machines_;
+ unsigned max_jobs_;
+ unsigned jobs_;
+ unsigned requests_;
+ cairo::unique_pattern job_pattern_;
+ unsigned force_columns_;
+};
+
+} // namespace
+
+IceccMonMon::IceccMonMon(std::shared_ptr<PollLooper> const& looper)
+ : MonMon(looper) {}
+
+std::unique_ptr<IceccMonMon> create_icecc_monmon(
+ std::shared_ptr<PollLooper> const& looper, unsigned columns) {
+ return std::make_unique<IceccMonMonImpl>(looper, columns);
+}