From f4be2e765e5a7f4c7ae8c0ac85f70d800c3bfc68 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 14 Aug 2025 09:50:59 -0500 Subject: [PATCH] xo-alloc: + gc history xo-imgui: gui examples --- CMakeLists.txt | 1 + xo-alloc/include/xo/alloc/ArenaAlloc.hpp | 15 + xo-alloc/include/xo/alloc/GC.hpp | 28 + xo-alloc/include/xo/alloc/GcStatistics.hpp | 66 ++ xo-alloc/include/xo/alloc/ListAlloc.hpp | 3 + xo-alloc/include/xo/alloc/generation.hpp | 1 + xo-alloc/src/alloc/ArenaAlloc.cpp | 12 + xo-alloc/src/alloc/GC.cpp | 118 +- xo-alloc/src/alloc/GcStatistics.cpp | 32 +- xo-alloc/src/alloc/ListAlloc.cpp | 17 +- xo-alloc/utest/CMakeLists.txt | 1 + xo-alloc/utest/GcStatistics.test.cpp | 4 +- xo-alloc/utest/ListAlloc.test.cpp | 11 +- xo-imgui/CMakeLists.txt | 36 + xo-imgui/cmake/xo-bootstrap-macros.cmake | 35 + xo-imgui/example/CMakeLists.txt | 2 + xo-imgui/example/ex1/CMakeLists.txt | 66 ++ xo-imgui/example/ex1/imgui_ex1.cpp | 329 ++++++ xo-imgui/example/ex2/CMakeLists.txt | 62 + xo-imgui/example/ex2/imgui_ex2.cpp | 1194 ++++++++++++++++++++ 20 files changed, 2015 insertions(+), 18 deletions(-) create mode 100644 xo-imgui/CMakeLists.txt create mode 100644 xo-imgui/cmake/xo-bootstrap-macros.cmake create mode 100644 xo-imgui/example/CMakeLists.txt create mode 100644 xo-imgui/example/ex1/CMakeLists.txt create mode 100644 xo-imgui/example/ex1/imgui_ex1.cpp create mode 100644 xo-imgui/example/ex2/CMakeLists.txt create mode 100644 xo-imgui/example/ex2/imgui_ex2.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1534cb88..226e9872 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,6 +116,7 @@ add_subdirectory(xo-reader) add_subdirectory(xo-jit) add_subdirectory(xo-pyjit) # +add_subdirectory(xo-imgui) # ---------------------------------------------------------------- # documentation. must follow add_subdirectory() for satellite projects diff --git a/xo-alloc/include/xo/alloc/ArenaAlloc.hpp b/xo-alloc/include/xo/alloc/ArenaAlloc.hpp index 64546cf3..66dd6a70 100644 --- a/xo-alloc/include/xo/alloc/ArenaAlloc.hpp +++ b/xo-alloc/include/xo/alloc/ArenaAlloc.hpp @@ -36,9 +36,24 @@ namespace xo { std::size_t z, bool debug_flag); + /** size of virtual address range reserved for this allocator **/ + std::size_t reserved() const { return this->size(); } + + std::size_t page_size() const { return page_z_; } std::byte * free_ptr() const { return free_ptr_; } void set_free_ptr(std::byte * x); + /** if address @p x is allocated from this arena, + * return true along with offset relative to base address @ref lo_ + * otherwise return false with 0 + **/ + std::pair location_of(const void * x) const; + + /** allocated span **/ + std::pair allocated_span() const { + return std::make_pair(lo_, free_ptr_); + } + /** Reset to empty state **/ void reset(std::size_t /*z_ignored*/) { this->clear(); } diff --git a/xo-alloc/include/xo/alloc/GC.hpp b/xo-alloc/include/xo/alloc/GC.hpp index a653b54b..ab58b902 100644 --- a/xo-alloc/include/xo/alloc/GC.hpp +++ b/xo-alloc/include/xo/alloc/GC.hpp @@ -50,6 +50,8 @@ namespace xo { bool allow_incremental_gc_ = true; /** true to report statistics **/ bool stats_flag_ = false; + /** remember basic gc statistics for this many GC's; separately for incremental + full GCs **/ + std::size_t stats_history_z_ = 256; /** true to enable debug logging **/ bool debug_flag_ = false; }; @@ -147,17 +149,24 @@ namespace xo { const GCRunstate & runstate() const { return runstate_; } const GcStatistics & native_gc_statistics() const { return gc_statistics_; } GcStatisticsExt get_gc_statistics() const; + const GcStatisticsHistory & gc_history() const { return gc_history_; } /** true iff GC permitted in current state **/ bool is_gc_enabled() const { return gc_enabled_ == 0; } /** true during (and only during) a GC cycle **/ bool gc_in_progress() const { return runstate_.in_progress(); } + /** @return reserved size of Nursery to-space **/ + std::size_t nursery_to_reserved() const; /** @return committed size of Nursery to-space **/ std::size_t nursery_to_committed() const; /** @return nursery bytes used before checkpoint **/ std::size_t nursery_before_checkpoint() const; /** @return nursery bytes used after checkpoint **/ std::size_t nursery_after_checkpoint() const; + /** @return allocated memory range for nursery **/ + std::pair nursery_span(role role) const; + /** @return reserved size of Tenured to-space **/ + std::size_t tenured_to_reserved() const; /** @return committed size of Tenured to-space **/ std::size_t tenured_to_committed() const; /** @return tenured bytes used before checkpoint **/ @@ -167,8 +176,24 @@ namespace xo { /** @return generation to which object at @p x belongs **/ generation_result tospace_generation_of(const void * x) const; + /** @return generation to which object at @p x belongs, + * location relative to base address for that generation, + * and allocated size of that generation + * @p role chooses between to-space and from-space + **/ + std::tuple location_of(role role, const void * x) const; + /** @return generation to which object at @p x belongs, + * location relative to base address for @p x, + * and allocated size of generation + **/ + std::tuple tospace_location_of(const void * x) const; /** @return generation that contains @p x, given it's in from-space **/ generation_result fromspace_generation_of(const void * x) const; + /** @return generation to which object at @p x belongs, + * location relative to base address for @p x, + * and allocated size of generation + **/ + std::tuple fromspace_location_of(const void * x) const; /** true iff from-space contains @p x **/ bool fromspace_contains(const void * x) const; /** @return free pointer for generation @p gen, i.e. nursery or tenured space **/ @@ -361,6 +386,9 @@ namespace xo { /** enabled when 0. disabled when <0 **/ int gc_enabled_ = 0; + /** rotating per-gc statistics history **/ + GcStatisticsHistory gc_history_; + /** for (optional) viz: invoke when copying individual objects **/ GcCopyCallbackSet gc_copy_cbset_; }; diff --git a/xo-alloc/include/xo/alloc/GcStatistics.hpp b/xo-alloc/include/xo/alloc/GcStatistics.hpp index 24cac461..d73f8947 100644 --- a/xo-alloc/include/xo/alloc/GcStatistics.hpp +++ b/xo-alloc/include/xo/alloc/GcStatistics.hpp @@ -6,6 +6,7 @@ #pragma once #include "generation.hpp" +#include "CircularBuffer.hpp" #include "xo/reflect/TypeDescr.hpp" #include "xo/indentlog/print/pretty.hpp" #include @@ -57,6 +58,8 @@ namespace xo { **/ class GcStatistics { public: + GcStatistics() = default; + /** update statistics after a GC cycle * @param upto. nursery -> incremental collection; tenured -> full collection * @param alloc_z. new allocations (since preceding GC) @@ -108,6 +111,7 @@ namespace xo { **/ class GcStatisticsExt : public GcStatistics { public: + GcStatisticsExt() = default; explicit GcStatisticsExt(const GcStatistics & x) : GcStatistics{x} {} /** @param os. write stats on this output stream **/ @@ -128,6 +132,63 @@ namespace xo { return os; } + /** @class GcStatisticsHistoryItem + * @brief info we want to record over time (won't have cumulative things in it) + **/ + class GcStatisticsHistoryItem { + public: + GcStatisticsHistoryItem() = default; + GcStatisticsHistoryItem(generation upto, + std::size_t new_alloc_z, + std::size_t survive_z, + std::size_t promote_z, + std::size_t persist_z, + std::size_t effort_z, + std::size_t garbage0_z, + std::size_t garbage1_z, + std::size_t garbageN_z) + : upto_{upto}, + new_alloc_z_{new_alloc_z}, + survive_z_{survive_z}, + promote_z_{promote_z}, + persist_z_{persist_z}, + effort_z_{effort_z}, + garbage0_z_{garbage0_z}, + garbage1_z_{garbage1_z}, + garbageN_z_{garbageN_z} + {} + + /** @param os. write stats on this output stream **/ + void display(std::ostream & os) const; + + /** type of GC that generated this record **/ + generation upto_; + /** #of bytes new allocation **/ + std::size_t new_alloc_z_ = 0; + /** #of bytes surviving their first collection (i.e. N0->N1) **/ + std::size_t survive_z_ = 0; + /** #of bytes promoted to tenured. + * Comprises all objects surviving their 2nd collection (i.e. N1->T) + **/ + std::size_t promote_z_ = 0; + /** #of bytes surviving 3rd of later collection **/ + std::size_t persist_z_ = 0; + /** #of bytes copied **/ + std::size_t effort_z_ = 0; + /** #of bytes garbage from N0 (i.e. survived 0 GCs) **/ + std::size_t garbage0_z_ = 0; + /** #of bytes garbage from N1 (i.e. survived 1 GCs) **/ + std::size_t garbage1_z_ = 0; + /** #of bytes garbage from T (i.e. survived 2+ GCs) **/ + std::size_t garbageN_z_ = 0; + }; + + inline std::ostream & operator<< (std::ostream & os, const GcStatisticsHistoryItem & x) { + x.display(os); + return os; + } + + using GcStatisticsHistory = CircularBuffer; } /*namespace gc*/ namespace print { @@ -145,6 +206,11 @@ namespace xo { struct ppdetail { static bool print_pretty(const ppindentinfo &, const xo::gc::GcStatisticsExt &); }; + + template<> + struct ppdetail { + static bool print_pretty(const ppindentinfo &, const xo::gc::GcStatisticsHistoryItem &); + }; } /*namespace print*/ } /*namespace xo*/ diff --git a/xo-alloc/include/xo/alloc/ListAlloc.hpp b/xo-alloc/include/xo/alloc/ListAlloc.hpp index 7b592b4e..75b148ac 100644 --- a/xo-alloc/include/xo/alloc/ListAlloc.hpp +++ b/xo-alloc/include/xo/alloc/ListAlloc.hpp @@ -34,6 +34,9 @@ namespace xo { static up make(const std::string & name, std::size_t cz, std::size_t nz, bool debug_flag); + /** page size used by underlying ArenaAlloc **/ + std::size_t page_size() const; + /** reset to have at least @p z bytes of storage **/ bool reset(std::size_t z); diff --git a/xo-alloc/include/xo/alloc/generation.hpp b/xo-alloc/include/xo/alloc/generation.hpp index 82c48808..2ed89276 100644 --- a/xo-alloc/include/xo/alloc/generation.hpp +++ b/xo-alloc/include/xo/alloc/generation.hpp @@ -30,6 +30,7 @@ namespace xo { tenured, not_found }; + } /*namespace gc*/ } /*namespace xo*/ diff --git a/xo-alloc/src/alloc/ArenaAlloc.cpp b/xo-alloc/src/alloc/ArenaAlloc.cpp index 49a2d16d..f3145bb2 100644 --- a/xo-alloc/src/alloc/ArenaAlloc.cpp +++ b/xo-alloc/src/alloc/ArenaAlloc.cpp @@ -113,6 +113,7 @@ namespace xo { } this->committed_z_ = align_offset_z; + this->limit_ = this->lo_ + committed_z_; return true; } @@ -133,6 +134,16 @@ namespace xo { } } + std::pair + ArenaAlloc::location_of(const void * x) const + { + if ((lo_ <= x) && (x < hi_)) { + return std::make_pair(true, reinterpret_cast(x) - lo_); + } else { + return std::make_pair(false, 0); + } + } + void ArenaAlloc::capture_object_statistics(capture_phase phase, ObjectStatistics * p_dest) const @@ -276,6 +287,7 @@ namespace xo { xtag("z0", z0), xtag("+pad", dz), xtag("z1", z1), + xtag("size", this->size()), xtag("avail", this->available())); this->free_ptr_ += z1; diff --git a/xo-alloc/src/alloc/GC.cpp b/xo-alloc/src/alloc/GC.cpp index 6ccc4d15..a552ae1e 100644 --- a/xo-alloc/src/alloc/GC.cpp +++ b/xo-alloc/src/alloc/GC.cpp @@ -79,6 +79,8 @@ namespace xo { mutation_log_[role2int(role::to_space)] = std::make_unique(); defer_mutation_log_ = std::make_unique(); + this->gc_history_ = CircularBuffer(config.stats_history_z_); + this->checkpoint(); } @@ -191,6 +193,12 @@ namespace xo { return retval; } + std::size_t + GC::nursery_to_reserved() const + { + return nursery_to()->reserved(); + } + std::size_t GC::nursery_to_committed() const { @@ -209,6 +217,17 @@ namespace xo { return nursery_to()->after_checkpoint(); } + std::pair + GC::nursery_span(role role) const { + return nursery(role)->allocated_span(); + } + + std::size_t + GC::tenured_to_reserved() const + { + return tenured_to()->reserved(); + } + std::size_t GC::tenured_to_committed() const { @@ -239,6 +258,40 @@ namespace xo { return generation_result::not_found; } + std::tuple + GC::location_of(role role, const void *x) const + { + { + auto space = this->tenured(role); + auto [is_tenured, offset] = space->location_of(x); + + if (is_tenured) + return std::make_tuple(generation_result::tenured, offset, space->allocated()); + } + + { + auto space = this->nursery(role); + auto [is_nursery, offset] = nursery(role)->location_of(x); + + if (is_nursery) + return std::make_tuple(generation_result::nursery, offset, space->allocated()); + } + + return std::make_tuple(generation_result::not_found, 0, 0); + } + + std::tuple + GC::tospace_location_of(const void * x) const + { + return location_of(role::to_space, x); + } + + std::tuple + GC::fromspace_location_of(const void * x) const + { + return location_of(role::from_space, x); + } + generation_result GC::fromspace_generation_of(const void * x) const { @@ -988,8 +1041,11 @@ namespace xo { { scope log(XO_DEBUG(config_.debug_flag_)); - std::size_t N_allocated = nursery_from()->after_checkpoint(); - std::size_t T_allocated = tenured_from()->after_checkpoint(); + std::size_t N0_before_gc = nursery_from()->after_checkpoint(); + std::size_t N1_before_gc = nursery_from()->before_checkpoint(); + + std::size_t T0_before_gc = tenured_from()->after_checkpoint(); + std::size_t T1_before_gc = tenured_from()->before_checkpoint(); std::size_t N_before_gc = nursery_from()->allocated(); std::size_t T_before_gc = tenured_from()->allocated(); @@ -998,9 +1054,37 @@ namespace xo { std::size_t T_after_gc = tenured_to()->allocated(); //std::byte * N_free_ptr = nursery_[role2int(role::to_space)]->free_ptr(); + std::size_t new_alloc_z = N0_before_gc; + /* survive_z: bytes surviving first collection */ + std::size_t survive_z = N_after_gc; + /* promote_z: bytes surviving 2nd collection */ std::size_t promote_z = (gc_statistics_.total_promoted_ - gc_statistics_.total_promoted_sab_); + /* #of bytes copied by this collection cycle */ + std::size_t effort_z = 0; + if (upto == generation::nursery) { + effort_z = N_after_gc + promote_z; + } else { + effort_z += N_after_gc + T_after_gc; + } + + /* persist_z: bytes surviving 3rd or later collection */ + std::size_t persist_z = 0; + if (upto == generation::tenured) + persist_z = T_after_gc - promote_z; + /* #of bytes found to be garbage on first collection + * (reminder: N_after_gc consists *entirely* of survives from N0_before_gc; + * + all such survivors are in N_after_gc) + */ + std::size_t garbage0_z = (N0_before_gc - N_after_gc); + /* #of bytes found to be garbage on 2nd collection */ + std::size_t garbage1_z = (N1_before_gc - promote_z); + /* #of bytes found to be garbage on 3rd or later collection */ + std::size_t garbageN_z = 0; + if (upto == generation::tenured) + garbageN_z = (T_before_gc - T_after_gc + promote_z); + /* Don't reset from-space here, it's unnecessary. * Would be permissible, but interferes with GC object modelling in * xo-object/utest/GC.test.cpp @@ -1014,25 +1098,38 @@ namespace xo { this->tenured_to()->checkpoint(); if (log) { - log(xtag("N_allocated", N_allocated)); - log(xtag("N_before_gc", N_before_gc)); + log(xtag("N0_before_gc", N0_before_gc)); + log(xtag("N1_before_gc", N1_before_gc)); log(xtag("N_after_gc", N_after_gc)); - log(xtag("T_allocated", T_allocated)); - log(xtag("T_before_gc", T_before_gc)); + + log(xtag("T0_before_gc", T0_before_gc)); + log(xtag("T1_before_gc", T1_before_gc)); log(xtag("T_after_gc", T_after_gc)); } + GcStatisticsHistoryItem item(upto, + new_alloc_z, + survive_z, + promote_z, + persist_z, + effort_z, + garbage0_z, + garbage1_z, + garbageN_z); + + this->gc_history_.push_back(item); + this->incr_gc_pending_ = false; - this->gc_statistics_.include_gc(generation::nursery, N_allocated, N_before_gc, N_after_gc, promote_z); + this->gc_statistics_.include_gc(generation::nursery, N0_before_gc, N_before_gc, N_after_gc, promote_z); if (upto == generation::tenured) { this->full_gc_pending_ = false; - this->gc_statistics_.include_gc(generation::tenured, T_allocated, T_before_gc, T_after_gc, 0); + this->gc_statistics_.include_gc(generation::tenured, T0_before_gc, T_before_gc, T_after_gc, 0); } else { // still want to update tenured stats for current alloc size this->gc_statistics_.update_snapshot(generation::tenured, T_after_gc); } - } + } /*cleanup_phase*/ void GC::execute_gc(generation upto) @@ -1090,6 +1187,9 @@ namespace xo { this->runstate_ = GCRunstate(); + // not this way.. reports cumulative stats + // this->gc_history_.push_back(this->get_gc_statistics()); + log && log("statistics:"); log && log(gc_statistics_); } diff --git a/xo-alloc/src/alloc/GcStatistics.cpp b/xo-alloc/src/alloc/GcStatistics.cpp index 7b7a6ff9..cb7ebebf 100644 --- a/xo-alloc/src/alloc/GcStatistics.cpp +++ b/xo-alloc/src/alloc/GcStatistics.cpp @@ -92,6 +92,22 @@ namespace xo { // << xtag("per_type_stats", per_type_stats_) << ">"; } + + void + GcStatisticsHistoryItem::display(std::ostream & os) const + { + os << ""; + } + } /*namespace gc*/ namespace print { @@ -148,7 +164,21 @@ namespace xo { refrtag("tenured_z", x.tenured_z_)); } - + bool + ppdetail::print_pretty(const ppindentinfo & ppii, + const xo::gc::GcStatisticsHistoryItem & x) + { + return ppii.pps()->pretty_struct(ppii, + "GcStatisticsHistoryItem", + refrtag("upto", gen2str(x.upto_)), + refrtag("survive_z", x.survive_z_), + refrtag("promote_z", x.promote_z_), + refrtag("persist_z", x.persist_z_), + refrtag("effort_z", x.effort_z_), + refrtag("garbage0_z", x.garbage0_z_), + refrtag("garbage1_z", x.garbage1_z_), + refrtag("garbageN_z", x.garbageN_z_)); + } } /*namespace print*/ } /*namespace xo*/ diff --git a/xo-alloc/src/alloc/ListAlloc.cpp b/xo-alloc/src/alloc/ListAlloc.cpp index 29cdb477..8b0a2dbb 100644 --- a/xo-alloc/src/alloc/ListAlloc.cpp +++ b/xo-alloc/src/alloc/ListAlloc.cpp @@ -70,6 +70,11 @@ namespace xo { return s_default_name; } + std::size_t + ListAlloc::page_size() const { + return hd_->page_size(); + } + std::size_t ListAlloc::size() const { return total_z_; @@ -109,8 +114,9 @@ namespace xo { ListAlloc::allocated() const { std::size_t total = 0; - if (hd_) + if (hd_) { total += hd_->allocated(); + } for (const auto & alloc : full_l_) total += alloc->allocated(); @@ -363,10 +369,17 @@ namespace xo { ListAlloc::alloc(std::size_t z) { scope log(XO_DEBUG(debug_flag_)); + /* ArenaAlloc::alloc() may modify its own size */ + + std::size_t z_pre = hd_->size(); std::byte * retval = hd_->alloc(z); - if (retval) + if (retval) { + std::size_t z_post = hd_->size(); + this->total_z_ += (z_post - z_pre); + return retval; + } log && log("space exhausted -> expand"); diff --git a/xo-alloc/utest/CMakeLists.txt b/xo-alloc/utest/CMakeLists.txt index 907bafcb..379ed925 100644 --- a/xo-alloc/utest/CMakeLists.txt +++ b/xo-alloc/utest/CMakeLists.txt @@ -12,6 +12,7 @@ set(UTEST_SRCS GcStatistics.test.cpp ObjectStatistics.test.cpp Forwarding1.test.cpp + CircularBuffer.test.cpp generation.test.cpp ) diff --git a/xo-alloc/utest/GcStatistics.test.cpp b/xo-alloc/utest/GcStatistics.test.cpp index 5f2fbe96..f5700f25 100644 --- a/xo-alloc/utest/GcStatistics.test.cpp +++ b/xo-alloc/utest/GcStatistics.test.cpp @@ -76,7 +76,7 @@ namespace xo { tag_config::tag_color_enabled = false; - GcStatisticsExt stats({}); + GcStatisticsExt stats; std::string s = tostr(stats); @@ -154,7 +154,7 @@ namespace xo { std::stringstream ss; ppconfig ppc; - GcStatisticsExt stats({}); + GcStatisticsExt stats; std::string actual = toppstr2(ppc, stats); std::string expected diff --git a/xo-alloc/utest/ListAlloc.test.cpp b/xo-alloc/utest/ListAlloc.test.cpp index 2db22bf2..0a62fd0b 100644 --- a/xo-alloc/utest/ListAlloc.test.cpp +++ b/xo-alloc/utest/ListAlloc.test.cpp @@ -12,7 +12,10 @@ namespace xo { namespace ut { TEST_CASE("ListAlloc", "[alloc][gc]") { - /** teeny weeny allocator **/ + /** teeny weeny allocator. + * but underlying ArenaAlloc works in multiples of VM page size + * (most likely 4k) + **/ up alloc = ListAlloc::make("test", 16, 32, false); REQUIRE(alloc->name() == "test"); @@ -24,7 +27,7 @@ namespace xo { std::byte * mem1 = alloc->alloc(20); REQUIRE(mem1); - REQUIRE(alloc->size() == 16 + 32); + REQUIRE(alloc->size() == alloc->page_size()); /* round up to multiple of 8 */ REQUIRE(alloc->before_checkpoint() == 24); REQUIRE(alloc->after_checkpoint() == 0); @@ -34,7 +37,7 @@ namespace xo { std::byte * mem2 = alloc->alloc(30); REQUIRE(mem2); - REQUIRE(alloc->size() == 16 + 32 + 48); + REQUIRE(alloc->size() == alloc->page_size()); REQUIRE(alloc->before_checkpoint() == 24); /* round up to multiple of 8 */ REQUIRE(alloc->after_checkpoint() == 32); @@ -42,7 +45,7 @@ namespace xo { std::byte * mem3 = alloc->alloc(40); REQUIRE(mem3); - REQUIRE(alloc->size() == 16 + 32 + 48 + 80); + REQUIRE(alloc->size() == alloc->page_size()); REQUIRE(alloc->before_checkpoint() == 24); /* already multiple of 8 */ REQUIRE(alloc->after_checkpoint() == 32 + 40); diff --git a/xo-imgui/CMakeLists.txt b/xo-imgui/CMakeLists.txt new file mode 100644 index 00000000..b23dfbc0 --- /dev/null +++ b/xo-imgui/CMakeLists.txt @@ -0,0 +1,36 @@ +# xo-imgui/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_imgui VERSION 0.1) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") # gcc-only! +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +add_subdirectory(example) +# add_subdirectory(utest) + +# set(SELF_LIB. ..) +# xo_install_library4(${SELF_LIB} ...) +# xo_export_cmake_config(${PROJECT_NAME} ...) + +if (XO_ENABLE_EXAMPLES) + install(TARGETS imgui_ex1 DESTINATION bin/imgui/example) +endif() + +# ---------------------------------------------------------------- +# docs targets depends on other library/utest/exec targets, +# must come after them +# +#add_subdirectory(docs) diff --git a/xo-imgui/cmake/xo-bootstrap-macros.cmake b/xo-imgui/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/xo-imgui/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/xo-imgui/example/CMakeLists.txt b/xo-imgui/example/CMakeLists.txt new file mode 100644 index 00000000..ac5b07f6 --- /dev/null +++ b/xo-imgui/example/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(ex1) +add_subdirectory(ex2) diff --git a/xo-imgui/example/ex1/CMakeLists.txt b/xo-imgui/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..979c176c --- /dev/null +++ b/xo-imgui/example/ex1/CMakeLists.txt @@ -0,0 +1,66 @@ +if (XO_ENABLE_EXAMPLES) + # imgui dependency + + find_path(IMGUI_INCLUDE_DIR + NAMES imgui/imgui.h + DOC "path to imgui header" + ) + if (IMGUI_INCLUDE_DIR) + message(STATUS "found imgui/imgui.h in IMGUI_INCLUDE_DIR=[${IMGUI_INCLUDE_DIR}]") + else() + message(FATAL_ERROR "unable to find imgui.h") + endif() + + # target executable + + set(SELF_EXE imgui_ex1) + add_executable(${SELF_EXE} imgui_ex1.cpp + ${IMGUI_INCLUDE_DIR}/imgui/imgui.cpp + ${IMGUI_INCLUDE_DIR}/imgui/imgui_demo.cpp + ${IMGUI_INCLUDE_DIR}/imgui/imgui_draw.cpp + ${IMGUI_INCLUDE_DIR}/imgui/imgui_widgets.cpp + ${IMGUI_INCLUDE_DIR}/imgui/imgui_tables.cpp + ${IMGUI_INCLUDE_DIR}/imgui/backends/imgui_impl_sdl2.cpp + ${IMGUI_INCLUDE_DIR}/imgui/backends/imgui_impl_opengl3.cpp + ) + xo_include_options2(${SELF_EXE}) + + # OpenGL dependency + + # have to choose between + # libGL.so + # or + # libOpenGL.so + libGLX.so # GLVND = OpenGL Vendor Neutral Dispatch (e.g. mesa|nvda) + # + # expect in .build/CMakeCache.txt: + # OPENGL_opengl_LIBRARY:FILEPATH=/path/to/libOpenGL.so + # OpenGL_DIR:PATH=OpenGL_DIR-NOTFOUND # no cmake config file + + set(OpenGL_GL_PREFERENCE GLVND) # or LEGACY + find_package(OpenGL REQUIRED) # find_package(OpenGL CONFIG REQUIRE) won't work + + target_link_libraries(${SELF_EXE} PUBLIC OpenGL::GL) +# target_link_libraries(${SELF_EXE} PUBLIC ${OPENGL_opengl_LIBRARY}) +# target_include_directories(${SELF_EXE} PUBLIC OpenGL_INCLUDE_DIRS) + #target_compile_options(${SELF_EXE} PUBLIC OpenGL_CFLAGS_OTHER) + #xo_external_dependency(${SELF_EXE} OpenGL) + + # GLEW dependency + + xo_external_pkgconfig_dependency(${SELF_EXE} GLEW glew) + + # GLFW dependency + +# find_package(glfw3 CONFIG REQUIRED) +# target_link_libraries(${SELF_EXE} PUBLIC glfw) # want -lglfw + + # SDL2 dependency + + xo_external_pkgconfig_dependency(${SELF_EXE} SDL2 sdl2) + + # would prefer to use jsut IMGUI_INCLUD_DIR, + # but imgui/backends/ .h files don't quote the imgui/ stem + # + target_include_directories(${SELF_EXE} PUBLIC ${IMGUI_INCLUDE_DIR}/imgui) + +endif() diff --git a/xo-imgui/example/ex1/imgui_ex1.cpp b/xo-imgui/example/ex1/imgui_ex1.cpp new file mode 100644 index 00000000..3745bdc9 --- /dev/null +++ b/xo-imgui/example/ex1/imgui_ex1.cpp @@ -0,0 +1,329 @@ +/* xo-imgui/example/ex1/imgui_ex1.cpp + * + * author: Roland Conybeare, Aug 2025 + */ + +#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 + +int main(int, char **) +{ + using namespace std; + + 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, + 1280, + 720, + 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 version: " << 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(1); // enable 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; + + 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; + + // Main Loop + bool done = false; + + while (!done) { + /** 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) + && (event.window.event == SDL_WINDOWEVENT_CLOSE) + && (event.window.windowID == SDL_GetWindowID(window))) + { + done = 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::SliderFloat("float", &counter_value, 0.0f, 1.0f); + ImGui::ColorEdit3("clear color", (float*)&clear_color); + + if (ImGui::Button("Button")) + ++counter; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("appl average %.3f ms/frame (%.1f fps)", + 1000.0f / io.Framerate, io.Framerate); + + 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; + +#ifdef NOPE2 + if (!glfwInit()) { + std::cerr << "glfwInit failed!" << std::endl; + return -1; + } + + std::cout << "GLFW version: " << glfwGetVersionString() << std::endl; + + // configure glfw + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + // this fails in nix-on-wsl + //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); + // maybe: + //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + // may need explicit hints + //glfwWindowHint(GLFW_RED_BITS, 8) + //glfwWindowHint(GLFW_GREEN_BITS, 8) + //glfwWindowHint(GLFW_BLUE_BITS, 8) + //glfwWindowHint(GLFW_ALPHA_BITS, 8) + //glfwWindowHint(GLFW_DEPTH_BITS, 24) + //glfwWindowHint(GLFW_STENCIL_BITS, 8) + //glfwWindowHint(GLFW_DOUBLEBUFFER, GL_TRUE) + // + // try + // $ glxinfo + // to get framebuffer info + + // Not working because: + // eglinfo + // looks in + // /run/opengl-driver/share/glvnd/egl_vendor.d + // /etc/glvnd/egl_vendor.d + // /usr/share/glvnd/egl_vendor.d + // + // these tell EGL which vendor libraries to load (mesa | nvidia) + + // create window + GLFWwindow * window = glfwCreateWindow(800, 600, "simple opengl triangle", NULL, NULL); + + if (!window) { + // getting error couldn't find framebuffer configuration matching requested + // opengl context. used by GLX (opengl extension to x11). + // defines things like: color depth, double buffering, stencil bits etc. + + + const char * descr = nullptr; + int code = glfwGetError(&descr); + std::cerr << "glfwCreateWindow failed :code " << code << " :msg " + << (descr ? descr : "?unknown") + << std::endl; + } +#endif + +#ifdef NOPE + + if (SDL_Init(SDL_INIT_VIDEO + | SDL_INIT_TIMER + | SDL_INIT_GAMECONTROLLER) != 0) + { + std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl; + return -1; + } + + // OpenGL attributes + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + + // SDL window + SDL_Window * window + = SDL_CreateWindow("xo imgui ex1", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + 800 /*width ?*/, + 600 /*height ?*/, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + + SDL_GLContext gl_context = SDL_GLContext(window); + SDL_GL_MakeCurrent(window, gl_context); + SDL_GL_SetSwapInterval(1); // enable vsync.. (wut!?) + + // imgui + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO & io = ImGui::GetIO(); (void)io; + + ImGui::StyleColorsDark(); + ImGui_ImplSDL2_InitForOpenGL(window, gl_context); +#endif + +#ifdef NOPE + // this gets: Failed to initialize OpenGL loader! + ImGui_ImplOpenGL3_Init("#version 330"); +#endif +} + +/* imgui_ex1.cpp */ diff --git a/xo-imgui/example/ex2/CMakeLists.txt b/xo-imgui/example/ex2/CMakeLists.txt new file mode 100644 index 00000000..c0671d01 --- /dev/null +++ b/xo-imgui/example/ex2/CMakeLists.txt @@ -0,0 +1,62 @@ +if (XO_ENABLE_EXAMPLES) + # imgui dependency + + find_path(IMGUI_INCLUDE_DIR + NAMES imgui/imgui.h + DOC "path to imgui header" + ) + if (IMGUI_INCLUDE_DIR) + message(STATUS "found imgui/imgui.h in IMGUI_INCLUDE_DIR=[${IMGUI_INCLUDE_DIR}]") + else() + message(FATAL_ERROR "unable to find imgui.h") + endif() + + # target executable + + set(SELF_EXE imgui_ex2) + add_executable(${SELF_EXE} imgui_ex2.cpp + ${IMGUI_INCLUDE_DIR}/imgui/imgui.cpp + ${IMGUI_INCLUDE_DIR}/imgui/imgui_demo.cpp + ${IMGUI_INCLUDE_DIR}/imgui/imgui_draw.cpp + ${IMGUI_INCLUDE_DIR}/imgui/imgui_widgets.cpp + ${IMGUI_INCLUDE_DIR}/imgui/imgui_tables.cpp + ${IMGUI_INCLUDE_DIR}/imgui/backends/imgui_impl_sdl2.cpp + ${IMGUI_INCLUDE_DIR}/imgui/backends/imgui_impl_opengl3.cpp + ) + xo_include_options2(${SELF_EXE}) + + # OpenGL dependency + + # have to choose between + # libGL.so + # or + # libOpenGL.so + libGLX.so # GLVND = OpenGL Vendor Neutral Dispatch (e.g. mesa|nvda) + # + # expect in .build/CMakeCache.txt: + # OPENGL_opengl_LIBRARY:FILEPATH=/path/to/libOpenGL.so + # OpenGL_DIR:PATH=OpenGL_DIR-NOTFOUND # no cmake config file + + set(OpenGL_GL_PREFERENCE GLVND) # or LEGACY + find_package(OpenGL REQUIRED) # find_package(OpenGL CONFIG REQUIRE) won't work + + target_link_libraries(${SELF_EXE} PUBLIC OpenGL::GL) + + # GLEW dependency + xo_external_pkgconfig_dependency(${SELF_EXE} GLEW glew) + + # GLFW dependency +# find_package(glfw3 CONFIG REQUIRED) +# target_link_libraries(${SELF_EXE} PUBLIC glfw) # want -lglfw + + # SDL2 dependency + xo_external_pkgconfig_dependency(${SELF_EXE} SDL2 sdl2) + + # would prefer to use just IMGUI_INCLUDE_DIR, + # but imgui/backends/ .h files don't quote the imgui/ stem + # + target_include_directories(${SELF_EXE} PUBLIC ${IMGUI_INCLUDE_DIR}/imgui) + + xo_dependency(${SELF_EXE} xo_object) + xo_dependency(${SELF_EXE} randomgen) + xo_dependency(${SELF_EXE} xo_alloc) +endif() diff --git a/xo-imgui/example/ex2/imgui_ex2.cpp b/xo-imgui/example/ex2/imgui_ex2.cpp new file mode 100644 index 00000000..d8aba0fe --- /dev/null +++ b/xo-imgui/example/ex2/imgui_ex2.cpp @@ -0,0 +1,1194 @@ +/* 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/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 + +using xo::gc::generation; + +struct ImRect { + ImRect() = default; + ImRect(const ImVec2 & tl, const ImVec2 & br) : top_left_{tl}, bottom_right_{br} {} + + 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)); + } + + 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(const char * mnemonic, std::size_t before_ckp, std::size_t after_ckp, + std::size_t reserved, std::size_t committed, std::size_t gc_threshold) + : mnemonic_{mnemonic}, 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_); } + + const char * mnemonic_ = nullptr; + 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 + ); + + 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; + +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(); + +public: + 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_}; +}; + +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 { + return GcStateDescription(GcGenerationDescription + ("N", + 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 + ("T", + 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 +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); //IM_COL32(0, 128, 0, 255) /*darker green*/); + + 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); + } + } +} + +using xo::scope; + +/** + * @p p_alloc_p0 @p p_alloc_p1 On exit contains corners of rectangle + * depicting allocated memory range + * @p p_x1 On exit *p_x1 contains x-coord of right-hand edge of rectangle + * depicting potential memory range + **/ +void +draw_generation(const GcStateDescription & gcdescr, + xo::gc::generation gen, + bool with_labels, + const ImRect & bounding_rect, + ImDrawList * draw_list, + ImRect * p_alloc_rect, + float * p_x1) +{ + //scope log(XO_DEBUG(with_labels)); + + using xo::gc::generation; + + /* mnemonic for gneeration. 'N' <-> nursery, 'T' <-> tenured */ + const char * G_mnemonic = ""; + /* bytes allocated to this generation since last GC + * if nursery: new allocation + * if tenured: promotions since last full GC + */ + std::size_t G0_to_size = 0; + /* bytes used for residents of this generation that have survived at least one GC */ + std::size_t G1_to_size = 0; + /* bytes of reserved memory for this generation's to-space */ + std::size_t G_to_reserved = 0; + /* bytes of committed memory for this generation's to-space */ + std::size_t G_to_committed = 0; + /* next GC trigges when G0_to_size reaches this threshold */ + std::size_t G_to_gc_threshold = 0; + + const GcGenerationDescription & gendescr = gcdescr.gen_state_v_[gen2int(gen)]; + + G_mnemonic = gendescr.mnemonic_; + G1_to_size = gendescr.before_checkpoint_; + G0_to_size = gendescr.after_checkpoint_; + G_to_reserved = gendescr.reserved_; + G_to_committed = gendescr.committed_; + G_to_gc_threshold = gendescr.gc_threshold_; + + std::size_t G_to_scale = 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 + */ + + ImRect chart_rect = bounding_rect; + /* e.g. N1: 34511 bytes */ + char g1_buf[255]; + + if (with_labels) { + snprintf(g1_buf, sizeof(g1_buf), "reserved: %lu bytes; committed: %lu bytes; %s\u2081: %lu bytes; %s\u2080: %lu bytes", + G_to_reserved, G_to_committed, G_mnemonic, G1_to_size, G_mnemonic, G0_to_size); + + auto textz = ImGui::CalcTextSize(g1_buf); + + assert(textz.y < bounding_rect.height()); + + chart_rect = bounding_rect.with_y_span(bounding_rect.y_lo() + textz.y + 2, bounding_rect.y_hi()); + + draw_list->AddText(bounding_rect.top_left(), + IM_COL32(255, 255, 192, 255), + g1_buf); + } + + float rh_text_dx = 0.0; + + /* rhs label text, e.g "Mem: 36k" */ + if (with_labels) { + char buf[255]; + snprintf(buf, sizeof(buf), "%s: %luk", + (G_to_gc_threshold > G_to_committed) ? G_mnemonic : "Mem", + G_to_scale / 1024); + + auto textz = ImGui::CalcTextSize(buf); + + rh_text_dx = 5 + textz.x; + + draw_list->AddText(ImVec2(chart_rect.x_hi() - textz.x, + chart_rect.y_mid() - 0.5 * textz.y), + IM_COL32(255, 255, 255, 255), + buf); + } + + /* chart rectangle */ + // TODO: rect.with_x_span(rect.x_lo(), rect.x_hi() - rh_text_dx) + draw_list->AddRect(chart_rect.top_left(), + ImVec2(chart_rect.x_hi() - rh_text_dx, chart_rect.y_hi()), + IM_COL32(255, 255, 255, 255) /*white*/); + + float display_w = chart_rect.width() - rh_text_dx; + float G1_w = (display_w * G1_to_size) / G_to_scale; + // TODO: rect.with_x_span(rect.x_lo(), rect.x_lo() + G1_w) + float G1_x1 = chart_rect.x_lo() + G1_w; + ImVec2 G1_p1(G1_x1, chart_rect.y_hi()); + + /* G1 */ + { + + char buf[255]; + + if (with_labels) + snprintf(buf, sizeof(buf), "%s\u2081: %luk", G_mnemonic, G1_to_size / 1024); /* N(1) */ + + char tooltip[255]; + snprintf(tooltip, sizeof(tooltip), + "%s\u2081: %lu - %s survivor size in bytes", + G_mnemonic, G1_to_size, + ((gen == xo::gc::generation::nursery) ? "nursery" : "tenured")); + + draw_filled_rect_with_label(with_labels ? buf : nullptr, + tooltip, + // TODO: rect.with_x_span(rect.x_lo(), rect.x_lo() + G1_w) + ImRect(chart_rect.top_left(), G1_p1), + IM_COL32( 0, 128, 0, 255) /*darker green*/, + IM_COL32(255, 255, 255, 255) /*white*/, + draw_list); + } + + float G0_x0 = G1_x1; + float G0_x1 = G0_x0 + (display_w * G0_to_size) / G_to_scale; + // TODO: rect.with_x_span(G0_x0, G0_x1); + ImVec2 G0_p0(G0_x0, chart_rect.y_lo()); + ImVec2 G0_p1(G0_x1, chart_rect.y_hi()); + + /* G0 */ + { + + char buf[255]; + + if (with_labels) + snprintf(buf, sizeof(buf), "%s\u2080: %luk", G_mnemonic, G0_to_size / 1024); /* N(0) */ + + char tooltip[255]; + snprintf(tooltip, sizeof(tooltip), + "%s\u2080: %lu - %s new alloc size in bytes", + G_mnemonic, G0_to_size, + ((gen == xo::gc::generation::nursery) ? "nursery" : "tenured")); + + draw_filled_rect_with_label(with_labels ? buf : nullptr, + tooltip, + ImRect(G0_p0, G0_p1), + IM_COL32( 32, 192, 32, 255) /*lighter green*/, + IM_COL32( 0, 0, 0, 255) /*black*/, + draw_list); + } + + /* mark where next gc will trigger */ + if (with_labels) { + const char * uparrow = reinterpret_cast(u8"\u25b3"); + + float ngc_w = (display_w * G_to_gc_threshold) / G_to_scale; + + auto tmp = ImGui::CalcTextSize(uparrow); + std::size_t uparrow_w = tmp.x; + double ngc_x = chart_rect.x_lo() + ngc_w - uparrow_w/2.0; + + ImVec2 marker_pos(ngc_x, chart_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", + (gen == generation::nursery ? "incremental" : "full"), + (gen == generation::nursery ? "nursery" : "tenured"), + G_to_gc_threshold); + + ImGui::SetTooltip("%s", marker_tt_buf); + } + } + + if (p_alloc_rect) + *p_alloc_rect = chart_rect.with_x_span(chart_rect.x_lo(), G0_x1); + if (p_x1) + *p_x1 = chart_rect.x_hi() - rh_text_dx; +} + +void +draw_nursery(const GcStateDescription & gcstate, + bool with_labels, + const ImRect & rect, + ImDrawList * draw_list, + ImRect * p_alloc_rect, + float * p_x1) +{ + using xo::gc::generation; + + draw_generation(gcstate, + generation::nursery, + with_labels, + rect, + draw_list, + p_alloc_rect, + p_x1); +} + +void +draw_tenured(const GcStateDescription & gcstate, + bool with_labels, + const ImRect & rect, + ImDrawList * draw_list, + ImRect * p_alloc_rect, + float * p_x1) +{ + using xo::gc::generation; + + draw_generation(gcstate, + generation::tenured, + with_labels, + rect, + draw_list, + p_alloc_rect, + p_x1); +} + +using xo::gc::GC; +using xo::gc::GcStatisticsExt; +using xo::gc::GcStatisticsHistory; +using xo::gc::GcStatisticsHistoryItem; +using xo::xtag; +using std::size_t; + +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 = 50; + 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) { + 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; + } + + /* 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 */ + float bar_w = display_w / gc_history.capacity(); + + /* 2nd loop: draw bars */ + std::size_t i = 0; + for (const GcStatisticsHistoryItem & stats : gc_history) { + /* x-coordinates of bar */ + float x_lo = lm + i * bar_w; + float x_hi = x_lo + bar_w - 1; + + /* y-coordinates of persist bar (survived 3+ GCs) */ + float ypsz_lo = (y_zero + - (display_h * stats.persist_z_ / y_scale)); + + draw_list->AddRectFilled(ImVec2(x_lo, ypsz_lo), ImVec2(x_hi, y_zero), + IM_COL32( 0, 64, 192, 255) /*darker blue*/); + + /* 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)); + + draw_list->AddRectFilled(ImVec2(x_lo, yp_lo), ImVec2(x_hi, yp_hi), + IM_COL32( 0, 128, 0, 255) /*darker green*/); + + /* 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)); + + draw_list->AddRectFilled(ImVec2(x_lo, ys_lo), ImVec2(x_hi, ys_hi), + IM_COL32( 32, 192, 32, 255)); + + // ----------------------------------------------------------- + + /* 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)); + + draw_list->AddRectFilled(ImVec2(x_lo, ygN_lo), ImVec2(x_hi, ygN_hi), + IM_COL32(255, 192, 32, 255)); + + /* 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)); + + draw_list->AddRectFilled(ImVec2(x_lo, y_zero), ImVec2(x_hi, yg1_hi), + IM_COL32(192, 192, 32, 255)); + + /* y-coordinates of garbage0 bar (killed on 1st GC) */ + float yg0_lo = yg1_hi; + float yg0_hi = (yg0_hi + + (display_h * stats.garbage0_z_ / y_scale)); + + draw_list->AddRectFilled(ImVec2(x_lo, yg0_lo), ImVec2(x_hi, yg0_hi), + IM_COL32(255, 255, 32, 255)); + + ++i; + } + + log && log(xtag("i", i)); +} + +void +draw_gc_state(const AppState & app_state, + const GcStateDescription & gcstate, + const ImRect & canvas_rect, + ImDrawList * draw_list, + ImRect * p_nursery_alloc_rect) +{ + float lm = 50; + float rm = 70; + float tm = 10; + float est_chart_text_height = 14; + float h = 20; // chart bar height + + // draw stuff + draw_list->AddRect(canvas_rect.top_left(), + canvas_rect.bottom_right(), + IM_COL32(255, 255, 255, 255)); + + /* bounding rectange for nursery display */ + ImRect N_space_rect(ImVec2(canvas_rect.x_lo() + lm, + canvas_rect.y_lo() + tm), + ImVec2(canvas_rect.x_hi() - rm, + canvas_rect.y_lo() + tm + h + est_chart_text_height)); + /* rectangle representing allocated nursery range*/ + ImRect N_alloc_rect; + float N_x1 = 0.0; + + draw_nursery(gcstate, + true /*with_labels*/, + N_space_rect, + draw_list, + &N_alloc_rect, + &N_x1); + + if (p_nursery_alloc_rect) + *p_nursery_alloc_rect = N_alloc_rect; + + /* N0_to_size..N_to_scale: in bytes */ + std::size_t N_to_scale = app_state.nursery_tospace_scale(); + + /* display_w .. N0_h : viewportcoords */ + std::size_t display_w = canvas_rect.width() - lm - rm; + + std::size_t x0 = canvas_rect.x_lo() + lm; + std::size_t x1 = canvas_rect.x_hi() - rm; + std::size_t n_y0 = canvas_rect.y_lo() + tm; + std::size_t n_y1 = n_y0 + h; + + // now turn to Tenured space + + std::size_t T_to_scale = app_state.tenured_tospace_scale(); + std::size_t T1_h = h; + + /* 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; + + /* 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); + + /* for smaller image of nursery */ + std::size_t t_y0 = canvas_rect.y_lo() + 70 + h + 20; + std::size_t t_y1 = t_y0 + T1_h; + + /* bounding rectangle for secondary nursery display */ + ImRect np_rect(ImVec2(x0 + (adj_display_w * (T_to_scale/TplusN_to_scale)), + t_y0 + est_chart_text_height), + ImVec2(x0 + adj_display_w, + t_y1 + est_chart_text_height)); + + // 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, //ImRect(ImVec2(np_x0, np_y0), ImVec2(np_x1, np_y1)), + draw_list, + nullptr, + nullptr); + } + + std::size_t h_y0 = t_y1 + est_chart_text_height; + + draw_tenured(gcstate, + true /*with labels*/, + ImRect(ImVec2(x0, t_y0), + ImVec2(x0 + (adj_display_w * (T_to_scale/TplusN_to_scale)) - TplusN_spacer, + h_y0)), + draw_list, + nullptr, + nullptr); + + draw_gc_history(gcstate, + generation::nursery, + app_state.gc_->gc_history(), + ImRect(ImVec2(x0, h_y0), + ImVec2(x1, h_y0 + 250)), + false /*debug_flag*/, + draw_list); + +#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; +}; + +struct DrawState { + up make_gc_copy_animation(AppState * app_state) { + return std::make_unique(app_state, this); + } + + ImDrawList * gcw_draw_list_ = nullptr; + + /* draw area */ + ImVec2 gcw_canvas_p0_; + ImVec2 gcw_canvas_p1_; + + /** rect displaying allocated nursery space **/ + ImRect gcw_nursery_alloc_rect_; +}; + +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::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)); + + const ImRect & nursery_rect = p_draw_state_->gcw_nursery_alloc_rect_; + + auto [x_coord_lo, x_coord_hi] = nursery_rect.x_span(); + + auto [gen, offset, alloc] = p_app_state_->gc_->fromspace_location_of(src_addr); + + if (gen == 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); + } + + double w0 = offset / static_cast(alloc); + float src0_x = ((1.0 - w0) * x_coord_lo) + (w0 * x_coord_hi); + + double w1 = (offset + z) / static_cast(alloc); + float src1_x = ((1.0 - w1) * x_coord_lo) + (w1 * x_coord_hi); + + ImRect src_rect = nursery_rect.with_x_span(src0_x, src1_x); + + p_draw_state_->gcw_draw_list_->AddRectFilled(src_rect.top_left(), + src_rect.bottom_right(), + IM_COL32(255, 128, 128, 255)); +} + +int main(int, char **) +{ + using namespace std; + + 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 version: " << 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(1); // enable 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, 0x208a, // subscript numerals + 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; + + app_state.gc_->add_gc_copy_callback(draw_state.make_gc_copy_animation(&app_state)); + + // Main Loop + bool done = false; + + while (!done) { + /** generate random alloc **/ + app_state.generate_random_mutation(); + + GcStateDescription gcstate = app_state.snapshot_gc_state(); + + /** 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); + } + } + } + + //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::SliderFloat("float", &counter_value, 0.0f, 1.0f); + ImGui::ColorEdit3("clear color", (float*)&clear_color); + + if (ImGui::Button("Button")) + ++counter; + ImGui::NewLine(); // ImGui::SameLine() + /* \u2080 = N0, \u2081 = N1 */ + ImGui::Text("alloc [%lu] avail [%lu] ", + gcstate.gc_allocated_, + gcstate.gc_available_); + //ImGui::NewLine(); + ImGui::Text("promoted [%lu]", + gcstate.total_promoted_); + + 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); + + 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_alloc_rect_); + + app_state.gc_->enable_gc(); + /* GC may run here, in which case control reenters via AnimateGcCopyCb; + * callback will rely on loop assignments to draw_area members. + */ + app_state.gc_->disable_gc(); + + 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_ex2.cpp */