diff --git a/framework/database.h b/framework/database.h index 3f46a5d..044eb54 100644 --- a/framework/database.h +++ b/framework/database.h @@ -272,7 +272,6 @@ struct database if (index > array_size(elements)) { - FOUNDATION_ASSERT_FAIL("Index is out of bound"); mutex.shared_unlock(); return false; } @@ -357,7 +356,6 @@ struct database if (index > array_size(elements)) { - FOUNDATION_ASSERT_FAIL("Index is out of bound"); mutex.exclusive_unlock(); return false; } @@ -365,7 +363,7 @@ struct database const T& value = elements[index - 1]; if (HASHER(value) != key) { - FOUNDATION_ASSERT_FAIL("Element has been invalidated"); + log_debugf(0, STRING_CONST("Element %" PRIhash " has been invalidated"), key); mutex.exclusive_unlock(); return false; } diff --git a/framework/imcache.cpp b/framework/imcache.cpp new file mode 100644 index 0000000..23b5d7b --- /dev/null +++ b/framework/imcache.cpp @@ -0,0 +1,282 @@ +/* + * Copyright 2023 - All rights reserved. + * License: https://equals-forty-two.com/LICENSE + */ + +#include "imcache.h" + +#include +#include +#include +#include + +#define HASH_IMCACHE static_hash_string("imcache", 7, 0xa6f67d96ae77631bULL) + +typedef enum class ImCacheValueType +{ + Null = 0, + Bool = 1 << 0, + Single = 1 << 1, + Double = 1 << 2, + Integer = 1 << 3, + Unsigned = 1 << 4, + Int64 = 1 << 5, + UInt64 = 1 << 6, + Symbol = 1 << 7, + +} imgui_cache_value_type_t; +DEFINE_ENUM_FLAGS(ImCacheValueType); + +struct im_cache_entry_t +{ + hash_t key{ 0 }; + const char* FOUNDATION_RESTRICT id{ nullptr }; + + imgui_cache_flags_t flags{ ImCacheFlags::None }; + imgui_cache_value_type_t type{ ImCacheValueType::Null }; + + union value_t + { + bool b{false}; + float f32; + double f64; + int32_t i32; + unsigned u32; + int64_t i64; + uint64_t u64; + string_table_symbol_t str; + } value; + + union fetcher_t + { + bool (*b)(const im_cache_args_t& args); + float (*f32)(const im_cache_args_t& args); + double (*f64)(const im_cache_args_t& args); + int32_t (*i32)(const im_cache_args_t& args); + unsigned (*u32)(const im_cache_args_t& args); + int64_t (*i64)(const im_cache_args_t& args); + uint64_t (*u64)(const im_cache_args_t& args); + string_const_t (*str)(const im_cache_args_t& args); + } fetcher; + + tick_t access{ 0 }; + tick_t updated{ 0 }; + void* context{ nullptr }; + size_t size{ 0 }; // Size if context was copied locally +}; + +FOUNDATION_FORCEINLINE hash_t hash_entry(const im_cache_entry_t& value) +{ + return value.key; +} + +static struct IMCACHE_MODULE { + database db{}; + dispatcher_thread_handle_t fetcher{ 0 }; + event_handle entry_updated_event{}; + +} *_im_cache; + +FOUNDATION_FORCEINLINE decltype(_im_cache->db)& imcache_db() +{ + FOUNDATION_ASSERT(_im_cache); + return _im_cache->db; +} + +FOUNDATION_FORCEINLINE event_handle& imcache_event() +{ + FOUNDATION_ASSERT(_im_cache); + return _im_cache->entry_updated_event; +} + +FOUNDATION_FORCEINLINE void imcache_event_signal() +{ + FOUNDATION_ASSERT(_im_cache); + _im_cache->entry_updated_event.signal(); +} + +FOUNDATION_STATIC void* imcache_fetcher_thread(void* context) +{ + hash_t* keys_to_dispose = nullptr; + auto& sevent = imcache_event(); + while (!thread_try_wait(0)) + { + sevent.wait(250); + + auto& db = imcache_db(); + for (auto it = db.begin(), end = db.end(); it != end; ++it) + { + if (time_elapsed(it->updated) < 0.250) + continue; + + if (time_elapsed(it->access) > 1.250) + array_push(keys_to_dispose, it->key); + + // Update entry + if (it->type == ImCacheValueType::Bool) + it->value.b = it->fetcher.b({it->context, it->size}); + else if (it->type == ImCacheValueType::Single) + it->value.f32 = it->fetcher.f32({it->context, it->size}); + else if (it->type == ImCacheValueType::Double) + it->value.f64 = it->fetcher.f64({it->context, it->size}); + else if (it->type == ImCacheValueType::Integer) + it->value.i32 = it->fetcher.i32({it->context, it->size}); + else if (it->type == ImCacheValueType::Unsigned) + it->value.u32 = it->fetcher.u32({it->context, it->size}); + else if (it->type == ImCacheValueType::Int64) + it->value.i64 = it->fetcher.i64({it->context, it->size}); + else if (it->type == ImCacheValueType::UInt64) + it->value.u64 = it->fetcher.u64({it->context, it->size}); + else if (it->type == ImCacheValueType::Symbol) + { + string_const_t str = it->fetcher.str({it->context, it->size}); + it->value.str = string_table_encode(str.str, str.length); + } + + it->updated = time_current(); + it->flags |= ImCacheFlags::Initialized; + } + + for (unsigned i = 0, count = array_size(keys_to_dispose); i < count; ++i) + db.remove(keys_to_dispose[i]); + + array_clear(keys_to_dispose); + } + + array_deallocate(keys_to_dispose); + return nullptr; +} + +FOUNDATION_FORCEINLINE void imcache_update_entry(im_cache_entry_t& entry, hash_t key, imgui_cache_flags_t flags, imgui_cache_value_type_t type, void* context, size_t size) +{ + entry.key = key; + entry.flags = flags; + entry.type = type; + entry.updated = 0; + + if (size > 0) + { + entry.context = memory_allocate(HASH_IMCACHE, size, 0, MEMORY_PERSISTENT); + memcpy(entry.context, context, size); + } + else + entry.context = context; + entry.size = size; + + // Initialize the fetcher thread on-demand only + if (_im_cache->fetcher == 0) + _im_cache->fetcher = dispatch_thread("imcache_fetcher", imcache_fetcher_thread); + + entry.access = time_current(); +} + +FOUNDATION_FORCEINLINE bool imcache_select(decltype(_im_cache->db)& db, hash_t key, im_cache_entry_t& entry) +{ + if (!db.select(key, entry)) + return false; + + if (time_elapsed(entry.updated) > 0.250) + imcache_event_signal(); + + // TODO: Queue access + return db.update(key, LR1(_1.access = time_current())); +} + +#define IM_CACHE_IMPL(__FIELD__, __TYPE__) \ + auto& db = imcache_db(); \ + auto& sevent = imcache_event(); \ + im_cache_entry_t entry; \ + if (imcache_select(db, key, entry)) \ + { \ + FOUNDATION_ASSERT(entry.type == __TYPE__); \ + return entry.value.__FIELD__; \ + } \ + imcache_update_entry(entry, key, flags, __TYPE__, context, size); \ + entry.fetcher.__FIELD__ = __FIELD__; \ + entry.value.__FIELD__ = default_value; \ + if (db.insert(entry) != 0) \ + { \ + sevent.signal(); \ + return entry.value.__FIELD__; \ + } \ + return default_value; + +bool imcache(hash_t key, bool(*b)(const im_cache_args_t& args), bool default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + IM_CACHE_IMPL(b, ImCacheValueType::Bool); +} + +float imcache(hash_t key, float(*f32)(const im_cache_args_t& args), float default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + IM_CACHE_IMPL(f32, ImCacheValueType::Single); +} + +double imcache(hash_t key, double(*f64)(const im_cache_args_t& args), double default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + IM_CACHE_IMPL(f64, ImCacheValueType::Double); +} + +int32_t imcache(hash_t key, int32_t(*i32)(const im_cache_args_t& args), int32_t default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + IM_CACHE_IMPL(i32, ImCacheValueType::Integer); +} + +uint32_t imcache(hash_t key, unsigned(*u32)(const im_cache_args_t& args), uint32_t default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + IM_CACHE_IMPL(u32, ImCacheValueType::Unsigned); +} + +int64_t imcache(hash_t key, int64_t(*i64)(const im_cache_args_t& args), int64_t default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + IM_CACHE_IMPL(i64, ImCacheValueType::Int64); +} + +uint64_t imcache(hash_t key, uint64_t(*u64)(const im_cache_args_t& args), uint64_t default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + IM_CACHE_IMPL(u64, ImCacheValueType::UInt64); +} + +string_const_t imcache(hash_t key, string_const_t(*str)(const im_cache_args_t& args), const char* default_value, size_t length, void* context, size_t size, imgui_cache_flags_t flags) +{ + auto& db = imcache_db(); + auto& sevent = imcache_event(); + + im_cache_entry_t entry; + if (imcache_select(db, key, entry)) + { + FOUNDATION_ASSERT(entry.type == ImCacheValueType::Symbol); + return string_table_decode_const(entry.value.str); + } + + imcache_update_entry(entry, key, flags, ImCacheValueType::Symbol, context, size); + entry.fetcher.str = str; + entry.value.str = string_table_encode(default_value, length); + if (db.insert(entry) != 0) + sevent.signal(); + + return string_table_decode_const(entry.value.str); +} + +FOUNDATION_STATIC void imcache_initialize() +{ + _im_cache = MEM_NEW(HASH_IMCACHE, IMCACHE_MODULE); +} + +FOUNDATION_STATIC void imcache_shutdown() +{ + if (dispatcher_thread_is_running(_im_cache->fetcher)) + dispatcher_thread_stop(_im_cache->fetcher); + + for (auto it = _im_cache->db.begin_exclusive_lock(), end = _im_cache->db.end_exclusive_lock(); it != end; ++it) + { + if (it->size > 0) + { + it->size = 0; + memory_deallocate(it->context); + it->context = nullptr; + } + } + MEM_DELETE(_im_cache); +} + +DEFINE_MODULE(IMCACHE, imcache_initialize, imcache_shutdown, MODULE_PRIORITY_UI_HEADLESS); diff --git a/framework/imcache.h b/framework/imcache.h new file mode 100644 index 0000000..502b35f --- /dev/null +++ b/framework/imcache.h @@ -0,0 +1,118 @@ +/* + * Copyright 2023 - All rights reserved. + * License: https://equals-forty-two.com/LICENSE + */ + +#pragma once + +#include + +typedef enum class ImCacheFlags +{ + None = 0, + Initialized = 1 << 0, +} imgui_cache_flags_t; +DEFINE_ENUM_FLAGS(ImCacheFlags); + +struct im_cache_args_t +{ + void* context; + size_t size; +}; + +bool imcache(hash_t key, bool(*b)(const im_cache_args_t& args), bool default_value, void* context, size_t size, imgui_cache_flags_t flags); +float imcache(hash_t key, float(*f32)(const im_cache_args_t& args), float default_value, void* context, size_t size, imgui_cache_flags_t flags); +double imcache(hash_t key, double(*f64)(const im_cache_args_t& args), double default_value, void* context, size_t size, imgui_cache_flags_t flags); +int32_t imcache(hash_t key, int32_t(*i32)(const im_cache_args_t& args), int32_t default_value, void* context, size_t size, imgui_cache_flags_t flags); +uint32_t imcache(hash_t key, unsigned(*u32)(const im_cache_args_t& args), uint32_t default_value, void* context, size_t size, imgui_cache_flags_t flags); +int64_t imcache(hash_t key, int64_t(*i64)(const im_cache_args_t& args), int64_t default_value, void* context, size_t size, imgui_cache_flags_t flags); +uint64_t imcache(hash_t key, uint64_t(*u64)(const im_cache_args_t& args), uint64_t default_value, void* context, size_t size, imgui_cache_flags_t flags); +string_const_t imcache(hash_t key, string_const_t(*string)(const im_cache_args_t& args), const char* default_value, size_t length, void* context, size_t size, imgui_cache_flags_t flags); + +// Short query + +template +FOUNDATION_FORCEINLINE T imcache(hash_t key, T default_value) +{ + return imcache(key, nullptr, default_value, nullptr, 0, ImCacheFlags::None); +} + +template +FOUNDATION_FORCEINLINE T imcache(const char (&id)[N], T default_value) +{ + hash_t key = string_hash(id, N - 1); + return imcache(key, nullptr, default_value, nullptr, 0, ImCacheFlags::None); +} + +// Short query with functor + +template +FOUNDATION_FORCEINLINE T imcache(hash_t key, T(*f)(const im_cache_args_t& args), T default_value) +{ + return imcache(key, f, default_value, nullptr, 0, ImCacheFlags::None); +} + +template +FOUNDATION_FORCEINLINE T imcache(const char(&id)[N], T(*f)(const im_cache_args_t& args), T default_value) +{ + hash_t key = string_hash(id, N - 1); + return imcache(key, f, default_value, nullptr, 0, ImCacheFlags::None); +} + +// Full query with name id + +template +FOUNDATION_FORCEINLINE bool imcache(const char (&id)[N], bool(*b)(const im_cache_args_t& args), bool default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + const hash_t key = string_hash(id, N - 1); + return imcache(key, b, (float)default_value, context, size, flags); +} + +template +FOUNDATION_FORCEINLINE float imcache(const char(&id)[N], float(*f32)(const im_cache_args_t& args), float default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + const hash_t key = string_hash(id, N - 1); + return imcache(key, f32, default_value, context, size, flags); +} + +template +FOUNDATION_FORCEINLINE double imcache(const char(&id)[N], double(*f64)(const im_cache_args_t& args), double default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + const hash_t key = string_hash(id, N - 1); + return imcache(key, f64, default_value, context, size, flags); +} + +template +FOUNDATION_FORCEINLINE int32_t imcache(const char(&id)[N], int32_t(*i32)(const im_cache_args_t& args), int32_t default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + const hash_t key = string_hash(id, N - 1); + return imcache(key, i32, default_value, context, size, flags); +} + +template +FOUNDATION_FORCEINLINE uint32_t imcache(const char(&id)[N], unsigned(*u32)(const im_cache_args_t& args), uint32_t default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + const hash_t key = string_hash(id, N - 1); + return imcache(key, u32, default_value, context, size, flags); +} + +template +FOUNDATION_FORCEINLINE int64_t imcache(const char(&id)[N], int64_t(*i64)(const im_cache_args_t& args), int64_t default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + const hash_t key = string_hash(id, N - 1); + return imcache(key, i64, default_value, context, size, flags); +} + +template +FOUNDATION_FORCEINLINE uint64_t imcache(const char(&id)[N], uint64_t(*u64)(const im_cache_args_t& args), uint64_t default_value, void* context, size_t size, imgui_cache_flags_t flags) +{ + const hash_t key = string_hash(id, N - 1); + return imcache(key, u64, default_value, context, size, flags); +} + +template +FOUNDATION_FORCEINLINE string_const_t imcache(const char(&id)[N], string_const_t(*string)(const im_cache_args_t& args), const char* default_value, size_t length, void* context, size_t size, imgui_cache_flags_t flags) +{ + const hash_t key = string_hash(id, N - 1); + return imcache(key, string, default_value, length, context, size, flags); +} diff --git a/framework/tests/imcache_tests.cpp b/framework/tests/imcache_tests.cpp new file mode 100644 index 0000000..091c9e2 --- /dev/null +++ b/framework/tests/imcache_tests.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 2023 - All rights reserved. + * License: https://equals-forty-two.com/LICENSE + */ + +#include + +#if BUILD_DEVELOPMENT + +#include "test_utils.h" + +#include +#include +#include + +#include + +template T wait_one(const im_cache_args_t& args) +{ + thread_sleep(1000); + return AndReturn; +}; + +TEST_SUITE("ImCache") +{ + TEST_CASE("Basic") + { + float f1 = imcache("test1", [](auto){ return 33.0f; }, -1.0f, nullptr, SIZE_C(0), ImCacheFlags::None); + float f2 = imcache("test2", [](auto){ return 43.0f; }, -2.0f, nullptr, SIZE_C(0), ImCacheFlags::None); + + int itr = 0; + while (f1 != 33.0f || f2 != 43.0f) + { + f1 = imcache("test1", -1.0f); + f2 = imcache("test2", -2.0f); + ++itr; + } + + MESSAGE("Iterations: ", itr); + CHECK_EQ(f1, 33.0f); + CHECK_EQ(f2, 43.0f); + } + + TEST_CASE("Boolean") + { + static int counter = 0; + tick_t s = time_current(); + auto win = window_open("Test", [](window_handle_t win) + { + constexpr auto f = [](const im_cache_args_t& args) + { + thread_sleep(1000); + return true; + }; + + if (imcache("bool", f, false)) + window_close(win); + + counter++; + + }, WindowFlags::Transient); + + REQUIRE(window_valid(win)); + + while (window_valid(win)) + { + dispatcher_update(); + window_update(); + } + + CHECK_GT(counter, 0); + CHECK_GE(time_elapsed(s), 1.0); + } + + TEST_CASE("Double") + { + auto win = window_open("Test Doubles", [](window_handle_t win) + { + if (imcache("double", wait_one, 0.0)) + window_close(win); + }, WindowFlags::Transient); + REQUIRE(window_valid(win)); + + tick_t timeout = time_current(); + while (time_elapsed(timeout) < 30.0 && window_valid(win)) + { + window_update(); + dispatcher_update(); + } + + REQUIRE_FALSE(window_valid(win)); + } +} + +#endif // BUILD_DEVELOPMENT diff --git a/framework/window.cpp b/framework/window.cpp index 2b6a450..579afa2 100644 --- a/framework/window.cpp +++ b/framework/window.cpp @@ -148,7 +148,12 @@ FOUNDATION_STATIC unsigned int window_index(window_handle_t window_handle) FOUNDATION_STATIC window_t* window_handle_lookup(window_handle_t window_handle) { - return _window_module->windows[window_index(window_handle)]; + if (window_handle == 0) + return nullptr; + if (window_handle > array_size(_window_module->windows)) + return nullptr; + auto index = window_index(window_handle); + return _window_module->windows[index]; } FOUNDATION_STATIC window_t* window_allocate(GLFWwindow* glfw_window, window_flags_t flags) @@ -923,12 +928,19 @@ FOUNDATION_STATIC void window_render(window_t* win) bgfx::frame(); } -FOUNDATION_STATIC void window_update() +bool window_valid(window_handle_t window_handle) +{ + return window_handle_lookup(window_handle) != nullptr; +} + +void window_update() { const unsigned window_count = array_size(_window_module->windows); if (window_count == 0) return; + glfwPollEvents(); + // Capture the current contexts ImGuiContext* current_imgui_context = ImGui::GetCurrentContext(); ImPlotContext* current_implot_context = ImPlot::GetCurrentContext(); diff --git a/framework/window.h b/framework/window.h index 5ed2d5e..206a758 100644 --- a/framework/window.h +++ b/framework/window.h @@ -155,3 +155,14 @@ void window_close(window_handle_t window_handle); /*! Handle the main Windows/ menu items. */ void window_menu(); + +/*! Update the window system. */ +void window_update(); + +/*! Checks if the window is valid. + * + * @param window_handle The handle of the window. + * + * @return True if the window is valid, false otherwise. + */ +bool window_valid(window_handle_t window_handle); diff --git a/sources/pattern.cpp b/sources/pattern.cpp index 7e74e04..6317144 100644 --- a/sources/pattern.cpp +++ b/sources/pattern.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -2443,6 +2444,12 @@ FOUNDATION_STATIC bool pattern_update_year_after_year_results(pattern_t* pattern FOUNDATION_STATIC void pattern_update(pattern_t* pattern) { + if (!pattern->stock->is_resolving(FETCH_ALL)) + { + string_const_t code = string_table_decode_const(pattern->code); + stock_update(STRING_ARGS(code), pattern->stock, FETCH_ALL, 8.0); + } + pattern_update_year_after_year_results(pattern); pattern_compute_years_performance_ratios(pattern); } @@ -2540,6 +2547,8 @@ FOUNDATION_STATIC void pattern_render_notes_and_analysis(pattern_t* pattern, boo FOUNDATION_STATIC void pattern_render(pattern_handle_t handle, pattern_render_flags_t render_flags = PatternRenderFlags::None) { + TIME_TRACKER("pattern_render"); + const ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | @@ -2551,8 +2560,6 @@ FOUNDATION_STATIC void pattern_render(pattern_handle_t handle, pattern_render_fl pattern_t* pattern = (pattern_t*)pattern_get(handle); string_const_t code = string_table_decode_const(pattern->code); - if (!pattern->stock->is_resolving(FETCH_ALL)) - stock_update(STRING_ARGS(code), pattern->stock, FETCH_ALL, 8.0); char pattern_id[64]; string_format(STRING_BUFFER(pattern_id), STRING_CONST("Pattern###%.*s_7"), STRING_FORMAT(code)); @@ -2561,8 +2568,7 @@ FOUNDATION_STATIC void pattern_render(pattern_handle_t handle, pattern_render_fl pattern_update(pattern); - string_const_t code_str = string_table_decode_const(pattern->code); - ImGui::TableSetupColumn(code_str.str, ImGuiTableColumnFlags_WidthFixed, + ImGui::TableSetupColumn(code.str, ImGuiTableColumnFlags_WidthFixed, IM_SCALEF(220), 0U, table_cell_right_aligned_column_label, nullptr); string_const_t graph_column_title = CTEXT("Graph");