307 lines
11 KiB
C++
307 lines
11 KiB
C++
/* +------------------------------------------------------+
|
|
____/ \____ /| - Open source game framework licensed to freely use, |
|
|
\ / / | copy, modify and sell without restriction |
|
|
+--\ ^__^ /--+ | |
|
|
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
|
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
|
| SPACE ~~~~~ | /
|
|
| ~~~~~~~ BOX |/
|
|
+-------------*/
|
|
|
|
#pragma once
|
|
|
|
/* Standard library */
|
|
#include <fstream>
|
|
#include <ostream>
|
|
#include <iomanip>
|
|
#include <functional>
|
|
|
|
/* SPACEBOX distributed libraries */
|
|
#include "json/json.hpp"
|
|
|
|
/* SPACEBOX */
|
|
#include "filesystem.hpp"
|
|
#include "Animation.hpp"
|
|
#include "Log.hpp"
|
|
#include "extension.hpp"
|
|
|
|
class Configuration
|
|
{
|
|
|
|
private:
|
|
|
|
Animation auto_refresher = Animation(std::bind(&Configuration::refresh, this));
|
|
fs::file_time_type config_modification_time = fs::file_time_type::clock::now();
|
|
std::vector<fs::path> files_to_refresh;
|
|
|
|
/*!
|
|
* @warning This JSON will be copied when the object is copied, and it is potentially a large copy because it can copy an arbitrary
|
|
* amount of JSON data. Because a game object by definition has a single configuration, this can be optimized in the future so that
|
|
* the JSON is stored in a member of the game object. Arguably it also doesn't make sense to be copying the data because the JSON is
|
|
* expected to change during runtime.
|
|
*/
|
|
nlohmann::json config;
|
|
|
|
/*!
|
|
* Fill the config JSON with default values set by the framework.
|
|
*/
|
|
void set_defaults();
|
|
|
|
/*!
|
|
* Pass a JSON object along with key names to get value at the specified hierarchy of keys.
|
|
*
|
|
* @warning This probably should not be called directly and is just used to provide a recursive call for variadic
|
|
* access through Configuration::operator()
|
|
*
|
|
* @param hierarchy string that appends the key as a string to the end as the function runs recursively to represent the hierarchy
|
|
* in case of an error
|
|
* @param json a JSON element in the configuration
|
|
* @param key first-level key
|
|
* @param keys zero or more next level keys
|
|
*/
|
|
template<class Key, class... Keys>
|
|
const nlohmann::json& access(std::ostringstream& hierarchy, const nlohmann::json& json, const Key& key, const Keys&... keys) const
|
|
{
|
|
hierarchy << '"' << key << '"';
|
|
if constexpr (sizeof...(keys) > 0)
|
|
{
|
|
hierarchy << " > ";
|
|
return access(hierarchy, json[key], keys...);
|
|
}
|
|
else
|
|
{
|
|
hierarchy << ")";
|
|
try
|
|
{
|
|
return json.at(key);
|
|
}
|
|
catch (const std::exception& exception)
|
|
{
|
|
std::ostringstream message;
|
|
message << "Error accessing configuration at " << hierarchy.str() << ": " << exception.what();
|
|
throw std::runtime_error(message.str());
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
|
|
/*!
|
|
* Construct a Configuration object. The path argument is the location to look for a user configuration. If valid JSON is found,
|
|
* it will be merged into the hard-coded default assignments, overwriting any existing key/value pairs. The path will be watched
|
|
* for changes if auto refresh is turned on.
|
|
*/
|
|
Configuration();
|
|
|
|
/*!
|
|
* Get a writable JSON object corresponding to a key in the configuration.
|
|
*
|
|
* The JSON object's value can be used directly if it can be implictly type-cast, and it can be assigned a new value. See
|
|
* https://nlohmann.github.io/json/api/basic_json/ for further information on how to read and write the JSON object.
|
|
*
|
|
* @param key Top level key corresponding to a section of the SPACEBOX configuration JSON
|
|
* @return writable nlohmann::JSON object reference
|
|
*/
|
|
nlohmann::json& operator[](const std::string& key);
|
|
|
|
/*!
|
|
* Get a read-only JSON object reference to the specified keys, each given as a separate argument. If no keys are given,
|
|
* a reference to the entire configuration JSON object is returned.
|
|
*
|
|
* @return read-only JSON object reference
|
|
*/
|
|
const nlohmann::json& operator[](const std::string& key) const;
|
|
|
|
/*!
|
|
* @return Read-only JSON object reference to the full configuration
|
|
*/
|
|
const nlohmann::json& operator()() const;
|
|
|
|
/*!
|
|
* Get a read-only reference to the JSON object at the given hierarchy of keys. The key must exist in the configuration, otherwise an exception
|
|
* will be thrown. To get a reference to JSON object for a new key, use sb::Configuration::operator[](const std::string) instead.
|
|
*
|
|
* Example,
|
|
*
|
|
* std::cout << _configuration() << std::endl;
|
|
* std::cout << _configuration("recording", "enabled") << " " << _configuration("input") << " " <<
|
|
* _configuration("levels", 0, 3, 1) << std::endl;
|
|
*
|
|
* Prints,
|
|
*
|
|
* [full config...]
|
|
* true {"any-key-ignore-commands":[],"default-unsuppress-delay":0.7,"ignore-repeat-keypress":true,\
|
|
* "suppress-any-key-on-mods":true, "system-any-key-ignore-commands":["fullscreen","screenshot","record","quit"]} 261.0
|
|
*
|
|
* @param keys hierarchy of keys used to look up a specific JSON object in the config
|
|
* @return read-only reference to JSON object specified by keys
|
|
*/
|
|
template<typename... Key>
|
|
const nlohmann::json& operator()(const Key&... keys) const
|
|
{
|
|
std::ostringstream hierarchy;
|
|
hierarchy << "(";
|
|
return access(hierarchy, config, keys...);
|
|
}
|
|
|
|
/*!
|
|
* Merge new SPACEBOX configuration JSON with the JSON already in memory, overwriting any existing key/value pairs.
|
|
*
|
|
* @param incoming JSON object populated with SPACEBOX configuration settings
|
|
*/
|
|
void merge(const nlohmann::json& incoming);
|
|
|
|
/*!
|
|
* Merge new SPACEBOX configuration from a given path to a JSON file.
|
|
*
|
|
* @param path JSON file path with new configuration values
|
|
*/
|
|
void merge(const fs::path& path);
|
|
|
|
/*!
|
|
* @overload void Configuration::merge(const fs::path& path)
|
|
*/
|
|
void merge(const std::string& path);
|
|
|
|
/*!
|
|
* @overload void Configuration::merge(const fs::path& path)
|
|
*/
|
|
void merge(const char* path);
|
|
|
|
/*!
|
|
* Open the JSON file at a given path and return the contents as a JSON object.
|
|
*
|
|
* @param path Filesystem path to a JSON file
|
|
* @return A nlohmann::json object containing the contents of the given file
|
|
*/
|
|
static nlohmann::json json_from_file(const fs::path& path);
|
|
|
|
/*!
|
|
* @overload Configuration::json_from_file(const fs::path&)
|
|
*/
|
|
static nlohmann::json json_from_file(const std::string& path);
|
|
|
|
/*!
|
|
* @overload Configuration::json_from_file(const fs::path&)
|
|
*/
|
|
static nlohmann::json json_from_file(const char* path);
|
|
|
|
/*!
|
|
* Enable auto refresh. Auto refresh watches the file at the given path for changes and loads them automatically at every interval
|
|
* specified in the configuration under "configuration" -> "auto-refresh-interval".
|
|
*
|
|
* @param file_to_refresh path to a configuration JSON
|
|
*/
|
|
void enable_auto_refresh(const fs::path& file_to_refresh);
|
|
|
|
/*!
|
|
* Disable auto refresh. The file previously set with Configuration::enable_auto_refresh will no longer be watched for changes.
|
|
*/
|
|
void disable_auto_refresh();
|
|
|
|
/*!
|
|
* Check if the user config file was modified and merge if so.
|
|
*/
|
|
void refresh();
|
|
|
|
/*!
|
|
* Update the auto refresher with the given timestamp, which should be the timestamp passed to Game::update.
|
|
*
|
|
* @param timestamp seconds elapsed since the program started
|
|
*/
|
|
void update(float timestamp);
|
|
|
|
};
|
|
|
|
/* Extend GLM so nlohmann::json can read and write glm::vec */
|
|
namespace glm
|
|
{
|
|
template <length_t dimensions, typename VectorType, qualifier qualifier>
|
|
void to_json(nlohmann::json& j, const vec<dimensions, VectorType, qualifier>& v)
|
|
{
|
|
if constexpr (dimensions == 2) {
|
|
j = nlohmann::json{v.x, v.y};
|
|
}
|
|
else if constexpr (dimensions == 3) {
|
|
j = nlohmann::json{v.x, v.y, v.z};
|
|
}
|
|
else if constexpr (dimensions == 4) {
|
|
j = nlohmann::json{v.x, v.y, v.z, v.w};
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Convert JSON value into GLM vertex of 2 - 4 dimensions. The resulting vertex will be the dimensions specified in the template
|
|
* argument. If the JSON value is an array with the same number of dimensions, it is converted directly into the vertex. If the
|
|
* JSON value is a scalar, the vertex is created with the dimensions all set to the scalar. If the JSON value is an array of smaller
|
|
* dimension, the missing dimensions are set to 0. If the JSON value is an array of larger dimension, the extra dimensions are left
|
|
* out of the vertex.
|
|
*
|
|
* @param j JSON object containing a scalar or array to be converted into a 2-4D GLM vertex
|
|
* @param v reference to a vertex that will be set to the value contained in the JSON
|
|
*/
|
|
template <length_t dimensions, typename VectorType, qualifier qualifier>
|
|
void from_json(const nlohmann::json& j, vec<dimensions, VectorType, qualifier>& v)
|
|
{
|
|
for (std::size_t ii = 0; ii < dimensions; ii++)
|
|
{
|
|
if (j.is_array())
|
|
{
|
|
if (j.size() < ii + 1)
|
|
{
|
|
v[ii] = 0;
|
|
}
|
|
else
|
|
{
|
|
j.at(ii).get_to(v[ii]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
j.get_to(v[ii]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Extend std::filesystem so nlohmann::json can read and write std::filesystem::path */
|
|
#if defined(__UBUNTU18__)
|
|
namespace std::experimental::filesystem
|
|
#else
|
|
namespace std::filesystem
|
|
#endif
|
|
{
|
|
template <typename T>
|
|
void to_json(nlohmann::json& j, const path& p)
|
|
{
|
|
j = nlohmann::json{p};
|
|
}
|
|
|
|
template <typename T>
|
|
void from_json(const nlohmann::json& j, path& p)
|
|
{
|
|
j.at(0).get_to(p);
|
|
}
|
|
}
|
|
|
|
namespace std
|
|
{
|
|
/*!
|
|
* Stream the entire JSON when the stream operator is called.
|
|
*
|
|
* @param out Output stream
|
|
* @param configuration Configuration object being output to the stream
|
|
* @return A reference to the input stream
|
|
*/
|
|
std::ostream& operator<<(std::ostream& out, const Configuration& configuration);
|
|
}
|
|
|
|
/* Add Configuration class to the sb namespace. This should be the default location, but Configuration is left in the global namespace
|
|
* for backward compatibility. */
|
|
namespace sb
|
|
{
|
|
using ::Configuration;
|
|
}
|
|
|
|
#include "Delegate.hpp"
|