Added config registry/option files

This commit is contained in:
thecozies 2024-07-11 12:28:11 -05:00 committed by Mr-Wiseguy
parent 594cdde05a
commit f39d08434d
8 changed files with 447 additions and 6 deletions

View file

@ -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"

View file

@ -0,0 +1,17 @@
#ifndef __RECOMP_CONFIG_H__
#define __RECOMP_CONFIG_H__
#include <stdlib.h>
#include <unordered_map>
#include <string>
#include <variant>
#include <mutex>
#include "json/json.hpp"
// namespace recomp::config
#include "config/ConfigStore.hpp"
#include "config/ConfigRegistry.hpp"
#include "config/ConfigOption.hpp"
#endif

View file

@ -0,0 +1,31 @@
#include "config.hpp"
#include <string>
#include <unordered_map>
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

View file

@ -0,0 +1,67 @@
#ifndef __RECOMP_CONFIG_OPTION_H__
#define __RECOMP_CONFIG_OPTION_H__
#include <string>
#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<std::string, nlohmann::detail::value_t> 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

View file

@ -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<ConfigOptionType>(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<bool>(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<int>();
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<std::string>();
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<int>(j, "default");
}
// Max is required
if (j.find("max") != j.end()) {
max = get_value_in_json<int>(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<int>(j, "min");
if (default_val < min) default_val = min;
}
if (j.find("step") != j.end()) {
step = get_value_in_json<int>(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

View file

@ -0,0 +1,66 @@
#ifndef __RECOMP_CONFIG_REGISTRY_H__
#define __RECOMP_CONFIG_REGISTRY_H__
#include <string>
#include <vector>
#include <string>
#include <unordered_map>
#include <variant>
#include <map>
#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<std::string, JSONRef> config_registry_key_reference_map;
// config group -> json
typedef std::unordered_map<std::string, nlohmann::json> 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 <typename T>
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 <typename T>
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

View file

@ -1,6 +1,6 @@
#include "config_store.hpp"
#include "config.hpp"
namespace recomp {
namespace recomp::config {
ConfigStore config_store = {{}, {}};

View file

@ -21,13 +21,13 @@ struct string_hash {
}
};
namespace recomp {
namespace recomp::config {
typedef std::variant<std::string, int> config_store_value;
typedef std::unordered_map<std::string, config_store_value, string_hash, std::equal_to<>> 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;
};