diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml index 5952f8e..9d5c51e 100644 --- a/.github/workflows/analyze.yml +++ b/.github/workflows/analyze.yml @@ -11,6 +11,7 @@ on: - '**/CMakeLists.txt' - '**/.clang-tidy' - '**/.clang-format' + workflow_dispatch: env: home: /home/runner diff --git a/Engine/Scene/src/Scene/Assets/AssetResource_AssimpLoad.cpp b/Engine/Scene/src/Scene/Assets/AssetResource_AssimpLoad.cpp index 4538829..b71c879 100644 --- a/Engine/Scene/src/Scene/Assets/AssetResource_AssimpLoad.cpp +++ b/Engine/Scene/src/Scene/Assets/AssetResource_AssimpLoad.cpp @@ -236,69 +236,88 @@ void loadMaterials(AssetResource &assetResource, const aiScene *scene) { } } -std::shared_ptr loadMetadataEntry(const aiMetadataEntry &entry) { +void loadMetadataEntry(const aiMetadataEntry &entry, Json::Value &out) { switch (entry.mType) { case AI_BOOL: { const bool data = *static_cast(entry.mData); - return Json::boolean(data); + out.value = data; + return; } case AI_INT32: { const double data = *static_cast(entry.mData); - return Json::number(data); + out.value = data; + return; } case AI_UINT64: { - const double data = *static_cast(entry.mData); - return Json::number(data); + const uint64_t data = *static_cast(entry.mData); + out.value = static_cast(data); + return; } case AI_FLOAT: { const double data = *static_cast(entry.mData); - return Json::number(data); + out.value = data; + return; } case AI_DOUBLE: { const double data = *static_cast(entry.mData); - return Json::number(data); + out.value = data; + return; + } + case AI_AISTRING: + { + const aiString *data = static_cast(entry.mData); + out.value = data->C_Str(); + return; } case AI_AIVECTOR3D: { + out.value = Json::Array(); + auto &array(std::get(out.value)); const aiVector3D *data = static_cast(entry.mData); - Json::Array array_data; - array_data.push_back(Json::number(data->x)); - array_data.push_back(Json::number(data->y)); - array_data.push_back(Json::number(data->z)); - return Json::array(array_data); + array.emplace_back(data->x); + array.emplace_back(data->y); + array.emplace_back(data->z); + return; } case AI_AIMETADATA: { + out.value = Json::Object(); + auto &object(std::get(out.value)); const aiMetadata *data = static_cast(entry.mData); - Json::Object object_data; for (unsigned int i = 0; i < data->mNumProperties; ++i) { const aiString &key(data->mKeys[i]); const aiMetadataEntry &value(data->mValues[i]); - object_data[key.C_Str()] = loadMetadataEntry(value); + assert(key.C_Str() != nullptr); + loadMetadataEntry(value, object[key.C_Str()]); } - return Json::object(object_data); + return; } case AI_INT64: { - const double data = *static_cast(entry.mData); - return Json::number(data); + const int64_t data = *static_cast(entry.mData); + out.value = static_cast(data); + return; } case AI_UINT32: { const double data = *static_cast(entry.mData); - return Json::number(data); + out.value = data; + return; + } + default: + { + out = Json::Value(); + return; } - default: break; } - return Json::null(); } -void loadMetadata(const aiMetadata *metadata, Json::Object &dst) { +void loadMetadata(const aiMetadata *metadata, Json::Object &out) { if (metadata == nullptr) return; @@ -306,7 +325,8 @@ void loadMetadata(const aiMetadata *metadata, Json::Object &dst) { const aiString &key(metadata->mKeys[i]); const aiMetadataEntry &value(metadata->mValues[i]); - dst[key.C_Str()] = loadMetadataEntry(value); + assert(key.C_Str() != nullptr); + loadMetadataEntry(value, out[key.C_Str()]); } } diff --git a/Engine/Utils/include/Utils/Json.hpp b/Engine/Utils/include/Utils/Json.hpp index 6271696..1deb0df 100644 --- a/Engine/Utils/include/Utils/Json.hpp +++ b/Engine/Utils/include/Utils/Json.hpp @@ -13,19 +13,20 @@ namespace Stone::Json { struct Value; -using Object = std::unordered_map>; -using Array = std::vector>; +using Object = std::unordered_map; +using Array = std::vector; struct Value { std::variant value; - explicit Value(Object obj); - explicit Value(Array arr); - explicit Value(std::string str); - explicit Value(double num); - explicit Value(bool b); - explicit Value(std::nullptr_t n = nullptr); + Value() : value(nullptr) { + } + + template , T>>> + Value(T &&val) : value(std::forward(val)) { + } template bool is() const { @@ -46,17 +47,17 @@ struct Value { return std::get(value); } - static std::shared_ptr parseString(const std::string &input); - static std::shared_ptr parseFile(const std::string &path); + static void parseString(const std::string &input, Value &out); + static void parseFile(const std::string &path, Value &out); std::string serialize() const; }; -std::shared_ptr object(const Object &obj = {}); -std::shared_ptr array(const Array &arr = {}); -std::shared_ptr string(const std::string &str = ""); -std::shared_ptr number(double num = 0.0); -std::shared_ptr boolean(bool b = false); -std::shared_ptr null(); +Value object(const Object &obj = {}); +Value array(const Array &arr = {}); +Value string(const std::string &str = ""); +Value number(double num = 0.0); +Value boolean(bool b = false); +Value null(); enum class TokenType { @@ -74,6 +75,8 @@ enum class TokenType { EndOfFile }; +std::string to_string(TokenType type); + struct Token { TokenType type; std::string value; @@ -98,16 +101,16 @@ class Parser { public: explicit Parser(const std::string &input); - std::shared_ptr parse(); + void parse(Value &out); private: Lexer _lexer; Token _currentToken; - std::shared_ptr _parseValue(); - std::shared_ptr _parseObject(); - std::shared_ptr _parseArray(); - std::shared_ptr _parsePrimitive(); + void _parseValue(Value &out); + void _parseObject(Value &out); + void _parseArray(Value &out); + void _parsePrimitive(Value &out); void _consume(TokenType expected); }; diff --git a/Engine/Utils/src/Utils/Json.cpp b/Engine/Utils/src/Utils/Json.cpp index 5cf5874..f2e53e6 100644 --- a/Engine/Utils/src/Utils/Json.cpp +++ b/Engine/Utils/src/Utils/Json.cpp @@ -5,62 +5,68 @@ #include "Utils/FileSystem.hpp" #include "Utils/StringExt.hpp" +#include #include namespace Stone::Json { -Value::Value(Object obj) : value(std::move(obj)) { +void Value::parseString(const std::string &input, Value &out) { + Parser parser(input); + parser.parse(out); } -Value::Value(Array arr) : value(std::move(arr)) { +void Value::parseFile(const std::string &path, Value &out) { + parseString(Utils::readTextFile(path), out); } -Value::Value(std::string str) : value(std::move(str)) { +std::string Value::serialize() const { + Serializer serializer; + return serializer.serialize(*this); } -Value::Value(double num) : value(num) { +Value object(const Object &obj) { + return {obj}; } -Value::Value(bool b) : value(b) { +Value array(const Array &arr) { + return {arr}; } -Value::Value(std::nullptr_t n) : value(n) { +Value string(const std::string &str) { + return {str}; } -std::shared_ptr Value::parseString(const std::string &input) { - Parser parser(input); - return parser.parse(); +Value number(double num) { + return {num}; } -std::shared_ptr Value::parseFile(const std::string &path) { - return parseString(Utils::readTextFile(path)); +Value boolean(bool b) { + return {b}; } -std::string Value::serialize() const { - Serializer serializer; - return serializer.serialize(*this); +Value null() { + return {}; } -std::shared_ptr object(const Object &obj) { - return std::make_shared(obj); -} -std::shared_ptr array(const Array &arr) { - return std::make_shared(arr); -} -std::shared_ptr string(const std::string &str) { - return std::make_shared(str); -} -std::shared_ptr number(double num) { - return std::make_shared(num); -} -std::shared_ptr boolean(bool b) { - return std::make_shared(b); -} -std::shared_ptr null() { - return std::make_shared(); -} +std::string to_string(TokenType type) { + switch (type) { + case TokenType::LeftBrace: return "LeftBrace '{'"; + case TokenType::RightBrace: return "RightBrace '}'"; + case TokenType::LeftBracket: return "LeftBracket '['"; + case TokenType::RightBracket: return "RightBracket ']'"; + case TokenType::Comma: return "Comma ','"; + case TokenType::Colon: return "Colon ':'"; + case TokenType::String: return "String"; + case TokenType::Number: return "Number"; + case TokenType::True: return "True"; + case TokenType::False: return "False"; + case TokenType::Null: return "Null"; + case TokenType::EndOfFile: return "EndOfFile"; + default: return "Unknown"; + } +} Lexer::Lexer(const std::string &input) : _input(input) { } @@ -136,31 +142,34 @@ Parser::Parser(const std::string &input) : _lexer(input) { _currentToken = _lexer.nextToken(); } -std::shared_ptr Parser::parse() { - return _parseValue(); +void Parser::parse(Value &out) { + return _parseValue(out); } -std::shared_ptr Parser::_parseValue() { +void Parser::_parseValue(Value &out) { switch (_currentToken.type) { - case TokenType::LeftBrace: return _parseObject(); - case TokenType::LeftBracket: return _parseArray(); + case TokenType::LeftBrace: return _parseObject(out); + case TokenType::LeftBracket: return _parseArray(out); case TokenType::String: case TokenType::Number: case TokenType::True: case TokenType::False: - case TokenType::Null: return _parsePrimitive(); + case TokenType::Null: return _parsePrimitive(out); default: throw std::runtime_error("Unexpected token in input"); } } -std::shared_ptr Parser::_parseObject() { - Object object; +void Parser::_parseObject(Value &out) { + out.value = Object(); + auto &object(std::get(out.value)); _consume(TokenType::LeftBrace); while (_currentToken.type != TokenType::RightBrace) { - std::string key = _currentToken.value; + const std::string key = _currentToken.value; // Store the key before consuming the tokens _consume(TokenType::String); _consume(TokenType::Colon); - object[key] = _parseValue(); + Value value; + _parseValue(value); + object[key] = std::move(value); if (_currentToken.type == TokenType::Comma) { _consume(TokenType::Comma); } else { @@ -168,14 +177,14 @@ std::shared_ptr Parser::_parseObject() { } } _consume(TokenType::RightBrace); - return std::make_shared(object); } -std::shared_ptr Parser::_parseArray() { - Array array; +void Parser::_parseArray(Value &out) { + out.value = Array(); + auto &array(std::get(out.value)); _consume(TokenType::LeftBracket); while (_currentToken.type != TokenType::RightBracket) { - array.push_back(_parseValue()); + _parseValue(array.emplace_back()); if (_currentToken.type == TokenType::Comma) { _consume(TokenType::Comma); } else { @@ -183,26 +192,23 @@ std::shared_ptr Parser::_parseArray() { } } _consume(TokenType::RightBracket); - return std::make_shared(array); } -std::shared_ptr Parser::_parsePrimitive() { - std::shared_ptr value; +void Parser::_parsePrimitive(Value &out) { switch (_currentToken.type) { - case TokenType::String: value = std::make_shared(_currentToken.value); break; - case TokenType::Number: value = std::make_shared(std::stod(_currentToken.value)); break; - case TokenType::True: value = std::make_shared(true); break; - case TokenType::False: value = std::make_shared(false); break; - case TokenType::Null: value = std::make_shared(); break; + case TokenType::String: out.value = _currentToken.value; break; + case TokenType::Number: out.value = std::stod(_currentToken.value); break; + case TokenType::True: out.value = true; break; + case TokenType::False: out.value = false; break; + case TokenType::Null: out.value = std::nullptr_t(); break; default: throw std::runtime_error("Unexpected token in input"); } _currentToken = _lexer.nextToken(); - return value; } void Parser::_consume(TokenType expected) { if (_currentToken.type != expected) { - throw std::runtime_error("Unexpected token in input"); + throw std::runtime_error("Expected " + to_string(expected) + ", but got " + _currentToken.value); } _currentToken = _lexer.nextToken(); } @@ -220,7 +226,7 @@ void Serializer::operator()(const Object &object) { if (!first) _ss << ","; _ss << "\"" << pair.first << "\":"; - std::visit(*this, pair.second->value); + std::visit(*this, pair.second.value); first = false; } _ss << "}"; @@ -232,7 +238,7 @@ void Serializer::operator()(const Array &array) { for (const auto &item : array) { if (!first) _ss << ","; - std::visit(*this, item->value); + std::visit(*this, item.value); first = false; } _ss << "]"; diff --git a/Engine/Utils/test/test_Json.cpp b/Engine/Utils/test/test_Json.cpp index 7ddbe87..308480a 100644 --- a/Engine/Utils/test/test_Json.cpp +++ b/Engine/Utils/test/test_Json.cpp @@ -7,80 +7,94 @@ using namespace Stone; TEST(Json, ParseEmptyObject) { std::string jsonString = "{}"; - auto json = Json::Value::parseString(jsonString); + Json::Value json; + Json::Value::parseString(jsonString, json); - ASSERT_TRUE(json->is()); - ASSERT_TRUE(json->get().empty()); + ASSERT_TRUE(json.is()); + ASSERT_TRUE(json.get().empty()); } TEST(Json, ParseSimpleObject) { std::string jsonString = R"({"name": "John", "age": 30, "isStudent": false})"; - auto json = Json::Value::parseString(jsonString); + Json::Value json; + Json::Value::parseString(jsonString, json); - ASSERT_TRUE(json->is()); + ASSERT_TRUE(json.is()); - Json::Object obj = json->get(); - ASSERT_TRUE(obj["name"]->is()); - ASSERT_EQ(obj["name"]->get(), "John"); + Json::Object obj = json.get(); + ASSERT_TRUE(obj["name"].is()); + ASSERT_EQ(obj["name"].get(), "John"); - ASSERT_TRUE(obj["age"]->is()); - ASSERT_EQ(obj["age"]->get(), 30); + ASSERT_TRUE(obj["age"].is()); + ASSERT_EQ(obj["age"].get(), 30); - ASSERT_TRUE(obj["isStudent"]->is()); - ASSERT_EQ(obj["isStudent"]->get(), false); + ASSERT_TRUE(obj["isStudent"].is()); + ASSERT_EQ(obj["isStudent"].get(), false); } TEST(Json, ParseArray) { std::string jsonString = R"([1, "two", true, null])"; - auto json = Json::Value::parseString(jsonString); + Json::Value json; + Json::Value::parseString(jsonString, json); - ASSERT_TRUE(json->is()); + ASSERT_TRUE(json.is()); - Json::Array arr = json->get(); + Json::Array arr = json.get(); ASSERT_EQ(arr.size(), 4); - ASSERT_TRUE(arr[0]->is()); - ASSERT_EQ(arr[0]->get(), 1); + ASSERT_TRUE(arr[0].is()); + ASSERT_EQ(arr[0].get(), 1); - ASSERT_TRUE(arr[1]->is()); - ASSERT_EQ(arr[1]->get(), "two"); + ASSERT_TRUE(arr[1].is()); + ASSERT_EQ(arr[1].get(), "two"); - ASSERT_TRUE(arr[2]->is()); - ASSERT_EQ(arr[2]->get(), true); + ASSERT_TRUE(arr[2].is()); + ASSERT_EQ(arr[2].get(), true); - ASSERT_TRUE(arr[3]->isNull()); + ASSERT_TRUE(arr[3].isNull()); } TEST(Json, ParseNestedObject) { std::string jsonString = R"({"person": {"name": "John", "age": 30}})"; - auto json = Json::Value::parseString(jsonString); + Json::Value json; + Json::Value::parseString(jsonString, json); - ASSERT_TRUE(json->is()); + ASSERT_TRUE(json.is()); - Json::Object obj = json->get(); - ASSERT_TRUE(obj["person"]->is()); + Json::Object obj = json.get(); + ASSERT_TRUE(obj["person"].is()); - Json::Object personObj = obj["person"]->get(); - ASSERT_TRUE(personObj["name"]->is()); - ASSERT_EQ(personObj["name"]->get(), "John"); + Json::Object personObj = obj["person"].get(); + ASSERT_TRUE(personObj["name"].is()); + ASSERT_EQ(personObj["name"].get(), "John"); - ASSERT_TRUE(personObj["age"]->is()); - ASSERT_EQ(personObj["age"]->get(), 30); + ASSERT_TRUE(personObj["age"].is()); + ASSERT_EQ(personObj["age"].get(), 30); } TEST(Json, MalformedJsonThrowsException) { std::string jsonString = R"({"name": "John")"; // Missing closing brace - EXPECT_THROW({ auto json = Json::Value::parseString(jsonString); }, std::runtime_error); + EXPECT_THROW( + { + Json::Value json; + Json::Value::parseString(jsonString, json); + }, + std::runtime_error); } TEST(Json, MalformedJsonThrowsException2) { std::string jsonString = R"({"name": John})"; // No quotes around string - EXPECT_THROW({ auto json = Json::Value::parseString(jsonString); }, std::runtime_error); + EXPECT_THROW( + { + Json::Value json; + Json::Value::parseString(jsonString, json); + }, + std::runtime_error); } /* @@ -92,7 +106,7 @@ TEST(JsonSerializer, SerializeEmptyObject) { Json::Object obj; auto value = Json::object(obj); - std::string result = value->serialize(); + std::string result = value.serialize(); ASSERT_EQ(result, "{}"); } @@ -102,29 +116,30 @@ TEST(JsonSerializer, SerializeSimpleObject) { { Json::Object obj; - obj["name"] = Json::string("John"); - obj["age"] = Json::number(30.0); - obj["isStudent"] = Json::boolean(false); + obj["name"] = "John"; + obj["age"] = 30.0; + obj["isStudent"] = false; auto value = Json::object(obj); - result = value->serialize(); + result = value.serialize(); } - auto json = Json::Value::parseString(result); + Json::Value json; + Json::Value::parseString(result, json); - ASSERT_TRUE(json->is()); + ASSERT_TRUE(json.is()); - auto obj = json->get(); + auto obj = json.get(); - ASSERT_TRUE(obj["name"]->is()); - ASSERT_EQ(obj["name"]->get(), "John"); + ASSERT_TRUE(obj["name"].is()); + ASSERT_EQ(obj["name"].get(), "John"); - ASSERT_TRUE(obj["age"]->is()); - ASSERT_EQ(obj["age"]->get(), 30); + ASSERT_TRUE(obj["age"].is()); + ASSERT_EQ(obj["age"].get(), 30); - ASSERT_TRUE(obj["isStudent"]->is()); - ASSERT_EQ(obj["isStudent"]->get(), false); + ASSERT_TRUE(obj["isStudent"].is()); + ASSERT_EQ(obj["isStudent"].get(), false); } TEST(JsonSerializer, SerializeArray) { @@ -134,26 +149,27 @@ TEST(JsonSerializer, SerializeArray) { { auto value = Json::array({Json::number(1.0), Json::string("two"), Json::boolean(true), Json::null()}); - result = value->serialize(); + result = value.serialize(); } - auto json = Json::Value::parseString(result); + Json::Value json; + Json::Value::parseString(result, json); - ASSERT_TRUE(json->is()); + ASSERT_TRUE(json.is()); - auto obj = json->get(); + auto obj = json.get(); ASSERT_EQ(obj.size(), 4); - ASSERT_TRUE(obj[0]->is()); - ASSERT_EQ(obj[0]->get(), 1); + ASSERT_TRUE(obj[0].is()); + ASSERT_EQ(obj[0].get(), 1); - ASSERT_TRUE(obj[1]->is()); - ASSERT_EQ(obj[1]->get(), "two"); + ASSERT_TRUE(obj[1].is()); + ASSERT_EQ(obj[1].get(), "two"); - ASSERT_TRUE(obj[2]->is()); - ASSERT_EQ(obj[2]->get(), true); + ASSERT_TRUE(obj[2].is()); + ASSERT_EQ(obj[2].get(), true); - ASSERT_TRUE(obj[3]->isNull()); + ASSERT_TRUE(obj[3].isNull()); } TEST(JsonSerializer, SerializeNestedObject) { @@ -165,24 +181,25 @@ TEST(JsonSerializer, SerializeNestedObject) { {"person", Json::object({{"name", Json::string("John")}, {"age", Json::number(30.0)}})} }); - result = value->serialize(); + result = value.serialize(); } - auto json = Json::Value::parseString(result); + Json::Value json; + Json::Value::parseString(result, json); - ASSERT_TRUE(json->is()); + ASSERT_TRUE(json.is()); - auto obj = json->get(); + auto obj = json.get(); - ASSERT_TRUE(obj["person"]->is()); + ASSERT_TRUE(obj["person"].is()); - auto personObj = obj["person"]->get(); + auto personObj = obj["person"].get(); - ASSERT_TRUE(personObj["name"]->is()); - ASSERT_EQ(personObj["name"]->get(), "John"); + ASSERT_TRUE(personObj["name"].is()); + ASSERT_EQ(personObj["name"].get(), "John"); - ASSERT_TRUE(personObj["age"]->is()); - ASSERT_EQ(personObj["age"]->get(), 30); + ASSERT_TRUE(personObj["age"].is()); + ASSERT_EQ(personObj["age"].get(), 30); } TEST(JsonSerializer, SerializeComplexObject) { @@ -194,40 +211,41 @@ TEST(JsonSerializer, SerializeComplexObject) { addressObj["zip"] = Json::string("10001"); Json::Object obj; - obj["name"] = Json::string("John"); - obj["age"] = Json::number(30.0); - obj["isStudent"] = Json::boolean(false); - obj["scores"] = Json::array({Json::number(85.5), Json::number(92.0), Json::number(78.5)}); - obj["address"] = Json::object(addressObj); + obj["name"] = "John"; + obj["age"] = 30.0; + obj["isStudent"] = false; + obj["scores"] = Json::array({85.5, 92.0, 78.5}); + obj["address"] = addressObj; auto value = Json::object(obj); - result = value->serialize(); + result = value.serialize(); } - auto json = Json::Value::parseString(result); + Json::Value json; + Json::Value::parseString(result, json); - ASSERT_TRUE(json->is()); + ASSERT_TRUE(json.is()); - auto obj = json->get(); - ASSERT_TRUE(obj["name"]->is()); - ASSERT_EQ(obj["name"]->get(), "John"); + auto obj = json.get(); + ASSERT_TRUE(obj["name"].is()); + ASSERT_EQ(obj["name"].get(), "John"); - ASSERT_TRUE(obj["age"]->is()); - ASSERT_EQ(obj["age"]->get(), 30); + ASSERT_TRUE(obj["age"].is()); + ASSERT_EQ(obj["age"].get(), 30); - ASSERT_TRUE(obj["isStudent"]->is()); - ASSERT_EQ(obj["isStudent"]->get(), false); + ASSERT_TRUE(obj["isStudent"].is()); + ASSERT_EQ(obj["isStudent"].get(), false); - ASSERT_TRUE(obj["scores"]->is()); - auto scores = obj["scores"]->get(); + ASSERT_TRUE(obj["scores"].is()); + auto scores = obj["scores"].get(); ASSERT_EQ(scores.size(), 3); - ASSERT_EQ(scores[0]->get(), 85.5); - ASSERT_EQ(scores[1]->get(), 92); - ASSERT_EQ(scores[2]->get(), 78.5); - - ASSERT_TRUE(obj["address"]->is()); - auto address = obj["address"]->get(); - ASSERT_EQ(address["city"]->get(), "New York"); - ASSERT_EQ(address["zip"]->get(), "10001"); + ASSERT_EQ(scores[0].get(), 85.5); + ASSERT_EQ(scores[1].get(), 92); + ASSERT_EQ(scores[2].get(), 78.5); + + ASSERT_TRUE(obj["address"].is()); + auto address = obj["address"].get(); + ASSERT_EQ(address["city"].get(), "New York"); + ASSERT_EQ(address["zip"].get(), "10001"); } diff --git a/examples/scop/main.cpp b/examples/scop/main.cpp index c49b569..74b5797 100644 --- a/examples/scop/main.cpp +++ b/examples/scop/main.cpp @@ -114,6 +114,7 @@ int main(int argc, char **argv) { auto asset = assetsBundle->loadResource(argv[1]); auto node = asset->getRootNode(); window->getWorld()->addChild(node); + std::cout << Stone::Json::object(asset->getMetadatas()).serialize() << std::endl; node->writeHierarchy(std::cout); }