From 4268dc60474ef8efe6a9ea826e504d4fb4460f44 Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Sat, 8 Aug 2026 17:50:18 +0800 Subject: [PATCH] refactor(block): migrate block data to JSON and resource locations Replace TOML block definitions with JSON files loaded from asset locations. Block names are now ResourceLocation keys, IDs are resolved from registry.json, and reusable JSON parsing helpers were added. --- include/Cubed/gameplay/block.hpp | 13 +- include/Cubed/gameplay/block_manager.hpp | 8 +- include/Cubed/tools/json_utils.hpp | 70 ++++++++++- include/Cubed/tools/resource_location.hpp | 23 +++- src/gameplay/block_manager.cpp | 145 +++++++++++++--------- src/gameplay/server_world.cpp | 12 +- src/tools/json_utils.cpp | 30 ++++- 7 files changed, 219 insertions(+), 82 deletions(-) diff --git a/include/Cubed/gameplay/block.hpp b/include/Cubed/gameplay/block.hpp index 4411097..18c1053 100644 --- a/include/Cubed/gameplay/block.hpp +++ b/include/Cubed/gameplay/block.hpp @@ -1,5 +1,7 @@ #pragma once +#include "Cubed/tools/resource_location.hpp" + #include #include #include @@ -41,7 +43,7 @@ struct LookBlock { }; struct BlockData { - std::string name; + ResourceLocation name{}; BlockType id = 0; bool is_liquid = false; @@ -55,14 +57,7 @@ struct BlockData { bool is_blend = false; bool is_transitional = false; float roughness = 1.0f; - BlockData(std::string_view b_name, BlockType b_id, bool liquid, - bool passable, bool cross_plane, bool transparent, bool gas, - bool discard, bool blend, bool transitional, float r) - : name(b_name), id(b_id), is_liquid(liquid), is_gas(gas), - is_passable(passable), is_cross_plane(cross_plane), - is_transparent(transparent), is_discard(discard), is_blend(blend), - is_transitional(transitional), roughness(r) {} - BlockData() { name = ""; } + BlockData() = default; }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/block_manager.hpp b/include/Cubed/gameplay/block_manager.hpp index 4fb8e28..c29f6a4 100644 --- a/include/Cubed/gameplay/block_manager.hpp +++ b/include/Cubed/gameplay/block_manager.hpp @@ -10,7 +10,7 @@ public: static void init(); static unsigned sums(); static unsigned cross_plane_sum(); - static const std::string& name_form_id(BlockType id); + static const ResourceLocation& name_form_id(BlockType id); static bool is_gas(BlockType id); static bool is_liquid(BlockType id); @@ -24,13 +24,15 @@ public: static float roughness(BlockType id); static BlockType cross_plane_index(BlockType id); - static BlockType id_from_name(const std::string& name); + static BlockType id_from_name(std::string_view name); + static BlockType id_from_name(const ResourceLocation& name); private: using BlockMap = tbb::concurrent_hash_map; using acc = BlockMap::accessor; using cacc = BlockMap::const_accessor; - using IDMap = tbb::concurrent_hash_map; + using IDMap = tbb::concurrent_hash_map; using CrossPlaneMap = tbb::concurrent_hash_map; static inline const BlockData EMPTY; diff --git a/include/Cubed/tools/json_utils.hpp b/include/Cubed/tools/json_utils.hpp index 043742c..41c152d 100644 --- a/include/Cubed/tools/json_utils.hpp +++ b/include/Cubed/tools/json_utils.hpp @@ -1,8 +1,72 @@ +#pragma once +#include "Cubed/tools/log.hpp" +#include #include #include +#include #include -namespace Tools { -std::unordered_map +namespace Cubed::Tools { + +namespace detail { +template inline constexpr bool always_false_v = false; // NOLINT +} // namespace detail + +inline std::unordered_map doc_to_map(const rapidjson::Document& doc); -} \ No newline at end of file + +bool parse_json(rapidjson::Document& doc, const std::filesystem::path& path); + +template +bool get_json_value(const rapidjson::Value& value, const char* key, T& out) { + using ValueType = std::decay_t; + if (!value.HasMember(key)) { + Logger::error("json don't has key {}", key); + return false; + } + const auto& v = value[key]; + if constexpr (std::is_same_v) { + if (!v.IsBool()) { + Logger::error("json key {} value is not bool", key); + return false; + } + out = v.GetBool(); + } else if constexpr (std::is_same_v) { + if (!v.IsString()) { + Logger::error("json key {} value is not string", key); + return false; + } + out = v.GetString(); + } else if constexpr (std::is_same_v) { + if (!v.IsNumber()) { + Logger::error("json key {} value is not number", key); + return false; + } + out = v.GetFloat(); + } else if constexpr (std::is_same_v) { + if (!v.IsNumber()) { + Logger::error("json key {} value is not number", key); + return false; + } + out = v.GetDouble(); + } else if constexpr (std::is_integral_v && + std::is_unsigned_v) { + if (!v.IsUint64()) { + Logger::error("json key {} value is not uint", key); + return false; + } + out = static_cast(v.GetUint64()); + } else if constexpr (std::is_integral_v && + std::is_signed_v) { + if (!v.IsInt64()) { + Logger::error("json key {} value is not int", key); + return false; + } + out = static_cast(v.GetInt64()); + } else { + static_assert(detail::always_false_v, + "get_json_value: unsupported type"); + } + return true; +} +} // namespace Cubed::Tools \ No newline at end of file diff --git a/include/Cubed/tools/resource_location.hpp b/include/Cubed/tools/resource_location.hpp index 2bec44c..c1a13b4 100644 --- a/include/Cubed/tools/resource_location.hpp +++ b/include/Cubed/tools/resource_location.hpp @@ -28,9 +28,30 @@ struct ResourceLocation { std::string(str.substr(it + 1))}; } + static std::string get_assets_path(std::string_view ns) { + if (ns == "cubed") { + return ASSETS_PATH "cubed"; + } + return std::string(ns); + } + bool operator==(const ResourceLocation& o) const { return (ns == o.ns) && (path == o.path); } - std::size_t hash() const { return std::hash()(to_string()); } + struct Hash { + std::size_t hash(const ResourceLocation& p) const { + return ResourceLocation::hash(p); + } + bool equal(const ResourceLocation& a, const ResourceLocation& b) const { + return a == b; + } + }; + + static std::size_t hash(const ResourceLocation& p) { + return std::hash()(p.to_string()); + } + + std::string assets_path() const { return get_assets_path(ns); } }; + } // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/block_manager.cpp b/src/gameplay/block_manager.cpp index 03c6527..71b1c32 100644 --- a/src/gameplay/block_manager.cpp +++ b/src/gameplay/block_manager.cpp @@ -1,20 +1,16 @@ #include "Cubed/gameplay/block_manager.hpp" #include "Cubed/tools/cubed_assert.hpp" +#include "Cubed/tools/json_utils.hpp" #include "Cubed/tools/log.hpp" -#include "Cubed/tools/toml.utils.hpp" +#include "Cubed/tools/resource_location.hpp" #include #include - +#include namespace fs = std::filesystem; - +using namespace rapidjson; using namespace std::string_literals; -using namespace Cubed::TOML; -namespace { -std::string block_data_dir = ASSETS_PATH + "data/block"s; - -} // namespace namespace Cubed { @@ -27,7 +23,7 @@ unsigned BlockManager::cross_plane_sum() { return m_cross_plane_map.size(); } -const std::string& BlockManager::name_form_id(BlockType id) { +const ResourceLocation& BlockManager::name_form_id(BlockType id) { cacc c; if (!m_datas.find(c, id)) { ASSERT(false); @@ -113,62 +109,91 @@ float BlockManager::roughness(BlockType id) { } void BlockManager::init() { - fs::path data_path{block_data_dir}; + fs::path root_path{ + ResourceLocation::get_assets_path(ResourceLocation::DEFAULT_NAMESPACE)}; + fs::path block_path = root_path / "blocks"; + + fs::create_directories(block_path); + + fs::path register_path = root_path / "registry.json"; + + Document registry; + if (!Tools::parse_json(registry, register_path)) { + Logger::error("Can't parse registry.json"); + ASSERT(false); + return; + } + + if (!registry.HasMember("blocks")) { + throw std::runtime_error("registry.json don't has blocks key"); + } + + auto& blocks_registry = registry["blocks"]; + std::vector> types; - for (auto entry : fs::recursive_directory_iterator(data_path)) { + + for (auto entry : fs::recursive_directory_iterator(block_path)) { if (!entry.is_regular_file()) { continue; } - if (entry.path().filename() == "template.toml") { + if (entry.path().extension() != ".json") { continue; } - toml::table block; - try { - block = toml::parse_file(entry.path().string()); - } catch (const toml::parse_error& err) { - Logger::error("Load Block Data {} Fail, Parser Error {}", - entry.path().string(), err.what()); - ASSERT(false); + + Document doc; + if (!Tools::parse_json(doc, entry.path())) { + continue; } - auto id = block["id"].value(); - if (id == std::nullopt) { - Logger::error("Very Serious Error, Block Id Not Find !!!, Please " - "Check The Block Data Integrity"); - std::abort(); - } - auto name = block["name"].value(); - if (name == std::nullopt) { + + BlockData data; + + std::string path; + if (!Tools::get_json_value(doc, "name", path)) { Logger::error("Very Serious Error, Block Name Not Find !!!, Please " "Check The Block Data Integrity"); - std::abort(); + continue; } - auto is_liquid = safe_get_value(block, "is_liquid", false); - auto is_passable = safe_get_value(block, "is_passable", false); - auto is_cross_plane = safe_get_value(block, "is_cross_plane", false); - auto is_transparent = safe_get_value(block, "is_transparent", false); - auto is_gas = safe_get_value(block, "is_gas", false); - auto is_discard = safe_get_value(block, "is_discard", false); - auto is_blend = safe_get_value(block, "is_blend", false); - auto is_transitional = safe_get_value(block, "is_transitional", false); - auto roughness = safe_get_value(block, "roughness", 1.0); - - BlockData data{*name, - static_cast(*id), - *is_liquid, - *is_passable, - *is_cross_plane, - *is_transparent, - *is_gas, - *is_discard, - *is_blend, - *is_transitional, - static_cast(*roughness)}; - - if (!m_datas.emplace(static_cast(*id), std::move(data))) { - Logger::error("Block Type {} already exist!", *id); + data.name.path = path; + data.name.ns = ResourceLocation::DEFAULT_NAMESPACE; + if (!Tools::get_json_value(blocks_registry, path.c_str(), data.id)) { + Logger::error("Very Serious Error, Block Id Not Find !!!, Please " + "Check The Block Data Integrity"); + continue; } - m_id_map.emplace(*name, static_cast(*id)); - types.emplace_back(*is_cross_plane, static_cast(*id)); + + if (!doc.HasMember("properties")) { + Logger::error("Block {} doesn't have properties", + data.name.to_string()); + continue; + } + if (!doc["properties"].IsObject()) { + Logger::error("Block {} properties are not json object", + data.name.to_string()); + continue; + } + auto& properties = doc["properties"]; + + Tools::get_json_value(properties, "is_liquid", data.is_liquid); + Tools::get_json_value(properties, "is_passable", data.is_passable); + Tools::get_json_value(properties, "is_cross_plane", + data.is_cross_plane); + Tools::get_json_value(properties, "is_transparent", + data.is_transparent); + Tools::get_json_value(properties, "is_gas", data.is_gas); + Tools::get_json_value(properties, "is_discard", data.is_discard); + Tools::get_json_value(properties, "is_blend", data.is_blend); + Tools::get_json_value(properties, "is_transitional", + data.is_transitional); + Tools::get_json_value(properties, "roughness", data.roughness); + + const auto LOCATION = data.name; + const auto IS_CROSS_PLANE = data.is_cross_plane; + const auto ID = data.id; + if (!m_datas.emplace(data.id, std::move(data))) { + Logger::error("Block {} already exist!", LOCATION.to_string()); + } + m_id_map.emplace(LOCATION, ID); + types.emplace_back(IS_CROSS_PLANE, ID); } std::sort(types.begin(), types.end(), [](const auto& a, const auto& b) { return a.second < b.second; }); @@ -188,12 +213,20 @@ BlockType BlockManager::cross_plane_index(BlockType id) { return c->second; } -BlockType BlockManager::id_from_name(const std::string& name) { +BlockType BlockManager::id_from_name(std::string_view name) { + auto s = ResourceLocation::parse(name); + if (s) { + return id_from_name(*s); + } + return 0; +} + +BlockType BlockManager::id_from_name(const ResourceLocation& name) { IDMap::const_accessor c; if (m_id_map.find(c, name)) { return c->second; } - Logger::error("BlockManager: Can't fin Block {}", name); + Logger::error("BlockManager: Can't fin Block {}", name.to_string()); ASSERT(false); return 0; } diff --git a/src/gameplay/server_world.cpp b/src/gameplay/server_world.cpp index 5fae431..b9b078e 100644 --- a/src/gameplay/server_world.cpp +++ b/src/gameplay/server_world.cpp @@ -4,6 +4,7 @@ #include "Cubed/gameplay/packet.hpp" #include "Cubed/gameplay/session.hpp" #include "Cubed/tools/cubed_assert.hpp" +#include "Cubed/tools/json_utils.hpp" #include "Cubed/tools/log.hpp" #include "Cubed/tools/math_tools.hpp" #include "Cubed/tools/net_utils.hpp" @@ -12,7 +13,6 @@ #include #include -#include #include using namespace std::chrono; using namespace std::chrono_literals; @@ -210,15 +210,13 @@ void ServerWorld::init_world(RunMode mode) { Logger::info("sensitive filter {}", m_enable_filter.load()); m_voice_chat = m_config.get("voice_chat", true); Logger::info("voice chat {}", m_voice_chat.load()); + try { fs::path path = std::format("{}SensitiveLexicon.json", ASSETS_PATH); - std::ifstream s{path}; - if (!s.is_open()) { - throw std::runtime_error("can't open SensitiveLexicon.json"); - } - IStreamWrapper isw(s); Document doc; - doc.ParseStream(isw); + if (!Tools::parse_json(doc, path)) { + throw std::runtime_error("Can't parse SensitiveLexicon.json"); + } m_filter.load(doc); } catch (const std::exception& e) { Logger::error("Load SensitiveLexicon.json Fail"); diff --git a/src/tools/json_utils.cpp b/src/tools/json_utils.cpp index 69b48d3..0814aef 100644 --- a/src/tools/json_utils.cpp +++ b/src/tools/json_utils.cpp @@ -1,15 +1,20 @@ #include "Cubed/tools/json_utils.hpp" +#include "Cubed/tools/log.hpp" + +#include #include +#include #include #include #include #include -namespace Tools { +namespace Cubed::Tools { using rapidjson::Document; +using rapidjson::IStreamWrapper; using rapidjson::Value; - +namespace fs = std::filesystem; namespace { std::string serialize_value(const Value& v, rapidjson::StringBuffer& buf) { rapidjson::Writer w(buf); @@ -51,4 +56,23 @@ std::unordered_map doc_to_map(const Document& doc) { } return m; } -} // namespace Tools \ No newline at end of file + +bool parse_json(rapidjson::Document& doc, const std::filesystem::path& path) { + + std::ifstream file{path}; + if (!file.is_open()) { + Logger::error("Can't parse json {}", path.string()); + return false; + } + IStreamWrapper isw{file}; + doc.ParseStream(isw); + if (doc.HasParseError()) { + auto code = doc.GetParseError(); + Logger::error("Parse {} failed, error code {}", path.string(), + static_cast(code)); + return false; + } + return true; +} + +} // namespace Cubed::Tools \ No newline at end of file