spacebox/src/Configuration.hpp

227 lines
7.2 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
#include <fstream>
#include <ostream>
#include <iomanip>
#include "json/json.hpp"
#include "filesystem.hpp"
#include "Node.hpp"
#include "Animation.hpp"
#include "Log.hpp"
#include "extension.hpp"
class Configuration : public Node
{
private:
Animation auto_refresher = Animation(&Configuration::refresh, this);
fs::file_time_type config_file_modification_time;
nlohmann::json config;
fs::path file_to_refresh;
/*!
* 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 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(const nlohmann::json& json, const Key& key, const Keys&... keys) const
{
if constexpr (sizeof...(keys) > 0)
{
return access(json[key], keys...);
}
else
{
return json[key];
}
}
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.
*
* @param parent SPACEBOX Node rooted at the Game object
*/
Configuration(Node* parent);
/*!
* 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
*/
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 value at the given hierarchy of keys. If no type is specified for the return type, a
* JSON object will be returned.
*
* 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 value in the config JSON
* @return read-only reference to value specified by keys
*/
template<typename... Key>
const nlohmann::json& operator()(const Key&... keys) const
{
return access(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(fs::path path)
*/
void merge(const std::string& path);
/*!
* @overload void Configuration::merge(fs::path path)
*/
void merge(const char* path);
/*!
* Enable auto refresh. Auto refresh watches the file at the given path for changes and loads them automatically every interval given
* in seconds.
*
* @param file_to_refresh path to a configuration JSON
* @param interval amount of seconds between each refresh
*/
void enable_auto_refresh(const fs::path& file_to_refresh, float interval);
/*!
* 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::vec2 */
namespace glm
{
template <typename T>
void to_json(nlohmann::json& j, const vec<2, T, defaultp>& v)
{
j = nlohmann::json{v.x, v.y};
}
template <typename T>
void from_json(const nlohmann::json& j, vec<2, T, defaultp>& v)
{
j.at(0).get_to(v.x);
j.at(1).get_to(v.y);
}
}
/* Extend std::filesystem so nlohmann::json can read and write std::filesystem::path */
#if defined(__MINGW32__)
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;
}