diff --git a/.gitignore b/.gitignore index f935cf5..95a02ba 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ Release/ # xmake 缓存 .xmake/ +.cache/ # clangd 编译数据库(生成产物) compile_commands.json diff --git a/include/data_storage.h b/include/data_storage.h index 48b7846..a1db9eb 100644 --- a/include/data_storage.h +++ b/include/data_storage.h @@ -16,8 +16,8 @@ namespace write{ bool id(); bool name(int id, const std::string& new_name); bool gender(int id, const std::string& new_gender); - bool old_point(int id, const long long& new_old_point); - bool point(int id, const long long& new_point); + bool old_score(int id, const long long& new_old_score); + bool score(int id, const long long& new_score); bool old_rank(int id, const int& new_old_rank); bool rank(int id, const int& new_rank); } diff --git a/old/main.cpp b/old/main.cpp index 1d06a16..dc4e98a 100644 --- a/old/main.cpp +++ b/old/main.cpp @@ -21,8 +21,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/src/data_storage.cpp b/src/data_storage.cpp new file mode 100644 index 0000000..be3906d --- /dev/null +++ b/src/data_storage.cpp @@ -0,0 +1,236 @@ +// 业务存储层:把 FileStore 引擎接到 data_storage.h 的字段级 API +// 班级语义:read::xxx(class) 激活班级(学生/规则/礼物各走独立表单例); +// write::xxx 作用于当前激活班级(无班级参数的签名按当前班级处理) +// 持久化语义:write 只改内存 + 置脏标记,由调用方在动作边界显式 save()。 +// 注意:include 必须放在 import 之前——MSVC 下消费 TU 在 import 后再 include 模块全局片段已含 +// 的 STL 头(/ 等)会触发 C2572 默认模板参数重复定义 +#include "data_storage.h" + +#include +#include +#include +#include +#include +#include + +import storage; + +namespace +{ + std::string path_for(const std::string &class_name, const char *stem) + { + if (class_name.empty() || class_name == "default") + return std::string(stem) + ".dat"; + return std::string(stem) + "_" + class_name + ".dat"; + } + + // 三张表各自的单例门面:学生 = FileInstance,规则/礼物 = 同构 Record 的不同类型 + using RuleStore = points::FileStore; + using GiftStore = points::FileStore; + + template + Store &ensure_store(Store * /*tag*/, const std::string &class_name, const char *stem) + { + auto path = path_for(class_name, stem); + try + { + auto &s = Store::get(); + if (s.path() != path) + s.switch_to(path); // 切班级:脏则自动落盘 + return s; + } catch (const std::logic_error &) + { // 未初始化 + Store::init(path); + return Store::get(); + } + } + + points::FileInstance &ensure_students(const std::string &class_name) + { + return ensure_store( + static_cast(nullptr), + class_name, + "students"); + } + + RuleStore &ensure_rules(const std::string &class_name) + { + return ensure_store(static_cast(nullptr), class_name, "rules"); + } + + GiftStore &ensure_gifts(const std::string &class_name) + { + return ensure_store(static_cast(nullptr), class_name, "gifts"); + } + + // UTF-8 安全截断:不超过 max_bytes,且不在多字节序列中间切断 + void copy_utf8_truncated(std::byte *dest, size_t max_bytes, const std::string &src) + { + size_t n = std::min(src.size(), max_bytes); + if (n < src.size()) + { // 需要截断:回退到完整字符边界 + while (n > 0 && (static_cast(src[n]) & 0xC0u) == 0x80u) + --n; + } + std::memcpy(dest, src.data(), n); + std::memset(dest + n, 0, max_bytes - n); + } + + std::string current_class = "default"; // read::xxx 激活,write::xxx 消费 +} // namespace + +namespace read +{ + void students(const std::string &class_name) + { + current_class = class_name; + ensure_students(class_name); // 加载即激活 + } + + void rules(const std::string &class_name) + { + current_class = class_name; + ensure_rules(class_name).load(); // 强制从盘重读 + } + + void gifts(const std::string &class_name) + { + current_class = class_name; + ensure_gifts(class_name).load(); + } + + std::map config(const std::string & /*class_name*/) + { + std::map kv; + std::ifstream fin("config.ini"); + std::string line; + while (std::getline(fin, line)) + { + auto pos = line.find('='); + if (pos == std::string::npos) + continue; + kv[line.substr(0, pos)] = line.substr(pos + 1); + } + return kv; + } +} // namespace read + +namespace write +{ + namespace student + { + // 签名歧义:header 只声明了 bool id(),按"新建一个空学生并返回是否成功"实现 + bool id() + { + ensure_students(current_class).add(); + return true; + } + + bool name(int id, const std::string &new_name) + { + auto &fi = ensure_students(current_class); + auto *r = fi.find(id); + if (!r) return false; + copy_utf8_truncated(r->name, sizeof(r->name), new_name); + fi.mark_dirty(); + return true; + } + + bool gender(int id, const std::string &new_gender) + { + auto &fi = ensure_students(current_class); + auto *r = fi.find(id); + if (!r) return false; + copy_utf8_truncated(r->gender, sizeof(r->gender), new_gender); + fi.mark_dirty(); + return true; + } + + bool old_score(int id, const long long &new_old_score) + { + auto &fi = ensure_students(current_class); + auto *r = fi.find(id); + if (!r) return false; + r->old_score = new_old_score; + fi.mark_dirty(); + return true; + } + + bool score(int id, const long long &new_score) + { + auto &fi = ensure_students(current_class); + auto *r = fi.find(id); + if (!r) return false; + r->score = new_score; + fi.mark_dirty(); + return true; + } + + bool old_rank(int id, const int &new_old_rank) + { + auto &fi = ensure_students(current_class); + auto *r = fi.find(id); + if (!r) return false; + r->old_rank = new_old_rank; + fi.mark_dirty(); + return true; + } + + bool rank(int id, const int &new_rank) + { + auto &fi = ensure_students(current_class); + auto *r = fi.find(id); + if (!r) return false; + r->rank = new_rank; + fi.mark_dirty(); + return true; + } + } // namespace student + + namespace rule + { + // rule_num 1..N 映射到槽位 rule_num-1(引擎 add() 分配,槽位即编号) + bool desc(int rule_num, const std::string &new_desc) + { + auto &s = ensure_rules(current_class); + auto *r = s.find(static_cast(rule_num - 1)); + if (!r) return false; + copy_utf8_truncated(r->desc, sizeof(r->desc), new_desc); + s.mark_dirty(); + return true; + } + + bool delta(int rule_num, const int &new_delta) + { + auto &s = ensure_rules(current_class); + auto *r = s.find(static_cast(rule_num - 1)); + if (!r) return false; + r->delta = new_delta; + s.mark_dirty(); + return true; + } + } // namespace rule + + namespace gift + { + bool desc(int gift_num, const std::string &new_desc) + { + auto &s = ensure_gifts(current_class); + auto *r = s.find(static_cast(gift_num - 1)); + if (!r) return false; + copy_utf8_truncated(r->desc, sizeof(r->desc), new_desc); + s.mark_dirty(); + return true; + } + + bool delta(int gift_num, const int &new_delta) + { + auto &s = ensure_gifts(current_class); + auto *r = s.find(static_cast(gift_num - 1)); + if (!r) return false; + r->delta = new_delta; + s.mark_dirty(); + return true; + } + } // namespace gift +} // namespace write diff --git a/src/exceptions.ixx b/src/exceptions.ixx new file mode 100644 index 0000000..03992c6 --- /dev/null +++ b/src/exceptions.ixx @@ -0,0 +1,15 @@ +// +// Created by jimmy on 2026/8/4. +// +module; +#include + +export module exceptions; + +export namespace points +{ + struct file_format_error : std::runtime_error + { + using std::runtime_error::runtime_error; + }; +} diff --git a/src/storage_management.cpp b/src/storage_management.cpp new file mode 100644 index 0000000..88fb2c3 --- /dev/null +++ b/src/storage_management.cpp @@ -0,0 +1,104 @@ +module; +#include +#include +#include +#include +#include +#include + +module storage; +import exceptions; + +namespace points +{ + // ---- Record(学生,161B)---- + void Record::put(std::string &buf, const Record &r) + { + buf.push_back(static_cast(r.flags)); + auto put = [&buf](auto v) + { + buf.append(reinterpret_cast(&v), sizeof(v)); + }; + put(r.id); + put(r.next); + buf.append(reinterpret_cast(r.name), 64); + buf.append(reinterpret_cast(r.gender), 64); + put(r.old_score); + put(r.score); + put(r.old_rank); + put(r.rank); + } + + void Record::get(std::string_view s, size_t slot, Record &r) + { + const size_t off = slot * get_record_field_size(); + if (s.size() < off + get_record_field_size()) + throw file_format_error("Record slot out of range"); + // 与 put 严格镜像:同序、同长度,跑指针逐个字段取 + const char *p = s.data() + off; + r.flags = static_cast(*p); + ++p; + memcpy(&r.id, p, sizeof(r.id)); + p += sizeof(r.id); + memcpy(&r.next, p, sizeof(r.next)); + p += sizeof(r.next); + memcpy(r.name, p, 64); + p += 64; + memcpy(r.gender, p, 64); + p += 64; + memcpy(&r.old_score, p, sizeof(r.old_score)); + p += sizeof(r.old_score); + memcpy(&r.score, p, sizeof(r.score)); + p += sizeof(r.score); + memcpy(&r.old_rank, p, sizeof(r.old_rank)); + p += sizeof(r.old_rank); + memcpy(&r.rank, p, sizeof(r.rank)); + } + + void Record::clear(Record &r) + { + std::memset(r.name, 0, sizeof(r.name)); + std::memset(r.gender, 0, sizeof(r.gender)); + r.old_score = 0; + r.score = 0; + r.old_rank = 0; + r.rank = 0; + } + + // ---- RuleRecord(规则/礼物,77B;GiftRecord 继承)---- + void RuleRecord::put(std::string &buf, const RuleRecord &r) + { + buf.push_back(static_cast(r.flags)); + auto put = [&buf](auto v) + { + buf.append(reinterpret_cast(&v), sizeof(v)); + }; + put(r.id); + put(r.next); + buf.append(reinterpret_cast(r.desc), 64); + put(r.delta); + } + + void RuleRecord::get(std::string_view s, size_t slot, RuleRecord &r) + { + const size_t off = slot * rule_disk_size(); + if (s.size() < off + rule_disk_size()) + throw file_format_error("Rule slot out of range"); + const char *p = s.data() + off; + r.flags = static_cast(*p); + ++p; + memcpy(&r.id, p, sizeof(r.id)); + p += sizeof(r.id); + memcpy(&r.next, p, sizeof(r.next)); + p += sizeof(r.next); + memcpy(r.desc, p, 64); + p += 64; + memcpy(&r.delta, p, sizeof(r.delta)); + } + + void RuleRecord::clear(RuleRecord &r) + { + std::memset(r.desc, 0, sizeof(r.desc)); + r.delta = 0; + } +} diff --git a/src/storage_management.ixx b/src/storage_management.ixx new file mode 100644 index 0000000..34fc463 --- /dev/null +++ b/src/storage_management.ixx @@ -0,0 +1,405 @@ +// Storage Management Module for Points +// copyright (c) 2026 SECTL, Licensed under GPL-3.0 +module; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__cpp_impl_reflection) +#include +#endif + +export module storage; +import exceptions; + +// 模块私有:CRC-32(IEEE 802.3,poly 0xEDB88320),覆盖整个记录区。 +// 定义在接口里(inline):FileStore 模板在消费 TU 实例化时也要调用它 +namespace +{ + constexpr std::array make_crc32_table() + { + std::array t{}; + for (uint32_t i = 0; i < 256; ++i) + { + uint32_t c = i; + for (int k = 0; k < 8; ++k) + c = (c & 1) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1); + t[i] = c; + } + return t; + } + + constexpr auto crc32_table = make_crc32_table(); +} + +inline uint32_t crc32(const void *data, size_t len) +{ + const auto *p = static_cast(data); + uint32_t c = 0xFFFFFFFFu; + for (size_t i = 0; i < len; ++i) + c = crc32_table[(c ^ p[i]) & 0xFF] ^ (c >> 8); + return c ^ 0xFFFFFFFFu; +} + +export namespace points +{ + constexpr char FILE_HEADER_MAGIC[4] = {'P', 'N', 'T', 'S'}; + constexpr uint32_t FILE_VERSION = 1; + + struct FileHeader + { + char magic[4]; + uint32_t version; + uint32_t header_size; + uint32_t record_size; + uint32_t capacity; + uint32_t alive_count; + uint32_t free_head; + uint32_t crc32; + }; + + // 学生记录。磁盘布局见 get_record_field_size;flags bit0 = alive, + // 空闲槽借 next 挂空闲链表。序列化/清零由各记录类型自行提供。 + struct Record + { + uint8_t flags; + uint32_t id; + uint32_t next; + std::byte name[64]; + std::byte gender[64]; + int64_t old_score; + int64_t score; + uint32_t old_rank; + uint32_t rank; + + static void put(std::string &buf, const Record &r); // 字段连续贴(161B,无 padding) + static void get(std::string_view s, size_t slot, Record &r); + + static void clear(Record &r); // 新增时字段清零 + static consteval int disk_size(); // 磁盘记录长度 + }; + + constexpr uint32_t file_header_size = sizeof(FileHeader); + + // 磁盘记录长度 = 字段连续和(≠ sizeof(Record),padding 不入文件) + // flags(1) id(4) next(4) name(64) gender(64) old_score(8) score(8) old_rank(4) rank(4) = 161 + #if defined(__cpp_impl_reflection) + // P2996 反射(GCC 16.1+ -freflection):字段尺寸由 Record 直接推导,数组/断言整体退役 + consteval int get_record_field_size() + { + std::size_t s = 0; + constexpr auto ctx = std::meta::access_context::unchecked(); + for (auto m: std::meta::nonstatic_data_members_of(^^Record, ctx)) + s += std::meta::size_of(m); + return static_cast(s); + } + #else + // 反射不可用(clang-cl/MSVC)时的替身:数组 + 逐字段断言 + constexpr int record_field_sizes[9] = {1, 4, 4, 64, 64, 8, 8, 4, 4}; + // 数组 ↔ 结构体逐字段钉死:改 Record 忘了数组,编译报错而不是运行时错位 + static_assert(sizeof(Record::flags) == record_field_sizes[0]); + static_assert(sizeof(Record::id) == record_field_sizes[1]); + static_assert(sizeof(Record::next) == record_field_sizes[2]); + static_assert(sizeof(Record::name) == record_field_sizes[3]); + static_assert(sizeof(Record::gender) == record_field_sizes[4]); + static_assert(sizeof(Record::old_score) == record_field_sizes[5]); + static_assert(sizeof(Record::score) == record_field_sizes[6]); + static_assert(sizeof(Record::old_rank) == record_field_sizes[7]); + static_assert(sizeof(Record::rank) == record_field_sizes[8]); + + consteval int get_record_field_size() + { + return std::accumulate( + std::begin(record_field_sizes), + std::end(record_field_sizes), + 0); + } + #endif + // 两条路径都必须得出同一个磁盘长度,编译期钉死 + static_assert(get_record_field_size() == 161, "record disk size"); + + consteval int Record::disk_size() + { + return get_record_field_size(); + } + + // 规则/礼物记录:desc 描述 + delta 分值,77B = 1+4+4+64+4 + struct RuleRecord + { + uint8_t flags; + uint32_t id; + uint32_t next; + std::byte desc[64]; + int32_t delta; + + static void put(std::string &buf, const RuleRecord &r); + + static void get(std::string_view s, size_t slot, RuleRecord &r); + + static void clear(RuleRecord &r); + + static consteval int disk_size(); + }; + + #if defined(__cpp_impl_reflection) + consteval int rule_disk_size() + { + std::size_t s = 0; + constexpr auto ctx = std::meta::access_context::unchecked(); + for (auto m: std::meta::nonstatic_data_members_of(^^RuleRecord, ctx)) + s += std::meta::size_of(m); + return static_cast(s); + } + #else + constexpr int rule_field_sizes[5] = {1, 4, 4, 64, 4}; + static_assert(sizeof(RuleRecord::flags) == rule_field_sizes[0]); + static_assert(sizeof(RuleRecord::id) == rule_field_sizes[1]); + static_assert(sizeof(RuleRecord::next) == rule_field_sizes[2]); + static_assert(sizeof(RuleRecord::desc) == rule_field_sizes[3]); + static_assert(sizeof(RuleRecord::delta) == rule_field_sizes[4]); + + consteval int rule_disk_size() + { + return std::accumulate( + std::begin(rule_field_sizes), + std::end(rule_field_sizes), + 0); + } + #endif + static_assert(rule_disk_size() == 77, "rule disk size"); + + consteval int RuleRecord::disk_size() + { + return rule_disk_size(); + } + + // 礼物 = 规则同构(desc + delta),独立类型以让 FileStore 各自持有单例 + struct GiftRecord : RuleRecord + { + static consteval int disk_size() + { + return rule_disk_size(); + } + }; + + // 空闲链表哨兵:0xFFFFFFFF = 无空闲槽 + inline constexpr uint32_t FREE_NIL = 0xFFFFFFFFu; + + // 通用定长记录文件引擎:header(32B) + 记录数组,空闲链表复用,CRC 校验, + // tmp+rename 原子写。每记录类型一个单例(FileStore::get())。 + // T 必须提供:put/get/clear/disk_size。 + template + class FileStore + { + public: + static void init(const std::filesystem::path &path) + { + if (inst_) throw std::logic_error("FileStore already initialized"); + inst_ = std::unique_ptr(new FileStore(path)); + } + + static FileStore &get() + { + if (!inst_) throw std::logic_error("FileStore not initialized"); + return *inst_; + } + + FileStore(const FileStore &) = delete; + + FileStore &operator=(const FileStore &) = delete; + + const std::filesystem::path &path() const noexcept + { + return file_path_; + } + + void load() + { + if (!std::filesystem::exists(file_path_)) + { // 全新表:空库 + pool_.clear(); + capacity_ = 0; + alive_ = 0; + free_head_ = FREE_NIL; + loaded_ = true; + dirty_ = false; + return; + } + std::ifstream fin(file_path_, std::ios::binary); + fin.exceptions(std::ios::failbit | std::ios::badbit); + FileHeader h; + fin.read(reinterpret_cast(&h), sizeof(h)); + if (std::memcmp(h.magic, FILE_HEADER_MAGIC, 4) != 0) + throw file_format_error("文件头魔数不符"); + if (h.version != FILE_VERSION) + throw file_format_error("文件版本不符"); + if (h.header_size != file_header_size || h.record_size != static_cast< + uint32_t>(T::disk_size())) + throw file_format_error("文件头尺寸字段不符"); + + std::string buf(static_cast(h.record_size) * h.capacity, '\0'); + fin.read(buf.data(), static_cast(buf.size())); + if (crc32(buf.data(), buf.size()) != h.crc32) + throw file_format_error("记录区 CRC 校验失败"); + + pool_.resize(h.capacity); + uint32_t chain = FREE_NIL; + uint32_t tail = FREE_NIL; + uint32_t alive = 0; + for (uint32_t i = 0; i < h.capacity; ++i) + { + T::get(buf, i, pool_[i]); + if (pool_[i].flags & 0x01u) + { + ++alive; + pool_[i].next = 0; + } + else + { + pool_[i].next = FREE_NIL; + if (tail == FREE_NIL) chain = i; + else pool_[tail].next = i; + tail = i; + } + } + if (alive != h.alive_count) + throw file_format_error("存活计数与文件头不符"); + uint32_t n = 0; + for (uint32_t s = chain; s != FREE_NIL; s = pool_[s].next) + if (++n > h.capacity) + throw file_format_error("空闲链表成环或超长"); + if (n != h.capacity - h.alive_count) + throw file_format_error("空闲链表长度不符"); + + capacity_ = h.capacity; + alive_ = alive; + free_head_ = chain; + loaded_ = true; + dirty_ = false; + } + + void save() + { + if (!dirty_) return; // 懒写:无变更不落盘 + FileHeader h; + std::memcpy(h.magic, FILE_HEADER_MAGIC, 4); + h.version = FILE_VERSION; + h.header_size = file_header_size; + h.record_size = static_cast(T::disk_size()); + h.capacity = capacity_; + h.alive_count = alive_; + h.free_head = free_head_; + h.crc32 = 0; + + std::string buf; + buf.reserve( + file_header_size + static_cast(capacity_) * T::disk_size()); + buf.append(reinterpret_cast(&h), sizeof(h)); + for (uint32_t i = 0; i < capacity_; ++i) + T::put(buf, pool_[i]); + h.crc32 = crc32(buf.data() + sizeof(h), buf.size() - sizeof(h)); + std::memcpy(buf.data(), &h, sizeof(h)); + + auto tmp = file_path_; + tmp += ".tmp"; + { + std::ofstream fout(tmp, std::ios::binary); + fout.exceptions(std::ios::failbit | std::ios::badbit); + fout.write(buf.data(), static_cast(buf.size())); + } + std::filesystem::rename(tmp, file_path_); // 原子替换 + dirty_ = false; + loaded_ = true; + } + + void switch_to(const std::filesystem::path &path) + { + if (path == file_path_) return; + save(); // 脏则先落盘当前表 + file_path_ = path; + loaded_ = false; + load(); + } + + T *find(uint32_t id) noexcept + { + if (id >= capacity_) return nullptr; + auto &r = pool_[id]; + return (r.flags & 0x01u) ? &r : nullptr; + } + + const T *find(uint32_t id) const noexcept + { + if (id >= capacity_) return nullptr; + const auto &r = pool_[id]; + return (r.flags & 0x01u) ? &r : nullptr; + } + + uint32_t add() + { + uint32_t slot; + if (free_head_ != FREE_NIL) + { // 复用空洞(LIFO) + slot = free_head_; + free_head_ = pool_[slot].next; + } + else + { // 追加新槽 + slot = capacity_++; + pool_.emplace_back(); + } + auto &r = pool_[slot]; + r.flags = 0x01u; + r.id = slot; + r.next = 0; + T::clear(r); + ++alive_; + dirty_ = true; + return slot; + } + + bool remove(uint32_t id) + { + auto *r = find(id); + if (!r) return false; + r->flags = 0; // 借 next 挂空闲链 + r->next = free_head_; + free_head_ = id; + --alive_; + dirty_ = true; + return true; + } + + void mark_dirty() noexcept + { + dirty_ = true; + } + + private: + explicit FileStore(std::filesystem::path path) : file_path_(std::move(path)) + {} + + std::filesystem::path file_path_; + std::vector pool_; + uint32_t capacity_ = 0; + uint32_t alive_ = 0; + uint32_t free_head_ = FREE_NIL; + bool loaded_ = false; + bool dirty_ = false; + static std::unique_ptr inst_; + }; + + template + std::unique_ptr > FileStore::inst_; + + // 学生表门面(保持既有 API/单例语义) + using FileInstance = FileStore; +} diff --git a/xmake.lua b/xmake.lua index 12e879c..1f79758 100644 --- a/xmake.lua +++ b/xmake.lua @@ -7,14 +7,21 @@ local APP_VERSION = "1.0.0" set_version(APP_VERSION) set_languages("c++20") -add_requires("nlohmann_json","libcurl") +-- gcc 工具链下不声明包:xmake 包缓存会把 MSVC 编译的 libcurl 串给 gcc(__GSHandlerCheck 链接失败), +-- 且 points_modernize(模块/反射目标)不用任何包;old 目标保持 clang-cl 编译 +if get_config("toolchain") ~= "gcc" then + add_requires("nlohmann_json","libcurl") +end target("points_main") set_kind("binary") add_files("old/main.cpp") - add_packages("nlohmann_json", "libcurl") + if get_config("toolchain") ~= "gcc" then + add_packages("nlohmann_json", "libcurl") + end add_defines(string.format('APP_VERSION="%s"', APP_VERSION)) - if is_plat("windows") then + -- /utf-8 是 MSVC/clang-cl 专用;GCC 默认按 UTF-8 读源码 + if get_config("toolchain") ~= "gcc" then add_cxflags("/utf-8", {force = true}) -- main.cpp 是 UTF-8 无 BOM,MSVC 默认按 GBK 读 end @@ -23,3 +30,21 @@ target("points_main") -- 2. 生成的 rc 不会自动编译,必须手动加进源文件 add_files("$(builddir)/version.rc", {always_added = true}) + +target("points_modernize") + set_kind("binary") + add_files("src/storage_management.ixx") + add_files("src/exceptions.ixx") + add_files("src/storage_management.cpp") + add_files("src/data_storage.cpp") + add_includedirs("include") + add_defines(string.format('APP_VERSION="%s"', APP_VERSION)) + -- /utf-8 是 MSVC/clang-cl 专用;GCC 默认按 UTF-8 读源码 + if get_config("toolchain") ~= "gcc" then + add_cxflags("/utf-8", {force = true}) + end + -- GCC 16.1+ 启用 C++26 反射(P2996,-freflection);源码侧用 __cpp_impl_reflection 自动检测 + -- 注:xmake 3.0 DSL 无进程执行/版本探测 API,不解析 gcc 版本;gcc < 16 会因 -freflection 明确报错 + if get_config("toolchain") == "gcc" then + add_cxflags("-std=c++26", "-freflection", {force = true}) + end