[WIP] Add TOML Serialization support for aarbitrary game data.
Some checks failed
buildbot/elemental-game-macos-builder Build done.
buildbot/elemental-game-freebsd-builder Build done.
buildbot/elemental-game-linux-builder Build done.

- Now partly using the new TOML serialization feature from IOCore
- Remove JsonConfigFile class and move it to IOCore
This commit is contained in:
S David 2024-08-05 00:46:29 -04:00
parent ebfc03b968
commit 091ebd7afe
12 changed files with 37 additions and 333 deletions

View File

@ -11,7 +11,6 @@ PRIVATE
target_link_libraries(phong PRIVATE
elemental
IOCore
nlohmann_json
)

View File

@ -11,6 +11,7 @@
#include "types/rendering.hpp"
#include "IOCore/util/serialization.hpp"
#include "IOCore/util/toml.hpp"
#include <nlohmann/json.hpp>
@ -20,8 +21,7 @@ namespace elemental {
struct GameSettings {
RendererSettings renderer_settings;
// DEFINE_TOML_FIELDS(renderer_settings);
NLOHMANN_DEFINE_TYPE_INTRUSIVE(GameSettings, renderer_settings);
JSON_SERIALIZABLE(GameSettings, renderer_settings);
};
} // namespace elemental

View File

@ -15,7 +15,6 @@
#include "util/debug.hpp"
#include "IOCore/Exception.hpp"
#include "JsonConfigFile.hpp"
#include "LoopRegulator.hpp"
#include "SdlEventSource.hpp"
#include "SdlRenderer.hpp"

View File

@ -14,7 +14,9 @@
#include "IObserver.hpp"
#include "IOCore/Application.hpp"
#include "JsonConfigFile.hpp"
#include "IOCore/JsonConfigFile.hpp"
// #include "JsonConfigFile.hpp"
#include "LoopRegulator.hpp"
#include "Observable.hpp"
#include "Singleton.hpp"
@ -65,7 +67,7 @@ class Phong
SdlEventSource& event_emitter;
GameSettings settings;
configuration::JsonConfigFile settings_file;
IOCore::JsonConfigFile settings_file;
};
} // namespace elemental

View File

@ -914,9 +914,9 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = src/ \
include/ \
demo/
INPUT = Modules/elemental/ \
Apps/ \
Tests
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses

View File

@ -1,6 +1,5 @@
add_library(elemental
OBJECT
JsonConfigFile.cpp
LoopRegulator.cpp
Observable.cpp
SdlRenderer.cpp

View File

@ -1,55 +0,0 @@
/* JsonConfigFile.hpp
* Copyright © 2024 Saul D. Beniquez
* License: Mozilla Public License v. 2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v.2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "types.hpp"
#include "IOCore/FileResource.hpp"
#include <filesystem>
#include <nlohmann/json.hpp>
namespace elemental::configuration {
using IOCore::CreateDirs;
using IOCore::FileResource;
class JsonConfigFile : public FileResource {
public:
JsonConfigFile(
const std::filesystem::path& file_path,
CreateDirs mode = CreateDirs::Disable
);
~JsonConfigFile() override;
auto read() -> nlohmann::json&;
void write();
template<typename TData>
[[nodiscard]] auto get() const -> TData
{
return config_json.get<TData>();
}
template<typename T_>
void set(const T_& value)
{
config_json = value;
}
auto jsonDataRef() -> nlohmann::json& { return this->config_json; };
protected:
nlohmann::json config_json;
};
} // namespace elemental::configuration
// clang-format off
// vim: set foldmethod=syntax foldlevel=1 foldminlines=12 textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :

View File

@ -7,18 +7,17 @@
* obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <SDL.h>
#include <SDL_image.h>
#include "IOCore/Exception.hpp"
#include "IRenderer.hpp"
#include "SdlRenderer.hpp"
#include "util/debug.hpp"
#include "types/input.hpp"
#include "types/rendering.hpp"
#include "util/debug.hpp"
#include <IOCore/Exception.hpp>
#include <SDL.h>
#include <SDL_image.h>
#include <fmt/core.h>
#include <nlohmann/json.hpp>
#include <memory>
@ -53,10 +52,11 @@ void SdlRenderer::init(RendererSettings& settings)
IMG_Init(
IMG_INIT_JPG | IMG_INIT_PNG | IMG_INIT_TIF | IMG_INIT_WEBP
)) {
error_buffer.str("");
error_buffer << "Could not initialize SDL_Image: IMG_INIT() == 0"
<< std::flush;
throw IOCore::Exception(error_buffer.str());
HANDLE_SDL_ERROR(fmt::format(
"Could not initialize SDL_Image: {}",
IMG_GetError()
)
.c_str());
}
int window_xpos, window_ypos, window_width, window_height, res_width,
res_height;

View File

@ -9,7 +9,7 @@
#pragma once
#include "util/serialization.hpp"
#include "IOCore/util/serialization.hpp"
#include <cstdint>
#include <string>
@ -18,13 +18,15 @@ namespace elemental {
struct Point {
uint32_t x, y;
SERIALIZABLE(Point, x, y);
JSON_SERIALIZABLE(Point, x, y);
TOML_SERIALIZABLE(Point, x, y);
};
using Position2D = Point;
struct Area {
uint32_t width, height;
SERIALIZABLE(Area, width, height);
JSON_SERIALIZABLE(Area, width, height);
TOML_SERIALIZABLE(Area, width, height);
};
using Resolution = Area;
@ -38,7 +40,8 @@ struct Rectangle {
uint32_t& width = size.width;
uint32_t& height = size.height;
SERIALIZABLE(Rectangle, position, size);
JSON_SERIALIZABLE(Rectangle, position, size);
// TOML_SERIALIZABLE(Rectangle, position);
};
enum class WindowMode {
@ -46,23 +49,12 @@ enum class WindowMode {
Borderless = 0x01,
Fullscreen = 0x11,
};
// NOLINTNEXTLINE(readability-identifier-length)
SERIALIZABLE_ENUM(
WindowMode, { { WindowMode::Windowed, "windowed" },
{ WindowMode::Borderless, "borderless" },
{ WindowMode::Fullscreen, "fullscreen" } }
);
enum class WindowPlacement : int
{
Manual = 0x00,
Centered = 0x01
};
// NOLINTNEXTLINE(readability-identifier-length)
SERIALIZABLE_ENUM(
WindowPlacement, { { WindowPlacement::Manual, "manual" },
{ WindowPlacement::Centered, "centered" } }
JSON_SERIALIZABLE_ENUM( // NOLINT(readability-identifier-length)
WindowMode, { { WindowMode::Windowed, "Windowed" },
{ WindowMode::Borderless, "Borderless" },
{ WindowMode::Fullscreen, "Fullscreen" } }
);
enum class WindowPlacement : int { Manual = 0x00, Centered = 0x01 };
struct WindowParameters {
std::string title;
@ -71,14 +63,17 @@ struct WindowParameters {
Position2D position;
Area size;
SERIALIZABLE(WindowParameters, title, mode, placement, position, size);
JSON_SERIALIZABLE(
WindowParameters, title, mode, placement, position, size
);
};
struct RendererSettings {
WindowParameters window;
Resolution resolution;
int field1;
SERIALIZABLE(RendererSettings, window, resolution);
JSON_SERIALIZABLE(RendererSettings, window, resolution, field1);
};
} // namespace elemental

View File

@ -1,21 +0,0 @@
/* serialization.hpp
* Copyright © 2024 Saul D. Beniquez
* License: Mozilla Public License v. 2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v.2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <nlohmann/json.hpp>
/* NOLINTBEGIN(readability-identifier-length) */
#define SERIALIZABLE(...) NLOHMANN_DEFINE_TYPE_INTRUSIVE(__VA_ARGS__)
#define SERIALIZABLE_ENUM(...) NLOHMANN_JSON_SERIALIZE_ENUM(__VA_ARGS__)
/* NOLINTEND */
// clang-format off
// vim: set foldmethod=syntax foldminlines=10 textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :

View File

@ -8,9 +8,7 @@ add_executable(test-runner
Observable.test.cpp
sdl/SdlEventSource.test.cpp
SDL_Memory.test.cpp
JsonConfigFile.test.cpp
paths.test.cpp
)
set_target_properties(test-runner

View File

@ -1,212 +0,0 @@
/* JsonConfigFile.test.cpp
* }
* Copyright © 2024 Saul D. Beniquez
* License: Mozilla Public License v. 2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v.2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "JsonConfigFile.hpp"
#include "IOCore/Exception.hpp"
#include "sys/debuginfo.hpp"
#include "util/debug.hpp"
#include "test-utils/common.hpp"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <nlohmann/json.hpp>
using namespace elemental;
using namespace elemental::configuration;
namespace fs = std::filesystem;
constexpr c::const_string kINPUT_FILE_PATH = "/tmp/test_config.json";
constexpr c::const_string kBADFILE_PATH = "/tmp/test_bad_config.json";
constexpr c::const_string kNON_EXISTENT_PATH = "/hades/tmp/dne/file.json";
BEGIN_TEST_SUITE("elemental::JsonConfigFile")
{
namespace { // Test fixtures
struct SampleFileGenerator {
SampleFileGenerator()
{
if (!fs::exists(kINPUT_FILE_PATH)) {
std::ofstream new_file(
kINPUT_FILE_PATH,
std::ios::out | std::ios::trunc
);
new_file
<< R"({"key1": "value1", "key2": "value2"})"
<< std::endl;
new_file.close();
}
}
~SampleFileGenerator()
{
try {
if (fs::exists(kINPUT_FILE_PATH)) {
fs::remove(kINPUT_FILE_PATH);
}
} catch (std::exception& e) {
DBG_PRINT(e.what());
}
}
};
using TestFixture = SampleFileGenerator;
} // anonymous namespace
TEST("elemental::nlohmann::json is serializablable like "
"std::map<std::string,std::string>")
{
IOCore::Dictionary<std::string> test_data;
nlohmann::json jsonified;
test_data["one"] = "1";
test_data["resolution"] = "1280x720";
test_data["Hello"] = "world";
jsonified = test_data;
REQUIRE(test_data["one"] == jsonified["one"].get<std::string>());
REQUIRE(
test_data["resolution"] ==
jsonified["resolution"].get<std::string>()
);
REQUIRE(
test_data["Hello"] == jsonified["Hello"].get<std::string>()
);
}
FIXTURE_TEST("JsonConfigFile construction")
{
SECTION("JsonConfigFile w/ valid path")
{
auto config = JsonConfigFile(kINPUT_FILE_PATH);
auto& config_data = config.jsonDataRef();
REQUIRE(config_data.empty());
}
SECTION("JsonConfigFile w/ invalid path throws exception")
{
REQUIRE_THROWS_AS(
JsonConfigFile(kNON_EXISTENT_PATH),
IOCore::UnreachablePathException
);
}
}
FIXTURE_TEST("JsonConfigFile::Read")
{
auto config_file = JsonConfigFile(kINPUT_FILE_PATH);
auto& json_data = config_file.jsonDataRef();
SECTION("JsonConfigFile::Read w/ valid file")
{
config_file.read();
REQUIRE_FALSE(json_data.empty());
REQUIRE(json_data.size() == 2);
REQUIRE(json_data["key1"] == "value1");
REQUIRE(json_data["key2"] == "value2");
}
SECTION("JsonConfigFile::Read w/ bad file throws exception")
{
if (!fs::exists(kBADFILE_PATH)) {
std::ofstream fileout(kBADFILE_PATH);
fileout << R"({"Hello World"})" << std::endl;
fileout.close();
}
REQUIRE_THROWS_AS(
[&]() {
auto bad_config =
JsonConfigFile(kBADFILE_PATH);
bad_config.read();
}(),
IOCore::Exception
);
}
if (fs::exists(kBADFILE_PATH)) {
fs::remove(kBADFILE_PATH);
}
}
TEST("JsonConfigFile::Write()")
{
SECTION("Create File and Read It back In")
{
auto config_file = JsonConfigFile(kINPUT_FILE_PATH);
auto& test_data = config_file.jsonDataRef();
test_data["one"] = "1";
test_data["resolution"] = "1280x720";
test_data["Hello"] = "world";
config_file.write();
std::ifstream resulting_file(kINPUT_FILE_PATH);
nlohmann::json jobject;
resulting_file >> jobject;
auto written_data =
jobject.get<IOCore::Dictionary<std::string>>();
REQUIRE(
written_data["one"] ==
test_data["one"].get<std::string>()
);
REQUIRE(
written_data["resolution"] ==
test_data["resolution"].get<std::string>()
);
REQUIRE(
written_data["Hello"] ==
test_data["Hello"].get<std::string>()
);
}
fs::remove(kINPUT_FILE_PATH);
}
FIXTURE_TEST(
"JsonConfigFile::Get<T>() basically wraps nlohmann::json::get<T>()"
)
{
auto config_file = JsonConfigFile(kINPUT_FILE_PATH);
auto& json_data = config_file.jsonDataRef();
config_file.read();
auto obtained_data =
config_file.get<IOCore::Dictionary<std::string>>();
REQUIRE(obtained_data["key1"] == "value1");
REQUIRE(obtained_data["key2"] == "value2");
}
FIXTURE_TEST(
"JsonConfigFile::Set() basically wraps nlohmann::json::operator=()"
)
{
auto config_file = JsonConfigFile(kINPUT_FILE_PATH);
IOCore::Dictionary<std::string> test_data;
test_data["one"] = "1";
test_data["resolution"] = "1280x720";
test_data["Hello"] = "world";
config_file.set(test_data);
auto& json_data = config_file.jsonDataRef();
REQUIRE(json_data["one"] == test_data["one"]);
REQUIRE(json_data["resolution"] == test_data["resolution"]);
REQUIRE(json_data["Hello"] == test_data["Hello"]);
}
}
// clang-format off
// vim: set foldmethod=syntax textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :