From f39d08434d9ef1cc6b23c177037cd3f607ada4e4 Mon Sep 17 00:00:00 2001 From: thecozies <79979276+thecozies@users.noreply.github.com> Date: Thu, 11 Jul 2024 12:28:11 -0500 Subject: [PATCH] Added config registry/option files --- librecomp/CMakeLists.txt | 5 +- librecomp/include/librecomp/config.hpp | 17 ++ librecomp/src/config/ConfigOption.cpp | 31 +++ librecomp/src/config/ConfigOption.hpp | 67 +++++ librecomp/src/config/ConfigRegistry.cpp | 257 ++++++++++++++++++ librecomp/src/config/ConfigRegistry.hpp | 66 +++++ .../ConfigStore.cpp} | 4 +- .../config/ConfigStore.hpp} | 6 +- 8 files changed, 447 insertions(+), 6 deletions(-) create mode 100644 librecomp/include/librecomp/config.hpp create mode 100644 librecomp/src/config/ConfigOption.cpp create mode 100644 librecomp/src/config/ConfigOption.hpp create mode 100644 librecomp/src/config/ConfigRegistry.cpp create mode 100644 librecomp/src/config/ConfigRegistry.hpp rename librecomp/src/{config_store.cpp => config/ConfigStore.cpp} (93%) rename librecomp/{include/librecomp/config_store.hpp => src/config/ConfigStore.hpp} (95%) diff --git a/librecomp/CMakeLists.txt b/librecomp/CMakeLists.txt index 431ef5f..e2c7979 100644 --- a/librecomp/CMakeLists.txt +++ b/librecomp/CMakeLists.txt @@ -8,7 +8,9 @@ set(CMAKE_CXX_EXTENSIONS OFF) # Define the library add_library(librecomp STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/ai.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/src/config_store.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/config/ConfigOption.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/config/ConfigRegistry.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/config/ConfigStore.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/cont.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/dp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/eep.cpp" @@ -35,6 +37,7 @@ add_library(librecomp STATIC target_include_directories(librecomp PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_SOURCE_DIR}/src" "${PROJECT_SOURCE_DIR}/../ultramodern/include" "${PROJECT_SOURCE_DIR}/../thirdparty" "${PROJECT_SOURCE_DIR}/../thirdparty/concurrentqueue" diff --git a/librecomp/include/librecomp/config.hpp b/librecomp/include/librecomp/config.hpp new file mode 100644 index 0000000..4ddbfc3 --- /dev/null +++ b/librecomp/include/librecomp/config.hpp @@ -0,0 +1,17 @@ +#ifndef __RECOMP_CONFIG_H__ +#define __RECOMP_CONFIG_H__ + +#include +#include +#include +#include +#include +#include "json/json.hpp" + +// namespace recomp::config + +#include "config/ConfigStore.hpp" +#include "config/ConfigRegistry.hpp" +#include "config/ConfigOption.hpp" + +#endif diff --git a/librecomp/src/config/ConfigOption.cpp b/librecomp/src/config/ConfigOption.cpp new file mode 100644 index 0000000..c6ca3f2 --- /dev/null +++ b/librecomp/src/config/ConfigOption.cpp @@ -0,0 +1,31 @@ + +#include "config.hpp" + +#include +#include + +using json = nlohmann::json; +namespace recomp::config { + +bool ConfigOption::validate(json& opt_json) { + auto type_struct = get_type_structure(); + for (const auto& [key, expected_type] : type_struct) { + if (!opt_json.contains(key) || opt_json[key].type() != expected_type) { + return false; + } + } + return true; +} + +ConfigOption::ConfigOption(json& opt_json) +{ + type = opt_json[ConfigOption::schema::type]; + key = opt_json[ConfigOption::schema::key]; +} + +ConfigOption::~ConfigOption() +{ + +} + +} // namespace Rml diff --git a/librecomp/src/config/ConfigOption.hpp b/librecomp/src/config/ConfigOption.hpp new file mode 100644 index 0000000..71a067b --- /dev/null +++ b/librecomp/src/config/ConfigOption.hpp @@ -0,0 +1,67 @@ +#ifndef __RECOMP_CONFIG_OPTION_H__ +#define __RECOMP_CONFIG_OPTION_H__ + +#include +#include "json/json.hpp" + +namespace recomp::config { + +enum class ConfigOptionType { + Label, + // Base types + Checkbox, + RadioTabs, + Dropdown, + Range, + Color, + Trigger, + // Group types + CheckboxGroup, + Group, + OptionCount +}; + +NLOHMANN_JSON_SERIALIZE_ENUM(ConfigOptionType, { + {ConfigOptionType::Label, "Label"}, + {ConfigOptionType::Checkbox, "Checkbox"}, + {ConfigOptionType::RadioTabs, "RadioTabs"}, + {ConfigOptionType::Dropdown, "Dropdown"}, + {ConfigOptionType::Range, "Range"}, + {ConfigOptionType::Color, "Color"}, + {ConfigOptionType::Trigger, "Trigger"}, + {ConfigOptionType::CheckboxGroup, "CheckboxGroup"}, + {ConfigOptionType::Group, "Group"} +}); + +typedef std::unordered_map config_type_structure; + +inline bool config_option_is_group(ConfigOptionType option_type) { + return option_type == ConfigOptionType::Group || option_type == ConfigOptionType::CheckboxGroup; +} + +/** + Base Config Option class. Defines what type of option or group it is, and + sets the key. + */ + +class ConfigOption { +public: + ConfigOption(nlohmann::json& opt_json); + virtual ~ConfigOption(); + + bool validate(nlohmann::json& opt_json); + + ConfigOptionType type; + std::string key; + + struct schema { + static constexpr const char* type = "type"; + static constexpr const char* key = "key"; + }; + +protected: + virtual const config_type_structure& get_type_structure() const = 0; +}; + +} +#endif diff --git a/librecomp/src/config/ConfigRegistry.cpp b/librecomp/src/config/ConfigRegistry.cpp new file mode 100644 index 0000000..75e34e1 --- /dev/null +++ b/librecomp/src/config/ConfigRegistry.cpp @@ -0,0 +1,257 @@ +#include "config.hpp" + +using json = nlohmann::json; + +namespace recomp::config { + +ConfigRegistry config_registry = {{}, {}}; + +#define TODO_PARSE_ERROR(m, t) printf("Value at key: \"%s\" was invalid. Expected: \"%s\"\n", m, t) + +std::string get_string_in_json(const json& j, const std::string& key) { + std::string ret; + + auto find_it = j.find(key); + if (find_it != j.end()) { + auto at_val = j.at(key); + if (!at_val.is_string()) { + TODO_PARSE_ERROR(key, "string"); + } + + find_it->get_to(ret); + } + + return ret; +} + +std::string get_string_in_json_with_default(const json& j, const std::string& key, const std::string& default_val) { + std::string ret; + + auto find_it = j.find(key); + if (find_it != j.end()) { + auto at_val = j.at(key); + if (!at_val.is_string()) { + return default_val; + } + + find_it->get_to(ret); + } else { + return default_val; + } + + return ret; +} + +static bool validate_json_value_is_array(const json &j, const std::string& json_path, const std::string& arr_key) { + const auto &options = j.find(arr_key); + if (options == j.end()) { + TODO_PARSE_ERROR(json_path + "/" + arr_key, "array"); + return false; + } + const auto &opt_array = j[arr_key]; + + if (!opt_array.is_array()) { + TODO_PARSE_ERROR(json_path + "/" + arr_key, "array"); + return false; + } + + return true; +} + +// Option +void register_config_option( + const json& j, + const std::string& previous_key, // previous path before current key + const std::string& config_group, + const std::string& json_path // path to this json object from the root +) { + const std::string key = get_string_in_json(j, ConfigOption::schema::key); + const std::string this_key = previous_key + "/" + key; + + ConfigOptionType type = get_value_in_json(j, ConfigOption::schema::type); + + config_registry.key_ref_map[this_key] = { config_group, json_path }; + + switch (type) { + case ConfigOptionType::Checkbox: { + bool default_val = false; + if (j.find("default") != j.end()) { + default_val = get_value_in_json(j, "default"); + } + set_config_store_value_and_default(this_key, default_val, default_val); + break; + } + case ConfigOptionType::Color: { + const std::string rgb_strings[] = {"r", "g", "b"}; + + // read and ensure default is an array of numbers + if (j.find("default") != j.end()) { + if (!validate_json_value_is_array(j, json_path, "default")) { + return; + } + + const auto &col_array = j["default"]; + + for (int i = 0; i < col_array.size(); i++) { + const auto &j_opt = col_array[i]; + if (!j_opt.is_number()) { + TODO_PARSE_ERROR(this_key + "/default/" + std::to_string(i) , "number"); + return; + } + const int opt_val = j_opt.get(); + set_config_store_value_and_default(this_key + ":" + rgb_strings[i], opt_val, opt_val); + } + } else { + // "default" doesn't exist, set to black + set_config_store_value_and_default(this_key + ":" + rgb_strings[0], 0, 0); + set_config_store_value_and_default(this_key + ":" + rgb_strings[1], 0, 0); + set_config_store_value_and_default(this_key + ":" + rgb_strings[2], 0, 0); + } + + break; + } + case ConfigOptionType::RadioTabs: { + if (!validate_json_value_is_array(j, json_path, "values")) { + return; + } + + int default_val = 0; + if (j.find("default") != j.end()) { + const auto &opt_array = j["values"]; + const std::string default_val_string = get_string_in_json(j, "default"); + // Based on default value's string, find which option index corresponds + for (int i = 0; i < opt_array.size(); i++) { + const auto &j_opt = opt_array[i]; + if (!j_opt.is_string()) { + TODO_PARSE_ERROR(key + "/values/" + std::to_string(i) , "string"); + return; + } + const std::string opt_val = j_opt.get(); + if (opt_val == default_val_string) { + default_val = i; + break; + } + } + } + set_config_store_value_and_default(this_key, default_val, default_val); + break; + } + case ConfigOptionType::Range: { + int default_val = 0; + int max = 0; + int min = 0; + int step = 1; + + if (j.find("default") != j.end()) { + default_val = get_value_in_json(j, "default"); + } + + // Max is required + if (j.find("max") != j.end()) { + max = get_value_in_json(j, "max"); + if (default_val > max) default_val = max; + } else { + TODO_PARSE_ERROR(key + "/max", "int"); + return; + } + + if (j.find("min") != j.end()) { + min = get_value_in_json(j, "min"); + if (default_val < min) default_val = min; + } + + if (j.find("step") != j.end()) { + step = get_value_in_json(j, "step"); + } + + assert(max > min); + assert(step < max - min); + set_config_store_value_and_default(this_key, default_val, default_val); + break; + } + } + if (j.find("label") != j.end()) { + const std::string label = get_string_in_json(j, "label"); + set_config_store_value(this_key, label); + } + + if ((type == ConfigOptionType::Group || type == ConfigOptionType::CheckboxGroup) && j.find("options") != j.end()) { + if (!validate_json_value_is_array(j, json_path, "options")) { + return; + } + + const auto &opt_array = j["options"]; + + for (int i = 0; i < opt_array.size(); i++) { + const auto &el = opt_array[i]; + register_config_option( + el, + this_key, + config_group, + json_path + "/options/" + std::to_string(i) + ); + } + } +} + +void register_config(const std::string &json_str, const std::string &config_group) { + config_registry.group_json_map[config_group] = json::parse(json_str); + const auto &j = config_registry.group_json_map[config_group]; + + if (!j.is_array()) { + TODO_PARSE_ERROR("/", "array"); + return; + } + + for (int i = 0; i < j.size(); i++) { + const auto &el = j[i]; + register_config_option( + el, + config_group, + config_group, + "/" + std::to_string(i) // json_path at top level + ); + } +} + +void register_translation(const std::string &json_str, const std::string &config_group) { + const auto &j = json::parse(json_str); + + if (!j.is_object()) { + TODO_PARSE_ERROR("/", "object"); + return; + } + + for (auto& el : j.items()) + { + std::string translation_key = "translations/" + config_group + "/" + el.key(); + const std::string value = el.value(); + set_config_store_value(translation_key, value); + } +} + +json get_json_from_key(const std::string &config_key) { + if (config_registry.key_ref_map.find(config_key) == config_registry.key_ref_map.end()) { + // TODO: handle not finding config_key + printf("FAILURE: AddOptionTypeElement failed to find config_key '%s' in config_registry.key_ref_map\n", config_key); + } + const JSONRef& json_ref = config_registry.key_ref_map[config_key]; + const json& group_json = config_registry.group_json_map[json_ref.config_group]; + json::json_pointer pointer(json_ref.json_path); + return group_json[pointer]; +} + +bool config_key_is_base_group(const std::string &config_group_key) { + // determine if the key references a base group by checking the group_json_map + if (config_registry.group_json_map.find(config_group_key) == config_registry.group_json_map.end()) { + return false; + } + return true; +} + +json& get_group_json(const std::string &config_group_key) { + return config_registry.group_json_map[config_group_key]; +} + + +} // namespace recompui diff --git a/librecomp/src/config/ConfigRegistry.hpp b/librecomp/src/config/ConfigRegistry.hpp new file mode 100644 index 0000000..44ffdfb --- /dev/null +++ b/librecomp/src/config/ConfigRegistry.hpp @@ -0,0 +1,66 @@ +#ifndef __RECOMP_CONFIG_REGISTRY_H__ +#define __RECOMP_CONFIG_REGISTRY_H__ + +#include +#include +#include +#include +#include +#include +#include "json/json.hpp" + +namespace recomp::config { + struct JSONRef { + std::string config_group; + std::string json_path; // used as a json pointer + }; + // config key -> JSONRef + typedef std::unordered_map config_registry_key_reference_map; + // config group -> json + typedef std::unordered_map config_registry_group_json_map; + + struct ConfigRegistry { + config_registry_key_reference_map key_ref_map; + config_registry_group_json_map group_json_map; + }; + + extern ConfigRegistry config_registry; + + void register_config(const std::string& json_str, const std::string& config_group); + void register_translation(const std::string &json_str, const std::string &config_group); + + nlohmann::json get_json_from_key(const std::string &config_key); + std::string get_string_in_json(const nlohmann::json& j, const std::string& key); + std::string get_string_in_json_with_default(const nlohmann::json& j, const std::string& key, const std::string& default_val); + bool config_key_is_base_group(const std::string &config_group_key); + nlohmann::json& get_group_json(const std::string &config_group_key); + + #define TODO_PARSE_ERROR(m, t) printf("Value at key: \"%s\" was invalid. Expected: \"%s\"\n", m, t) + + template + T get_value_in_json(const nlohmann::json& j, const std::string& key) { + T ret; + + auto find_it = j.find(key); + if (find_it != j.end()) { + auto at_val = j.at(key); + find_it->get_to(ret); + } + + return ret; + } + + template + T get_value_in_json_with_default(const nlohmann::json& j, const std::string& key, T default_val) { + T ret = default_val; + + auto find_it = j.find(key); + if (find_it != j.end()) { + auto at_val = j.at(key); + find_it->get_to(ret); + } + + return ret; + } +} +#endif diff --git a/librecomp/src/config_store.cpp b/librecomp/src/config/ConfigStore.cpp similarity index 93% rename from librecomp/src/config_store.cpp rename to librecomp/src/config/ConfigStore.cpp index 3e96540..ad01966 100644 --- a/librecomp/src/config_store.cpp +++ b/librecomp/src/config/ConfigStore.cpp @@ -1,6 +1,6 @@ -#include "config_store.hpp" +#include "config.hpp" -namespace recomp { +namespace recomp::config { ConfigStore config_store = {{}, {}}; diff --git a/librecomp/include/librecomp/config_store.hpp b/librecomp/src/config/ConfigStore.hpp similarity index 95% rename from librecomp/include/librecomp/config_store.hpp rename to librecomp/src/config/ConfigStore.hpp index 91ce7f7..74c2952 100644 --- a/librecomp/include/librecomp/config_store.hpp +++ b/librecomp/src/config/ConfigStore.hpp @@ -21,13 +21,13 @@ struct string_hash { } }; -namespace recomp { +namespace recomp::config { typedef std::variant config_store_value; typedef std::unordered_map> config_store_map; struct ConfigStore { - recomp::config_store_map map; - recomp::config_store_map default_map; + config_store_map map; + config_store_map default_map; std::mutex store_mutex; std::mutex default_store_mutex; };