From aff5896671b46d36cd6e77ac9d11e3d51bb8c620 Mon Sep 17 00:00:00 2001 From: Nicolas Phister Date: Tue, 7 Mar 2023 09:50:13 +0100 Subject: [PATCH] adjustable: split into two systems --- kengine/adjustable/README.md | 3 +- kengine/adjustable/imgui/systems/system.cpp | 192 ++----------------- kengine/adjustable/imgui/systems/system.md | 18 +- kengine/adjustable/ini/README.md | 6 + kengine/adjustable/ini/systems/system.cpp | 198 ++++++++++++++++++++ kengine/adjustable/ini/systems/system.hpp | 11 ++ kengine/adjustable/ini/systems/system.md | 3 + 7 files changed, 241 insertions(+), 190 deletions(-) create mode 100644 kengine/adjustable/ini/README.md create mode 100644 kengine/adjustable/ini/systems/system.cpp create mode 100644 kengine/adjustable/ini/systems/system.hpp create mode 100644 kengine/adjustable/ini/systems/system.md diff --git a/kengine/adjustable/README.md b/kengine/adjustable/README.md index ecde45334..83d0d9495 100644 --- a/kengine/adjustable/README.md +++ b/kengine/adjustable/README.md @@ -6,4 +6,5 @@ Expose global values that the user may adjust. * [values](data/values.md): component that exposes values to adjust Sub-libaries: -* [imgui](imgui/) \ No newline at end of file +* [kengine_adjustable_imgui](imgui/) +* [kengine_adjustable_ini](ini/) \ No newline at end of file diff --git a/kengine/adjustable/imgui/systems/system.cpp b/kengine/adjustable/imgui/systems/system.cpp index 6665a7be1..c1e24fd4b 100644 --- a/kengine/adjustable/imgui/systems/system.cpp +++ b/kengine/adjustable/imgui/systems/system.cpp @@ -1,7 +1,7 @@ #include "system.hpp" // stl -#include +#include // entt #include @@ -15,11 +15,7 @@ // putils #include "putils/forward_to.hpp" -#include "putils/ini_file.hpp" -#include "putils/scn/scn.hpp" #include "putils/split.hpp" -#include "putils/static_assert.hpp" -#include "putils/vector.hpp" // kengine #include "kengine/adjustable/data/values.hpp" @@ -32,37 +28,20 @@ #include "kengine/imgui/tool/data/tool.hpp" #include "kengine/main_loop/functions/execute.hpp" -#ifndef KENGINE_DEFAULT_ADJUSTABLE_SAVE_PATH -#define KENGINE_DEFAULT_ADJUSTABLE_SAVE_PATH "." -#endif - -#ifndef KENGINE_ADJUSTABLE_SAVE_FILE -#define KENGINE_ADJUSTABLE_SAVE_FILE "adjust.ini" -#endif - -#ifndef KENGINE_ADJUSTABLE_SEPARATOR -#define KENGINE_ADJUSTABLE_SEPARATOR ';' -#endif - -#ifndef KENGINE_MAX_ADJUSTABLES -#define KENGINE_MAX_ADJUSTABLES 256 -#endif - namespace kengine::adjustable::imgui { static constexpr auto log_category = "adjustable_imgui"; struct system { entt::registry & r; - const entt::scoped_connection connection = r.on_destroy().connect<&system::on_destroy_adjustable>(this); bool * enabled; - putils::ini_file loaded_file = load_ini_file(); struct section { using section_map = std::map; section_map subsections; struct entry { + entt::entity e = entt::null; values * component = nullptr; std::vector values_pass_search; // Indexed by values->values }; @@ -72,6 +51,10 @@ namespace kengine::adjustable::imgui { }; section root_section; + // Remove section from ImGui tree when adjustable is destroyed + const entt::scoped_connection connection = r.on_destroy().connect<&system::on_destroy_adjustable>(this); + + // Add new section to ImGui tree when adjustable is created struct processed {}; kengine::new_entity_processor processor{ r, putils_forward_to_this(on_construct_adjustable) }; @@ -108,15 +91,6 @@ namespace kengine::adjustable::imgui { processor.process(); if (ImGui::Begin("Adjustables", enabled)) { - ImGui::Columns(2); - if (ImGui::Button("Save", { -1.f, 0.f })) - save(); - ImGui::NextColumn(); - if (ImGui::Button("Load", { -1.f, 0.f })) - loaded_file = load_ini_file(); - ImGui::Columns(); - - ImGui::Separator(); if (ImGui::InputText("Name", name_search, sizeof(name_search))) { kengine_logf(r, verbose, log_category, "Name search changed to '{}'", name_search); search_out_of_date = true; @@ -177,7 +151,7 @@ namespace kengine::adjustable::imgui { size_t i = 0; for (auto & value : entry.component->entries) { if (entry.values_pass_search[i]) - draw(value); + draw(entry.e, value); ++i; } } @@ -189,22 +163,25 @@ namespace kengine::adjustable::imgui { } } - void draw(values::value & value) noexcept { + void draw(entt::entity e, values::value & value) noexcept { KENGINE_PROFILING_SCOPE; ImGui::Columns(2); ImGui::Text("%s", value.name.c_str()); ImGui::NextColumn(); + bool changed = false; + switch (value.type) { case values::value_type::Int: { auto & s = value.int_storage; if (value.get_enum_names != nullptr) - ImGui::Combo(string("##{}", value.name).c_str(), s.ptr != nullptr ? s.ptr : &s.value, value.get_enum_names(), (int)value.enum_count); + changed = ImGui::Combo(string("##{}", value.name).c_str(), s.ptr != nullptr ? s.ptr : &s.value, value.get_enum_names(), (int)value.enum_count); else { ImGui::PushItemWidth(-1.f); auto val = s.ptr != nullptr ? *s.ptr : s.value; if (ImGui::InputInt((string("##") + value.name).c_str(), &val, 1, 100, ImGuiInputTextFlags_EnterReturnsTrue)) { + changed = true; s.value = val; if (s.ptr != nullptr) *s.ptr = val; @@ -218,6 +195,7 @@ namespace kengine::adjustable::imgui { ImGui::PushItemWidth(-1.f); auto val = s.ptr != nullptr ? *s.ptr : s.value; if (ImGui::InputFloat((string("##") + value.name).c_str(), &val, 0.f, 0.f, "%.6f", ImGuiInputTextFlags_EnterReturnsTrue)) { + changed = true; s.value = val; if (s.ptr != nullptr) *s.ptr = val; @@ -227,7 +205,7 @@ namespace kengine::adjustable::imgui { } case values::value_type::Bool: { auto & s = value.bool_storage; - ImGui::Checkbox((string("##") + value.name).c_str(), s.ptr != nullptr ? s.ptr : &s.value); + changed = ImGui::Checkbox((string("##") + value.name).c_str(), s.ptr != nullptr ? s.ptr : &s.value); if (s.ptr != nullptr) s.value = *s.ptr; break; @@ -239,7 +217,7 @@ namespace kengine::adjustable::imgui { ImGui::OpenPopup("color picker popup"); if (ImGui::BeginPopup("color picker popup")) { - ImGui::ColorPicker4(value.name.c_str(), color); + changed = ImGui::ColorPicker4(value.name.c_str(), color); ImGui::EndPopup(); } if (s.ptr != nullptr) @@ -253,20 +231,23 @@ namespace kengine::adjustable::imgui { } } + // Notify other systems that the value changed + if (changed) + r.patch(e); + ImGui::Columns(); } void on_construct_adjustable(entt::entity e, values & comp) noexcept { KENGINE_PROFILING_SCOPE; - init_adjustable(comp); - section * current_section = &root_section; const auto section_names = putils::split(comp.section.c_str(), '/'); for (const auto & section_name : section_names) current_section = ¤t_section->subsections[section_name]; section::entry entry; + entry.e = e; entry.component = ∁ current_section->entries.push_back(std::move(entry)); @@ -301,139 +282,6 @@ namespace kengine::adjustable::imgui { search_out_of_date = true; return false; } - - void init_adjustable(values & comp) noexcept { - KENGINE_PROFILING_SCOPE; - kengine_logf(r, verbose, log_category, "Initializing section {}", comp.section); - - const auto it = loaded_file.sections.find(comp.section.c_str()); - if (it == loaded_file.sections.end()) { - kengine_logf(r, warning, log_category, "Section '{}' not found in INI file", comp.section); - return; - } - - const auto & section = it->second; - for (auto & value : comp.entries) { - const auto it = section.values.find(value.name.c_str()); - if (it == section.values.end()) { - kengine_logf(r, warning, log_category, "Value for '{}' not found in INI file", value.name); - continue; - } - - kengine_logf(r, verbose, log_category, "Initializing {} to {}", value.name, it->second); - set_value(value, it->second.c_str()); - } - } - - void set_value(values::value & value, const char * s) noexcept { - KENGINE_PROFILING_SCOPE; - - const auto assign_ptr = [](auto & storage) { - if (storage.ptr != nullptr) - *storage.ptr = storage.value; - }; - - const std::string_view view(s); - switch (value.type) { - case values::value_type::Int: { - const auto result = scn::scan_default(view, value.int_storage.value); - if (!result) - kengine_assert_failed(r, "{}", result.error().msg()); - assign_ptr(value.int_storage); - break; - } - case values::value_type::Float: { - const auto result = scn::scan_default(view, value.float_storage.value); - if (!result) - kengine_assert_failed(r, "{}", result.error().msg()); - assign_ptr(value.float_storage); - break; - } - case values::value_type::Bool: { - const auto result = scn::scan_default(view, value.bool_storage.value); - if (!result) - kengine_assert_failed(r, "{}", result.error().msg()); - assign_ptr(value.bool_storage); - break; - } - case values::value_type::Color: { - const auto result = scn::scan_default(view, value.color_storage.value); - if (!result) - kengine_assert_failed(r, "{}", result.error().msg()); - assign_ptr(value.color_storage); - break; - } - default: { - static_assert(magic_enum::enum_count() == 5); // + 1 for Invalid - kengine_assert_failed(r, "Unknown values::value type"); - break; - } - } - } - - putils::ini_file load_ini_file() noexcept { - KENGINE_PROFILING_SCOPE; - kengine_log(r, verbose, log_category, "Loading from " KENGINE_ADJUSTABLE_SAVE_FILE); - - std::ifstream f(KENGINE_ADJUSTABLE_SAVE_FILE); - - putils::ini_file ret; - f >> ret; - return ret; - } - - void save() noexcept { - KENGINE_PROFILING_SCOPE; - kengine_log(r, verbose, log_category, "Saving to " KENGINE_ADJUSTABLE_SAVE_FILE); - - std::ofstream f(KENGINE_ADJUSTABLE_SAVE_FILE, std::ofstream::trunc); - if (!f) { - kengine_assert_failed(r, "Failed to open '" KENGINE_ADJUSTABLE_SAVE_FILE "' with write permissions"); - return; - } - - putils::ini_file ini; - - for (const auto & [e, comp] : r.view().each()) { - kengine_logf(r, verbose, log_category, "Adding section {} to INI file", e, comp.section); - auto & section = ini.sections[comp.section.c_str()]; - - for (const auto & value : comp.entries) { - kengine_logf(r, verbose, log_category, "Adding value {} to section {} of INI file", e, value.name); - auto & ini_value = section.values[value.name.c_str()]; - - switch (value.type) { - case values::value_type::Int: { - ini_value = fmt::format("{}", value.int_storage.value); - kengine_logf(r, verbose, log_category, "Adding int value {}", e, ini_value); - break; - } - case values::value_type::Float: { - ini_value = fmt::format("{}", value.float_storage.value); - kengine_logf(r, verbose, log_category, "Adding float value {}", e, ini_value); - break; - } - case values::value_type::Bool: { - ini_value = fmt::format("{}", value.bool_storage.value); - kengine_logf(r, verbose, log_category, "Adding bool value {}", e, ini_value); - break; - } - case values::value_type::Color: { - ini_value = fmt::format("{}", putils::to_rgba(value.color_storage.value)); - kengine_logf(r, verbose, log_category, "Adding color value {}", e, ini_value); - break; - } - default: { - static_assert(magic_enum::enum_count() == 5); // + 1 for Invalid - kengine_assert_failed(r, "Unknown values::value type"); - break; - } - } - } - } - - f << ini; - } }; DEFINE_KENGINE_SYSTEM_CREATOR( diff --git a/kengine/adjustable/imgui/systems/system.md b/kengine/adjustable/imgui/systems/system.md index 9e5fa991e..f90fa0ee5 100644 --- a/kengine/adjustable/imgui/systems/system.md +++ b/kengine/adjustable/imgui/systems/system.md @@ -6,20 +6,4 @@ An adjustable value can specify a category by formatting its `name` like so: ``` [Category/Subcategory/...] Name ``` -Categories are then displayed together in a tree format. - -## Serialization - -Saving and loading to CSV are also implemented. - -The directory in which the CSV file is saved defaults to "." and can be adjusted by defining the `KENGINE_DEFAULT_ADJUSTABLE_SAVE_PATH` macro. - -The name of the CSV file defaults to "adjust.cnf" and can be adjusted by defining the `KENGINE_ADJUSTABLE_SAVE_FILE` macro. - -The name of the CSV file defaults to "adjust.cnf" and can be adjusted by defining the `KENGINE_ADJUSTABLE_SEPARATOR` macro. - -The maximum number of sections in an adjustable's name defaults to 8 and can be adjusted by defining the `KENGINE_MAX_ADJUSTABLE_SECTIONS` macro. - -The maximum number of adjustables defaults to 256 and can be adjusted by defining the `KENGINE_MAX_ADJUSTABLES` macro. - -The precision of floating point adjustables defaults to 5 and can be adjusted by defining the `KENGINE_ADJUSTABLE_FLOAT_PRECISION` macro. \ No newline at end of file +Categories are then displayed together in a tree format. \ No newline at end of file diff --git a/kengine/adjustable/ini/README.md b/kengine/adjustable/ini/README.md new file mode 100644 index 000000000..9dccb8611 --- /dev/null +++ b/kengine/adjustable/ini/README.md @@ -0,0 +1,6 @@ +# kengine_adjustable_loader + +System that saves and loads adjustables to and from an INI file. + +* [systems](systems) + * [system](systems/system.md) diff --git a/kengine/adjustable/ini/systems/system.cpp b/kengine/adjustable/ini/systems/system.cpp new file mode 100644 index 000000000..f546570a6 --- /dev/null +++ b/kengine/adjustable/ini/systems/system.cpp @@ -0,0 +1,198 @@ +#include "system.hpp" + +// stl +#include + +// entt +#include +#include + +// magic_enum +#include + +// putils +#include "putils/forward_to.hpp" +#include "putils/ini_file.hpp" +#include "putils/scn/scn.hpp" + +// kengine +#include "kengine/adjustable/data/values.hpp" +#include "kengine/core/assert/helpers/kengine_assert.hpp" +#include "kengine/core/helpers/new_entity_processor.hpp" +#include "kengine/core/log/helpers/kengine_log.hpp" +#include "kengine/core/profiling/helpers/kengine_profiling_scope.hpp" +#include "kengine/main_loop/functions/execute.hpp" + +#ifndef KENGINE_ADJUSTABLE_SAVE_FILE +#define KENGINE_ADJUSTABLE_SAVE_FILE "adjust.ini" +#endif + +namespace kengine::adjustable::ini { + static constexpr auto log_category = "adjustable_ini"; + + struct system { + entt::registry & r; + putils::ini_file loaded_file = load_ini_file(); + + // Initialize new adjustables with the INI contents + struct processed {}; + kengine::new_entity_processor processor{ r, putils_forward_to_this(on_construct_adjustable) }; + + // Save to the INI file when adjustables change + const entt::scoped_connection connection = r.on_update().connect<&system::save>(this); + + system(entt::handle e) noexcept + : r(*e.registry()) { + KENGINE_PROFILING_SCOPE; + kengine_log(r, log, log_category, "Initializing"); + + e.emplace(putils_forward_to_this(execute)); + processor.process(); + } + + void execute(float delta_time) noexcept { + KENGINE_PROFILING_SCOPE; + kengine_log(r, very_verbose, log_category, "Executing"); + + processor.process(); + } + + void on_construct_adjustable(entt::entity e, values & comp) noexcept { + KENGINE_PROFILING_SCOPE; + kengine_logf(r, verbose, log_category, "Initializing section {}", comp.section); + + const auto it = loaded_file.sections.find(comp.section.c_str()); + if (it == loaded_file.sections.end()) { + kengine_logf(r, warning, log_category, "Section '{}' not found in INI file", comp.section); + return; + } + + const auto & section = it->second; + for (auto & value : comp.entries) { + const auto it = section.values.find(value.name.c_str()); + if (it == section.values.end()) { + kengine_logf(r, warning, log_category, "Value for '{}' not found in INI file", value.name); + continue; + } + + kengine_logf(r, verbose, log_category, "Initializing {} to {}", value.name, it->second); + set_value(value, it->second.c_str()); + } + } + + void set_value(values::value & value, const char * s) noexcept { + KENGINE_PROFILING_SCOPE; + + const auto assign_ptr = [](auto & storage) { + if (storage.ptr != nullptr) + *storage.ptr = storage.value; + }; + + const std::string_view view(s); + switch (value.type) { + case values::value_type::Int: { + const auto result = scn::scan_default(view, value.int_storage.value); + if (!result) + kengine_assert_failed(r, "{}", result.error().msg()); + assign_ptr(value.int_storage); + break; + } + case values::value_type::Float: { + const auto result = scn::scan_default(view, value.float_storage.value); + if (!result) + kengine_assert_failed(r, "{}", result.error().msg()); + assign_ptr(value.float_storage); + break; + } + case values::value_type::Bool: { + const auto result = scn::scan_default(view, value.bool_storage.value); + if (!result) + kengine_assert_failed(r, "{}", result.error().msg()); + assign_ptr(value.bool_storage); + break; + } + case values::value_type::Color: { + const auto result = scn::scan_default(view, value.color_storage.value); + if (!result) + kengine_assert_failed(r, "{}", result.error().msg()); + assign_ptr(value.color_storage); + break; + } + default: { + static_assert(magic_enum::enum_count() == 5); // + 1 for Invalid + kengine_assert_failed(r, "Unknown values::value type"); + break; + } + } + } + + putils::ini_file load_ini_file() noexcept { + KENGINE_PROFILING_SCOPE; + kengine_log(r, verbose, log_category, "Loading from " KENGINE_ADJUSTABLE_SAVE_FILE); + + std::ifstream f(KENGINE_ADJUSTABLE_SAVE_FILE); + + putils::ini_file ret; + f >> ret; + return ret; + } + + void save(entt::registry &, entt::entity e) noexcept { + KENGINE_PROFILING_SCOPE; + kengine_logf(r, verbose, log_category, "Saving to " KENGINE_ADJUSTABLE_SAVE_FILE " because {} changed", e); + + std::ofstream f(KENGINE_ADJUSTABLE_SAVE_FILE, std::ofstream::trunc); + if (!f) { + kengine_assert_failed(r, "Failed to open '" KENGINE_ADJUSTABLE_SAVE_FILE "' with write permissions"); + return; + } + + putils::ini_file ini; + + for (const auto & [e, comp] : r.view().each()) { + kengine_logf(r, verbose, log_category, "Adding section {} to INI file", e, comp.section); + auto & section = ini.sections[comp.section.c_str()]; + + for (const auto & value : comp.entries) { + kengine_logf(r, verbose, log_category, "Adding value {} to section {} of INI file", e, value.name); + auto & ini_value = section.values[value.name.c_str()]; + + switch (value.type) { + case values::value_type::Int: { + ini_value = fmt::format("{}", value.int_storage.value); + kengine_logf(r, verbose, log_category, "Adding int value {}", e, ini_value); + break; + } + case values::value_type::Float: { + ini_value = fmt::format("{}", value.float_storage.value); + kengine_logf(r, verbose, log_category, "Adding float value {}", e, ini_value); + break; + } + case values::value_type::Bool: { + ini_value = fmt::format("{}", value.bool_storage.value); + kengine_logf(r, verbose, log_category, "Adding bool value {}", e, ini_value); + break; + } + case values::value_type::Color: { + ini_value = fmt::format("{}", value.color_storage.value); + kengine_logf(r, verbose, log_category, "Adding color value {}", e, ini_value); + break; + } + default: { + static_assert(magic_enum::enum_count() == 5); // + 1 for Invalid + kengine_assert_failed(r, "Unknown values::value type"); + break; + } + } + } + } + + f << ini; + } + }; + + DEFINE_KENGINE_SYSTEM_CREATOR( + system, + system::processed + ) +} diff --git a/kengine/adjustable/ini/systems/system.hpp b/kengine/adjustable/ini/systems/system.hpp new file mode 100644 index 000000000..adc7b9ed8 --- /dev/null +++ b/kengine/adjustable/ini/systems/system.hpp @@ -0,0 +1,11 @@ +#pragma once + +// entt +#include + +// kengine +#include "kengine/system_creator/helpers/system_creator_helper.hpp" + +namespace kengine::adjustable::ini { + DECLARE_KENGINE_SYSTEM_CREATOR(KENGINE_ADJUSTABLE_INI_EXPORT, system) +} \ No newline at end of file diff --git a/kengine/adjustable/ini/systems/system.md b/kengine/adjustable/ini/systems/system.md new file mode 100644 index 000000000..6913dc27c --- /dev/null +++ b/kengine/adjustable/ini/systems/system.md @@ -0,0 +1,3 @@ +# [system](system.hpp) + +System that saves and loads adjustables to an INI file. \ No newline at end of file