mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2025-10-30 08:02:29 +00:00
Added config registry/option files
This commit is contained in:
parent
594cdde05a
commit
f39d08434d
8 changed files with 447 additions and 6 deletions
|
|
@ -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"
|
||||
|
|
|
|||
17
librecomp/include/librecomp/config.hpp
Normal file
17
librecomp/include/librecomp/config.hpp
Normal 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
|
||||
31
librecomp/src/config/ConfigOption.cpp
Normal file
31
librecomp/src/config/ConfigOption.cpp
Normal 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
|
||||
67
librecomp/src/config/ConfigOption.hpp
Normal file
67
librecomp/src/config/ConfigOption.hpp
Normal 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
|
||||
257
librecomp/src/config/ConfigRegistry.cpp
Normal file
257
librecomp/src/config/ConfigRegistry.cpp
Normal 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
|
||||
66
librecomp/src/config/ConfigRegistry.hpp
Normal file
66
librecomp/src/config/ConfigRegistry.hpp
Normal 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
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#include "config_store.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
namespace recomp {
|
||||
namespace recomp::config {
|
||||
|
||||
ConfigStore config_store = {{}, {}};
|
||||
|
||||
|
|
@ -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;
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue