#ifdef DEBUG /* xo-imgui/example/ex1/imgui_ex4a.cpp * * author: Roland Conybeare, Aug 2025 */ #include "xo/object/Integer.hpp" #include "xo/object/List.hpp" #include "xo/alloc/GC.hpp" #include "xo/alloc/Object.hpp" #include "xo/randomgen/xoshiro256.hpp" #include "xo/randomgen/random_seed.hpp" #include "xo/object/Integer.hpp" #include "xo/flatstring/flatstring.hpp" #include "xo/indentlog/scope.hpp" #include #include #include #include "SDL_events.h" #include "imgui.h" #include "backends/imgui_impl_sdl2.h" #include "backends/imgui_impl_opengl3.h" //#include #include #include #include #include using xo::gc::generation; ImVec2 operator+(const ImVec2 & p1, const ImVec2 & p2) { return ImVec2(p1.x + p2.x, p1.y + p2.y); } struct ImRect { ImRect() = default; ImRect(const ImVec2 & tl, const ImVec2 & br) : top_left_{tl}, bottom_right_{br} {} ImRect(float x_lo, float y_lo, float x_hi, float y_hi) : top_left_{x_lo, y_lo}, bottom_right_{x_hi, y_hi} {} static ImRect from_xy_span(const ImVec2 & x_span, const ImVec2 & y_span) { return ImRect(ImVec2{x_span.x, y_span.x}, ImVec2{x_span.y, y_span.y}); } std::pair x_span() const { return std::make_pair(top_left_.x, bottom_right_.x); } std::pair y_span() const { return std::make_pair(top_left_.y, bottom_right_.y); } const ImVec2 & top_left() const { return top_left_; } const ImVec2 & bottom_right() const { return bottom_right_; } float x_lo() const { return top_left_.x; } float x_hi() const { return bottom_right_.x; } float y_lo() const { return top_left_.y; } float y_hi() const { return bottom_right_.y; } float width() const { return bottom_right_.x - top_left_.x; } float height() const { return bottom_right_.y - top_left_.y; } float x_mid() const { return 0.5 * (top_left_.x + bottom_right_.x); } float y_mid() const { return 0.5 * (top_left_.y + bottom_right_.y); } ImVec2 bottom_left() const { return ImVec2(x_lo(), y_hi()); } ImVec2 top_right() const { return ImVec2(x_hi(), y_lo()); } ImRect with_x_span(float x0, float x1) const { return ImRect(ImVec2(x0, top_left_.y), ImVec2(x1, bottom_right_.y)); } ImRect with_y_span(float y0, float y1) const { return ImRect(ImVec2(top_left_.x, y0), ImVec2(bottom_right_.x, y1)); } ImRect within_margin(const ImRect & margin) const { return ImRect(this->x_lo() + margin.x_lo(), this->y_lo() + margin.y_lo(), this->x_hi() - margin.x_hi(), this->y_hi() - margin.y_hi()); } ImRect within_right_margin(float dx) const { return ImRect(this->x_lo(), this->y_lo(), this->x_hi() - dx, this->y_hi()); } ImRect within_top_margin(float dy) const { return ImRect(this->x_lo(), this->y_lo() + dy, this->x_hi(), this->y_hi()); } ImRect within_bottom_margin(float dy) const { return ImRect(this->x_lo(), this->y_lo(), this->x_hi(), this->y_hi() - dy); } ImRect translate(const ImVec2 & dist) { return ImRect(top_left_ + dist, bottom_right_ + dist); } /** Require: 0.0 <= p <= 1.0 **/ ImRect left_fraction(float p, float min_width = 0.0, float max_width = 999999.0) const { max_width = std::min(max_width, this->width()); float w = std::clamp(p * this->width(), min_width, max_width); return ImRect(top_left_, ImVec2(this->x_lo() + w, this->y_hi())); } /** Require: 0.0 <= p <= 1.0 **/ ImRect right_fraction(float p, float min_width = 0.0, float max_width = 999999.0) const { max_width = std::min(max_width, this->width()); float w = std::clamp(p * this->width(), min_width, max_width); return ImRect(ImVec2(this->x_hi() - w, this->y_lo()), bottom_right_); } /** Require: 0.0 <= p <= q <= 1.0 **/ ImRect mid_x_fraction(float p, float q) const { assert(p <= q); float w = this->width(); return this->with_x_span(this->x_lo() + p * w, this->x_lo() + q * w); } /** Require: 0.0 <= p <= 1.0 **/ ImRect top_fraction(float p, float min_height = 0.0, float max_height = 999999.0) const { max_height = std::min(max_height, this->height()); float h = std::clamp(p * this->height(), min_height, max_height); return ImRect(top_left_, ImVec2(this->x_hi(), this->y_lo() + h)); } /** Require: 0.0 <= p <= 1.0 **/ ImRect bottom_fraction(float p, float min_height = 0.0, float max_height = 999999.0) const { max_height = std::min(max_height, this->height()); float h = std::clamp(p * this->height(), min_height, max_height); return ImRect(ImVec2(this->x_lo(), this->y_hi() - h), bottom_right_); } ImVec2 top_left_{0, 0}; ImVec2 bottom_right_{0, 0}; }; /* We need GUI to be able to fall behind true GC state, so we can animate transitions. * To help make this work, provide a model for GC state sufficient to drive rendering. */ struct GcGenerationDescription { GcGenerationDescription() = default; GcGenerationDescription(generation gen, const char * name, const char * mnemonic, const char * gc_type, std::uint8_t polarity, std::size_t tospace_scale, std::size_t before_ckp, std::size_t after_ckp, std::size_t reserved, std::size_t committed, std::size_t gc_threshold) : name_{name}, mnemonic_{mnemonic}, gc_type_{gc_type}, polarity_{polarity}, tospace_scale_{tospace_scale}, before_checkpoint_{before_ckp}, after_checkpoint_{after_ckp}, reserved_{reserved}, committed_{committed}, gc_threshold_{gc_threshold} {} /** scale (in bytes) for drawing space **/ std::size_t scale() const { return std::max(committed_, gc_threshold_); } /** nursery or tenured **/ generation generation_; /** "nursery" or "tenured" **/ const char * name_ = nullptr; /** "N" or "T" **/ const char * mnemonic_ = nullptr; /** "incremental" or "full" **/ const char * gc_type_ = nullptr; /** alternates between {0, 1} on each GC **/ std::uint8_t polarity_ = 0; /** size of to-space in bytes represented on screen. * (note however when we animate GC, space roles have already reversed, * so then this will refer to old to-space = new from-space) **/ std::size_t tospace_scale_ = 0; std::size_t before_checkpoint_ = 0; std::size_t after_checkpoint_ = 0; std::size_t reserved_ = 0; std::size_t committed_ = 0; // G_to_gc_threshold = G1_to_size + gc->config().incr_gc_threshold_; std::size_t gc_threshold_ = 0; }; /* We need GUI to be able to fall behind true GC state, so we can animate transitions. * To help make this work, provide a model for GC state sufficient to drive rendering. */ struct GcStateDescription { using generation = xo::gc::generation; GcStateDescription(const GcGenerationDescription & nursery, const GcGenerationDescription & tenured, std::size_t gc_size, std::size_t gc_committed, std::size_t gc_allocated, std::size_t gc_available, std::size_t gc_mlog_size, std::size_t total_promoted, std::size_t total_n_mutation ); const GcGenerationDescription & get_gendescr(generation g) const { return gen_state_v_[gen2int(g)]; } std::array(generation::N)> gen_state_v_; /** see @ref GC::size **/ std::size_t gc_size_ = 0; /** see @ref GC::committed **/ std::size_t gc_committed_ = 0; /** see @ref GC::allocated **/ std::size_t gc_allocated_ = 0; /** see @ref GC::available **/ std::size_t gc_available_ = 0; /** see @ref GC::mlog_size **/ std::size_t gc_mlog_size_ = 0; /** see @ref GcStatistics::total_promoted_ **/ std::size_t total_promoted_ = 0; /** see @ref GcStatistics::n_mutation_ **/ std::size_t total_n_mutation_ = 0; }; GcStateDescription::GcStateDescription(const GcGenerationDescription & nursery, const GcGenerationDescription & tenured, std::size_t gc_size, std::size_t gc_committed, std::size_t gc_allocated, std::size_t gc_available, std::size_t gc_mlog_size, std::size_t total_promoted, std::size_t total_n_mutation) : gc_size_{gc_size}, gc_committed_{gc_committed}, gc_allocated_{gc_allocated}, gc_available_{gc_available}, gc_mlog_size_{gc_mlog_size}, total_promoted_{total_promoted}, total_n_mutation_{total_n_mutation} { gen_state_v_[gen2int(generation::nursery)] = nursery; gen_state_v_[gen2int(generation::tenured)] = tenured; } using xo::gp; using xo::up; using xo::Object; using xo::obj::List; using xo::obj::Integer; using xo::rng::xoshiro256ss; using xo::rng::Seed; /** details of a single copy event performed by GC **/ struct GcCopyDetail { GcCopyDetail(std::size_t z, generation src, std::size_t src_offset, std::size_t src_space_z, generation dest, std::size_t dest_offset, std::size_t dest_z) : z_{z}, src_gen_{src}, src_offset_{src_offset}, src_space_z_{src_space_z}, dest_gen_{dest}, dest_offset_{dest_offset}, dest_z_{dest_z} {} /** object size in bytes **/ std::size_t z_ = 0; /** source location **/ generation src_gen_; /** offset from start of allocator **/ std::size_t src_offset_ = 0; /** size of source space. could store this separately **/ std::size_t src_space_z_ = 0; /** destination location **/ generation dest_gen_; /** offset from start of allocator **/ std::size_t dest_offset_ = 0; /** size of destination space. (could store this separately). **/ std::size_t dest_z_ = 0; }; struct AppState { using GC = xo::gc::GC; public: AppState(); std::size_t nursery_tospace_scale() const; std::size_t tenured_tospace_scale() const; GcStateDescription snapshot_gc_state() const; void generate_random_mutation(); void generate_random_mutations(); public: int alloc_per_cycle_ = 1; /** if gc triggered, remembers which whether incremental or full **/ generation upto_ = generation::nursery; up gc_; std::size_t next_int_ = 0; std::size_t next_root_ = 0; std::vector> gc_root_v_{100}; Seed seed_; xoshiro256ss rng_{seed_}; /** remember details for each object copied by GC, so we can animate **/ std::vector copy_detail_v_; /** max offset for destination, given copied to nursery **/ std::size_t copy_detail_max_nursery_dest_offset_ = 0; std::size_t copy_detail_nursery_dest_size_ = 0; std::size_t copy_detail_max_tenured_dest_offset_ = 0; std::size_t copy_detail_tenured_dest_size_ = 0; }; AppState::AppState() { this->gc_ = (GC::make ( {.initial_nursery_z_ = 1024*1024, .initial_tenured_z_ = 1024*1024*1024, .incr_gc_threshold_ = 16*1024, .full_gc_threshold_ = 128*1024, .stats_flag_ = true, .debug_flag_ = false})); Object::mm = gc_.get(); for (auto & x: gc_root_v_) gc_->add_gc_root(x.ptr_address()); gc_->disable_gc(); } std::size_t AppState::nursery_tospace_scale() const { std::size_t N1_to_size = gc_->nursery_before_checkpoint(); std::size_t N_to_committed = gc_->nursery_to_committed(); std::size_t N_to_incr_gc_threshold = N1_to_size + gc_->config().incr_gc_threshold_; std::size_t N_to_scale = std::max(N_to_committed, N_to_incr_gc_threshold); return N_to_scale; } std::size_t AppState::tenured_tospace_scale() const { std::size_t T1_to_size = gc_->tenured_before_checkpoint(); std::size_t T_to_committed = gc_->tenured_to_committed(); std::size_t T_to_full_gc_threshold = T1_to_size + gc_->config().full_gc_threshold_; std::size_t T_to_scale = std::max(T_to_committed, T_to_full_gc_threshold); return T_to_scale; } GcStateDescription AppState::snapshot_gc_state() const { /** NOTE: this gets invoked before GC gets opportunity to run. * in the event that GC does run, from- and to- spaces will * have been reversed (near beginning of GC phase) * * This means that nursery_to_reserved() etc. actually refer to from-space * *during gc* **/ // TOOD: may want to use GC::get_gc_statistics() to replace multiple round trips return GcStateDescription(GcGenerationDescription (generation::nursery, "nursery", "N", "incremental", gc_->nursery_polarity(), this->nursery_tospace_scale(), gc_->nursery_before_checkpoint(), gc_->nursery_after_checkpoint(), gc_->nursery_to_reserved(), gc_->nursery_to_committed(), gc_->nursery_before_checkpoint() + gc_->config().incr_gc_threshold_), GcGenerationDescription (generation::tenured, "tenured", "T", "full", gc_->tenured_polarity(), this->tenured_tospace_scale(), gc_->tenured_before_checkpoint(), gc_->tenured_after_checkpoint(), gc_->tenured_to_reserved(), gc_->tenured_to_committed(), gc_->tenured_before_checkpoint() + gc_->config().full_gc_threshold_), gc_->size(), gc_->committed(), gc_->allocated(), gc_->available(), gc_->mlog_size(), gc_->native_gc_statistics().total_promoted_, gc_->native_gc_statistics().n_mutation_ ); } void AppState::generate_random_mutation() { if (rng_() % 1000 > (5 * 1000) / 7) { /* p=16% integer */ gc_root_v_[next_root_++] = Integer::make(next_int_); } else if (rng_() % 1000 > (3 * 1000) / 7) { /* p=16% cons */ gp random_car = gc_root_v_.at(rng_() % gc_root_v_.size()); if (random_car.is_null()) random_car = List::nil; /* this will always incorporate existing list as tail of new list */ gp random_cdr = List::from(gc_root_v_[next_root_]); if (random_cdr.is_null()) random_cdr = List::nil; gp random_cons = List::cons(random_car, random_cdr); gc_root_v_[next_root_++] = random_cons; } else if (rng_() % 1000 > (0 * 1000) / 7) { /* p=24% mutation */ gp random_list = List::from(gc_root_v_.at(rng_() % gc_root_v_.size())); if (!random_list.is_null()) { if (rng_() % 2 == 0) { /* pick up some random object, assign as head */ gp random_car = gc_root_v_.at(rng_() % gc_root_v_.size()); random_list->assign_head(random_car); } else { /* pick up some random object; if List, assign tail as tail */ gp random_cdr = List::from(gc_root_v_.at(rng_() % gc_root_v_.size())); if (!random_cdr.is_null() && !random_cdr->is_nil()) random_list->assign_rest(random_cdr->rest()); } } } if (next_root_ >= gc_root_v_.size()) this->next_root_ = 0; } void AppState::generate_random_mutations() { for (int i = 0; i < this->alloc_per_cycle_; ++i) { this->generate_random_mutation(); } } void draw_filled_rect_with_label(const char * text, const char * tooltip, const ImRect & rect, ImU32 fillcolor, ImU32 textcolor, ImDrawList * draw_list) { draw_list->AddRectFilled(rect.top_left(), rect.bottom_right(), fillcolor); if ((rect.width() > 0.0) && (rect.height() > 0.0)) { ImGui::SetCursorScreenPos(rect.top_left()); ImGui::InvisibleButton("ttbutton", ImVec2(rect.width(), rect.height())); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", tooltip); } } if (text) { auto textz = ImGui::CalcTextSize(text); /* N1 can be empty: but in that case don't bother to label it */ if (textz.x < rect.width()) { draw_list->AddText(ImVec2(rect.x_mid() - 0.5 * textz.x, rect.y_mid() - 0.5 * textz.y), textcolor, text); } } } void draw_filled_rect(const char * tooltip, const ImRect & rect, ImU32 fillcolor, ImDrawList * draw_list) { draw_filled_rect_with_label(nullptr, tooltip, rect, fillcolor, IM_COL32(255, 255, 255, 255), draw_list); } using xo::gc::GC; using xo::gc::GcStatisticsExt; using xo::gc::GcStatisticsHistory; using xo::gc::GcStatisticsHistoryItem; using xo::xtag; using xo::scope; using xo::flatstring; using std::size_t; /** @class GenerationLayout * @brief layout for displaying a single collector generation. * * @text * reserved: xxx committed: xxxx G1: xxx bytes G0: xxx bytes * +--------------------------+--------------------+-------------------------+ * to | G1 | G0 | | * +--------------------------+--------------------+-------------------------+ * Mem: 28k * +-------------------------------------------------------------------------+ * from | | * +-------------------------------------------------------------------------+ * ^ * layout elements: * * <-a-><------------------------------ mem_w ------------------------------------><---b----> * <------------------------- ngc_w ----------------------------> * <--------- G1_w ----------> <------- G0_w -----> * * a (lh_text_dx): width for left-hand-side text * b (rh_text_dx): width for right-hand-side text * mem_w: width for contiguous committed memory * ngc_w: location (relative to start of GC memory range) of next-collection trigger * G1_w: width for occupied memory that has survived one GC in this space * G0_w: width for memory allocated since last GC * * @endtext * **/ struct GenerationLayout { GenerationLayout() = default; GenerationLayout(const GcGenerationDescription & gendescr, const ImRect & br, bool with_labels); const char * name() const { return gendescr_.name_; } const char * mnemonic() const { return gendescr_.mnemonic_; } const char * gc_type() const { return gendescr_.gc_type_; } std::size_t to_G1_size() const { return gendescr_.before_checkpoint_; } std::size_t to_G0_size() const { return gendescr_.after_checkpoint_; } std::size_t to_gc_threhsold() const { return gendescr_.gc_threshold_; } float to_scale() const { /** note: deliberate size_t->float conversion here **/ return gendescr_.scale(); } ImRect to_g1_rect() const { return mem_rect_to_.left_fraction(this->to_G1_size() / this->to_scale()); } ImRect to_g0_rect() const { return mem_rect_to_.mid_x_fraction(this->to_G1_size() / this->to_scale(), (this->to_G1_size() + this->to_G0_size()) / this->to_scale()); } ImRect to_alloc_rect() const { return mem_rect_to_.left_fraction((this->to_G1_size() + this->to_G0_size()) / this->to_scale()); } ImRect from_alloc_rect() const { /* use the same sizing as for source generation */ return mem_rect_from_.left_fraction((this->to_G1_size() + this->to_G0_size()) / this->to_scale()); } /** size-related statistics for generation to be displayed **/ GcGenerationDescription gendescr_; /** bounding rectangle. all drawing for generation display will be inside this rectanglge **/ ImRect bounding_rect_; /** true iff text labels enabled **/ bool with_labels_ = false; /** text height in screen units **/ float text_dy_ = 0.0; /** chart rectangle. bounding rectangle less room for headline text **/ ImRect chart_withlabel_rect_; ImRect chart_nolabel_rect_; /** text for RH label. something like "N: 28k/40k" **/ flatstring<80> rh_text_; /** width of .rh_text in screen units **/ float rh_text_dx_ = 0.0; /** rectangle representing from-space memory range **/ ImRect mem_rect_from_; /** rectangle representing to-space memory range **/ ImRect mem_rect_to_; }; GenerationLayout::GenerationLayout(const GcGenerationDescription & gendescr, const ImRect & br, bool with_labels) : gendescr_{gendescr}, bounding_rect_{br}, with_labels_{with_labels} { this->text_dy_ = ImGui::CalcTextSize("SAMPLE TEXT").y; if (with_labels_) { snprintf(this->rh_text_.data(), rh_text_.capacity(), "%s: %luk", gendescr_.mnemonic_, std::max(gendescr_.gc_threshold_, gendescr_.committed_) / 1024); rh_text_.ensure_final_null(); auto textz = ImGui::CalcTextSize(rh_text_.c_str()); /* allow margin between rh edge of mem range and beginning of label */ this->rh_text_dx_ = 5 + textz.x; } else { this->rh_text_dx_ = 0.0; } if (with_labels_) { this->chart_withlabel_rect_ = bounding_rect_.within_top_margin(text_dy_ + 2); this->chart_nolabel_rect_ = chart_withlabel_rect_.within_right_margin(rh_text_dx_); } else { this->chart_withlabel_rect_ = bounding_rect_; this->chart_nolabel_rect_ = bounding_rect_; } this->mem_rect_from_ = chart_nolabel_rect_.top_fraction(0.45); this->mem_rect_to_ = chart_nolabel_rect_.bottom_fraction(0.45); if (gendescr_.polarity_ == 1) std::swap(this->mem_rect_from_, this->mem_rect_to_); } /** * @p polarity 0 -> draw from-space above to-space; 1 -> draw from-space below to-space * @p p_x1 On exit *p_x1 contains x-coord of right-hand edge of rectangle * depicting potential memory range **/ void draw_generation(const GenerationLayout & layout, ImDrawList * draw_list) { //scope log(XO_DEBUG(with_labels)); using xo::gc::generation; /* next GC trigges when G0_to_size reaches this threshold */ std::size_t G_to_gc_threshold = 0; G_to_gc_threshold = layout.gendescr_.gc_threshold_; std::size_t G_to_scale = layout.gendescr_.scale(); /* * committed: G_to_committed * G1: G1_to_size * G0: G0_to_size * ckp: G1_to_size * ngc: G_to_gc_threshold * * <----------------------------- committed ---------------------------> * <------------------ used ------------------> <-------- free --------> * <------- G1 --------> <-------- G0 --------> * |NNNNNNNNNNNNNNNNNNNNN|nnnnnnnnnnnnnnnnnnnnnn|________________________| * ^ ^ * ckp ngc * * in screen coords: * * horizontally: * * rect.x_lo rect.x_hi * v v * * <--------------------------- display_w -----------------------------> <-+-> * <------------------------ ngc_w ----------------------> \- rh_text_dx * <------- G1_w ------> <-------- G0_w ------> * ^ ^ ^ ^ ^ * x0 G0_x0 G0_x1 ngc_w x1 * *p_x0 *p_g0_x1 *p_x1 * * vertically: * * <- rect.y_lo * ^ * * v * <- rect.y_hi */ /* e.g. N1: 34511 bytes */ char g1_buf[255]; ImU32 label_color = IM_COL32(255, 255, 192, 255); /*super pale yellow*/ if (layout.with_labels_) { snprintf(g1_buf, sizeof(g1_buf), "reserved: %lu bytes; committed: %lu bytes; %s\u2081: %lu bytes; %s\u2080: %lu bytes", layout.gendescr_.reserved_, layout.gendescr_.committed_, layout.mnemonic(), layout.gendescr_.before_checkpoint_, layout.mnemonic(), layout.gendescr_.after_checkpoint_); draw_list->AddText(layout.bounding_rect_.top_left(), label_color, g1_buf); } if (layout.with_labels_) { auto textz = ImGui::CalcTextSize(layout.rh_text_.c_str()); draw_list->AddText(ImVec2(layout.chart_withlabel_rect_.x_hi() - textz.x, layout.chart_withlabel_rect_.y_mid() - 0.5 * textz.y), label_color, layout.rh_text_.c_str()); } ImU32 outline_color = IM_COL32(255, 255, 255, 255); /*white*/ /* chart rectangle */ draw_list->AddRect(layout.mem_rect_from_.top_left(), layout.mem_rect_from_.bottom_right(), outline_color); draw_list->AddRect(layout.mem_rect_to_.top_left(), layout.mem_rect_to_.bottom_right(), outline_color); float display_w = layout.mem_rect_from_.width(); float G1_w = display_w * layout.to_G1_size() / layout.to_scale(); float G1_x1 = layout.mem_rect_from_.x_lo() + G1_w; ImRect G1_rect = layout.to_g1_rect(); //ImRect G1_rect = layout.mem_rect_to_.left_fraction(layout.to_G1_size() / layout.to_scale()); /* G1 (i.e. N1 or T1) */ { ImU32 G1_color = IM_COL32( 0, 128, 0, 255); ImU32 text_color = IM_COL32(255, 255, 255, 255); char buf[255]; if (layout.with_labels_) snprintf(buf, sizeof(buf), "%s\u2081: %luk", layout.mnemonic(), layout.to_G1_size() / 1024); /* N1 / T1 */ char tooltip[255]; snprintf(tooltip, sizeof(tooltip), "%s\u2081: %lu - %s survivor size in bytes", layout.mnemonic(), layout.to_G1_size(), layout.name()); draw_filled_rect_with_label(layout.with_labels_ ? buf : nullptr, tooltip, G1_rect, G1_color, text_color, draw_list); } ImRect G0_rect = layout.to_g0_rect(); /* G0 (i.e. N0 or T0) */ { ImU32 G0_color = IM_COL32( 32, 192, 32, 255); ImU32 text_color = IM_COL32( 0, 0, 0, 255); char buf[255]; if (layout.with_labels_) snprintf(buf, sizeof(buf), "%s\u2080: %luk", layout.mnemonic(), layout.to_G0_size() / 1024); /* N(0) */ char tooltip[255]; snprintf(tooltip, sizeof(tooltip), "%s\u2080: %lu - %s new alloc size in bytes", layout.mnemonic(), layout.to_G0_size(), layout.name()); draw_filled_rect_with_label(layout.with_labels_ ? buf : nullptr, tooltip, G0_rect, G0_color, text_color, draw_list); } /* mark where next gc will trigger */ if (layout.with_labels_) { const char * uparrow = reinterpret_cast(u8"\u25b3"); float ngc_w = (display_w * layout.gendescr_.gc_threshold_) / G_to_scale; auto tmp = ImGui::CalcTextSize(uparrow); std::size_t uparrow_w = tmp.x; double ngc_x = layout.chart_withlabel_rect_.x_lo() + ngc_w - uparrow_w/2.0; ImVec2 marker_pos(ngc_x, layout.chart_withlabel_rect_.y_hi()); draw_list->AddText(marker_pos, IM_COL32(255, 128, 128, 255) /*red*/, uparrow); ImGui::SetCursorScreenPos(marker_pos); ImGui::InvisibleButton("mkbutton", tmp); if (ImGui::IsItemHovered()) { char marker_tt_buf[255]; snprintf(marker_tt_buf, sizeof(marker_tt_buf), "Next %s GC when size(%s) >= %lu bytes", layout.gc_type(), layout.name(), layout.gendescr_.gc_threshold_); ImGui::SetTooltip("%s", marker_tt_buf); } } } /*draw_generation*/ void draw_nursery(const GcStateDescription & gcstate, bool with_labels, const ImRect & rect, ImDrawList * draw_list, GenerationLayout * p_layout) { using xo::gc::generation; const GcGenerationDescription & gendescr = gcstate.get_gendescr(generation::nursery); GenerationLayout layout(gendescr, rect, with_labels); draw_generation(layout, draw_list); if (p_layout) *p_layout = layout; } void draw_tenured(const GcStateDescription & gcstate, bool with_labels, const ImRect & rect, ImDrawList * draw_list, GenerationLayout * p_layout) { using xo::gc::generation; const GcGenerationDescription & gendescr = gcstate.get_gendescr(generation::tenured); GenerationLayout layout(gendescr, rect, with_labels); draw_generation(layout, draw_list); if (p_layout) *p_layout = layout; } /** for history tooltip, choose which statistic to headline **/ enum class gc_history_headline { survive, promote, persist, garbage0, garbage1, garbageN, N }; xo::flatstring<512> write_gc_history_tooltip(gc_history_headline headline, const GcStatisticsHistoryItem & stats) { xo::flatstring<512> retval; xo::flatstring<512> headline_str; switch (headline) { case gc_history_headline::survive: snprintf(headline_str.data(), headline_str.capacity(), "survive: %lu: bytes surviving 1st GC after allocation", stats.survive_z_); break; case gc_history_headline::promote: snprintf(headline_str.data(), headline_str.capacity(), "promote: %lu: bytes surviving 2nd GC; if nursery promote to tenured", stats.promote_z_); break; case gc_history_headline::persist: snprintf(headline_str.data(), headline_str.capacity(), "persist: %lu: bytes surviving 3+ GCs. Only non-zero for full collections", stats.persist_z_); break; case gc_history_headline::garbage0: snprintf(headline_str.data(), headline_str.capacity(), "garbage\u2080: %lu: bytes collected on 1st GC after allocation", stats.garbage0_z_); break; case gc_history_headline::garbage1: snprintf(headline_str.data(), headline_str.capacity(), "garbage\u2081: %lu: bytes collected on 2nd GC after allocation", stats.garbage1_z_); break; case gc_history_headline::garbageN: snprintf(headline_str.data(), headline_str.capacity(), "garbage\u2099: %lu: bytes collected on 3rd or later GC after allocation", stats.garbageN_z_); break; case gc_history_headline::N: assert(false); break; } snprintf(retval.data(), retval.capacity(), "%s\n" "\n" " gcseq: %lu\n" " type: %s\n" " alloc: %lu\n" " survive: %lu\n" " promote: %lu\n" " persist: %lu\n" " garbage\u2080: %lu\n" /*garbage0*/ " garbage\u2081: %lu\n" /*garbage1*/ " garbage\u2099: %lu\n" /*garbageN*/ " effort: %lu dt: %.1lfus\n" " copy efficiency: %.1lf%% collection rate: %.0lf bytes/sec", headline_str.c_str(), stats.gc_seq_, (stats.upto_ == generation::nursery) ? "incremental" : "FULL", stats.new_alloc_z_, stats.survive_z_, stats.promote_z_, stats.persist_z_, stats.garbage0_z_, stats.garbage1_z_, stats.garbageN_z_, stats.effort_z_, 1e-3 * stats.dt_.scale(), 100.0 * stats.efficiency(), stats.collection_rate() ); return retval.ensure_final_null(); } /** stacked bar chart * * @param gen if @ref generation::nursery, only display nursery collections. * otherwise display both **/ void draw_gc_history(const GcStateDescription & gcstate, generation gen, const GcStatisticsHistory & gc_history, const ImRect & bounding_rect, bool debug_flag, ImDrawList * draw_list) { scope log(XO_DEBUG(debug_flag)); float lm = 10; float tm = 25; /* we're going to make a bar chart */ /* x_scale,y_scale in GC units (i.e. bytes) */ size_t x_scale = gc_history.capacity(); size_t yplus_scale = 0; size_t yminus_scale = 0; float display_w = bounding_rect.width() - lm; float display_h = bounding_rect.height() - tm; /* 1st loop: figure out max y scale */ for (const GcStatisticsHistoryItem & stats : gc_history) { if ((gen == stats.upto_) || (gen == generation::tenured)) { //size_t na = stats.new_alloc_z_ - stats.survive_z_; /*new allocs, but dont' double-count survive_z*/ size_t sz = stats.survive_z_; /*survive 1st gc */ size_t pz = stats.promote_z_; /*survive 2nd gc */ size_t psz = stats.persist_z_; /*survive 3+ gc */ size_t g0z = stats.garbage0_z_; size_t g1z = stats.garbage1_z_; size_t gNz = stats.garbageN_z_; if (yplus_scale < sz + pz + psz) yplus_scale = sz + pz + psz; if (yminus_scale < g0z + g1z + gNz) yminus_scale = g0z + g1z + gNz; } else { ; } } /* y-coord of x-axis */ float y_zero = bounding_rect.y_lo() + tm + (display_h * yplus_scale) / (yplus_scale + yminus_scale); float y_scale = yplus_scale + yminus_scale; /* width of 1 bar in screen coords */ constexpr float c_min_bar_w = 5.0; float bar_w = std::max(c_min_bar_w, display_w / gc_history.capacity()); /* 2nd loop: draw bars */ std::size_t i = 0; for (const GcStatisticsHistoryItem & stats : gc_history) { if ((gen == stats.upto_) || (gen == generation::tenured)) { /* * ys_lo +--+ * | | survive_z (survived 1st GC) * | | * yp_lo +--+ * | | promote_z (sruvived 2nd GC) * | | * ypsz_lo +--+ * | | persist_z (survived 3+ GCs) * | | * y_zero +--+ * | | gN (killed on 3+ GC) * | | * ygN_hi +--+ * | | g1 (killed on 2nd GC) * | | * yg1_hi +--+ * | | g0 (killed on 1st GC) * | | * yg0_hi +--+ */ ImU32 persist_color = IM_COL32( 0, 64, 192, 255); /*darker blue*/ ImU32 promote_color = IM_COL32( 0, 128, 0, 255); /*darker green*/ ImU32 survive_color = IM_COL32( 32, 192, 32, 255); /*lighter green*/ ImU32 garbageN_color = IM_COL32(255, 128, 64, 255); /*darker orange*/ ImU32 garbage1_color = IM_COL32(255, 192, 128, 255); /*medium orange*/ ImU32 garbage0_color = IM_COL32(255, 255, 192, 255); /*pale yellow*/ /* x-coordinates of bar */ float x_lo = bounding_rect.x_lo() + lm + i * bar_w; float x_hi = x_lo + bar_w - 1; ImVec2 x_span{x_lo, x_hi}; /* y-coordinates of persist bar (survived 3+ GCs) */ float ypsz_lo = (y_zero - (display_h * stats.persist_z_ / y_scale)); { xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::persist, stats); draw_filled_rect(tt.c_str(), ImRect::from_xy_span(x_span, ImVec2(ypsz_lo, y_zero)), persist_color, draw_list); } /* y-coordinates of promote bar (survived 2nd GC) */ float yp_hi = ypsz_lo; float yp_lo = (yp_hi - (display_h * stats.promote_z_ / y_scale)); { xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::promote, stats); draw_filled_rect(tt.c_str(), ImRect::from_xy_span(x_span, ImVec2(yp_lo, yp_hi)), promote_color, draw_list); } /* y-coordinates of survivor bar (survived 1st GC) */ float ys_hi = yp_lo; float ys_lo = (ys_hi - (display_h * stats.survive_z_ / y_scale)); { xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::survive, stats); draw_filled_rect(tt.c_str(), ImRect::from_xy_span(x_span, ImVec2(ys_lo, ys_hi)), survive_color, draw_list); } // ----------------------------------------------------------- /* y-coordinates of garbageN bar (killed on 3+ GC) */ float ygN_lo = y_zero; float ygN_hi = (y_zero + (display_h * stats.garbageN_z_ / y_scale)); { xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::garbageN, stats); draw_filled_rect(tt.c_str(), ImRect::from_xy_span(x_span, ImVec2(ygN_lo, ygN_hi)), garbageN_color, draw_list); } /* y-coordinates of garbage1 bar (killed on 2nd GC) */ float yg1_lo = ygN_hi; float yg1_hi = (yg1_lo + (display_h * stats.garbage1_z_ / y_scale)); { xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::garbage1, stats); draw_filled_rect(tt.c_str(), ImRect(ImVec2(x_lo, yg1_lo), ImVec2(x_hi, yg1_hi)), garbage1_color, draw_list); } /* y-coordinates of garbage0 bar (killed on 1st GC) */ float yg0_lo = yg1_hi; float yg0_hi = (yg0_lo + (display_h * stats.garbage0_z_ / y_scale)); { xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::garbage0, stats); draw_filled_rect(tt.c_str(), ImRect(ImVec2(x_lo, yg0_lo), ImVec2(x_hi, yg0_hi)), garbage0_color, draw_list); } } else { /* draw nothing */ ; } ++i; } log && log(xtag("i", i)); } void draw_gc_efficiency(const GcStateDescription & gcstate, //generation gen, const GcStatisticsHistory & gc_history, const ImRect & bounding_rect, bool debug_flag, ImDrawList * draw_list) { scope log(XO_DEBUG(debug_flag)); float lm = 10; float tm = 25; /* we're going to make a level chart */ /* x_scale,y_scale in GC units (i.e. bytes) */ size_t x_scale = gc_history.capacity(); size_t yplus_scale = 1; size_t yminus_scale = 0; float display_w = bounding_rect.width() - lm; float display_h = bounding_rect.height() - tm; #ifdef NOPE // don't need this. y-scale is [0.0, 1.0] /* 1st loop: figure out max y scale */ for (const GcStatisticsHistoryItem & stats : gc_history) { if ((gen == stats.upto_) || (gen == generation::tenured)) { //size_t na = stats.new_alloc_z_ - stats.survive_z_; /*new allocs, but dont' double-count survive_z*/ size_t sz = stats.survive_z_; /*survive 1st gc */ size_t pz = stats.promote_z_; /*survive 2nd gc */ size_t psz = stats.persist_z_; /*survive 3+ gc */ size_t g0z = stats.garbage0_z_; size_t g1z = stats.garbage1_z_; size_t gNz = stats.garbageN_z_; if (yplus_scale < sz + pz + psz) yplus_scale = sz + pz + psz; if (yminus_scale < g0z + g1z + gNz) yminus_scale = g0z + g1z + gNz; } else { ; } } #endif /* y-coord of x-axis */ float y_zero = bounding_rect.y_lo() + tm + display_h; //float y_zero = bounding_rect.y_lo() + tm + (display_h * yplus_scale) / (yplus_scale + yminus_scale); float y_scale = 1.0; /* width of 1 bar in screen coords */ constexpr float c_min_bar_w = 5.0; float bar_w = std::max(c_min_bar_w, display_w / gc_history.capacity()); /* TODO: use temporary arena */ std::vector line_points; line_points.reserve(gc_history.size()); ImU32 average_color = IM_COL32(255, 255, 64, 255); /*solid yellow*/ ImU32 sample_color = IM_COL32(255, 255, 255, 255); /*white*/ /* 2nd loop: draw levels */ std::size_t i = 0; for (const GcStatisticsHistoryItem & stats : gc_history) { //std::vector line_points = { /* your points */ }; //draw_list->AddPolyline(line_points.data(), line_points.size(), // IM_COL32(255, 255, 0, 255), false, 2.0f); float y = y_zero - display_h * stats.efficiency(); float y_mean = y_zero - display_h * stats.average_efficiency(); /* x-coordinates of point */ float x = bounding_rect.x_lo() + lm + i * bar_w + 0.5 * bar_w; line_points.push_back(ImVec2(x, y_mean)); draw_list->AddCircleFilled(ImVec2(x, y), 2.0f, sample_color); ++i; } draw_list->AddPolyline(line_points.data(), line_points.size(), average_color, false, 1.0f /*line width?*/); log && log(xtag("i", i)); } /*draw_gc_efficiency*/ void draw_gc_alloc_state(const GcStateDescription & gcstate, const ImRect & canvas_rect, ImDrawList * draw_list, GenerationLayout * p_nursery_layout, GenerationLayout * p_tenured_layout //ImRect * p_nursery_alloc_rect, //ImRect * p_tenured_alloc_rect ) { constexpr float c_est_chart_text_height = 14.0; constexpr float c_min_h = 7; // chart bar height constexpr float c_max_h = 40; // chart bar height /* bounding rectange for nursery display */ ImRect N_space_rect = canvas_rect.top_fraction(0.5, c_min_h + c_est_chart_text_height, c_max_h + c_est_chart_text_height); //assert(N_space_rect.height() >= c_min_h + c_est_chart_text_height); //assert(N_space_rect.height() <= c_max_h + c_est_chart_text_height); /* rectangle representing allocated nursery range */ //float N_x1 = 0.0; GenerationLayout N_layout; draw_nursery(gcstate, true /*with_labels*/, N_space_rect, draw_list, &N_layout); float N_x1 = N_layout.chart_nolabel_rect_.x_hi(); if (p_nursery_layout) *p_nursery_layout = N_layout; // if (p_nursery_alloc_rect) // *p_nursery_alloc_rect = N_layout.to_alloc_rect(); /* N0_to_size..N_to_scale: in bytes */ std::size_t N_to_scale = gcstate.gen_state_v_[gen2int(generation::nursery)].tospace_scale_; /* display_w .. N0_h : viewportcoords */ std::size_t display_w = canvas_rect.width(); std::size_t x0 = canvas_rect.x_lo(); std::size_t x1 = canvas_rect.x_hi(); // now turn to Tenured space std::size_t T_to_scale = gcstate.gen_state_v_[gen2int(generation::tenured)].tospace_scale_; /* want to put to-scale image of nursery next to to-scale image of tenured; * but also want space between them. */ float TplusN_to_scale = N_to_scale + T_to_scale; /* space between T, N images */ float TplusN_spacer = 10; /* bounding rectange for tenured display */ ImRect T_space_rect = (canvas_rect .within_bottom_margin(c_est_chart_text_height) .bottom_fraction(0.5, c_min_h + c_est_chart_text_height, c_max_h + c_est_chart_text_height)); if (N_space_rect.y_hi() > T_space_rect.y_lo()) { T_space_rect = T_space_rect.translate(ImVec2(0, N_space_rect.y_hi() - T_space_rect.y_lo())); } assert(T_space_rect.y_lo() >= N_space_rect.y_hi()); /* for smaller image of nursery */ //std::size_t t_y0 = canvas_rect.y_lo() + 70 + alloc_height + 20; /* for side-by-side tenured + nursery, with both on same scale * 2nd term is horiz space used for N label like 'Mem: 28k' */ std::size_t adj_display_w = display_w - (N_space_rect.x_hi() - N_x1); /* bounding rectangle for secondary nursery display */ ImRect np_rect(ImVec2(x0 + (adj_display_w * (T_to_scale/TplusN_to_scale)), T_space_rect.y_lo() + c_est_chart_text_height), ImVec2(x0 + adj_display_w, T_space_rect.y_hi())); // redraw nursery to same scale as tenured { draw_list->AddLine(N_space_rect.bottom_left(), np_rect.top_left(), IM_COL32(128, 128, 128, 255) /*grey*/); draw_list->AddLine(ImVec2(N_x1, N_space_rect.y_hi()), np_rect.top_right(), IM_COL32(128, 128, 128, 255) /*grey*/); draw_nursery(gcstate, false /*no labels*/, np_rect, draw_list, nullptr); } /* rectangle representing allocated tenured range */ GenerationLayout T_layout; draw_tenured(gcstate, true /*with labels*/, ImRect(ImVec2(x0, T_space_rect.y_lo()), ImVec2(x0 + (adj_display_w * (T_to_scale/TplusN_to_scale)) - TplusN_spacer, T_space_rect.y_hi())), draw_list, &T_layout); if (p_tenured_layout) *p_tenured_layout = T_layout; // if (p_tenured_alloc_rect) // *p_tenured_alloc_rect = T_layout.to_alloc_rect(); } /*draw_gc_alloc_state*/ void draw_gc_state(const AppState & app_state, const GcStateDescription & gcstate, const ImRect & canvas_rect, ImDrawList * draw_list, GenerationLayout * p_nursery_layout, GenerationLayout * p_tenured_layout, //ImRect * p_nursery_alloc_rect, //ImRect * p_tenured_alloc_rect, ImRect * p_history_rect) { // draw stuff draw_list->AddRect(canvas_rect.top_left(), canvas_rect.bottom_right(), IM_COL32(255, 255, 255, 255)); /* TODO: does this reset coord space? */ ImRect alloc_rect; { ImGui::BeginChild("top pane", ImVec2(0, 105), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeY); alloc_rect = ImRect(canvas_rect.top_left() + ImGui::GetWindowContentRegionMin(), canvas_rect.top_left() + ImGui::GetWindowContentRegionMax()); ImRect draw_rect = alloc_rect.within_margin(ImRect(50, 10, 70, 10)); draw_list->PushClipRect(draw_rect.top_left(), draw_rect.bottom_right()); draw_gc_alloc_state(gcstate, draw_rect, draw_list, p_nursery_layout, p_tenured_layout //p_nursery_alloc_rect, //p_tenured_alloc_rect ); draw_list->PopClipRect(); ImGui::EndChild(); } ImRect history_rect; { ImGui::BeginChild("left pane", ImVec2(800, 0), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX); /* history below alloc area */ history_rect = ImRect(alloc_rect.bottom_left() + ImGui::GetWindowContentRegionMin(), alloc_rect.bottom_left() + ImGui::GetWindowContentRegionMax()); if (p_history_rect) *p_history_rect = history_rect; draw_list->PushClipRect(history_rect.top_left(), history_rect.bottom_right()); float lm = 50; float rm = 70; float tm = 10; std::size_t x0 = history_rect.x_lo() + lm; std::size_t x1 = history_rect.x_hi() - rm; std::size_t h_y0 = history_rect.y_lo() + tm; /* just incremental (nursery) collections */ ImRect incremental_rect = history_rect.top_fraction(0.33); draw_gc_history(gcstate, generation::nursery, app_state.gc_->gc_history(), incremental_rect, false /*debug_flag*/, draw_list); /* just full (nursery+tenured) collections */ ImRect full_rect = history_rect.bottom_fraction(0.67).top_fraction(0.5); /* both nursery + full collections */ draw_gc_history(gcstate, generation::tenured, app_state.gc_->gc_history(), full_rect, false /*debug_flag*/, draw_list); ImRect efficiency_rect = history_rect.bottom_fraction(0.67).bottom_fraction(0.5); draw_gc_efficiency(gcstate, app_state.gc_->gc_history(), efficiency_rect, false /*debug_flag*/, draw_list); draw_list->PopClipRect(); ImGui::EndChild(); } ImGui::Text("placeholder text"); /* BeginChild() again ? */ #ifdef NOPE draw_list->AddCircleFilled(ImVec2(canvas_p0.x + 50, canvas_p0.y + 50), 30.0f, IM_COL32(255, 0, 0, 255)); draw_list->AddText(ImVec2(canvas_p0.x + 10, canvas_p0.y + 10), IM_COL32(255, 255, 255, 255), "Hello 2D!"); #endif } struct DrawState; struct AnimateGcCopyCb : public xo::gc::GcCopyCallback { using generation = xo::gc::generation; explicit AnimateGcCopyCb(AppState * appstate, DrawState * drawstate) : p_app_state_{appstate}, p_draw_state_{drawstate} {} virtual void notify_gc_copy(std::size_t z, const void * src_addr, const void * dest_addr, generation src_gen, generation dest_gen); AppState * p_app_state_ = nullptr; DrawState * p_draw_state_ = nullptr; }; enum class draw_state_type { alloc, animate_gc }; struct DrawState { up make_gc_copy_animation(AppState * app_state) { return std::make_unique(app_state, this); } draw_state_type state_type_ = draw_state_type::alloc; /** budgeted time period over which to animate gc copy **/ int animate_copy_budget_ms_ = 2000; /** start time of current copy animation **/ std::chrono::steady_clock::time_point animate_copy_t0_; /** when animating copy step, display objects from AppState::copy_detail_v_[i] * where i < .animate_copy_hi_ / 100 * AppState::copy_detail_v_.size() **/ float animate_copy_hi_pct_ = 0; ImDrawList * gcw_draw_list_ = nullptr; /** draw area **/ ImVec2 gcw_canvas_p0_; ImVec2 gcw_canvas_p1_; /** layout for nursery display **/ GenerationLayout gcw_nursery_layout_; /** layout for tenured display **/ GenerationLayout gcw_tenured_layout_; /** rect displaying gc history (strip charts) **/ ImRect gcw_history_rect_; }; ImRect map_src_alloc_to_screen(const GcCopyDetail & copy_detail, const ImRect & space_rect) { // TODO: methods on copy_detail / and/or ImPoint auto [x_coord_lo, x_coord_hi] = space_rect.x_span(); double w0 = copy_detail.src_offset_ / static_cast(copy_detail.src_space_z_); float src0_x = ((1.0 - w0) * x_coord_lo) + (w0 * x_coord_hi); double w1 = ((copy_detail.src_offset_ + copy_detail.z_) / static_cast(copy_detail.src_space_z_)); float src1_x = ((1.0 - w1) * x_coord_lo) + (w1 * x_coord_hi); return space_rect.with_x_span(src0_x, src1_x); } ImRect map_dest_alloc_to_screen(const GcCopyDetail & copy_detail, const ImRect & space_rect) { /* for from-space, want to use full width of memory space */ auto [x_coord_lo, x_coord_hi] = space_rect.x_span(); // dest_space_z_ ? double w0 = copy_detail.dest_offset_ / static_cast(copy_detail.dest_z_); float dest0_x = ((1.0 - w0) * x_coord_lo) + (w0 * x_coord_hi); double w1 = ((copy_detail.dest_offset_ + copy_detail.z_) / static_cast(copy_detail.dest_z_)); float dest1_x = ((1.0 - w1) * x_coord_lo) + (w1 * x_coord_hi); return space_rect.with_x_span(dest0_x, dest1_x); } /* editor bait: animate_copy() */ void animate_gc_copy(const AppState & app_state, const DrawState & draw_state, ImDrawList * draw_list) { /* NOTE: this only runs during GC copy. * draw_state.gcw_nursery_layout_ and gcw_tenured_layout_ * are taken from snapshots made before GC began, * ergo before to/from spaces were swapped */ ImRect nursery_src_rect = draw_state.gcw_nursery_layout_.to_alloc_rect(); ImRect tenured_src_rect = draw_state.gcw_tenured_layout_.to_alloc_rect(); ImRect nursery_dest_rect = draw_state.gcw_nursery_layout_.mem_rect_from_; ImRect tenured_dest_rect; if (app_state.upto_ == generation::nursery) { tenured_dest_rect = draw_state.gcw_tenured_layout_.mem_rect_to_; } else { tenured_dest_rect = draw_state.gcw_tenured_layout_.mem_rect_from_; } std::size_t n_copy = app_state.copy_detail_v_.size(); /* grade from black-to-white between lo_copy and hi_copy. * Note we allow animate_copy_hi_pct_ > 100.0 */ float lo_copy = 0.01 * std::max(0.0, draw_state.animate_copy_hi_pct_ - 14.0) * n_copy; float hi_copy = 0.01 * draw_state.animate_copy_hi_pct_ * n_copy; /* remember max copy offset seen in {nursery, tenured} space respectively, * so we can label it */ std::size_t last_nursery_dest_offset = 0; ImRect last_nursery_dest_rect; std::size_t first_tenured_dest_offset = 0; ImRect first_tenured_dest_rect; std::size_t last_tenured_dest_offset = 0; ImRect last_tenured_dest_rect; std::size_t i_copy = 0; for (const auto & copy_detail : app_state.copy_detail_v_) { /* cutout for each copied object */ { float wt = (i_copy > lo_copy) ? static_cast(i_copy) / hi_copy : 0.0; /* grey fading to black */ //ImU32 color = IM_COL32(wt*128, 64+wt*64, wt*128, 255); ImU32 color = IM_COL32( 96, 224, 255, 255); ImRect src_rect; if (copy_detail.src_gen_ == generation::nursery) { src_rect = map_src_alloc_to_screen(copy_detail, nursery_src_rect); } else { src_rect = map_src_alloc_to_screen(copy_detail, tenured_src_rect); } draw_list->AddRectFilled(src_rect.top_left(), src_rect.bottom_right(), color); } if (copy_detail.dest_gen_ == generation::nursery) { last_nursery_dest_rect = map_dest_alloc_to_screen(copy_detail, nursery_dest_rect); last_nursery_dest_offset = copy_detail.dest_offset_; } else if (copy_detail.dest_gen_ == generation::tenured) { last_tenured_dest_rect = map_dest_alloc_to_screen(copy_detail, tenured_dest_rect); last_tenured_dest_offset = copy_detail.dest_offset_; if (first_tenured_dest_rect.width() == 0) { first_tenured_dest_rect = last_tenured_dest_rect; first_tenured_dest_offset = copy_detail.dest_offset_; } } if (++i_copy >= hi_copy) { break; } } if (last_nursery_dest_rect.width() > 0.0) { //ImU32 color = IM_COL32(64, 255, static_cast(64 + (128 * wt)), 255); ImU32 color = IM_COL32( 0, 96, 192, 255); draw_list->AddRectFilled(nursery_dest_rect.top_left(), last_nursery_dest_rect.bottom_right(), color); char buf[255]; snprintf(buf, sizeof(buf), "N\u2081: %luk", last_nursery_dest_offset / 1024); auto textz = ImGui::CalcTextSize(buf); ImU32 text_color = IM_COL32(255, 255, 255, 255); /*black*/ ImVec2 text_pos = ImVec2(0.5 * (nursery_dest_rect.x_lo() + last_nursery_dest_rect.x_hi() - textz.x), nursery_dest_rect.y_mid() - 0.5 * textz.y); if (text_pos.x < nursery_dest_rect.x_lo()) text_pos.x = nursery_dest_rect.x_lo() + 2; else if (text_pos.x < nursery_dest_rect.x_lo() + 0.5 * textz.x) text_pos.x = nursery_dest_rect.x_lo() + 0.5 * textz.x; draw_list->AddText(text_pos, text_color, buf); } if (last_tenured_dest_rect.width() > 0.0) { ImU32 color = IM_COL32( 0, 96, 192, 255); draw_list->AddRectFilled(first_tenured_dest_rect.top_left(), last_tenured_dest_rect.bottom_right(), color); char buf[255]; snprintf(buf, sizeof(buf), "+%luk", (last_tenured_dest_offset - first_tenured_dest_offset) / 1024); auto textz = ImGui::CalcTextSize(buf); ImU32 text_color = IM_COL32(255, 255, 255, 255); /*black*/ float x0 = first_tenured_dest_rect.x_lo(); float x1 = last_tenured_dest_rect.x_hi(); ImVec2 text_pos = ImVec2(0.5 * (x0 + x1 - textz.x), tenured_dest_rect.y_mid() - 0.5 * textz.y); if (text_pos.x < x0 + 2) text_pos.x = x0 + 2; else if (text_pos.x < x0 + 0.5 * textz.x) text_pos.x = x0 + 0.5 * textz.x; draw_list->AddText(text_pos, text_color, buf); } } void AnimateGcCopyCb::notify_gc_copy(std::size_t z, const void * src_addr, const void * dest_addr, generation src_gen, generation dest_gen) { using xo::scope; using xo::xtag; using xo::gc::generation_result; using xo::gc::generation; using xo::gc::role; scope log(XO_DEBUG(false), xtag("z", z), xtag("src", src_addr), xtag("dest", dest_addr), xtag("src_gen", src_gen), xtag("dest_gen", dest_gen)); auto [src_gen2, src_offset, src_alloc, src_size] = p_app_state_->gc_->fromspace_location_of(src_addr); if (src_gen2 == generation_result::not_found) { auto [lo, hi] = p_app_state_->gc_->nursery_span(role::from_space); log && log(xtag("N.from.lo", (void*)lo), xtag("N.from.hi", (void*)hi)); assert(false); } generation src_valid_gen = xo::gc::valid_genresult2gen(src_gen2); auto [dest_gen2, dest_offset, _, dest_size] = p_app_state_->gc_->tospace_location_of(dest_addr); generation dest_valid_gen = xo::gc::valid_genresult2gen(dest_gen2); p_app_state_->copy_detail_v_.push_back(GcCopyDetail(z, src_valid_gen, src_offset, src_alloc, dest_valid_gen, dest_offset, dest_size)); if (dest_valid_gen == generation::nursery) { p_app_state_->copy_detail_max_nursery_dest_offset_ = std::max(p_app_state_->copy_detail_max_nursery_dest_offset_, dest_offset); p_app_state_->copy_detail_nursery_dest_size_ = std::max(p_app_state_->copy_detail_nursery_dest_size_, dest_size); } else if (dest_valid_gen == generation::tenured) { p_app_state_->copy_detail_max_tenured_dest_offset_ = std::max(p_app_state_->copy_detail_max_tenured_dest_offset_, dest_offset); p_app_state_->copy_detail_tenured_dest_size_ = std::max(p_app_state_->copy_detail_tenured_dest_size_, dest_size); } /* will be animated across frames, see animate_gc_copy() */ } int main(int, char **) { using namespace std; scope log(XO_DEBUG(true)); std::cout << "Hello, world!" << std::endl; SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "0"); SDL_Init(SDL_INIT_VIDEO); SDL_version compiled; SDL_VERSION(&compiled); std::cerr << "SDL version: " << (int)compiled.major << "." << (int)compiled.minor << "." << (int)compiled.patch << std::endl; SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); #if 0 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); #endif std::cerr << "SDL video driver: " << SDL_GetCurrentVideoDriver() << std::endl; SDL_Window * window = SDL_CreateWindow("imgui + sdl2 + opengl", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 2000, 1000, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); if (window) { std::cerr << "SDL_CreateWindow done" << std::endl; } else { std::cerr << "SDL_CreateWindow failed: [" << SDL_GetError() << "]" << std::endl; SDL_Quit(); return -1; } SDL_GLContext gl_context = SDL_GL_CreateContext(window); int major, minor; SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major); SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor); std::cerr << "Requested OpenGL vtersion: " << major << "." << minor << std::endl; if (gl_context) { std::cerr << "SDL_GL_CreateContext done" << std::endl; } else { std::cerr << "SDL_GL_CreateContext failed: [" << SDL_GetError() << "]" << std::endl; return -1; } if (SDL_GL_MakeCurrent(window, gl_context) != 0) { std::cerr << "SDL_GL_MakeCurrent failed: [" << SDL_GetError() << "]" << std::endl; SDL_GL_DeleteContext(gl_context); SDL_DestroyWindow(window); SDL_Quit(); return -1; } SDL_GL_SetSwapInterval(0); // disable vsync GLenum glew_status = glewInit(); if (glew_status == GLEW_OK) { std::cerr << "glewInit done" << std::endl; } else { std::cerr << "glewInit failed: [" << glewGetErrorString(glew_status) << std::endl; return -1; } const GLubyte * version = glGetString(GL_VERSION); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); if (version) { std::cerr << "OpenGL version: [" << version << "]" << std::endl; } else { std::cerr << "OpenGL version not available" << std::endl; } IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO & io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Load noto sans font from unix environment NOTO_ONFTS_PATH // (see xo-umbrella2/default.nix shellHook) const char * fonts_path = std::getenv("DEJAVU_FONTS_PATH"); if (fonts_path) { const float font_size = 14.0f; std::string font_path = xo::tostr(fonts_path, "/truetype/DejaVuSans.ttf"); /* check file exists */ std::ifstream font_in(font_path); if (font_in.good()) { std::cerr << "loading font [" << font_path << "]" << std::endl; ImFont * font = io.Fonts->AddFontFromFileTTF(font_path.c_str(), font_size); if (font) { std::cerr << "font loaded" << std::endl; ImFontConfig config; config.MergeMode = true; // latin extended chars static const ImWchar latin_ranges[] = { 0x0020, 0x00ff, // basic latin + latin supplement 0x0100, 0x017f, // latin extended-A 0x0180, 0x024f, // latin extended-B 0x2080, 0x2099, // subscript numerals + letters through n 0x25b2, 0x25b4, // arrows 0, }; io.Fonts->AddFontFromFileTTF(font_path.c_str(), font_size, &config, latin_ranges); } else { std::cerr << "font file load failed" << std::endl; std::cerr << "Fallback to default ImGui font" << std::endl; } } } else { std::cerr << "Expected DEJAVU_FONTS_PATH environment var." << std::endl; std::cerr << "Fallback to default ImGui font" << std::endl; } ImGui::StyleColorsDark(); ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL3_Init("#version 330"); bool show_demo_window = true; bool show_another_window = false; ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); float counter_value = 0.0f; using xo::obj::Integer; using xo::obj::List; using xo::rng::xoshiro256ss; using xo::rng::Seed; using xo::up; using xo::gp; using xo::gc::GC; using xo::Object; AppState app_state; DrawState draw_state; /* note: during gc copy animation, this records state _before_ gc was triggered */ GcStateDescription gcstate = app_state.snapshot_gc_state(); app_state.gc_->add_gc_copy_callback(draw_state.make_gc_copy_animation(&app_state)); // Main Loop bool done = false; while (!done) { /** on each draw cycle, app state falls into categories: * 1. allocation * multiple draw cycles because many allocations per gc. * 2. garbage collection * multiple draw cycles to animate copying process * Settle conflict between {GC, imgui} as to who drives event loop, * in favor of imgui; achieve this by copying what GC did, * so that we can animate it over multiple draw cycles **/ switch (draw_state.state_type_) { case draw_state_type::alloc: { /** generate random alloc **/ app_state.generate_random_mutations(); gcstate = app_state.snapshot_gc_state(); app_state.upto_ = (app_state.gc_->is_full_gc_pending() ? generation::tenured : generation::nursery); /* GC may run here, in which case control reenters via AnimateGcCopyCb; * that callback captures copy details (per object!) in AppState */ if (app_state.gc_->enable_gc_once()) { log && log(xtag("gc-type", (app_state.upto_ == generation::tenured) ? "full" : "incremental")); draw_state.state_type_ = draw_state_type::animate_gc; draw_state.animate_copy_t0_ = std::chrono::steady_clock::now(); } break; } case draw_state_type::animate_gc: { /* don't update gcstate while animating, * that would use post-GC space sizing */ break; } } /** poll + handle events */ SDL_Event event; while (SDL_PollEvent(&event)) { ImGui_ImplSDL2_ProcessEvent(&event); if (event.type == SDL_QUIT) done = true; if (event.type == SDL_WINDOWEVENT) { if (event.window.event == SDL_WINDOWEVENT_CLOSE) { if (event.window.windowID == SDL_GetWindowID(window)) { done = true; } } else if (event.window.event == SDL_WINDOWEVENT_RESIZED) { // defer handling resize until device available this->framebuffer_resized_flag_ = true; } } } //int w, h; //SDL_GetWindowSize(window, &w, &h); //glViewport(0, 0, w, h); glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); // draw dear imgui frame ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); ImGui::SetNextWindowPos(ImVec2(0, 0)); ImGui::SetNextWindowSize(io.DisplaySize); ImGui::Begin("Background", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoDecoration); ImGui::End(); // 1. big demo window if (show_demo_window) ImGui::ShowDemoWindow(&show_demo_window); // 2. show window that we create ourselves { static int counter = 0; ImGui::Begin("Hello, world!"); //ImGui::Text("This is totes useful text..."); ImGui::Checkbox("demo window", &show_demo_window); //ImGui::Checkbox("second window", &show_another_window); ImGui::SliderInt("alloc/cycle", &app_state.alloc_per_cycle_, 1, 100); ImGui::SliderInt("copy animation budget", &draw_state.animate_copy_budget_ms_, 10, 10000); //ImGui::SliderFloat("alloc/cycle", &alloc_per_cycle, 0.0f, 1.0f); //ImGui::ColorEdit3("clear color", (float*)&clear_color); //if (ImGui::Button("Button")) // ++counter; ImGui::NewLine(); // ImGui::SameLine() /* N\u2080 = N0, N\u2081 = N1 */ ImGui::Text("alloc [%lu] avail [%lu] ", gcstate.gc_allocated_, gcstate.gc_available_); //ImGui::NewLine(); ImGui::Text("promoted [%lu] copy animation [%lu / %lu]", gcstate.total_promoted_, static_cast(draw_state.animate_copy_hi_pct_ * app_state.copy_detail_v_.size() / 100), app_state.copy_detail_v_.size()); ImGui::Text("mutation [%lu] mlog [%lu]", gcstate.total_n_mutation_, gcstate.gc_mlog_size_); ImGui::Text("appl average %.3f ms/frame (%.1f fps)", 1000.0f / io.Framerate, io.Framerate); ImGui::Text("layout:" " nursery-src alloc rect [%.1f %.1f %.1f %.1f]" " nursery-dest alloc rect [%.1f %.1f %.1f %.1f]" " history rect [%.1f %.1f %.1f %.1f]", draw_state.gcw_nursery_layout_.to_alloc_rect().x_lo(), draw_state.gcw_nursery_layout_.to_alloc_rect().y_lo(), draw_state.gcw_nursery_layout_.to_alloc_rect().x_hi(), draw_state.gcw_nursery_layout_.to_alloc_rect().y_hi(), draw_state.gcw_nursery_layout_.from_alloc_rect().x_lo(), draw_state.gcw_nursery_layout_.from_alloc_rect().y_lo(), draw_state.gcw_nursery_layout_.from_alloc_rect().x_hi(), draw_state.gcw_nursery_layout_.from_alloc_rect().y_hi(), draw_state.gcw_history_rect_.x_lo(), draw_state.gcw_history_rect_.y_lo(), draw_state.gcw_history_rect_.x_hi(), draw_state.gcw_history_rect_.y_hi()); ImGui::Text("nursery-dest copy offset [%lu] / size [%lu]" " tenured-dest copy offset [%lu] / size [%lu]", app_state.copy_detail_max_nursery_dest_offset_, app_state.copy_detail_nursery_dest_size_, app_state.copy_detail_max_tenured_dest_offset_, app_state.copy_detail_tenured_dest_size_ ); ImDrawList * draw_list = ImGui::GetWindowDrawList(); ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y); /* stash so GC copy animation can find it */ draw_state.gcw_draw_list_ = draw_list; draw_state.gcw_canvas_p0_ = canvas_p0; draw_state.gcw_canvas_p1_ = canvas_p1; draw_gc_state(app_state, gcstate, ImRect(canvas_p0, canvas_p1), draw_list, &draw_state.gcw_nursery_layout_, &draw_state.gcw_tenured_layout_, //nullptr, //&draw_state.gcw_tenured_alloc_rect_, &draw_state.gcw_history_rect_); if (draw_state.state_type_ == draw_state_type::animate_gc) { auto animate_copy_t1 = std::chrono::steady_clock::now(); auto animate_dt = animate_copy_t1 - draw_state.animate_copy_t0_; float animate_fraction_spent = (std::chrono::duration_cast(animate_dt).count() / static_cast(draw_state.animate_copy_budget_ms_)); draw_state.animate_copy_hi_pct_ = 100.0 * animate_fraction_spent; animate_gc_copy(app_state, draw_state, draw_list); /* see 25.0 constant in animate_gc_copy() */ if (draw_state.animate_copy_hi_pct_ >= 114) { draw_state.state_type_ = draw_state_type::alloc; draw_state.animate_copy_hi_pct_ = 0; app_state.copy_detail_v_.clear(); app_state.copy_detail_max_nursery_dest_offset_ = 0; app_state.copy_detail_nursery_dest_size_ = 0; app_state.copy_detail_max_tenured_dest_offset_ = 0; app_state.copy_detail_tenured_dest_size_ = 0; } } ImGui::End(); } // 3. another window if (show_another_window) { ImGui::Begin("another window", &show_another_window); ImGui::Text("hello from second window"); if (ImGui::Button("close me")) show_another_window = false; ImGui::End(); } // rendering ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); SDL_GL_SwapWindow(window); } std::cerr << "cleanup.." << std::endl; ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); SDL_GL_DeleteContext(gl_context); SDL_DestroyWindow(window); SDL_Quit(); std::cerr << "All done, goodbye..." << std::endl; return 0; } /* xo-imgui/example/ex1/imgui_ex2.cpp * * author: Roland Conybeare, Aug 2025 */ #include "xo/object/Integer.hpp" #include "xo/object/List.hpp" #include "xo/alloc/GC.hpp" #include "xo/alloc/Object.hpp" #include "xo/randomgen/xoshiro256.hpp" #include "xo/randomgen/random_seed.hpp" #include "xo/object/Integer.hpp" #include "xo/flatstring/flatstring.hpp" #include "xo/indentlog/scope.hpp" #include #include #include #include "SDL_events.h" #include "imgui.h" #include "backends/imgui_impl_sdl2.h" #include "backends/imgui_impl_opengl3.h" //#include #ifdef NOPE #include #endif #include #include #include #include using xo::gc::generation; ImVec2 operator+(const ImVec2 & p1, const ImVec2 & p2) { return ImVec2(p1.x + p2.x, p1.y + p2.y); } struct ImRect { ImRect() = default; ImRect(const ImVec2 & tl, const ImVec2 & br) : top_left_{tl}, bottom_right_{br} {} ImRect(float x_lo, float y_lo, float x_hi, float y_hi) : top_left_{x_lo, y_lo}, bottom_right_{x_hi, y_hi} {} static ImRect from_xy_span(const ImVec2 & x_span, const ImVec2 & y_span) { return ImRect(ImVec2{x_span.x, y_span.x}, ImVec2{x_span.y, y_span.y}); } std::pair x_span() const { return std::make_pair(top_left_.x, bottom_right_.x); } std::pair y_span() const { return std::make_pair(top_left_.y, bottom_right_.y); } const ImVec2 & top_left() const { return top_left_; } const ImVec2 & bottom_right() const { return bottom_right_; } float x_lo() const { return top_left_.x; } float x_hi() const { return bottom_right_.x; } float y_lo() const { return top_left_.y; } float y_hi() const { return bottom_right_.y; } float width() const { return bottom_right_.x - top_left_.x; } float height() const { return bottom_right_.y - top_left_.y; } float x_mid() const { return 0.5 * (top_left_.x + bottom_right_.x); } float y_mid() const { return 0.5 * (top_left_.y + bottom_right_.y); } ImVec2 bottom_left() const { return ImVec2(x_lo(), y_hi()); } ImVec2 top_right() const { return ImVec2(x_hi(), y_lo()); } ImRect with_x_span(float x0, float x1) const { return ImRect(ImVec2(x0, top_left_.y), ImVec2(x1, bottom_right_.y)); } ImRect with_y_span(float y0, float y1) const { return ImRect(ImVec2(top_left_.x, y0), ImVec2(bottom_right_.x, y1)); } ImRect within_margin(const ImRect & margin) const { return ImRect(this->x_lo() + margin.x_lo(), this->y_lo() + margin.y_lo(), this->x_hi() - margin.x_hi(), this->y_hi() - margin.y_hi()); } ImRect within_right_margin(float dx) const { return ImRect(this->x_lo(), this->y_lo(), this->x_hi() - dx, this->y_hi()); } ImRect within_top_margin(float dy) const { return ImRect(this->x_lo(), this->y_lo() + dy, this->x_hi(), this->y_hi()); } ImRect within_bottom_margin(float dy) const { return ImRect(this->x_lo(), this->y_lo(), this->x_hi(), this->y_hi() - dy); } ImRect translate(const ImVec2 & dist) { return ImRect(top_left_ + dist, bottom_right_ + dist); } /** Require: 0.0 <= p <= 1.0 **/ ImRect left_fraction(float p, float min_width = 0.0, float max_width = 999999.0) const { max_width = std::min(max_width, this->width()); float w = std::clamp(p * this->width(), min_width, max_width); return ImRect(top_left_, ImVec2(this->x_lo() + w, this->y_hi())); } /** Require: 0.0 <= p <= 1.0 **/ ImRect right_fraction(float p, float min_width = 0.0, float max_width = 999999.0) const { max_width = std::min(max_width, this->width()); float w = std::clamp(p * this->width(), min_width, max_width); return ImRect(ImVec2(this->x_hi() - w, this->y_lo()), bottom_right_); } /** Require: 0.0 <= p <= q <= 1.0 **/ ImRect mid_x_fraction(float p, float q) const { assert(p <= q); float w = this->width(); return this->with_x_span(this->x_lo() + p * w, this->x_lo() + q * w); } /** Require: 0.0 <= p <= 1.0 **/ ImRect top_fraction(float p, float min_height = 0.0, float max_height = 999999.0) const { max_height = std::min(max_height, this->height()); float h = std::clamp(p * this->height(), min_height, max_height); return ImRect(top_left_, ImVec2(this->x_hi(), this->y_lo() + h)); } /** Require: 0.0 <= p <= 1.0 **/ ImRect bottom_fraction(float p, float min_height = 0.0, float max_height = 999999.0) const { max_height = std::min(max_height, this->height()); float h = std::clamp(p * this->height(), min_height, max_height); return ImRect(ImVec2(this->x_lo(), this->y_hi() - h), bottom_right_); } ImVec2 top_left_{0, 0}; ImVec2 bottom_right_{0, 0}; }; /* We need GUI to be able to fall behind true GC state, so we can animate transitions. * To help make this work, provide a model for GC state sufficient to drive rendering. */ struct GcGenerationDescription { GcGenerationDescription() = default; GcGenerationDescription(generation gen, const char * name, const char * mnemonic, const char * gc_type, std::uint8_t polarity, std::size_t tospace_scale, std::size_t before_ckp, std::size_t after_ckp, std::size_t reserved, std::size_t committed, std::size_t gc_threshold) : name_{name}, mnemonic_{mnemonic}, gc_type_{gc_type}, polarity_{polarity}, tospace_scale_{tospace_scale}, before_checkpoint_{before_ckp}, after_checkpoint_{after_ckp}, reserved_{reserved}, committed_{committed}, gc_threshold_{gc_threshold} {} /** scale (in bytes) for drawing space **/ std::size_t scale() const { return std::max(committed_, gc_threshold_); } /** nursery or tenured **/ generation generation_; /** "nursery" or "tenured" **/ const char * name_ = nullptr; /** "N" or "T" **/ const char * mnemonic_ = nullptr; /** "incremental" or "full" **/ const char * gc_type_ = nullptr; /** alternates between {0, 1} on each GC **/ std::uint8_t polarity_ = 0; /** size of to-space in bytes represented on screen. * (note however when we animate GC, space roles have already reversed, * so then this will refer to old to-space = new from-space) **/ std::size_t tospace_scale_ = 0; std::size_t before_checkpoint_ = 0; std::size_t after_checkpoint_ = 0; std::size_t reserved_ = 0; std::size_t committed_ = 0; // G_to_gc_threshold = G1_to_size + gc->config().incr_gc_threshold_; std::size_t gc_threshold_ = 0; }; /* We need GUI to be able to fall behind true GC state, so we can animate transitions. * To help make this work, provide a model for GC state sufficient to drive rendering. */ struct GcStateDescription { using generation = xo::gc::generation; GcStateDescription(const GcGenerationDescription & nursery, const GcGenerationDescription & tenured, std::size_t gc_size, std::size_t gc_committed, std::size_t gc_allocated, std::size_t gc_available, std::size_t gc_mlog_size, std::size_t total_promoted, std::size_t total_n_mutation ); const GcGenerationDescription & get_gendescr(generation g) const { return gen_state_v_[gen2int(g)]; } std::array(generation::N)> gen_state_v_; /** see @ref GC::size **/ std::size_t gc_size_ = 0; /** see @ref GC::committed **/ std::size_t gc_committed_ = 0; /** see @ref GC::allocated **/ std::size_t gc_allocated_ = 0; /** see @ref GC::available **/ std::size_t gc_available_ = 0; /** see @ref GC::mlog_size **/ std::size_t gc_mlog_size_ = 0; /** see @ref GcStatistics::total_promoted_ **/ std::size_t total_promoted_ = 0; /** see @ref GcStatistics::n_mutation_ **/ std::size_t total_n_mutation_ = 0; }; GcStateDescription::GcStateDescription(const GcGenerationDescription & nursery, const GcGenerationDescription & tenured, std::size_t gc_size, std::size_t gc_committed, std::size_t gc_allocated, std::size_t gc_available, std::size_t gc_mlog_size, std::size_t total_promoted, std::size_t total_n_mutation) : gc_size_{gc_size}, gc_committed_{gc_committed}, gc_allocated_{gc_allocated}, gc_available_{gc_available}, gc_mlog_size_{gc_mlog_size}, total_promoted_{total_promoted}, total_n_mutation_{total_n_mutation} { gen_state_v_[gen2int(generation::nursery)] = nursery; gen_state_v_[gen2int(generation::tenured)] = tenured; } using xo::gp; using xo::up; using xo::Object; using xo::obj::List; using xo::obj::Integer; using xo::rng::xoshiro256ss; using xo::rng::Seed; /** details of a single copy event performed by GC **/ struct GcCopyDetail { GcCopyDetail(std::size_t z, generation src, std::size_t src_offset, std::size_t src_space_z, generation dest, std::size_t dest_offset, std::size_t dest_z) : z_{z}, src_gen_{src}, src_offset_{src_offset}, src_space_z_{src_space_z}, dest_gen_{dest}, dest_offset_{dest_offset}, dest_z_{dest_z} {} /** object size in bytes **/ std::size_t z_ = 0; /** source location **/ generation src_gen_; /** offset from start of allocator **/ std::size_t src_offset_ = 0; /** size of source space. could store this separately **/ std::size_t src_space_z_ = 0; /** destination location **/ generation dest_gen_; /** offset from start of allocator **/ std::size_t dest_offset_ = 0; /** size of destination space. (could store this separately). **/ std::size_t dest_z_ = 0; }; struct AppState { using GC = xo::gc::GC; public: AppState(); std::size_t nursery_tospace_scale() const; std::size_t tenured_tospace_scale() const; GcStateDescription snapshot_gc_state() const; void generate_random_mutation(); void generate_random_mutations(); public: int alloc_per_cycle_ = 1; /** if gc triggered, remembers which whether incremental or full **/ generation upto_ = generation::nursery; up gc_; std::size_t next_int_ = 0; std::size_t next_root_ = 0; std::vector> gc_root_v_{100}; Seed seed_; xoshiro256ss rng_{seed_}; /** remember details for each object copied by GC, so we can animate **/ std::vector copy_detail_v_; /** max offset for destination, given copied to nursery **/ std::size_t copy_detail_max_nursery_dest_offset_ = 0; std::size_t copy_detail_nursery_dest_size_ = 0; std::size_t copy_detail_max_tenured_dest_offset_ = 0; std::size_t copy_detail_tenured_dest_size_ = 0; }; AppState::AppState() { this->gc_ = (GC::make ( {.initial_nursery_z_ = 1024*1024, .initial_tenured_z_ = 1024*1024*1024, .incr_gc_threshold_ = 16*1024, .full_gc_threshold_ = 128*1024, .stats_flag_ = true, .debug_flag_ = false})); Object::mm = gc_.get(); for (auto & x: gc_root_v_) gc_->add_gc_root(x.ptr_address()); gc_->disable_gc(); } std::size_t AppState::nursery_tospace_scale() const { std::size_t N1_to_size = gc_->nursery_before_checkpoint(); std::size_t N_to_committed = gc_->nursery_to_committed(); std::size_t N_to_incr_gc_threshold = N1_to_size + gc_->config().incr_gc_threshold_; std::size_t N_to_scale = std::max(N_to_committed, N_to_incr_gc_threshold); return N_to_scale; } std::size_t AppState::tenured_tospace_scale() const { std::size_t T1_to_size = gc_->tenured_before_checkpoint(); std::size_t T_to_committed = gc_->tenured_to_committed(); std::size_t T_to_full_gc_threshold = T1_to_size + gc_->config().full_gc_threshold_; std::size_t T_to_scale = std::max(T_to_committed, T_to_full_gc_threshold); return T_to_scale; } GcStateDescription AppState::snapshot_gc_state() const { /** NOTE: this gets invoked before GC gets opportunity to run. * in the event that GC does run, from- and to- spaces will * have been reversed (near beginning of GC phase) * * This means that nursery_to_reserved() etc. actually refer to from-space * *during gc* **/ // TOOD: may want to use GC::get_gc_statistics() to replace multiple round trips return GcStateDescription(GcGenerationDescription (generation::nursery, "nursery", "N", "incremental", gc_->nursery_polarity(), this->nursery_tospace_scale(), gc_->nursery_before_checkpoint(), gc_->nursery_after_checkpoint(), gc_->nursery_to_reserved(), gc_->nursery_to_committed(), gc_->nursery_before_checkpoint() + gc_->config().incr_gc_threshold_), GcGenerationDescription (generation::tenured, "tenured", "T", "full", gc_->tenured_polarity(), this->tenured_tospace_scale(), gc_->tenured_before_checkpoint(), gc_->tenured_after_checkpoint(), gc_->tenured_to_reserved(), gc_->tenured_to_committed(), gc_->tenured_before_checkpoint() + gc_->config().full_gc_threshold_), gc_->size(), gc_->committed(), gc_->allocated(), gc_->available(), gc_->mlog_size(), gc_->native_gc_statistics().total_promoted_, gc_->native_gc_statistics().n_mutation_ ); } void AppState::generate_random_mutation() { if (rng_() % 1000 > (5 * 1000) / 7) { /* p=16% integer */ gc_root_v_[next_root_++] = Integer::make(next_int_); } else if (rng_() % 1000 > (3 * 1000) / 7) { /* p=16% cons */ gp random_car = gc_root_v_.at(rng_() % gc_root_v_.size()); if (random_car.is_null()) random_car = List::nil; /* this will always incorporate existing list as tail of new list */ gp random_cdr = List::from(gc_root_v_[next_root_]); if (random_cdr.is_null()) random_cdr = List::nil; gp random_cons = List::cons(random_car, random_cdr); gc_root_v_[next_root_++] = random_cons; } else if (rng_() % 1000 > (0 * 1000) / 7) { /* p=24% mutation */ gp random_list = List::from(gc_root_v_.at(rng_() % gc_root_v_.size())); if (!random_list.is_null()) { if (rng_() % 2 == 0) { /* pick up some random object, assign as head */ gp random_car = gc_root_v_.at(rng_() % gc_root_v_.size()); random_list->assign_head(random_car); } else { /* pick up some random object; if List, assign tail as tail */ gp random_cdr = List::from(gc_root_v_.at(rng_() % gc_root_v_.size())); if (!random_cdr.is_null() && !random_cdr->is_nil()) random_list->assign_rest(random_cdr->rest()); } } } if (next_root_ >= gc_root_v_.size()) this->next_root_ = 0; } void AppState::generate_random_mutations() { for (int i = 0; i < this->alloc_per_cycle_; ++i) { this->generate_random_mutation(); } } void draw_filled_rect_with_label(const char * text, const char * tooltip, const ImRect & rect, ImU32 fillcolor, ImU32 textcolor, ImDrawList * draw_list) { draw_list->AddRectFilled(rect.top_left(), rect.bottom_right(), fillcolor); if ((rect.width() > 0.0) && (rect.height() > 0.0)) { ImGui::SetCursorScreenPos(rect.top_left()); ImGui::InvisibleButton("ttbutton", ImVec2(rect.width(), rect.height())); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", tooltip); } } if (text) { auto textz = ImGui::CalcTextSize(text); /* N1 can be empty: but in that case don't bother to label it */ if (textz.x < rect.width()) { draw_list->AddText(ImVec2(rect.x_mid() - 0.5 * textz.x, rect.y_mid() - 0.5 * textz.y), textcolor, text); } } } void draw_filled_rect(const char * tooltip, const ImRect & rect, ImU32 fillcolor, ImDrawList * draw_list) { draw_filled_rect_with_label(nullptr, tooltip, rect, fillcolor, IM_COL32(255, 255, 255, 255), draw_list); } using xo::gc::GC; using xo::gc::GcStatisticsExt; using xo::gc::GcStatisticsHistory; using xo::gc::GcStatisticsHistoryItem; using xo::xtag; using xo::scope; using xo::flatstring; using std::size_t; /** @class GenerationLayout * @brief layout for displaying a single collector generation. * * @text * reserved: xxx committed: xxxx G1: xxx bytes G0: xxx bytes * +--------------------------+--------------------+-------------------------+ * to | G1 | G0 | | * +--------------------------+--------------------+-------------------------+ * Mem: 28k * +-------------------------------------------------------------------------+ * from | | * +-------------------------------------------------------------------------+ * ^ * layout elements: * * <-a-><------------------------------ mem_w ------------------------------------><---b----> * <------------------------- ngc_w ----------------------------> * <--------- G1_w ----------> <------- G0_w -----> * * a (lh_text_dx): width for left-hand-side text * b (rh_text_dx): width for right-hand-side text * mem_w: width for contiguous committed memory * ngc_w: location (relative to start of GC memory range) of next-collection trigger * G1_w: width for occupied memory that has survived one GC in this space * G0_w: width for memory allocated since last GC * * @endtext * **/ struct GenerationLayout { GenerationLayout() = default; GenerationLayout(const GcGenerationDescription & gendescr, const ImRect & br, bool with_labels); const char * name() const { return gendescr_.name_; } const char * mnemonic() const { return gendescr_.mnemonic_; } const char * gc_type() const { return gendescr_.gc_type_; } std::size_t to_G1_size() const { return gendescr_.before_checkpoint_; } std::size_t to_G0_size() const { return gendescr_.after_checkpoint_; } std::size_t to_gc_threhsold() const { return gendescr_.gc_threshold_; } float to_scale() const { /** note: deliberate size_t->float conversion here **/ return gendescr_.scale(); } ImRect to_g1_rect() const { return mem_rect_to_.left_fraction(this->to_G1_size() / this->to_scale()); } ImRect to_g0_rect() const { return mem_rect_to_.mid_x_fraction(this->to_G1_size() / this->to_scale(), (this->to_G1_size() + this->to_G0_size()) / this->to_scale()); } ImRect to_alloc_rect() const { return mem_rect_to_.left_fraction((this->to_G1_size() + this->to_G0_size()) / this->to_scale()); } ImRect from_alloc_rect() const { /* use the same sizing as for source generation */ return mem_rect_from_.left_fraction((this->to_G1_size() + this->to_G0_size()) / this->to_scale()); } /** size-related statistics for generation to be displayed **/ GcGenerationDescription gendescr_; /** bounding rectangle. all drawing for generation display will be inside this rectanglge **/ ImRect bounding_rect_; /** true iff text labels enabled **/ bool with_labels_ = false; /** text height in screen units **/ float text_dy_ = 0.0; /** chart rectangle. bounding rectangle less room for headline text **/ ImRect chart_withlabel_rect_; ImRect chart_nolabel_rect_; /** text for RH label. something like "N: 28k/40k" **/ flatstring<80> rh_text_; /** width of .rh_text in screen units **/ float rh_text_dx_ = 0.0; /** rectangle representing from-space memory range **/ ImRect mem_rect_from_; /** rectangle representing to-space memory range **/ ImRect mem_rect_to_; }; GenerationLayout::GenerationLayout(const GcGenerationDescription & gendescr, const ImRect & br, bool with_labels) : gendescr_{gendescr}, bounding_rect_{br}, with_labels_{with_labels} { this->text_dy_ = ImGui::CalcTextSize("SAMPLE TEXT").y; if (with_labels_) { snprintf(this->rh_text_.data(), rh_text_.capacity(), "%s: %luk", gendescr_.mnemonic_, std::max(gendescr_.gc_threshold_, gendescr_.committed_) / 1024); rh_text_.ensure_final_null(); auto textz = ImGui::CalcTextSize(rh_text_.c_str()); /* allow margin between rh edge of mem range and beginning of label */ this->rh_text_dx_ = 5 + textz.x; } else { this->rh_text_dx_ = 0.0; } if (with_labels_) { this->chart_withlabel_rect_ = bounding_rect_.within_top_margin(text_dy_ + 2); this->chart_nolabel_rect_ = chart_withlabel_rect_.within_right_margin(rh_text_dx_); } else { this->chart_withlabel_rect_ = bounding_rect_; this->chart_nolabel_rect_ = bounding_rect_; } this->mem_rect_from_ = chart_nolabel_rect_.top_fraction(0.45); this->mem_rect_to_ = chart_nolabel_rect_.bottom_fraction(0.45); if (gendescr_.polarity_ == 1) std::swap(this->mem_rect_from_, this->mem_rect_to_); } /** * @p polarity 0 -> draw from-space above to-space; 1 -> draw from-space below to-space * @p p_x1 On exit *p_x1 contains x-coord of right-hand edge of rectangle * depicting potential memory range **/ void draw_generation(const GenerationLayout & layout, ImDrawList * draw_list) { //scope log(XO_DEBUG(with_labels)); using xo::gc::generation; /* next GC trigges when G0_to_size reaches this threshold */ std::size_t G_to_gc_threshold = 0; G_to_gc_threshold = layout.gendescr_.gc_threshold_; std::size_t G_to_scale = layout.gendescr_.scale(); /* * committed: G_to_committed * G1: G1_to_size * G0: G0_to_size * ckp: G1_to_size * ngc: G_to_gc_threshold * * <----------------------------- committed ---------------------------> * <------------------ used ------------------> <-------- free --------> * <------- G1 --------> <-------- G0 --------> * |NNNNNNNNNNNNNNNNNNNNN|nnnnnnnnnnnnnnnnnnnnnn|________________________| * ^ ^ * ckp ngc * * in screen coords: * * horizontally: * * rect.x_lo rect.x_hi * v v * * <--------------------------- display_w -----------------------------> <-+-> * <------------------------ ngc_w ----------------------> \- rh_text_dx * <------- G1_w ------> <-------- G0_w ------> * ^ ^ ^ ^ ^ * x0 G0_x0 G0_x1 ngc_w x1 * *p_x0 *p_g0_x1 *p_x1 * * vertically: * * <- rect.y_lo * ^ * * v * <- rect.y_hi */ /* e.g. N1: 34511 bytes */ char g1_buf[255]; ImU32 label_color = IM_COL32(255, 255, 192, 255); /*super pale yellow*/ if (layout.with_labels_) { snprintf(g1_buf, sizeof(g1_buf), "reserved: %lu bytes; committed: %lu bytes; %s\u2081: %lu bytes; %s\u2080: %lu bytes", layout.gendescr_.reserved_, layout.gendescr_.committed_, layout.mnemonic(), layout.gendescr_.before_checkpoint_, layout.mnemonic(), layout.gendescr_.after_checkpoint_); draw_list->AddText(layout.bounding_rect_.top_left(), label_color, g1_buf); } if (layout.with_labels_) { auto textz = ImGui::CalcTextSize(layout.rh_text_.c_str()); draw_list->AddText(ImVec2(layout.chart_withlabel_rect_.x_hi() - textz.x, layout.chart_withlabel_rect_.y_mid() - 0.5 * textz.y), label_color, layout.rh_text_.c_str()); } ImU32 outline_color = IM_COL32(255, 255, 255, 255); /*white*/ /* chart rectangle */ draw_list->AddRect(layout.mem_rect_from_.top_left(), layout.mem_rect_from_.bottom_right(), outline_color); draw_list->AddRect(layout.mem_rect_to_.top_left(), layout.mem_rect_to_.bottom_right(), outline_color); float display_w = layout.mem_rect_from_.width(); float G1_w = display_w * layout.to_G1_size() / layout.to_scale(); float G1_x1 = layout.mem_rect_from_.x_lo() + G1_w; ImRect G1_rect = layout.to_g1_rect(); //ImRect G1_rect = layout.mem_rect_to_.left_fraction(layout.to_G1_size() / layout.to_scale()); /* G1 (i.e. N1 or T1) */ { ImU32 G1_color = IM_COL32( 0, 128, 0, 255); ImU32 text_color = IM_COL32(255, 255, 255, 255); char buf[255]; if (layout.with_labels_) snprintf(buf, sizeof(buf), "%s\u2081: %luk", layout.mnemonic(), layout.to_G1_size() / 1024); /* N1 / T1 */ char tooltip[255]; snprintf(tooltip, sizeof(tooltip), "%s\u2081: %lu - %s survivor size in bytes", layout.mnemonic(), layout.to_G1_size(), layout.name()); draw_filled_rect_with_label(layout.with_labels_ ? buf : nullptr, tooltip, G1_rect, G1_color, text_color, draw_list); } ImRect G0_rect = layout.to_g0_rect(); /* G0 (i.e. N0 or T0) */ { ImU32 G0_color = IM_COL32( 32, 192, 32, 255); ImU32 text_color = IM_COL32( 0, 0, 0, 255); char buf[255]; if (layout.with_labels_) snprintf(buf, sizeof(buf), "%s\u2080: %luk", layout.mnemonic(), layout.to_G0_size() / 1024); /* N(0) */ char tooltip[255]; snprintf(tooltip, sizeof(tooltip), "%s\u2080: %lu - %s new alloc size in bytes", layout.mnemonic(), layout.to_G0_size(), layout.name()); draw_filled_rect_with_label(layout.with_labels_ ? buf : nullptr, tooltip, G0_rect, G0_color, text_color, draw_list); } /* mark where next gc will trigger */ if (layout.with_labels_) { const char * uparrow = reinterpret_cast(u8"\u25b3"); float ngc_w = (display_w * layout.gendescr_.gc_threshold_) / G_to_scale; auto tmp = ImGui::CalcTextSize(uparrow); std::size_t uparrow_w = tmp.x; double ngc_x = layout.chart_withlabel_rect_.x_lo() + ngc_w - uparrow_w/2.0; ImVec2 marker_pos(ngc_x, layout.chart_withlabel_rect_.y_hi()); draw_list->AddText(marker_pos, IM_COL32(255, 128, 128, 255) /*red*/, uparrow); ImGui::SetCursorScreenPos(marker_pos); ImGui::InvisibleButton("mkbutton", tmp); if (ImGui::IsItemHovered()) { char marker_tt_buf[255]; snprintf(marker_tt_buf, sizeof(marker_tt_buf), "Next %s GC when size(%s) >= %lu bytes", layout.gc_type(), layout.name(), layout.gendescr_.gc_threshold_); ImGui::SetTooltip("%s", marker_tt_buf); } } } /*draw_generation*/ void draw_nursery(const GcStateDescription & gcstate, bool with_labels, const ImRect & rect, ImDrawList * draw_list, GenerationLayout * p_layout) { using xo::gc::generation; const GcGenerationDescription & gendescr = gcstate.get_gendescr(generation::nursery); GenerationLayout layout(gendescr, rect, with_labels); draw_generation(layout, draw_list); if (p_layout) *p_layout = layout; } void draw_tenured(const GcStateDescription & gcstate, bool with_labels, const ImRect & rect, ImDrawList * draw_list, GenerationLayout * p_layout) { using xo::gc::generation; const GcGenerationDescription & gendescr = gcstate.get_gendescr(generation::tenured); GenerationLayout layout(gendescr, rect, with_labels); draw_generation(layout, draw_list); if (p_layout) *p_layout = layout; } /** for history tooltip, choose which statistic to headline **/ enum class gc_history_headline { survive, promote, persist, garbage0, garbage1, garbageN, N }; xo::flatstring<512> write_gc_history_tooltip(gc_history_headline headline, const GcStatisticsHistoryItem & stats) { xo::flatstring<512> retval; xo::flatstring<512> headline_str; switch (headline) { case gc_history_headline::survive: snprintf(headline_str.data(), headline_str.capacity(), "survive: %lu: bytes surviving 1st GC after allocation", stats.survive_z_); break; case gc_history_headline::promote: snprintf(headline_str.data(), headline_str.capacity(), "promote: %lu: bytes surviving 2nd GC; if nursery promote to tenured", stats.promote_z_); break; case gc_history_headline::persist: snprintf(headline_str.data(), headline_str.capacity(), "persist: %lu: bytes surviving 3+ GCs. Only non-zero for full collections", stats.persist_z_); break; case gc_history_headline::garbage0: snprintf(headline_str.data(), headline_str.capacity(), "garbage\u2080: %lu: bytes collected on 1st GC after allocation", stats.garbage0_z_); break; case gc_history_headline::garbage1: snprintf(headline_str.data(), headline_str.capacity(), "garbage\u2081: %lu: bytes collected on 2nd GC after allocation", stats.garbage1_z_); break; case gc_history_headline::garbageN: snprintf(headline_str.data(), headline_str.capacity(), "garbage\u2099: %lu: bytes collected on 3rd or later GC after allocation", stats.garbageN_z_); break; case gc_history_headline::N: assert(false); break; } snprintf(retval.data(), retval.capacity(), "%s\n" "\n" " gcseq: %lu\n" " type: %s\n" " alloc: %lu\n" " survive: %lu\n" " promote: %lu\n" " persist: %lu\n" " garbage\u2080: %lu\n" /*garbage0*/ " garbage\u2081: %lu\n" /*garbage1*/ " garbage\u2099: %lu\n" /*garbageN*/ " effort: %lu dt: %.1lfus\n" " copy efficiency: %.1lf%% collection rate: %.0lf bytes/sec", headline_str.c_str(), stats.gc_seq_, (stats.upto_ == generation::nursery) ? "incremental" : "FULL", stats.new_alloc_z_, stats.survive_z_, stats.promote_z_, stats.persist_z_, stats.garbage0_z_, stats.garbage1_z_, stats.garbageN_z_, stats.effort_z_, 1e-3 * stats.dt_.scale(), 100.0 * stats.efficiency(), stats.collection_rate() ); return retval.ensure_final_null(); } /** stacked bar chart * * @param gen if @ref generation::nursery, only display nursery collections. * otherwise display both **/ void draw_gc_history(const GcStateDescription & gcstate, generation gen, const GcStatisticsHistory & gc_history, const ImRect & bounding_rect, bool debug_flag, ImDrawList * draw_list) { scope log(XO_DEBUG(debug_flag)); float lm = 10; float tm = 25; /* we're going to make a bar chart */ /* x_scale,y_scale in GC units (i.e. bytes) */ size_t x_scale = gc_history.capacity(); size_t yplus_scale = 0; size_t yminus_scale = 0; float display_w = bounding_rect.width() - lm; float display_h = bounding_rect.height() - tm; /* 1st loop: figure out max y scale */ for (const GcStatisticsHistoryItem & stats : gc_history) { if ((gen == stats.upto_) || (gen == generation::tenured)) { //size_t na = stats.new_alloc_z_ - stats.survive_z_; /*new allocs, but dont' double-count survive_z*/ size_t sz = stats.survive_z_; /*survive 1st gc */ size_t pz = stats.promote_z_; /*survive 2nd gc */ size_t psz = stats.persist_z_; /*survive 3+ gc */ size_t g0z = stats.garbage0_z_; size_t g1z = stats.garbage1_z_; size_t gNz = stats.garbageN_z_; if (yplus_scale < sz + pz + psz) yplus_scale = sz + pz + psz; if (yminus_scale < g0z + g1z + gNz) yminus_scale = g0z + g1z + gNz; } else { ; } } /* y-coord of x-axis */ float y_zero = bounding_rect.y_lo() + tm + (display_h * yplus_scale) / (yplus_scale + yminus_scale); float y_scale = yplus_scale + yminus_scale; /* width of 1 bar in screen coords */ constexpr float c_min_bar_w = 5.0; float bar_w = std::max(c_min_bar_w, display_w / gc_history.capacity()); /* 2nd loop: draw bars */ std::size_t i = 0; for (const GcStatisticsHistoryItem & stats : gc_history) { if ((gen == stats.upto_) || (gen == generation::tenured)) { /* * ys_lo +--+ * | | survive_z (survived 1st GC) * | | * yp_lo +--+ * | | promote_z (sruvived 2nd GC) * | | * ypsz_lo +--+ * | | persist_z (survived 3+ GCs) * | | * y_zero +--+ * | | gN (killed on 3+ GC) * | | * ygN_hi +--+ * | | g1 (killed on 2nd GC) * | | * yg1_hi +--+ * | | g0 (killed on 1st GC) * | | * yg0_hi +--+ */ ImU32 persist_color = IM_COL32( 0, 64, 192, 255); /*darker blue*/ ImU32 promote_color = IM_COL32( 0, 128, 0, 255); /*darker green*/ ImU32 survive_color = IM_COL32( 32, 192, 32, 255); /*lighter green*/ ImU32 garbageN_color = IM_COL32(255, 128, 64, 255); /*darker orange*/ ImU32 garbage1_color = IM_COL32(255, 192, 128, 255); /*medium orange*/ ImU32 garbage0_color = IM_COL32(255, 255, 192, 255); /*pale yellow*/ /* x-coordinates of bar */ float x_lo = bounding_rect.x_lo() + lm + i * bar_w; float x_hi = x_lo + bar_w - 1; ImVec2 x_span{x_lo, x_hi}; /* y-coordinates of persist bar (survived 3+ GCs) */ float ypsz_lo = (y_zero - (display_h * stats.persist_z_ / y_scale)); { xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::persist, stats); draw_filled_rect(tt.c_str(), ImRect::from_xy_span(x_span, ImVec2(ypsz_lo, y_zero)), persist_color, draw_list); } /* y-coordinates of promote bar (survived 2nd GC) */ float yp_hi = ypsz_lo; float yp_lo = (yp_hi - (display_h * stats.promote_z_ / y_scale)); { xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::promote, stats); draw_filled_rect(tt.c_str(), ImRect::from_xy_span(x_span, ImVec2(yp_lo, yp_hi)), promote_color, draw_list); } /* y-coordinates of survivor bar (survived 1st GC) */ float ys_hi = yp_lo; float ys_lo = (ys_hi - (display_h * stats.survive_z_ / y_scale)); { xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::survive, stats); draw_filled_rect(tt.c_str(), ImRect::from_xy_span(x_span, ImVec2(ys_lo, ys_hi)), survive_color, draw_list); } // ----------------------------------------------------------- /* y-coordinates of garbageN bar (killed on 3+ GC) */ float ygN_lo = y_zero; float ygN_hi = (y_zero + (display_h * stats.garbageN_z_ / y_scale)); { xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::garbageN, stats); draw_filled_rect(tt.c_str(), ImRect::from_xy_span(x_span, ImVec2(ygN_lo, ygN_hi)), garbageN_color, draw_list); } /* y-coordinates of garbage1 bar (killed on 2nd GC) */ float yg1_lo = ygN_hi; float yg1_hi = (yg1_lo + (display_h * stats.garbage1_z_ / y_scale)); { xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::garbage1, stats); draw_filled_rect(tt.c_str(), ImRect(ImVec2(x_lo, yg1_lo), ImVec2(x_hi, yg1_hi)), garbage1_color, draw_list); } /* y-coordinates of garbage0 bar (killed on 1st GC) */ float yg0_lo = yg1_hi; float yg0_hi = (yg0_lo + (display_h * stats.garbage0_z_ / y_scale)); { xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::garbage0, stats); draw_filled_rect(tt.c_str(), ImRect(ImVec2(x_lo, yg0_lo), ImVec2(x_hi, yg0_hi)), garbage0_color, draw_list); } } else { /* draw nothing */ ; } ++i; } log && log(xtag("i", i)); } void draw_gc_efficiency(const GcStateDescription & gcstate, //generation gen, const GcStatisticsHistory & gc_history, const ImRect & bounding_rect, bool debug_flag, ImDrawList * draw_list) { scope log(XO_DEBUG(debug_flag)); float lm = 10; float tm = 25; /* we're going to make a level chart */ /* x_scale,y_scale in GC units (i.e. bytes) */ size_t x_scale = gc_history.capacity(); size_t yplus_scale = 1; size_t yminus_scale = 0; float display_w = bounding_rect.width() - lm; float display_h = bounding_rect.height() - tm; #ifdef NOPE // don't need this. y-scale is [0.0, 1.0] /* 1st loop: figure out max y scale */ for (const GcStatisticsHistoryItem & stats : gc_history) { if ((gen == stats.upto_) || (gen == generation::tenured)) { //size_t na = stats.new_alloc_z_ - stats.survive_z_; /*new allocs, but dont' double-count survive_z*/ size_t sz = stats.survive_z_; /*survive 1st gc */ size_t pz = stats.promote_z_; /*survive 2nd gc */ size_t psz = stats.persist_z_; /*survive 3+ gc */ size_t g0z = stats.garbage0_z_; size_t g1z = stats.garbage1_z_; size_t gNz = stats.garbageN_z_; if (yplus_scale < sz + pz + psz) yplus_scale = sz + pz + psz; if (yminus_scale < g0z + g1z + gNz) yminus_scale = g0z + g1z + gNz; } else { ; } } #endif /* y-coord of x-axis */ float y_zero = bounding_rect.y_lo() + tm + display_h; //float y_zero = bounding_rect.y_lo() + tm + (display_h * yplus_scale) / (yplus_scale + yminus_scale); float y_scale = 1.0; /* width of 1 bar in screen coords */ constexpr float c_min_bar_w = 5.0; float bar_w = std::max(c_min_bar_w, display_w / gc_history.capacity()); /* TODO: use temporary arena */ std::vector line_points; line_points.reserve(gc_history.size()); ImU32 average_color = IM_COL32(255, 255, 64, 255); /*solid yellow*/ ImU32 sample_color = IM_COL32(255, 255, 255, 255); /*white*/ /* 2nd loop: draw levels */ std::size_t i = 0; for (const GcStatisticsHistoryItem & stats : gc_history) { //std::vector line_points = { /* your points */ }; //draw_list->AddPolyline(line_points.data(), line_points.size(), // IM_COL32(255, 255, 0, 255), false, 2.0f); float y = y_zero - display_h * stats.efficiency(); float y_mean = y_zero - display_h * stats.average_efficiency(); /* x-coordinates of point */ float x = bounding_rect.x_lo() + lm + i * bar_w + 0.5 * bar_w; line_points.push_back(ImVec2(x, y_mean)); draw_list->AddCircleFilled(ImVec2(x, y), 2.0f, sample_color); ++i; } draw_list->AddPolyline(line_points.data(), line_points.size(), average_color, false, 1.0f /*line width?*/); log && log(xtag("i", i)); } /*draw_gc_efficiency*/ void draw_gc_alloc_state(const GcStateDescription & gcstate, const ImRect & canvas_rect, ImDrawList * draw_list, GenerationLayout * p_nursery_layout, GenerationLayout * p_tenured_layout //ImRect * p_nursery_alloc_rect, //ImRect * p_tenured_alloc_rect ) { constexpr float c_est_chart_text_height = 14.0; constexpr float c_min_h = 7; // chart bar height constexpr float c_max_h = 40; // chart bar height /* bounding rectange for nursery display */ ImRect N_space_rect = canvas_rect.top_fraction(0.5, c_min_h + c_est_chart_text_height, c_max_h + c_est_chart_text_height); //assert(N_space_rect.height() >= c_min_h + c_est_chart_text_height); //assert(N_space_rect.height() <= c_max_h + c_est_chart_text_height); /* rectangle representing allocated nursery range */ //float N_x1 = 0.0; GenerationLayout N_layout; draw_nursery(gcstate, true /*with_labels*/, N_space_rect, draw_list, &N_layout); float N_x1 = N_layout.chart_nolabel_rect_.x_hi(); if (p_nursery_layout) *p_nursery_layout = N_layout; // if (p_nursery_alloc_rect) // *p_nursery_alloc_rect = N_layout.to_alloc_rect(); /* N0_to_size..N_to_scale: in bytes */ std::size_t N_to_scale = gcstate.gen_state_v_[gen2int(generation::nursery)].tospace_scale_; /* display_w .. N0_h : viewportcoords */ std::size_t display_w = canvas_rect.width(); std::size_t x0 = canvas_rect.x_lo(); std::size_t x1 = canvas_rect.x_hi(); // now turn to Tenured space std::size_t T_to_scale = gcstate.gen_state_v_[gen2int(generation::tenured)].tospace_scale_; /* want to put to-scale image of nursery next to to-scale image of tenured; * but also want space between them. */ float TplusN_to_scale = N_to_scale + T_to_scale; /* space between T, N images */ float TplusN_spacer = 10; /* bounding rectange for tenured display */ ImRect T_space_rect = (canvas_rect .within_bottom_margin(c_est_chart_text_height) .bottom_fraction(0.5, c_min_h + c_est_chart_text_height, c_max_h + c_est_chart_text_height)); if (N_space_rect.y_hi() > T_space_rect.y_lo()) { T_space_rect = T_space_rect.translate(ImVec2(0, N_space_rect.y_hi() - T_space_rect.y_lo())); } assert(T_space_rect.y_lo() >= N_space_rect.y_hi()); /* for smaller image of nursery */ //std::size_t t_y0 = canvas_rect.y_lo() + 70 + alloc_height + 20; /* for side-by-side tenured + nursery, with both on same scale * 2nd term is horiz space used for N label like 'Mem: 28k' */ std::size_t adj_display_w = display_w - (N_space_rect.x_hi() - N_x1); /* bounding rectangle for secondary nursery display */ ImRect np_rect(ImVec2(x0 + (adj_display_w * (T_to_scale/TplusN_to_scale)), T_space_rect.y_lo() + c_est_chart_text_height), ImVec2(x0 + adj_display_w, T_space_rect.y_hi())); // redraw nursery to same scale as tenured { draw_list->AddLine(N_space_rect.bottom_left(), np_rect.top_left(), IM_COL32(128, 128, 128, 255) /*grey*/); draw_list->AddLine(ImVec2(N_x1, N_space_rect.y_hi()), np_rect.top_right(), IM_COL32(128, 128, 128, 255) /*grey*/); draw_nursery(gcstate, false /*no labels*/, np_rect, draw_list, nullptr); } /* rectangle representing allocated tenured range */ GenerationLayout T_layout; draw_tenured(gcstate, true /*with labels*/, ImRect(ImVec2(x0, T_space_rect.y_lo()), ImVec2(x0 + (adj_display_w * (T_to_scale/TplusN_to_scale)) - TplusN_spacer, T_space_rect.y_hi())), draw_list, &T_layout); if (p_tenured_layout) *p_tenured_layout = T_layout; // if (p_tenured_alloc_rect) // *p_tenured_alloc_rect = T_layout.to_alloc_rect(); } /*draw_gc_alloc_state*/ void draw_gc_state(const AppState & app_state, const GcStateDescription & gcstate, const ImRect & canvas_rect, ImDrawList * draw_list, GenerationLayout * p_nursery_layout, GenerationLayout * p_tenured_layout, //ImRect * p_nursery_alloc_rect, //ImRect * p_tenured_alloc_rect, ImRect * p_history_rect) { // draw stuff draw_list->AddRect(canvas_rect.top_left(), canvas_rect.bottom_right(), IM_COL32(255, 255, 255, 255)); /* TODO: does this reset coord space? */ ImRect alloc_rect; { ImGui::BeginChild("top pane", ImVec2(0, 105), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeY); alloc_rect = ImRect(canvas_rect.top_left() + ImGui::GetWindowContentRegionMin(), canvas_rect.top_left() + ImGui::GetWindowContentRegionMax()); ImRect draw_rect = alloc_rect.within_margin(ImRect(50, 10, 70, 10)); draw_list->PushClipRect(draw_rect.top_left(), draw_rect.bottom_right()); draw_gc_alloc_state(gcstate, draw_rect, draw_list, p_nursery_layout, p_tenured_layout //p_nursery_alloc_rect, //p_tenured_alloc_rect ); draw_list->PopClipRect(); ImGui::EndChild(); } ImRect history_rect; { ImGui::BeginChild("left pane", ImVec2(800, 0), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX); /* history below alloc area */ history_rect = ImRect(alloc_rect.bottom_left() + ImGui::GetWindowContentRegionMin(), alloc_rect.bottom_left() + ImGui::GetWindowContentRegionMax()); if (p_history_rect) *p_history_rect = history_rect; draw_list->PushClipRect(history_rect.top_left(), history_rect.bottom_right()); float lm = 50; float rm = 70; float tm = 10; std::size_t x0 = history_rect.x_lo() + lm; std::size_t x1 = history_rect.x_hi() - rm; std::size_t h_y0 = history_rect.y_lo() + tm; /* just incremental (nursery) collections */ ImRect incremental_rect = history_rect.top_fraction(0.33); draw_gc_history(gcstate, generation::nursery, app_state.gc_->gc_history(), incremental_rect, false /*debug_flag*/, draw_list); /* just full (nursery+tenured) collections */ ImRect full_rect = history_rect.bottom_fraction(0.67).top_fraction(0.5); /* both nursery + full collections */ draw_gc_history(gcstate, generation::tenured, app_state.gc_->gc_history(), full_rect, false /*debug_flag*/, draw_list); ImRect efficiency_rect = history_rect.bottom_fraction(0.67).bottom_fraction(0.5); draw_gc_efficiency(gcstate, app_state.gc_->gc_history(), efficiency_rect, false /*debug_flag*/, draw_list); draw_list->PopClipRect(); ImGui::EndChild(); } ImGui::Text("placeholder text"); /* BeginChild() again ? */ #ifdef NOPE draw_list->AddCircleFilled(ImVec2(canvas_p0.x + 50, canvas_p0.y + 50), 30.0f, IM_COL32(255, 0, 0, 255)); draw_list->AddText(ImVec2(canvas_p0.x + 10, canvas_p0.y + 10), IM_COL32(255, 255, 255, 255), "Hello 2D!"); #endif } struct DrawState; struct AnimateGcCopyCb : public xo::gc::GcCopyCallback { using generation = xo::gc::generation; explicit AnimateGcCopyCb(AppState * appstate, DrawState * drawstate) : p_app_state_{appstate}, p_draw_state_{drawstate} {} virtual void notify_gc_copy(std::size_t z, const void * src_addr, const void * dest_addr, generation src_gen, generation dest_gen); AppState * p_app_state_ = nullptr; DrawState * p_draw_state_ = nullptr; }; enum class draw_state_type { alloc, animate_gc }; struct DrawState { up make_gc_copy_animation(AppState * app_state) { return std::make_unique(app_state, this); } draw_state_type state_type_ = draw_state_type::alloc; /** budgeted time period over which to animate gc copy **/ int animate_copy_budget_ms_ = 2000; /** start time of current copy animation **/ std::chrono::steady_clock::time_point animate_copy_t0_; /** when animating copy step, display objects from AppState::copy_detail_v_[i] * where i < .animate_copy_hi_ / 100 * AppState::copy_detail_v_.size() **/ float animate_copy_hi_pct_ = 0; ImDrawList * gcw_draw_list_ = nullptr; /** draw area **/ ImVec2 gcw_canvas_p0_; ImVec2 gcw_canvas_p1_; /** layout for nursery display **/ GenerationLayout gcw_nursery_layout_; /** layout for tenured display **/ GenerationLayout gcw_tenured_layout_; /** rect displaying gc history (strip charts) **/ ImRect gcw_history_rect_; }; ImRect map_src_alloc_to_screen(const GcCopyDetail & copy_detail, const ImRect & space_rect) { // TODO: methods on copy_detail / and/or ImPoint auto [x_coord_lo, x_coord_hi] = space_rect.x_span(); double w0 = copy_detail.src_offset_ / static_cast(copy_detail.src_space_z_); float src0_x = ((1.0 - w0) * x_coord_lo) + (w0 * x_coord_hi); double w1 = ((copy_detail.src_offset_ + copy_detail.z_) / static_cast(copy_detail.src_space_z_)); float src1_x = ((1.0 - w1) * x_coord_lo) + (w1 * x_coord_hi); return space_rect.with_x_span(src0_x, src1_x); } ImRect map_dest_alloc_to_screen(const GcCopyDetail & copy_detail, const ImRect & space_rect) { /* for from-space, want to use full width of memory space */ auto [x_coord_lo, x_coord_hi] = space_rect.x_span(); // dest_space_z_ ? double w0 = copy_detail.dest_offset_ / static_cast(copy_detail.dest_z_); float dest0_x = ((1.0 - w0) * x_coord_lo) + (w0 * x_coord_hi); double w1 = ((copy_detail.dest_offset_ + copy_detail.z_) / static_cast(copy_detail.dest_z_)); float dest1_x = ((1.0 - w1) * x_coord_lo) + (w1 * x_coord_hi); return space_rect.with_x_span(dest0_x, dest1_x); } /* editor bait: animate_copy() */ void animate_gc_copy(const AppState & app_state, const DrawState & draw_state, ImDrawList * draw_list) { /* NOTE: this only runs during GC copy. * draw_state.gcw_nursery_layout_ and gcw_tenured_layout_ * are taken from snapshots made before GC began, * ergo before to/from spaces were swapped */ ImRect nursery_src_rect = draw_state.gcw_nursery_layout_.to_alloc_rect(); ImRect tenured_src_rect = draw_state.gcw_tenured_layout_.to_alloc_rect(); ImRect nursery_dest_rect = draw_state.gcw_nursery_layout_.mem_rect_from_; ImRect tenured_dest_rect; if (app_state.upto_ == generation::nursery) { tenured_dest_rect = draw_state.gcw_tenured_layout_.mem_rect_to_; } else { tenured_dest_rect = draw_state.gcw_tenured_layout_.mem_rect_from_; } std::size_t n_copy = app_state.copy_detail_v_.size(); /* grade from black-to-white between lo_copy and hi_copy. * Note we allow animate_copy_hi_pct_ > 100.0 */ float lo_copy = 0.01 * std::max(0.0, draw_state.animate_copy_hi_pct_ - 14.0) * n_copy; float hi_copy = 0.01 * draw_state.animate_copy_hi_pct_ * n_copy; /* remember max copy offset seen in {nursery, tenured} space respectively, * so we can label it */ std::size_t last_nursery_dest_offset = 0; ImRect last_nursery_dest_rect; std::size_t first_tenured_dest_offset = 0; ImRect first_tenured_dest_rect; std::size_t last_tenured_dest_offset = 0; ImRect last_tenured_dest_rect; std::size_t i_copy = 0; for (const auto & copy_detail : app_state.copy_detail_v_) { /* cutout for each copied object */ { float wt = (i_copy > lo_copy) ? static_cast(i_copy) / hi_copy : 0.0; /* grey fading to black */ //ImU32 color = IM_COL32(wt*128, 64+wt*64, wt*128, 255); ImU32 color = IM_COL32( 96, 224, 255, 255); ImRect src_rect; if (copy_detail.src_gen_ == generation::nursery) { src_rect = map_src_alloc_to_screen(copy_detail, nursery_src_rect); } else { src_rect = map_src_alloc_to_screen(copy_detail, tenured_src_rect); } draw_list->AddRectFilled(src_rect.top_left(), src_rect.bottom_right(), color); } if (copy_detail.dest_gen_ == generation::nursery) { last_nursery_dest_rect = map_dest_alloc_to_screen(copy_detail, nursery_dest_rect); last_nursery_dest_offset = copy_detail.dest_offset_; } else if (copy_detail.dest_gen_ == generation::tenured) { last_tenured_dest_rect = map_dest_alloc_to_screen(copy_detail, tenured_dest_rect); last_tenured_dest_offset = copy_detail.dest_offset_; if (first_tenured_dest_rect.width() == 0) { first_tenured_dest_rect = last_tenured_dest_rect; first_tenured_dest_offset = copy_detail.dest_offset_; } } if (++i_copy >= hi_copy) { break; } } if (last_nursery_dest_rect.width() > 0.0) { //ImU32 color = IM_COL32(64, 255, static_cast(64 + (128 * wt)), 255); ImU32 color = IM_COL32( 0, 96, 192, 255); draw_list->AddRectFilled(nursery_dest_rect.top_left(), last_nursery_dest_rect.bottom_right(), color); char buf[255]; snprintf(buf, sizeof(buf), "N\u2081: %luk", last_nursery_dest_offset / 1024); auto textz = ImGui::CalcTextSize(buf); ImU32 text_color = IM_COL32(255, 255, 255, 255); /*black*/ ImVec2 text_pos = ImVec2(0.5 * (nursery_dest_rect.x_lo() + last_nursery_dest_rect.x_hi() - textz.x), nursery_dest_rect.y_mid() - 0.5 * textz.y); if (text_pos.x < nursery_dest_rect.x_lo()) text_pos.x = nursery_dest_rect.x_lo() + 2; else if (text_pos.x < nursery_dest_rect.x_lo() + 0.5 * textz.x) text_pos.x = nursery_dest_rect.x_lo() + 0.5 * textz.x; draw_list->AddText(text_pos, text_color, buf); } if (last_tenured_dest_rect.width() > 0.0) { ImU32 color = IM_COL32( 0, 96, 192, 255); draw_list->AddRectFilled(first_tenured_dest_rect.top_left(), last_tenured_dest_rect.bottom_right(), color); char buf[255]; snprintf(buf, sizeof(buf), "+%luk", (last_tenured_dest_offset - first_tenured_dest_offset) / 1024); auto textz = ImGui::CalcTextSize(buf); ImU32 text_color = IM_COL32(255, 255, 255, 255); /*black*/ float x0 = first_tenured_dest_rect.x_lo(); float x1 = last_tenured_dest_rect.x_hi(); ImVec2 text_pos = ImVec2(0.5 * (x0 + x1 - textz.x), tenured_dest_rect.y_mid() - 0.5 * textz.y); if (text_pos.x < x0 + 2) text_pos.x = x0 + 2; else if (text_pos.x < x0 + 0.5 * textz.x) text_pos.x = x0 + 0.5 * textz.x; draw_list->AddText(text_pos, text_color, buf); } } void AnimateGcCopyCb::notify_gc_copy(std::size_t z, const void * src_addr, const void * dest_addr, generation src_gen, generation dest_gen) { using xo::scope; using xo::xtag; using xo::gc::generation_result; using xo::gc::generation; using xo::gc::role; scope log(XO_DEBUG(false), xtag("z", z), xtag("src", src_addr), xtag("dest", dest_addr), xtag("src_gen", src_gen), xtag("dest_gen", dest_gen)); auto [src_gen2, src_offset, src_alloc, src_size] = p_app_state_->gc_->fromspace_location_of(src_addr); if (src_gen2 == generation_result::not_found) { auto [lo, hi] = p_app_state_->gc_->nursery_span(role::from_space); log && log(xtag("N.from.lo", (void*)lo), xtag("N.from.hi", (void*)hi)); assert(false); } generation src_valid_gen = xo::gc::valid_genresult2gen(src_gen2); auto [dest_gen2, dest_offset, _, dest_size] = p_app_state_->gc_->tospace_location_of(dest_addr); generation dest_valid_gen = xo::gc::valid_genresult2gen(dest_gen2); p_app_state_->copy_detail_v_.push_back(GcCopyDetail(z, src_valid_gen, src_offset, src_alloc, dest_valid_gen, dest_offset, dest_size)); if (dest_valid_gen == generation::nursery) { p_app_state_->copy_detail_max_nursery_dest_offset_ = std::max(p_app_state_->copy_detail_max_nursery_dest_offset_, dest_offset); p_app_state_->copy_detail_nursery_dest_size_ = std::max(p_app_state_->copy_detail_nursery_dest_size_, dest_size); } else if (dest_valid_gen == generation::tenured) { p_app_state_->copy_detail_max_tenured_dest_offset_ = std::max(p_app_state_->copy_detail_max_tenured_dest_offset_, dest_offset); p_app_state_->copy_detail_tenured_dest_size_ = std::max(p_app_state_->copy_detail_tenured_dest_size_, dest_size); } /* will be animated across frames, see animate_gc_copy() */ } int main(int, char **) { using namespace std; scope log(XO_DEBUG(true)); std::cout << "Hello, world!" << std::endl; SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "0"); SDL_Init(SDL_INIT_VIDEO); SDL_version compiled; SDL_VERSION(&compiled); std::cerr << "SDL version: " << (int)compiled.major << "." << (int)compiled.minor << "." << (int)compiled.patch << std::endl; SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); #if 0 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); #endif std::cerr << "SDL video driver: " << SDL_GetCurrentVideoDriver() << std::endl; SDL_Window * window = SDL_CreateWindow("imgui + sdl2 + opengl", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 2000, 1000, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); if (window) { std::cerr << "SDL_CreateWindow done" << std::endl; } else { std::cerr << "SDL_CreateWindow failed: [" << SDL_GetError() << "]" << std::endl; SDL_Quit(); return -1; } SDL_GLContext gl_context = SDL_GL_CreateContext(window); int major, minor; SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major); SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor); std::cerr << "Requested OpenGL vtersion: " << major << "." << minor << std::endl; if (gl_context) { std::cerr << "SDL_GL_CreateContext done" << std::endl; } else { std::cerr << "SDL_GL_CreateContext failed: [" << SDL_GetError() << "]" << std::endl; return -1; } if (SDL_GL_MakeCurrent(window, gl_context) != 0) { std::cerr << "SDL_GL_MakeCurrent failed: [" << SDL_GetError() << "]" << std::endl; SDL_GL_DeleteContext(gl_context); SDL_DestroyWindow(window); SDL_Quit(); return -1; } SDL_GL_SetSwapInterval(0); // disable vsync GLenum glew_status = glewInit(); if (glew_status == GLEW_OK) { std::cerr << "glewInit done" << std::endl; } else { std::cerr << "glewInit failed: [" << glewGetErrorString(glew_status) << std::endl; return -1; } const GLubyte * version = glGetString(GL_VERSION); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); if (version) { std::cerr << "OpenGL version: [" << version << "]" << std::endl; } else { std::cerr << "OpenGL version not available" << std::endl; } IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO & io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Load noto sans font from unix environment NOTO_ONFTS_PATH // (see xo-umbrella2/default.nix shellHook) const char * fonts_path = std::getenv("DEJAVU_FONTS_PATH"); if (fonts_path) { const float font_size = 14.0f; std::string font_path = xo::tostr(fonts_path, "/truetype/DejaVuSans.ttf"); /* check file exists */ std::ifstream font_in(font_path); if (font_in.good()) { std::cerr << "loading font [" << font_path << "]" << std::endl; ImFont * font = io.Fonts->AddFontFromFileTTF(font_path.c_str(), font_size); if (font) { std::cerr << "font loaded" << std::endl; ImFontConfig config; config.MergeMode = true; // latin extended chars static const ImWchar latin_ranges[] = { 0x0020, 0x00ff, // basic latin + latin supplement 0x0100, 0x017f, // latin extended-A 0x0180, 0x024f, // latin extended-B 0x2080, 0x2099, // subscript numerals + letters through n 0x25b2, 0x25b4, // arrows 0, }; io.Fonts->AddFontFromFileTTF(font_path.c_str(), font_size, &config, latin_ranges); } else { std::cerr << "font file load failed" << std::endl; std::cerr << "Fallback to default ImGui font" << std::endl; } } } else { std::cerr << "Expected DEJAVU_FONTS_PATH environment var." << std::endl; std::cerr << "Fallback to default ImGui font" << std::endl; } ImGui::StyleColorsDark(); ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL3_Init("#version 330"); bool show_demo_window = true; bool show_another_window = false; ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); float counter_value = 0.0f; using xo::obj::Integer; using xo::obj::List; using xo::rng::xoshiro256ss; using xo::rng::Seed; using xo::up; using xo::gp; using xo::gc::GC; using xo::Object; AppState app_state; DrawState draw_state; /* note: during gc copy animation, this records state _before_ gc was triggered */ GcStateDescription gcstate = app_state.snapshot_gc_state(); app_state.gc_->add_gc_copy_callback(draw_state.make_gc_copy_animation(&app_state)); // Main Loop bool done = false; while (!done) { /** on each draw cycle, app state falls into categories: * 1. allocation * multiple draw cycles because many allocations per gc. * 2. garbage collection * multiple draw cycles to animate copying process * Settle conflict between {GC, imgui} as to who drives event loop, * in favor of imgui; achieve this by copying what GC did, * so that we can animate it over multiple draw cycles **/ switch (draw_state.state_type_) { case draw_state_type::alloc: { /** generate random alloc **/ app_state.generate_random_mutations(); gcstate = app_state.snapshot_gc_state(); app_state.upto_ = (app_state.gc_->is_full_gc_pending() ? generation::tenured : generation::nursery); /* GC may run here, in which case control reenters via AnimateGcCopyCb; * that callback captures copy details (per object!) in AppState */ if (app_state.gc_->enable_gc_once()) { log && log(xtag("gc-type", (app_state.upto_ == generation::tenured) ? "full" : "incremental")); draw_state.state_type_ = draw_state_type::animate_gc; draw_state.animate_copy_t0_ = std::chrono::steady_clock::now(); } break; } case draw_state_type::animate_gc: { /* don't update gcstate while animating, * that would use post-GC space sizing */ break; } } /** poll + handle events */ SDL_Event event; while (SDL_PollEvent(&event)) { ImGui_ImplSDL2_ProcessEvent(&event); if (event.type == SDL_QUIT) done = true; if (event.type == SDL_WINDOWEVENT) { if (event.window.event == SDL_WINDOWEVENT_CLOSE) { if (event.window.windowID == SDL_GetWindowID(window)) { done = true; } } else if (event.window.event == SDL_WINDOWEVENT_RESIZED) { // handle resize immediately int w, h; SDL_GetWindowSize(window, &w, &h); glViewport(0, 0, w, h); break; // to force render during resize } } } //int w, h; //SDL_GetWindowSize(window, &w, &h); //glViewport(0, 0, w, h); glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); // draw dear imgui frame ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); ImGui::SetNextWindowPos(ImVec2(0, 0)); ImGui::SetNextWindowSize(io.DisplaySize); ImGui::Begin("Background", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoDecoration); ImGui::End(); // 1. big demo window if (show_demo_window) ImGui::ShowDemoWindow(&show_demo_window); // 2. show window that we create ourselves { static int counter = 0; ImGui::Begin("Hello, world!"); //ImGui::Text("This is totes useful text..."); ImGui::Checkbox("demo window", &show_demo_window); //ImGui::Checkbox("second window", &show_another_window); ImGui::SliderInt("alloc/cycle", &app_state.alloc_per_cycle_, 1, 100); ImGui::SliderInt("copy animation budget", &draw_state.animate_copy_budget_ms_, 10, 10000); //ImGui::SliderFloat("alloc/cycle", &alloc_per_cycle, 0.0f, 1.0f); //ImGui::ColorEdit3("clear color", (float*)&clear_color); //if (ImGui::Button("Button")) // ++counter; ImGui::NewLine(); // ImGui::SameLine() /* N\u2080 = N0, N\u2081 = N1 */ ImGui::Text("alloc [%lu] avail [%lu] ", gcstate.gc_allocated_, gcstate.gc_available_); //ImGui::NewLine(); ImGui::Text("promoted [%lu] copy animation [%lu / %lu]", gcstate.total_promoted_, static_cast(draw_state.animate_copy_hi_pct_ * app_state.copy_detail_v_.size() / 100), app_state.copy_detail_v_.size()); ImGui::Text("mutation [%lu] mlog [%lu]", gcstate.total_n_mutation_, gcstate.gc_mlog_size_); ImGui::Text("appl average %.3f ms/frame (%.1f fps)", 1000.0f / io.Framerate, io.Framerate); ImGui::Text("layout:" " nursery-src alloc rect [%.1f %.1f %.1f %.1f]" " nursery-dest alloc rect [%.1f %.1f %.1f %.1f]" " history rect [%.1f %.1f %.1f %.1f]", draw_state.gcw_nursery_layout_.to_alloc_rect().x_lo(), draw_state.gcw_nursery_layout_.to_alloc_rect().y_lo(), draw_state.gcw_nursery_layout_.to_alloc_rect().x_hi(), draw_state.gcw_nursery_layout_.to_alloc_rect().y_hi(), draw_state.gcw_nursery_layout_.from_alloc_rect().x_lo(), draw_state.gcw_nursery_layout_.from_alloc_rect().y_lo(), draw_state.gcw_nursery_layout_.from_alloc_rect().x_hi(), draw_state.gcw_nursery_layout_.from_alloc_rect().y_hi(), draw_state.gcw_history_rect_.x_lo(), draw_state.gcw_history_rect_.y_lo(), draw_state.gcw_history_rect_.x_hi(), draw_state.gcw_history_rect_.y_hi()); ImGui::Text("nursery-dest copy offset [%lu] / size [%lu]" " tenured-dest copy offset [%lu] / size [%lu]", app_state.copy_detail_max_nursery_dest_offset_, app_state.copy_detail_nursery_dest_size_, app_state.copy_detail_max_tenured_dest_offset_, app_state.copy_detail_tenured_dest_size_ ); ImDrawList * draw_list = ImGui::GetWindowDrawList(); ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y); /* stash so GC copy animation can find it */ draw_state.gcw_draw_list_ = draw_list; draw_state.gcw_canvas_p0_ = canvas_p0; draw_state.gcw_canvas_p1_ = canvas_p1; draw_gc_state(app_state, gcstate, ImRect(canvas_p0, canvas_p1), draw_list, &draw_state.gcw_nursery_layout_, &draw_state.gcw_tenured_layout_, //nullptr, //&draw_state.gcw_tenured_alloc_rect_, &draw_state.gcw_history_rect_); if (draw_state.state_type_ == draw_state_type::animate_gc) { auto animate_copy_t1 = std::chrono::steady_clock::now(); auto animate_dt = animate_copy_t1 - draw_state.animate_copy_t0_; float animate_fraction_spent = (std::chrono::duration_cast(animate_dt).count() / static_cast(draw_state.animate_copy_budget_ms_)); draw_state.animate_copy_hi_pct_ = 100.0 * animate_fraction_spent; animate_gc_copy(app_state, draw_state, draw_list); /* see 25.0 constant in animate_gc_copy() */ if (draw_state.animate_copy_hi_pct_ >= 114) { draw_state.state_type_ = draw_state_type::alloc; draw_state.animate_copy_hi_pct_ = 0; app_state.copy_detail_v_.clear(); app_state.copy_detail_max_nursery_dest_offset_ = 0; app_state.copy_detail_nursery_dest_size_ = 0; app_state.copy_detail_max_tenured_dest_offset_ = 0; app_state.copy_detail_tenured_dest_size_ = 0; } } ImGui::End(); } // 3. another window if (show_another_window) { ImGui::Begin("another window", &show_another_window); ImGui::Text("hello from second window"); if (ImGui::Button("close me")) show_another_window = false; ImGui::End(); } // rendering ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); SDL_GL_SwapWindow(window); } std::cerr << "cleanup.." << std::endl; ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); SDL_GL_DeleteContext(gl_context); SDL_DestroyWindow(window); SDL_Quit(); std::cerr << "All done, goodbye..." << std::endl; return 0; } /* imgui_ex4a.cpp */ #endif //DEBUG #include "VulkanApp.hpp" int main() { MinimalImGuiVulkan app; try { app.run(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; } /* end imgui_ex4a.cpp */