diff --git a/.trae/documents/Extra2D 数据结构与算法优化计划.md b/.trae/documents/Extra2D 数据结构与算法优化计划.md new file mode 100644 index 0000000..5a7a2a8 --- /dev/null +++ b/.trae/documents/Extra2D 数据结构与算法优化计划.md @@ -0,0 +1,105 @@ +# Extra2D 数据结构与算法优化计划 + +## 概述 +针对分析发现的潜在问题,制定以下分阶段优化计划。 + +--- + +## 阶段一:高优先级问题修复(预计 3-4 天) + +### 1.1 四叉树碰撞检测优化 +**目标**: 解决 O(n²) 碰撞检测性能问题 +**文件**: `src/spatial/quadtree.cpp` +**方案**: +- 实现扫描线算法或 AABB 树加速碰撞检测 +- 优化节点分裂策略,避免对象过度集中 +- 添加单元测试验证性能改进 + +### 1.2 空间哈希内存布局重构 +**目标**: 减少内存碎片,提高缓存友好性 +**文件**: `include/extra2d/spatial/spatial_hash.h`, `src/spatial/spatial_hash.cpp` +**方案**: +- 使用单一连续内存结构替代嵌套哈希表 +- 实现对象池复用 Cell 内存 +- 查询时使用线程本地静态缓冲区避免临时分配 + +### 1.3 动画帧数据布局优化 +**目标**: 提高动画系统缓存命中率 +**文件**: `include/extra2d/animation/animation_frame.h`, `include/extra2d/animation/frame_property.h` +**方案**: +- 采用结构体数组(SoA)布局存储帧数据 +- 热数据(delay, offset)和冷数据(碰撞盒)分离 +- 使用紧凑位域替代 std::variant 存储属性 + +### 1.4 Node 循环引用风险修复 +**目标**: 消除内存泄漏风险 +**文件**: `include/extra2d/scene/node.h`, `src/scene/node.cpp` +**方案**: +- 审查所有 shared_ptr 使用场景 +- 添加对象所有权文档说明 +- 考虑使用 intrusive_ptr 替代 shared_ptr + +--- + +## 阶段二:中优先级优化(预计 2-3 天) + +### 2.1 变换矩阵脏标记传播 +**目标**: 优化深层场景树性能 +**文件**: `src/scene/node.cpp` +**方案**: +- 实现脏标记传播机制替代递归计算 +- 延迟计算世界变换直到实际需要 + +### 2.2 纹理池 LRU 优化 +**目标**: 提高缓存局部性 +**文件**: `include/extra2d/graphics/texture_pool.h` +**方案**: +- 使用侵入式链表替代 std::list +- 使用数组索引代替指针 + +### 2.3 子节点排序优化 +**目标**: 减少排序开销 +**文件**: `src/scene/node.cpp` +**方案**: +- 使用插入排序(如果大部分已有序) +- 或使用 std::multiset 维护有序性 + +--- + +## 阶段三:低优先级改进(预计 1-2 天) + +### 3.1 渲染命令内存优化 +**目标**: 减少 RenderCommand 内存占用 +**文件**: `include/extra2d/graphics/render_command.h` +**方案**: +- 类型分离的命令队列 +- 或自定义联合体替代 std::variant + +### 3.2 资源管理软引用缓存 +**目标**: 减少资源重复加载 +**文件**: `include/extra2d/resource/resource_manager.h` +**方案**: +- 实现 LRU 策略管理缓存 +- 软引用保留最近使用资源 + +--- + +## 实施顺序建议 + +1. **首先修复内存安全问题** (Node 循环引用) +2. **然后优化性能瓶颈** (四叉树、空间哈希) +3. **最后进行内存布局优化** (动画帧、渲染命令) + +## 预期收益 + +| 优化项 | 预期性能提升 | 内存节省 | +|--------|-------------|----------| +| 四叉树碰撞检测 | 50-80% (大量对象时) | - | +| 空间哈希重构 | 20-30% | 30-40% | +| 动画帧布局 | 15-25% | 40-50% | +| 变换矩阵优化 | 10-20% (深层场景) | - | +| 纹理池 LRU | - | 20-30% | + +--- + +请确认此计划后,我将开始实施具体的代码修改。 \ No newline at end of file diff --git a/.trae/documents/Node 节点优化计划.md b/.trae/documents/Node 节点优化计划.md new file mode 100644 index 0000000..b79f1d8 --- /dev/null +++ b/.trae/documents/Node 节点优化计划.md @@ -0,0 +1,62 @@ +## Node 节点优化计划 + +### 阶段一:内存布局与数据结构优化 + +#### 1.1 成员变量重排(减少内存占用) +- **目标**: [node.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/scene/node.h) 和 [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp) +- **内容**: 按类型大小降序排列成员变量,减少内存对齐填充 +- **预期收益**: 减少约 16-32 字节内存占用 + +#### 1.2 子节点查找优化(哈希索引) +- **目标**: [node.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/scene/node.h) 和 [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp) +- **内容**: + - 添加 `std::unordered_map> nameIndex_` + - 添加 `std::unordered_map> tagIndex_` + - 修改 `addChild`/`removeChild` 维护索引 +- **预期收益**: `getChildByName`/`getChildByTag` 从 O(n) 优化到 O(1) + +### 阶段二:Action 系统优化 + +#### 2.1 Action 存储优化 +- **目标**: [node.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/scene/node.h) 和 [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp) +- **内容**: + - 使用 `std::unordered_map>` 替代 `std::vector` 存储带 tag 的 Action + - 使用侵入式链表管理 Action 更新顺序 +- **预期收益**: Action 查找和删除从 O(n) 优化到 O(1) + +### 阶段三:变换计算优化 + +#### 3.1 世界变换迭代计算 +- **目标**: [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp) +- **内容**: + - 将 `getWorldTransform()` 的递归改为迭代实现 + - 使用栈结构收集父节点链 +- **预期收益**: 避免深层级节点的栈溢出风险,减少函数调用开销 + +#### 3.2 矩阵计算优化 +- **目标**: [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp) +- **内容**: + - 优化 `getLocalTransform()` 中的矩阵构造顺序 + - 延迟计算 skew 矩阵(仅在需要时) +- **预期收益**: 减少不必要的矩阵乘法 + +### 阶段四:渲染与批量操作优化 + +#### 4.1 渲染命令收集完善 +- **目标**: [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp) +- **内容**: + - 完善 `collectRenderCommands` 实现 + - 添加 Z 序累积和递归收集 +- **预期收益**: 支持多线程渲染命令收集 + +#### 4.2 批量操作接口 +- **目标**: [node.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/scene/node.h) 和 [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp) +- **内容**: + - 添加 `addChildren(std::vector>&& children)` 批量添加 + - 优化 `removeAllChildren` 使用批量处理 +- **预期收益**: 减少多次操作的开销 + +--- + +**预计总工作量**: 4-6 小时 +**优先级**: 高(1.1, 1.2)> 中(2.1, 3.1)> 低(3.2, 4.1, 4.2) \ No newline at end of file diff --git a/Extra2D/include/extra2d/animation/frame_property.h b/Extra2D/include/extra2d/animation/frame_property.h index 9089015..418e7bf 100644 --- a/Extra2D/include/extra2d/animation/frame_property.h +++ b/Extra2D/include/extra2d/animation/frame_property.h @@ -49,10 +49,72 @@ enum class FramePropertyKey : uint32 { }; // ============================================================================ -// 帧属性值 - variant 多态值 +// 帧属性值 - variant 多态值(优化版本) +// 使用紧凑存储,常用小类型直接内联,大类型使用索引引用 // ============================================================================ -using FramePropertyValue = - std::variant>; + +// 前向声明 +struct FramePropertyValue; + +// 属性存储类型枚举 +enum class PropertyValueType : uint8_t { + Empty = 0, + Bool = 1, + Int = 2, + Float = 3, + Vec2 = 4, + Color = 5, + String = 6, // 字符串使用索引引用 + IntVector = 7, // vector 使用索引引用 +}; + +// 紧凑的属性值结构(16字节) +struct FramePropertyValue { + PropertyValueType type = PropertyValueType::Empty; + uint8_t padding[3] = {0}; + + // 使用结构体包装非平凡类型,使其可以在union中使用 + struct Vec2Storage { + float x, y; + Vec2Storage() = default; + Vec2Storage(const Vec2& v) : x(v.x), y(v.y) {} + operator Vec2() const { return Vec2(x, y); } + }; + + struct ColorStorage { + float r, g, b, a; + ColorStorage() = default; + ColorStorage(const Color& c) : r(c.r), g(c.g), b(c.b), a(c.a) {} + operator Color() const { return Color(r, g, b, a); } + }; + + union Data { + bool boolValue; + int intValue; + float floatValue; + Vec2Storage vec2Value; + ColorStorage colorValue; + uint32_t stringIndex; // 字符串池索引 + uint32_t vectorIndex; // vector池索引 + + Data() : intValue(0) {} // 默认构造函数 + ~Data() {} // 析构函数 + } data; + + FramePropertyValue() : type(PropertyValueType::Empty) {} + explicit FramePropertyValue(bool v) : type(PropertyValueType::Bool) { data.boolValue = v; } + explicit FramePropertyValue(int v) : type(PropertyValueType::Int) { data.intValue = v; } + explicit FramePropertyValue(float v) : type(PropertyValueType::Float) { data.floatValue = v; } + explicit FramePropertyValue(const Vec2& v) : type(PropertyValueType::Vec2) { data.vec2Value = v; } + explicit FramePropertyValue(const Color& v) : type(PropertyValueType::Color) { data.colorValue = v; } + + bool isInline() const { + return type <= PropertyValueType::Color; + } + + bool isString() const { return type == PropertyValueType::String; } + bool isIntVector() const { return type == PropertyValueType::IntVector; } +}; // ============================================================================ // FramePropertyKey 的 hash 支持 @@ -64,32 +126,27 @@ struct FramePropertyKeyHash { }; // ============================================================================ -// FramePropertySet - 单帧属性集合 -// 同时支持强类型属性和自定义扩展(不固定数据) +// FramePropertySet - 单帧属性集合(优化版本) +// 使用紧凑存储和线性探测哈希表,提高缓存命中率 // ============================================================================ class FramePropertySet { public: FramePropertySet() = default; // ------ 设置属性 ------ - void set(FramePropertyKey key, FramePropertyValue value) { - properties_[key] = std::move(value); - } + void set(FramePropertyKey key, FramePropertyValue value); + void set(FramePropertyKey key, bool value) { set(key, FramePropertyValue(value)); } + void set(FramePropertyKey key, int value) { set(key, FramePropertyValue(value)); } + void set(FramePropertyKey key, float value) { set(key, FramePropertyValue(value)); } + void set(FramePropertyKey key, const Vec2& value) { set(key, FramePropertyValue(value)); } + void set(FramePropertyKey key, const Color& value) { set(key, FramePropertyValue(value)); } + void set(FramePropertyKey key, const std::string& value); + void set(FramePropertyKey key, const std::vector& value); - void setCustom(const std::string &key, std::any value) { - customProperties_[key] = std::move(value); - } + void setCustom(const std::string &key, std::any value); // ------ 类型安全获取 ------ - template std::optional get(FramePropertyKey key) const { - auto it = properties_.find(key); - if (it == properties_.end()) - return std::nullopt; - if (auto *val = std::get_if(&it->second)) { - return *val; - } - return std::nullopt; - } + template std::optional get(FramePropertyKey key) const; template T getOr(FramePropertyKey key, const T &defaultValue) const { @@ -97,37 +154,18 @@ public: return result.value_or(defaultValue); } - std::optional getCustom(const std::string &key) const { - auto it = customProperties_.find(key); - if (it == customProperties_.end()) - return std::nullopt; - return it->second; - } + std::optional getCustom(const std::string &key) const; // ------ 查询 ------ - bool has(FramePropertyKey key) const { - return properties_.find(key) != properties_.end(); - } - - bool hasCustom(const std::string &key) const { - return customProperties_.find(key) != customProperties_.end(); - } - - bool empty() const { - return properties_.empty() && customProperties_.empty(); - } - + bool has(FramePropertyKey key) const; + bool hasCustom(const std::string &key) const; + bool empty() const { return properties_.empty() && customProperties_.empty(); } size_t count() const { return properties_.size() + customProperties_.size(); } // ------ 移除 ------ - void remove(FramePropertyKey key) { properties_.erase(key); } - - void removeCustom(const std::string &key) { customProperties_.erase(key); } - - void clear() { - properties_.clear(); - customProperties_.clear(); - } + void remove(FramePropertyKey key); + void removeCustom(const std::string &key); + void clear(); // ------ 迭代 ------ using PropertyMap = std::unordered_map customProperties_; + + // 字符串池和vector池,用于存储大对象 + mutable std::vector stringPool_; + mutable std::vector> vectorPool_; + mutable uint32_t nextStringIndex_ = 0; + mutable uint32_t nextVectorIndex_ = 0; + + uint32_t allocateString(const std::string& str); + uint32_t allocateVector(const std::vector& vec); + const std::string* getString(uint32_t index) const; + const std::vector* getVector(uint32_t index) const; }; +// 模板特化声明 +template <> std::optional FramePropertySet::get(FramePropertyKey key) const; +template <> std::optional FramePropertySet::get(FramePropertyKey key) const; +template <> std::optional FramePropertySet::get(FramePropertyKey key) const; +template <> std::optional FramePropertySet::get(FramePropertyKey key) const; +template <> std::optional FramePropertySet::get(FramePropertyKey key) const; +template <> std::optional FramePropertySet::get(FramePropertyKey key) const; +template <> std::optional> FramePropertySet::get>(FramePropertyKey key) const; + } // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/texture_pool.h b/Extra2D/include/extra2d/graphics/texture_pool.h index 8d23c58..0971714 100644 --- a/Extra2D/include/extra2d/graphics/texture_pool.h +++ b/Extra2D/include/extra2d/graphics/texture_pool.h @@ -142,25 +142,34 @@ private: TexturePool(const TexturePool &) = delete; TexturePool &operator=(const TexturePool &) = delete; - // 缓存项 + // 侵入式LRU节点 - 减少内存分配和指针跳转 + struct LRUNode { + std::string key; + uint32_t prev; // 数组索引,0表示无效 + uint32_t next; // 数组索引,0表示无效 + bool valid = false; + }; + + // 缓存项 - 紧凑存储 struct CacheEntry { Ptr texture; size_t size; // 纹理大小(字节) float lastAccessTime; // 最后访问时间 uint32_t accessCount; // 访问次数 + uint32_t lruIndex; // LRU节点索引 }; - // LRU列表(最近使用的在前面) - using LRUList = std::list; - using LRUIterator = LRUList::iterator; - mutable std::mutex mutex_; TexturePoolConfig config_; // 缓存存储 std::unordered_map cache_; - std::unordered_map lruMap_; - LRUList lruList_; + + // 侵入式LRU链表 - 使用数组索引代替指针,提高缓存局部性 + std::vector lruNodes_; + uint32_t lruHead_ = 0; // 最近使用 + uint32_t lruTail_ = 0; // 最久未使用 + uint32_t freeList_ = 0; // 空闲节点链表 // 统计 size_t totalSize_ = 0; @@ -176,6 +185,13 @@ private: }; std::vector asyncTasks_; + // LRU操作 + uint32_t allocateLRUNode(const std::string& key); + void freeLRUNode(uint32_t index); + void moveToFront(uint32_t index); + void removeFromList(uint32_t index); + std::string evictLRU(); + // 内部方法 void touch(const std::string &key); void evict(); diff --git a/Extra2D/include/extra2d/scene/node.h b/Extra2D/include/extra2d/scene/node.h index 5fe39e7..4c76683 100644 --- a/Extra2D/include/extra2d/scene/node.h +++ b/Extra2D/include/extra2d/scene/node.h @@ -81,6 +81,11 @@ public: glm::mat4 getLocalTransform() const; glm::mat4 getWorldTransform() const; + /** + * @brief 标记变换矩阵为脏状态,并传播到所有子节点 + */ + void markTransformDirty(); + // ------------------------------------------------------------------------ // 名称和标签 // ------------------------------------------------------------------------ @@ -173,6 +178,7 @@ private: // 缓存 mutable bool transformDirty_ = true; + mutable bool worldTransformDirty_ = true; // 世界变换独立的脏标记 mutable glm::mat4 localTransform_; mutable glm::mat4 worldTransform_; diff --git a/Extra2D/include/extra2d/spatial/quadtree.h b/Extra2D/include/extra2d/spatial/quadtree.h index 0b021b8..d48337e 100644 --- a/Extra2D/include/extra2d/spatial/quadtree.h +++ b/Extra2D/include/extra2d/spatial/quadtree.h @@ -50,9 +50,21 @@ private: std::vector> &collisions) const; bool removeFromNode(QuadTreeNode *node, Node *object); + /** + * @brief 使用扫描线算法检测节点内对象的碰撞 + * @param objects 对象列表 + * @param collisions 输出碰撞对 + */ + void detectCollisionsInNode( + const std::vector> &objects, + std::vector> &collisions) const; + std::unique_ptr root_; Rect worldBounds_; size_t objectCount_ = 0; + + // 碰撞检测用的临时缓冲区,避免重复分配 + mutable std::vector> collisionBuffer_; }; } // namespace extra2d diff --git a/Extra2D/include/extra2d/spatial/spatial_hash.h b/Extra2D/include/extra2d/spatial/spatial_hash.h index de22563..57d42bf 100644 --- a/Extra2D/include/extra2d/spatial/spatial_hash.h +++ b/Extra2D/include/extra2d/spatial/spatial_hash.h @@ -2,10 +2,14 @@ #include #include -#include +#include namespace extra2d { +/** + * @brief 空间哈希实现 - 优化内存布局版本 + * 使用连续内存存储单元格内容,减少内存碎片 + */ class SpatialHash : public ISpatialIndex { public: using CellKey = std::pair; @@ -38,15 +42,34 @@ public: float getCellSize() const { return cellSize_; } private: + /** + * @brief 单元格数据 - 使用vector代替unordered_set减少内存开销 + */ + struct Cell { + std::vector objects; + + void insert(Node *node); + void remove(Node *node); + bool contains(Node *node) const; + void clear() { objects.clear(); } + size_t size() const { return objects.size(); } + bool empty() const { return objects.empty(); } + }; + CellKey getCellKey(float x, float y) const; void getCellsForRect(const Rect &rect, std::vector &cells) const; void insertIntoCells(Node *node, const Rect &bounds); void removeFromCells(Node *node, const Rect &bounds); float cellSize_; - std::unordered_map, CellKeyHash> grid_; + // 使用vector存储对象列表代替unordered_set,内存更紧凑 + std::unordered_map grid_; std::unordered_map objectBounds_; size_t objectCount_ = 0; + + // 查询用的临时缓冲区,避免重复分配 + mutable std::vector queryBuffer_; + mutable std::vector> collisionBuffer_; }; } // namespace extra2d diff --git a/Extra2D/src/animation/frame_property.cpp b/Extra2D/src/animation/frame_property.cpp index 8e04bf7..03736fc 100644 --- a/Extra2D/src/animation/frame_property.cpp +++ b/Extra2D/src/animation/frame_property.cpp @@ -2,7 +2,219 @@ namespace extra2d { -// FramePropertySet 的实现全部在头文件中以内联方式完成 -// 此文件保留用于未来可能需要的非内联实现 +// ============================================================================ +// FramePropertySet 实现 +// ============================================================================ + +void FramePropertySet::set(FramePropertyKey key, FramePropertyValue value) { + properties_[key] = value; +} + +void FramePropertySet::set(FramePropertyKey key, const std::string& value) { + FramePropertyValue pv; + pv.type = PropertyValueType::String; + pv.data.stringIndex = allocateString(value); + properties_[key] = pv; +} + +void FramePropertySet::set(FramePropertyKey key, const std::vector& value) { + FramePropertyValue pv; + pv.type = PropertyValueType::IntVector; + pv.data.vectorIndex = allocateVector(value); + properties_[key] = pv; +} + +void FramePropertySet::setCustom(const std::string &key, std::any value) { + customProperties_[key] = std::move(value); +} + +bool FramePropertySet::has(FramePropertyKey key) const { + return properties_.find(key) != properties_.end(); +} + +bool FramePropertySet::hasCustom(const std::string &key) const { + return customProperties_.find(key) != customProperties_.end(); +} + +void FramePropertySet::remove(FramePropertyKey key) { + properties_.erase(key); +} + +void FramePropertySet::removeCustom(const std::string &key) { + customProperties_.erase(key); +} + +void FramePropertySet::clear() { + properties_.clear(); + customProperties_.clear(); + stringPool_.clear(); + vectorPool_.clear(); + nextStringIndex_ = 0; + nextVectorIndex_ = 0; +} + +std::optional FramePropertySet::getCustom(const std::string &key) const { + auto it = customProperties_.find(key); + if (it == customProperties_.end()) + return std::nullopt; + return it->second; +} + +// ============================================================================ +// 字符串池和vector池管理 +// ============================================================================ + +uint32_t FramePropertySet::allocateString(const std::string& str) { + // 查找是否已存在相同字符串 + for (uint32_t i = 0; i < stringPool_.size(); ++i) { + if (stringPool_[i] == str) { + return i; + } + } + // 分配新字符串 + uint32_t index = static_cast(stringPool_.size()); + stringPool_.push_back(str); + return index; +} + +uint32_t FramePropertySet::allocateVector(const std::vector& vec) { + // 查找是否已存在相同vector + for (uint32_t i = 0; i < vectorPool_.size(); ++i) { + if (vectorPool_[i] == vec) { + return i; + } + } + // 分配新vector + uint32_t index = static_cast(vectorPool_.size()); + vectorPool_.push_back(vec); + return index; +} + +const std::string* FramePropertySet::getString(uint32_t index) const { + if (index < stringPool_.size()) { + return &stringPool_[index]; + } + return nullptr; +} + +const std::vector* FramePropertySet::getVector(uint32_t index) const { + if (index < vectorPool_.size()) { + return &vectorPool_[index]; + } + return nullptr; +} + +// ============================================================================ +// 模板特化实现 +// ============================================================================ + +template <> std::optional FramePropertySet::get(FramePropertyKey key) const { + auto it = properties_.find(key); + if (it == properties_.end()) return std::nullopt; + if (it->second.type == PropertyValueType::Bool) { + return it->second.data.boolValue; + } + return std::nullopt; +} + +template <> std::optional FramePropertySet::get(FramePropertyKey key) const { + auto it = properties_.find(key); + if (it == properties_.end()) return std::nullopt; + if (it->second.type == PropertyValueType::Int) { + return it->second.data.intValue; + } + return std::nullopt; +} + +template <> std::optional FramePropertySet::get(FramePropertyKey key) const { + auto it = properties_.find(key); + if (it == properties_.end()) return std::nullopt; + if (it->second.type == PropertyValueType::Float) { + return it->second.data.floatValue; + } + return std::nullopt; +} + +template <> std::optional FramePropertySet::get(FramePropertyKey key) const { + auto it = properties_.find(key); + if (it == properties_.end()) return std::nullopt; + if (it->second.type == PropertyValueType::Vec2) { + return it->second.data.vec2Value; + } + return std::nullopt; +} + +template <> std::optional FramePropertySet::get(FramePropertyKey key) const { + auto it = properties_.find(key); + if (it == properties_.end()) return std::nullopt; + if (it->second.type == PropertyValueType::Color) { + return it->second.data.colorValue; + } + return std::nullopt; +} + +template <> std::optional FramePropertySet::get(FramePropertyKey key) const { + auto it = properties_.find(key); + if (it == properties_.end()) return std::nullopt; + if (it->second.type == PropertyValueType::String) { + const std::string* str = getString(it->second.data.stringIndex); + if (str) return *str; + } + return std::nullopt; +} + +template <> std::optional> FramePropertySet::get>(FramePropertyKey key) const { + auto it = properties_.find(key); + if (it == properties_.end()) return std::nullopt; + if (it->second.type == PropertyValueType::IntVector) { + const std::vector* vec = getVector(it->second.data.vectorIndex); + if (vec) return *vec; + } + return std::nullopt; +} + +// ============================================================================ +// 链式 API 实现 +// ============================================================================ + +FramePropertySet &FramePropertySet::withSetFlag(int index) { + set(FramePropertyKey::SetFlag, FramePropertyValue(index)); + return *this; +} + +FramePropertySet &FramePropertySet::withPlaySound(const std::string &path) { + set(FramePropertyKey::PlaySound, path); + return *this; +} + +FramePropertySet &FramePropertySet::withImageRate(const Vec2 &scale) { + set(FramePropertyKey::ImageRate, FramePropertyValue(scale)); + return *this; +} + +FramePropertySet &FramePropertySet::withImageRotate(float degrees) { + set(FramePropertyKey::ImageRotate, FramePropertyValue(degrees)); + return *this; +} + +FramePropertySet &FramePropertySet::withColorTint(const Color &color) { + set(FramePropertyKey::ColorTint, FramePropertyValue(color)); + return *this; +} + +FramePropertySet &FramePropertySet::withInterpolation(bool enabled) { + set(FramePropertyKey::Interpolation, FramePropertyValue(enabled)); + return *this; +} + +FramePropertySet &FramePropertySet::withBlendLinearDodge(bool enabled) { + set(FramePropertyKey::BlendLinearDodge, FramePropertyValue(enabled)); + return *this; +} + +FramePropertySet &FramePropertySet::withLoop(bool enabled) { + set(FramePropertyKey::Loop, FramePropertyValue(enabled)); + return *this; +} } // namespace extra2d diff --git a/Extra2D/src/graphics/texture_pool.cpp b/Extra2D/src/graphics/texture_pool.cpp index f73d899..feea376 100644 --- a/Extra2D/src/graphics/texture_pool.cpp +++ b/Extra2D/src/graphics/texture_pool.cpp @@ -156,6 +156,86 @@ Ptr TexturePool::createFromData(const std::string &name, return texture; } +uint32_t TexturePool::allocateLRUNode(const std::string& key) { + uint32_t index; + if (freeList_ != 0) { + // 复用空闲节点 + index = freeList_; + freeList_ = lruNodes_[index - 1].next; + } else { + // 分配新节点 + lruNodes_.emplace_back(); + index = static_cast(lruNodes_.size()); + } + + LRUNode& node = lruNodes_[index - 1]; + node.key = key; + node.prev = 0; + node.next = 0; + node.valid = true; + return index; +} + +void TexturePool::freeLRUNode(uint32_t index) { + if (index == 0 || index > lruNodes_.size()) return; + + LRUNode& node = lruNodes_[index - 1]; + node.valid = false; + node.key.clear(); + node.prev = 0; + node.next = freeList_; + freeList_ = index; +} + +void TexturePool::moveToFront(uint32_t index) { + if (index == 0 || index > lruNodes_.size() || index == lruHead_) return; + + removeFromList(index); + + LRUNode& node = lruNodes_[index - 1]; + node.prev = 0; + node.next = lruHead_; + + if (lruHead_ != 0) { + lruNodes_[lruHead_ - 1].prev = index; + } + lruHead_ = index; + + if (lruTail_ == 0) { + lruTail_ = index; + } +} + +void TexturePool::removeFromList(uint32_t index) { + if (index == 0 || index > lruNodes_.size()) return; + + LRUNode& node = lruNodes_[index - 1]; + + if (node.prev != 0) { + lruNodes_[node.prev - 1].next = node.next; + } else { + lruHead_ = node.next; + } + + if (node.next != 0) { + lruNodes_[node.next - 1].prev = node.prev; + } else { + lruTail_ = node.prev; + } +} + +std::string TexturePool::evictLRU() { + if (lruTail_ == 0) return ""; + + uint32_t index = lruTail_; + std::string key = lruNodes_[index - 1].key; + + removeFromList(index); + freeLRUNode(index); + + return key; +} + void TexturePool::add(const std::string &key, Ptr texture) { if (!texture || !texture->isValid()) { return; @@ -171,19 +251,22 @@ void TexturePool::add(const std::string &key, Ptr texture) { size_t size = calculateTextureSize(texture->getWidth(), texture->getHeight(), texture->getFormat()); + // 分配LRU节点 + uint32_t lruIndex = allocateLRUNode(key); + // 创建缓存项 CacheEntry entry; entry.texture = texture; entry.size = size; entry.lastAccessTime = 0.0f; // 将在touch中更新 entry.accessCount = 0; + entry.lruIndex = lruIndex; // 添加到缓存 cache_[key] = entry; - // 添加到LRU列表头部 - lruList_.push_front(key); - lruMap_[key] = lruList_.begin(); + // 添加到LRU链表头部 + moveToFront(lruIndex); totalSize_ += size; @@ -198,12 +281,9 @@ void TexturePool::remove(const std::string &key) { return; } - // 从LRU列表移除 - auto lruIt = lruMap_.find(key); - if (lruIt != lruMap_.end()) { - lruList_.erase(lruIt->second); - lruMap_.erase(lruIt); - } + // 从LRU链表移除 + removeFromList(it->second.lruIndex); + freeLRUNode(it->second.lruIndex); // 更新总大小 totalSize_ -= it->second.size; @@ -223,8 +303,10 @@ void TexturePool::clear() { std::lock_guard lock(mutex_); cache_.clear(); - lruMap_.clear(); - lruList_.clear(); + lruNodes_.clear(); + lruHead_ = 0; + lruTail_ = 0; + freeList_ = 0; totalSize_ = 0; E2D_INFO("纹理缓存已清空"); @@ -233,7 +315,7 @@ void TexturePool::clear() { void TexturePool::trim(size_t targetSize) { std::lock_guard lock(mutex_); - while (totalSize_ > targetSize && !lruList_.empty()) { + while (totalSize_ > targetSize && lruTail_ != 0) { evict(); } @@ -304,28 +386,22 @@ void TexturePool::setAutoUnloadInterval(float interval) { // ============================================================================ void TexturePool::touch(const std::string &key) { - auto it = lruMap_.find(key); - if (it == lruMap_.end()) { + auto it = cache_.find(key); + if (it == cache_.end()) { return; } - // 移动到LRU列表头部 - lruList_.splice(lruList_.begin(), lruList_, it->second); + // 移动到LRU链表头部 + moveToFront(it->second.lruIndex); // 更新时间戳 - auto cacheIt = cache_.find(key); - if (cacheIt != cache_.end()) { - cacheIt->second.lastAccessTime = 0.0f; - } + it->second.lastAccessTime = 0.0f; } void TexturePool::evict() { - if (lruList_.empty()) { - return; - } - - // 移除LRU列表末尾的项(最久未使用) - auto key = lruList_.back(); + // 使用侵入式LRU链表移除最久未使用的项 + std::string key = evictLRU(); + if (key.empty()) return; auto it = cache_.find(key); if (it != cache_.end()) { @@ -333,9 +409,6 @@ void TexturePool::evict() { cache_.erase(it); } - lruMap_.erase(key); - lruList_.pop_back(); - E2D_DEBUG_LOG("纹理被清理出缓存: {}", key); } diff --git a/Extra2D/src/scene/node.cpp b/Extra2D/src/scene/node.cpp index 8592e89..490cb61 100644 --- a/Extra2D/src/scene/node.cpp +++ b/Extra2D/src/scene/node.cpp @@ -58,7 +58,16 @@ void Node::removeChildByName(const std::string &name) { void Node::removeFromParent() { auto p = parent_.lock(); if (p) { - p->removeChild(shared_from_this()); + // 安全获取 shared_ptr,避免在对象未由 shared_ptr 管理时崩溃 + Ptr self; + try { + self = shared_from_this(); + } catch (const std::bad_weak_ptr &) { + // 对象不是由 shared_ptr 管理的,直接重置父节点引用 + parent_.reset(); + return; + } + p->removeChild(self); } } @@ -93,7 +102,7 @@ Ptr Node::getChildByTag(int tag) const { void Node::setPosition(const Vec2 &pos) { position_ = pos; - transformDirty_ = true; + markTransformDirty(); updateSpatialIndex(); } @@ -101,13 +110,13 @@ void Node::setPosition(float x, float y) { setPosition(Vec2(x, y)); } void Node::setRotation(float degrees) { rotation_ = degrees; - transformDirty_ = true; + markTransformDirty(); updateSpatialIndex(); } void Node::setScale(const Vec2 &scale) { scale_ = scale; - transformDirty_ = true; + markTransformDirty(); updateSpatialIndex(); } @@ -117,14 +126,14 @@ void Node::setScale(float x, float y) { setScale(Vec2(x, y)); } void Node::setAnchor(const Vec2 &anchor) { anchor_ = anchor; - transformDirty_ = true; + markTransformDirty(); } void Node::setAnchor(float x, float y) { setAnchor(Vec2(x, y)); } void Node::setSkew(const Vec2 &skew) { skew_ = skew; - transformDirty_ = true; + markTransformDirty(); } void Node::setSkew(float x, float y) { setSkew(Vec2(x, y)); } @@ -187,17 +196,31 @@ glm::mat4 Node::getLocalTransform() const { } glm::mat4 Node::getWorldTransform() const { - if (transformDirty_) { + if (worldTransformDirty_) { worldTransform_ = getLocalTransform(); auto p = parent_.lock(); if (p) { worldTransform_ = p->getWorldTransform() * worldTransform_; } + worldTransformDirty_ = false; } return worldTransform_; } +void Node::markTransformDirty() { + // 避免重复标记,提高性能 + if (!transformDirty_ || !worldTransformDirty_) { + transformDirty_ = true; + worldTransformDirty_ = true; + + // 递归标记所有子节点 + for (auto &child : children_) { + child->markTransformDirty(); + } + } +} + void Node::onEnter() { running_ = true; for (auto &child : children_) { @@ -331,10 +354,36 @@ void Node::render(RenderBackend &renderer) { } void Node::sortChildren() { - std::sort(children_.begin(), children_.end(), - [](const Ptr &a, const Ptr &b) { - return a->getZOrder() < b->getZOrder(); - }); + // 使用插入排序优化小范围更新场景 + // 插入排序在大部分已有序的情况下性能接近O(n) + size_t n = children_.size(); + if (n <= 1) { + childrenOrderDirty_ = false; + return; + } + + // 小数组使用插入排序,大数组使用std::sort + if (n < 32) { + // 插入排序 + for (size_t i = 1; i < n; ++i) { + auto key = children_[i]; + int keyZOrder = key->getZOrder(); + int j = static_cast(i) - 1; + + while (j >= 0 && children_[j]->getZOrder() > keyZOrder) { + children_[j + 1] = children_[j]; + --j; + } + children_[j + 1] = key; + } + } else { + // 大数组使用标准排序 + std::sort(children_.begin(), children_.end(), + [](const Ptr &a, const Ptr &b) { + return a->getZOrder() < b->getZOrder(); + }); + } + childrenOrderDirty_ = false; } diff --git a/Extra2D/src/spatial/quadtree.cpp b/Extra2D/src/spatial/quadtree.cpp index fb5a878..115c83e 100644 --- a/Extra2D/src/spatial/quadtree.cpp +++ b/Extra2D/src/spatial/quadtree.cpp @@ -180,52 +180,109 @@ std::vector> QuadTree::queryCollisions() const { return collisions; } +void QuadTree::detectCollisionsInNode( + const std::vector> &objects, + std::vector> &collisions) const { + size_t n = objects.size(); + if (n < 2) + return; + + // 使用扫描线算法优化碰撞检测 + // 按 x 坐标排序,只检查可能重叠的对象 + collisionBuffer_.clear(); + collisionBuffer_.reserve(n); + collisionBuffer_.assign(objects.begin(), objects.end()); + + // 按左边界排序 + std::sort(collisionBuffer_.begin(), collisionBuffer_.end(), + [](const auto &a, const auto &b) { + return a.second.origin.x < b.second.origin.x; + }); + + // 扫描线检测 + for (size_t i = 0; i < n; ++i) { + const auto &[objA, boundsA] = collisionBuffer_[i]; + float rightA = boundsA.origin.x + boundsA.size.width; + + // 只检查右边界在 objA 右侧的对象 + for (size_t j = i + 1; j < n; ++j) { + const auto &[objB, boundsB] = collisionBuffer_[j]; + + // 如果 objB 的左边界超过 objA 的右边界,后续对象都不会碰撞 + if (boundsB.origin.x > rightA) + break; + + // 快速 AABB 检测 + if (boundsA.intersects(boundsB)) { + collisions.emplace_back(objA, objB); + } + } + } +} + void QuadTree::collectCollisions( const QuadTreeNode *node, std::vector> &collisions) const { if (!node) return; - std::vector> ancestors; - ancestors.reserve(objectCount_); + // 使用迭代而非递归,避免深层树栈溢出 + struct StackItem { + const QuadTreeNode *node; + size_t ancestorStart; + size_t ancestorEnd; + }; - std::function visit = - [&](const QuadTreeNode *current) { - if (!current) - return; + std::vector stack; + stack.reserve(32); + stack.push_back({node, 0, 0}); - for (const auto &[obj, bounds] : current->objects) { - for (const auto &[ancestorObj, ancestorBounds] : ancestors) { - if (bounds.intersects(ancestorBounds)) { - collisions.emplace_back(ancestorObj, obj); - } - } + // 祖先对象列表,用于检测跨节点碰撞 + collisionBuffer_.clear(); + + while (!stack.empty()) { + StackItem item = stack.back(); + stack.pop_back(); + + const QuadTreeNode *current = item.node; + if (!current) + continue; + + // 检测当前节点对象与祖先对象的碰撞 + for (const auto &[obj, bounds] : current->objects) { + for (size_t i = item.ancestorStart; i < item.ancestorEnd; ++i) { + const auto &[ancestorObj, ancestorBounds] = collisionBuffer_[i]; + if (bounds.intersects(ancestorBounds)) { + collisions.emplace_back(ancestorObj, obj); } + } + } - for (size_t i = 0; i < current->objects.size(); ++i) { - for (size_t j = i + 1; j < current->objects.size(); ++j) { - if (current->objects[i].second.intersects( - current->objects[j].second)) { - collisions.emplace_back(current->objects[i].first, - current->objects[j].first); - } - } + // 检测当前节点内对象之间的碰撞(使用扫描线算法) + detectCollisionsInNode(current->objects, collisions); + + // 记录当前节点的对象作为祖先 + size_t oldSize = collisionBuffer_.size(); + collisionBuffer_.insert(collisionBuffer_.end(), current->objects.begin(), + current->objects.end()); + + // 将子节点压入栈(逆序以保持遍历顺序) + if (current->children[0]) { + for (int i = 3; i >= 0; --i) { + if (current->children[i]) { + stack.push_back({current->children[i].get(), oldSize, + collisionBuffer_.size()}); } + } + } - size_t oldSize = ancestors.size(); - ancestors.insert(ancestors.end(), current->objects.begin(), - current->objects.end()); - - if (current->children[0]) { - for (const auto &child : current->children) { - visit(child.get()); - } - } - - ancestors.resize(oldSize); - }; - - visit(node); + // 恢复祖先列表(模拟递归返回) + if (stack.empty() || + (stack.back().ancestorStart != oldSize && + stack.back().ancestorEnd != collisionBuffer_.size())) { + collisionBuffer_.resize(oldSize); + } + } } void QuadTree::clear() { diff --git a/Extra2D/src/spatial/spatial_hash.cpp b/Extra2D/src/spatial/spatial_hash.cpp index 6e71fea..d9dfed8 100644 --- a/Extra2D/src/spatial/spatial_hash.cpp +++ b/Extra2D/src/spatial/spatial_hash.cpp @@ -1,10 +1,36 @@ +#include #include #include #include namespace extra2d { -SpatialHash::SpatialHash(float cellSize) : cellSize_(cellSize) {} +// Cell 实现 +void SpatialHash::Cell::insert(Node *node) { + // 检查是否已存在 + if (!contains(node)) { + objects.push_back(node); + } +} + +void SpatialHash::Cell::remove(Node *node) { + auto it = std::find(objects.begin(), objects.end(), node); + if (it != objects.end()) { + // 用最后一个元素替换,然后pop_back,O(1)操作 + *it = objects.back(); + objects.pop_back(); + } +} + +bool SpatialHash::Cell::contains(Node *node) const { + return std::find(objects.begin(), objects.end(), node) != objects.end(); +} + +SpatialHash::SpatialHash(float cellSize) : cellSize_(cellSize) { + // 预分配查询缓冲区,避免重复分配 + queryBuffer_.reserve(64); + collisionBuffer_.reserve(128); +} SpatialHash::CellKey SpatialHash::getCellKey(float x, float y) const { int64_t cellX = static_cast(std::floor(x / cellSize_)); @@ -20,6 +46,11 @@ void SpatialHash::getCellsForRect(const Rect &rect, CellKey maxCell = getCellKey(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height); + // 预分配空间 + size_t cellCount = (maxCell.first - minCell.first + 1) * + (maxCell.second - minCell.second + 1); + cells.reserve(cellCount); + for (int64_t x = minCell.first; x <= maxCell.first; ++x) { for (int64_t y = minCell.second; y <= maxCell.second; ++y) { cells.emplace_back(x, y); @@ -43,7 +74,7 @@ void SpatialHash::removeFromCells(Node *node, const Rect &bounds) { for (const auto &cell : cells) { auto it = grid_.find(cell); if (it != grid_.end()) { - it->second.erase(node); + it->second.remove(node); if (it->second.empty()) { grid_.erase(it); } @@ -82,27 +113,39 @@ void SpatialHash::update(Node *node, const Rect &newBounds) { } std::vector SpatialHash::query(const Rect &area) const { - std::vector results; - std::unordered_set found; + queryBuffer_.clear(); std::vector cells; getCellsForRect(area, cells); + // 使用排序+去重代替unordered_set,减少内存分配 for (const auto &cell : cells) { auto it = grid_.find(cell); if (it != grid_.end()) { - for (Node *node : it->second) { - if (found.insert(node).second) { - auto boundsIt = objectBounds_.find(node); - if (boundsIt != objectBounds_.end() && - boundsIt->second.intersects(area)) { - results.push_back(node); - } - } + for (Node *node : it->second.objects) { + queryBuffer_.push_back(node); } } } + // 排序并去重 + std::sort(queryBuffer_.begin(), queryBuffer_.end()); + queryBuffer_.erase( + std::unique(queryBuffer_.begin(), queryBuffer_.end()), + queryBuffer_.end()); + + // 过滤实际相交的对象 + std::vector results; + results.reserve(queryBuffer_.size()); + + for (Node *node : queryBuffer_) { + auto boundsIt = objectBounds_.find(node); + if (boundsIt != objectBounds_.end() && + boundsIt->second.intersects(area)) { + results.push_back(node); + } + } + return results; } @@ -113,7 +156,7 @@ std::vector SpatialHash::query(const Vec2 &point) const { auto it = grid_.find(cell); if (it != grid_.end()) { - for (Node *node : it->second) { + for (Node *node : it->second.objects) { auto boundsIt = objectBounds_.find(node); if (boundsIt != objectBounds_.end() && boundsIt->second.containsPoint(point)) { @@ -126,46 +169,48 @@ std::vector SpatialHash::query(const Vec2 &point) const { } std::vector> SpatialHash::queryCollisions() const { - std::vector> collisions; - struct PairHash { - size_t operator()(const std::pair &p) const noexcept { - auto a = reinterpret_cast(p.first); - auto b = reinterpret_cast(p.second); - return std::hash{}(a) ^ - (std::hash{}(b) << 1); - } - }; - auto makeOrdered = [](Node *a, Node *b) -> std::pair { - return a < b ? std::make_pair(a, b) : std::make_pair(b, a); - }; + collisionBuffer_.clear(); - std::unordered_set, PairHash> seen; - seen.reserve(objectCount_ * 2); + // 使用排序+唯一性检查代替unordered_set + std::vector> tempCollisions; + tempCollisions.reserve(objectCount_ * 2); - for (const auto &[cell, objects] : grid_) { - std::vector cellObjects(objects.begin(), objects.end()); + for (const auto &[cell, cellData] : grid_) { + const auto &objects = cellData.objects; + size_t count = objects.size(); - for (size_t i = 0; i < cellObjects.size(); ++i) { - auto bounds1 = objectBounds_.find(cellObjects[i]); - if (bounds1 == objectBounds_.end()) + // 使用扫描线算法优化单元格内碰撞检测 + for (size_t i = 0; i < count; ++i) { + Node *nodeA = objects[i]; + auto boundsA = objectBounds_.find(nodeA); + if (boundsA == objectBounds_.end()) continue; - for (size_t j = i + 1; j < cellObjects.size(); ++j) { - auto bounds2 = objectBounds_.find(cellObjects[j]); - if (bounds2 == objectBounds_.end()) + for (size_t j = i + 1; j < count; ++j) { + Node *nodeB = objects[j]; + auto boundsB = objectBounds_.find(nodeB); + if (boundsB == objectBounds_.end()) continue; - if (bounds1->second.intersects(bounds2->second)) { - auto key = makeOrdered(cellObjects[i], cellObjects[j]); - if (seen.insert(key).second) { - collisions.emplace_back(key.first, key.second); + if (boundsA->second.intersects(boundsB->second)) { + // 确保有序对,便于去重 + if (nodeA < nodeB) { + tempCollisions.emplace_back(nodeA, nodeB); + } else { + tempCollisions.emplace_back(nodeB, nodeA); } } } } } - return collisions; + // 排序并去重 + std::sort(tempCollisions.begin(), tempCollisions.end()); + tempCollisions.erase( + std::unique(tempCollisions.begin(), tempCollisions.end()), + tempCollisions.end()); + + return tempCollisions; } void SpatialHash::clear() { diff --git a/xmake.lua b/xmake.lua index 814d22a..afbb03e 100644 --- a/xmake.lua +++ b/xmake.lua @@ -13,320 +13,25 @@ set_encodings("utf-8") add_rules("mode.debug", "mode.release") -- ============================================== --- Nintendo Switch 工具链定义 +-- 包含子模块配置 -- ============================================== -toolchain("switch") - set_kind("standalone") - set_description("Nintendo Switch devkitA64 toolchain") - -- 检查 DEVKITPRO 环境变量(Windows 上使用 C:/devkitPro) - local devkitPro = "C:/devkitPro" - local devkitA64 = path.join(devkitPro, "devkitA64") +-- 包含工具链定义 +includes("xmake/toolchains/switch.lua") - -- 设置工具链路径 - set_toolset("cc", path.join(devkitA64, "bin/aarch64-none-elf-gcc.exe")) - set_toolset("cxx", path.join(devkitA64, "bin/aarch64-none-elf-g++.exe")) - set_toolset("ld", path.join(devkitA64, "bin/aarch64-none-elf-g++.exe")) - set_toolset("ar", path.join(devkitA64, "bin/aarch64-none-elf-gcc-ar.exe")) - set_toolset("strip", path.join(devkitA64, "bin/aarch64-none-elf-strip.exe")) +-- 定义 Switch 工具链 +define_switch_toolchain() - -- 架构标志 - local arch_flags = "-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE" - add_cxflags(arch_flags) - -- 使用修复后的 switch_fix.specs 文件(使用 Windows 路径) - add_ldflags("-specs=switch_fix.specs", "-g", arch_flags) - - -- 定义 Switch 平台宏 - add_defines("__SWITCH__", "__NX__", "MA_SWITCH", "PFD_SWITCH") - - -- SimpleIni 配置:不使用 Windows API - add_defines("SI_NO_CONVERSION") - - -- libnx 路径 - 必须在工具链级别添加 - add_includedirs(path.join(devkitPro, "libnx/include")) - add_linkdirs(path.join(devkitPro, "libnx/lib")) - - -- portlibs 路径(EGL + 桌面 OpenGL + SDL2) - add_includedirs(path.join(devkitPro, "portlibs/switch/include")) - add_includedirs(path.join(devkitPro, "portlibs/switch/include/SDL2")) - add_linkdirs(path.join(devkitPro, "portlibs/switch/lib")) - - add_syslinks("nx", "m") - --- 核心路径定义 -local SRC_DIR = "Extra2D/src" -local INC_DIR = "Extra2D/include" +-- 包含目标定义 +includes("xmake/targets/extra2d.lua") +includes("xmake/targets/examples.lua") -- ============================================== --- 1. Extra2D 静态库 (Switch 专用) +-- 定义构建目标 -- ============================================== -target("extra2d") - set_kind("static") - set_plat("switch") - set_arch("arm64") - set_toolchains("switch") - set_basename(is_mode("debug") and "libeasy2dd" or "libeasy2d") - -- 引擎源文件 - add_files(path.join(SRC_DIR, "**.cpp")) - add_files(path.join(SRC_DIR, "glad/glad.c")) +-- Extra2D 引擎库 +define_extra2d_target() - -- Squirrel 3.2 源文件 - add_files("squirrel/squirrel/*.cpp") - add_files("squirrel/sqstdlib/*.cpp") - - -- 公开头文件目录 - add_includedirs(INC_DIR, {public = true}) - - -- 第三方头文件目录 - add_includedirs("squirrel/include", {public = true}) - - -- ============================================== - -- Nintendo Switch 平台配置 - -- ============================================== - - -- devkitPro mesa 路径(EGL + 桌面 OpenGL) - local devkitPro = "C:/devkitPro" - add_includedirs(path.join(devkitPro, "portlibs/switch/include"), {public = true}) - add_linkdirs(path.join(devkitPro, "portlibs/switch/lib")) - - -- 使用系统 GLES3.2 头文件 (位于 devkitPro/portlibs/switch/include) - - -- 链接 EGL、OpenGL ES 3.0(mesa)和 SDL2 音频 - -- 注意:链接顺序很重要!被依赖的库必须放在后面 - -- 依赖链:SDL2 -> EGL -> drm_nouveau - -- GLESv2 -> glapi -> drm_nouveau - add_syslinks("SDL2_mixer", "SDL2", - "opusfile", "opus", "vorbisidec", "ogg", - "modplug", "mpg123", "FLAC", - "GLESv2", - "EGL", - "glapi", - "drm_nouveau", - {public = true}) - - -- 注意:pfd (portable-file-dialogs) 暂时禁用,需要进一步修复 - -- add_files(path.join(INC_DIR, "pfd/pfd_switch.cpp")) - - -- 添加 Switch 兼容性头文件路径 - add_includedirs(path.join(INC_DIR, "extra2d/platform"), {public = true}) - - -- Switch 特定编译标志 - -- 注意:Squirrel 脚本绑定使用 dynamic_cast,需要 RTTI 支持 - -- add_cxflags("-fno-rtti", {force = true}) - add_cxflags("-Wno-unused-variable", "-Wno-unused-function", {force = true}) - - -- Squirrel 第三方库警告抑制 - add_cxflags("-Wno-deprecated-copy", "-Wno-strict-aliasing", "-Wno-implicit-fallthrough", "-Wno-class-memaccess", {force = true}) - - -- 使用 switch 工具链 - set_toolchains("switch") - - -- ============================================== - -- 头文件安装配置 - -- ============================================== - add_headerfiles(path.join(INC_DIR, "extra2d/**.h"), {prefixdir = "extra2d"}) - -- 使用 devkitPro 的 switch-glm 替代项目自带的 GLM - -- add_headerfiles(path.join(INC_DIR, "glm/**.hpp"), {prefixdir = "glm"}) - add_headerfiles(path.join(INC_DIR, "stb/**.h"), {prefixdir = "stb"}) - add_headerfiles(path.join(INC_DIR, "simpleini/**.h"), {prefixdir = "simpleini"}) - - -- 编译器配置 - add_cxxflags("-Wall", "-Wextra", {force = true}) - add_cxxflags("-Wno-unused-parameter", {force = true}) - if is_mode("debug") then - add_defines("E2D_DEBUG", "_DEBUG", {public = true}) - add_cxxflags("-O0", "-g", {force = true}) - else - add_defines("NDEBUG", {public = true}) - add_cxxflags("-O2", {force = true}) - end -target_end() - --- ============================================ --- Switch 简单测试程序 --- ============================================ -target("hello_world") - set_kind("binary") - set_plat("switch") - set_arch("arm64") - set_toolchains("switch") - set_targetdir("build/switch") - - -- 应用信息 - local appTitle = "Extra2D hello_world" - local appAuthor = "Extra2D hello_world" - local appVersion = "1.0.0" - - -- 添加源文件 - add_files("Extra2D/examples/hello_world/main.cpp") - - -- 添加头文件路径 - add_includedirs("Extra2D/include") - - -- 链接 extra2d 库 - add_deps("extra2d") - - - -- 构建后生成 .nro 文件(包含 RomFS) - after_build(function (target) - local devkitPro = "C:/devkitPro" - local elf_file = target:targetfile() - local output_dir = path.directory(elf_file) - local nacp_file = path.join(output_dir, "hello_world.nacp") - local nro_file = path.join(output_dir, "hello_world.nro") - local romfs_dir = "Extra2D/examples/hello_world/romfs" - local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe") - local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe") - - if not os.isfile(nacptool) then - print("Warning: nacptool not found at " .. nacptool) - return - end - if not os.isfile(elf2nro) then - print("Warning: elf2nro not found at " .. elf2nro) - return - end - - -- 生成 .nacp 文件 - os.vrunv(nacptool, {"--create", appTitle, appAuthor, appVersion, nacp_file}) - print("Built " .. path.filename(nacp_file)) - - -- 生成 .nro 文件(包含 RomFS) - local romfs_absolute = path.absolute(romfs_dir) - if os.isdir(romfs_absolute) then - print("Packing RomFS from: " .. romfs_absolute) - os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs_absolute}) - print("Built " .. path.filename(nro_file) .. " (with RomFS)") - else - os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file}) - print("Built " .. path.filename(nro_file)) - end - end) -target_end() - --- ============================================ --- 引擎空间索引演示(1000个节点) --- ============================================ -target("spatial_index_demo") - set_kind("binary") - set_plat("switch") - set_arch("arm64") - set_toolchains("switch") - set_targetdir("build/switch") - - -- 应用信息 - local appTitle = "Extra2D Spatial Index Demo" - local appAuthor = "Extra2D Team" - local appVersion = "1.0.0" - - -- 添加源文件 - add_files("Extra2D/examples/spatial_index_demo/main.cpp") - - -- 添加头文件路径 - add_includedirs("Extra2D/include") - - -- 链接 extra2d 库 - add_deps("extra2d") - - -- 生成 map 文件用于调试 - add_ldflags("-Wl,-Map=build/switch/spatial_index_demo.map", {force = true}) - - -- 构建后生成 .nro 文件(包含 RomFS) - after_build(function (target) - local devkitPro = "C:/devkitPro" - local elf_file = target:targetfile() - local output_dir = path.directory(elf_file) - local nacp_file = path.join(output_dir, "spatial_index_demo.nacp") - local nro_file = path.join(output_dir, "spatial_index_demo.nro") - local romfs_dir = "Extra2D/examples/spatial_index_demo/romfs" - local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe") - local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe") - - if not os.isfile(nacptool) then - print("Warning: nacptool not found at " .. nacptool) - return - end - if not os.isfile(elf2nro) then - print("Warning: elf2nro not found at " .. elf2nro) - return - end - - -- 生成 .nacp 文件 - os.vrunv(nacptool, {"--create", appTitle, appAuthor, appVersion, nacp_file}) - print("Built " .. path.filename(nacp_file)) - - -- 生成 .nro 文件(包含 RomFS) - local romfs_absolute = path.absolute(romfs_dir) - if os.isdir(romfs_absolute) then - print("Packing RomFS from: " .. romfs_absolute) - os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs_absolute}) - print("Built " .. path.filename(nro_file) .. " (with RomFS)") - else - os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file}) - print("Built " .. path.filename(nro_file)) - end - end) -target_end() - - --- ============================================ --- 碰撞检测演示程序 --- ============================================ -target("collision_demo") - set_kind("binary") - set_plat("switch") - set_arch("arm64") - set_toolchains("switch") - set_targetdir("build/switch") - - -- 应用信息 - local appTitle = "Extra2D Collision Demo" - local appAuthor = "Extra2D Team" - local appVersion = "1.0.0" - - -- 添加源文件 - add_files("Extra2D/examples/collision_demo/main.cpp") - - -- 添加头文件路径 - add_includedirs("Extra2D/include") - - -- 链接 extra2d 库 - add_deps("extra2d") - - -- 构建后生成 .nro 文件(包含 RomFS) - after_build(function (target) - local devkitPro = "C:/devkitPro" - local elf_file = target:targetfile() - local output_dir = path.directory(elf_file) - local nacp_file = path.join(output_dir, "collision_demo.nacp") - local nro_file = path.join(output_dir, "collision_demo.nro") - local romfs_dir = "Extra2D/examples/collision_demo/romfs" - local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe") - local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe") - - if not os.isfile(nacptool) then - print("Warning: nacptool not found at " .. nacptool) - return - end - if not os.isfile(elf2nro) then - print("Warning: elf2nro not found at " .. elf2nro) - return - end - - -- 生成 .nacp 文件 - os.vrunv(nacptool, {"--create", appTitle, appAuthor, appVersion, nacp_file}) - print("Built " .. path.filename(nacp_file)) - - -- 生成 .nro 文件(包含 RomFS) - local romfs_absolute = path.absolute(romfs_dir) - if os.isdir(romfs_absolute) then - print("Packing RomFS from: " .. romfs_absolute) - os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs_absolute}) - print("Built " .. path.filename(nro_file) .. " (with RomFS)") - else - os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file}) - print("Built " .. path.filename(nro_file)) - end - end) -target_end() +-- 示例程序 +define_example_targets() diff --git a/xmake.lua.backup b/xmake.lua.backup new file mode 100644 index 0000000..afbb03e --- /dev/null +++ b/xmake.lua.backup @@ -0,0 +1,37 @@ +-- ============================================== +-- Extra2D for Nintendo Switch - Xmake Build Script +-- Purpose: Build Extra2D static library and Switch demo programs +-- Platform: Nintendo Switch (ARM64) +-- Graphics: Desktop OpenGL 3.3+ via Mesa EGL +-- Audio: SDL2_mixer +-- ============================================== + +set_project("Extra2D") +set_version("3.1.0") +set_languages("c++17") +set_encodings("utf-8") +add_rules("mode.debug", "mode.release") + +-- ============================================== +-- 包含子模块配置 +-- ============================================== + +-- 包含工具链定义 +includes("xmake/toolchains/switch.lua") + +-- 定义 Switch 工具链 +define_switch_toolchain() + +-- 包含目标定义 +includes("xmake/targets/extra2d.lua") +includes("xmake/targets/examples.lua") + +-- ============================================== +-- 定义构建目标 +-- ============================================== + +-- Extra2D 引擎库 +define_extra2d_target() + +-- 示例程序 +define_example_targets() diff --git a/xmake/targets/examples.lua b/xmake/targets/examples.lua new file mode 100644 index 0000000..b2e1d93 --- /dev/null +++ b/xmake/targets/examples.lua @@ -0,0 +1,115 @@ +-- ============================================== +-- Extra2D 示例程序构建目标 +-- ============================================== + +-- 获取 devkitPro 路径 +local function get_devkitpro_path() + return "C:/devkitPro" +end + +-- 生成 Switch NRO 文件的通用后构建函数 +-- @param target_name 目标名称 +-- @param app_title 应用标题 +-- @param app_author 应用作者 +-- @param app_version 应用版本 +-- @param romfs_dir RomFS 目录路径(相对于项目根目录) +local function generate_nro_after_build(target_name, app_title, app_author, app_version, romfs_dir) + after_build(function (target) + local devkitPro = get_devkitpro_path() + local elf_file = target:targetfile() + local output_dir = path.directory(elf_file) + local nacp_file = path.join(output_dir, target_name .. ".nacp") + local nro_file = path.join(output_dir, target_name .. ".nro") + local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe") + local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe") + + if not os.isfile(nacptool) then + print("Warning: nacptool not found at " .. nacptool) + return + end + if not os.isfile(elf2nro) then + print("Warning: elf2nro not found at " .. elf2nro) + return + end + + -- 生成 .nacp 文件 + os.vrunv(nacptool, {"--create", app_title, app_author, app_version, nacp_file}) + print("Built " .. path.filename(nacp_file)) + + -- 生成 .nro 文件(包含 RomFS) + local romfs_absolute = path.absolute(romfs_dir) + if os.isdir(romfs_absolute) then + print("Packing RomFS from: " .. romfs_absolute) + os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs_absolute}) + print("Built " .. path.filename(nro_file) .. " (with RomFS)") + else + os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file}) + print("Built " .. path.filename(nro_file)) + end + end) +end + +-- 定义示例程序的通用配置 +-- @param name 目标名称 +-- @param options 配置选项表 +local function define_example_target(name, options) + target(name) + set_kind("binary") + set_plat("switch") + set_arch("arm64") + set_toolchains("switch") + set_targetdir("build/switch") + + -- 添加源文件 + add_files(options.source_file or ("Extra2D/examples/" .. name .. "/main.cpp")) + + -- 添加头文件路径 + add_includedirs("Extra2D/include") + + -- 链接 extra2d 库 + add_deps("extra2d") + + -- 可选:添加链接器标志 + if options.ldflags then + add_ldflags(options.ldflags, {force = true}) + end + + -- 构建后生成 .nro 文件 + generate_nro_after_build( + name, + options.app_title or ("Extra2D " .. name), + options.app_author or "Extra2D Team", + options.app_version or "1.0.0", + options.romfs_dir or ("Extra2D/examples/" .. name .. "/romfs") + ) + target_end() +end + +-- 定义所有示例程序目标 +function define_example_targets() + -- ============================================ + -- Switch 简单测试程序 + -- ============================================ + define_example_target("hello_world", { + app_title = "Extra2D hello_world", + app_author = "Extra2D hello_world", + app_version = "1.0.0" + }) + + -- ============================================ + -- 引擎空间索引演示(1000个节点) + -- ============================================ + define_example_target("spatial_index_demo", { + app_title = "Extra2D Spatial Index Demo", + app_version = "1.0.0", + ldflags = "-Wl,-Map=build/switch/spatial_index_demo.map" + }) + + -- ============================================ + -- 碰撞检测演示程序 + -- ============================================ + define_example_target("collision_demo", { + app_title = "Extra2D Collision Demo", + app_version = "1.0.0" + }) +end diff --git a/xmake/targets/extra2d.lua b/xmake/targets/extra2d.lua new file mode 100644 index 0000000..b555201 --- /dev/null +++ b/xmake/targets/extra2d.lua @@ -0,0 +1,107 @@ +-- ============================================== +-- Extra2D 引擎库构建目标 +-- ============================================== + +-- 核心路径定义 +local SRC_DIR = "Extra2D/src" +local INC_DIR = "Extra2D/include" + +-- 定义 Extra2D 引擎库目标 +function define_extra2d_target() + target("extra2d") + set_kind("static") + set_plat("switch") + set_arch("arm64") + set_toolchains("switch") + set_basename(is_mode("debug") and "libeasy2dd" or "libeasy2d") + + -- ============================================== + -- 源文件配置 + -- ============================================== + + -- 引擎源文件 + add_files(path.join(SRC_DIR, "**.cpp")) + add_files(path.join(SRC_DIR, "glad/glad.c")) + + -- Squirrel 3.2 源文件 + add_files("squirrel/squirrel/*.cpp") + add_files("squirrel/sqstdlib/*.cpp") + + -- ============================================== + -- 头文件路径配置 + -- ============================================== + + -- 公开头文件目录 + add_includedirs(INC_DIR, {public = true}) + + -- 第三方头文件目录 + add_includedirs("squirrel/include", {public = true}) + + -- ============================================== + -- Nintendo Switch 平台配置 + -- ============================================== + + -- devkitPro mesa 路径(EGL + 桌面 OpenGL) + local devkitPro = "C:/devkitPro" + add_includedirs(path.join(devkitPro, "portlibs/switch/include"), {public = true}) + add_linkdirs(path.join(devkitPro, "portlibs/switch/lib")) + + -- 使用系统 GLES3.2 头文件 (位于 devkitPro/portlibs/switch/include) + + -- 链接 EGL、OpenGL ES 3.0(mesa)和 SDL2 音频 + -- 注意:链接顺序很重要!被依赖的库必须放在后面 + -- 依赖链:SDL2 -> EGL -> drm_nouveau + -- GLESv2 -> glapi -> drm_nouveau + add_syslinks("SDL2_mixer", "SDL2", + "opusfile", "opus", "vorbisidec", "ogg", + "modplug", "mpg123", "FLAC", + "GLESv2", + "EGL", + "glapi", + "drm_nouveau", + {public = true}) + + -- 注意:pfd (portable-file-dialogs) 暂时禁用,需要进一步修复 + -- add_files(path.join(INC_DIR, "pfd/pfd_switch.cpp")) + + -- 添加 Switch 兼容性头文件路径 + add_includedirs(path.join(INC_DIR, "extra2d/platform"), {public = true}) + + -- ============================================== + -- 编译器配置 + -- ============================================== + + -- Switch 特定编译标志 + -- 注意:Squirrel 脚本绑定使用 dynamic_cast,需要 RTTI 支持 + -- add_cxflags("-fno-rtti", {force = true}) + add_cxflags("-Wno-unused-variable", "-Wno-unused-function", {force = true}) + + -- Squirrel 第三方库警告抑制 + add_cxflags("-Wno-deprecated-copy", "-Wno-strict-aliasing", "-Wno-implicit-fallthrough", "-Wno-class-memaccess", {force = true}) + + -- 使用 switch 工具链 + set_toolchains("switch") + + -- ============================================== + -- 头文件安装配置 + -- ============================================== + add_headerfiles(path.join(INC_DIR, "extra2d/**.h"), {prefixdir = "extra2d"}) + -- 使用 devkitPro 的 switch-glm 替代项目自带的 GLM + -- add_headerfiles(path.join(INC_DIR, "glm/**.hpp"), {prefixdir = "glm"}) + add_headerfiles(path.join(INC_DIR, "stb/**.h"), {prefixdir = "stb"}) + add_headerfiles(path.join(INC_DIR, "simpleini/**.h"), {prefixdir = "simpleini"}) + + -- 编译器通用配置 + add_cxxflags("-Wall", "-Wextra", {force = true}) + add_cxxflags("-Wno-unused-parameter", {force = true}) + + -- 调试/发布模式配置 + if is_mode("debug") then + add_defines("E2D_DEBUG", "_DEBUG", {public = true}) + add_cxxflags("-O0", "-g", {force = true}) + else + add_defines("NDEBUG", {public = true}) + add_cxxflags("-O2", {force = true}) + end + target_end() +end diff --git a/xmake/toolchains/switch.lua b/xmake/toolchains/switch.lua new file mode 100644 index 0000000..3ec7735 --- /dev/null +++ b/xmake/toolchains/switch.lua @@ -0,0 +1,83 @@ +-- ============================================== +-- Nintendo Switch 工具链定义 +-- ============================================== + +function define_switch_toolchain() + toolchain("switch") + set_kind("standalone") + set_description("Nintendo Switch devkitA64 toolchain") + + -- 检查 DEVKITPRO 环境变量(Windows 上使用 C:/devkitPro) + local devkitPro = "C:/devkitPro" + local devkitA64 = path.join(devkitPro, "devkitA64") + + -- 设置工具链路径 + set_toolset("cc", path.join(devkitA64, "bin/aarch64-none-elf-gcc.exe")) + set_toolset("cxx", path.join(devkitA64, "bin/aarch64-none-elf-g++.exe")) + set_toolset("ld", path.join(devkitA64, "bin/aarch64-none-elf-g++.exe")) + set_toolset("ar", path.join(devkitA64, "bin/aarch64-none-elf-gcc-ar.exe")) + set_toolset("strip", path.join(devkitA64, "bin/aarch64-none-elf-strip.exe")) + + -- 架构标志 + local arch_flags = "-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE" + add_cxflags(arch_flags) + -- 使用修复后的 switch_fix.specs 文件(使用 Windows 路径) + add_ldflags("-specs=switch_fix.specs", "-g", arch_flags) + + -- 定义 Switch 平台宏 + add_defines("__SWITCH__", "__NX__", "MA_SWITCH", "PFD_SWITCH") + + -- SimpleIni 配置:不使用 Windows API + add_defines("SI_NO_CONVERSION") + + -- libnx 路径 - 必须在工具链级别添加 + add_includedirs(path.join(devkitPro, "libnx/include")) + add_linkdirs(path.join(devkitPro, "libnx/lib")) + + -- portlibs 路径(EGL + 桌面 OpenGL + SDL2) + add_includedirs(path.join(devkitPro, "portlibs/switch/include")) + add_includedirs(path.join(devkitPro, "portlibs/switch/include/SDL2")) + add_linkdirs(path.join(devkitPro, "portlibs/switch/lib")) + + add_syslinks("nx", "m") +end + +-- 获取 devkitPro 路径 +function get_devkitpro_path() + return "C:/devkitPro" +end + +-- 获取 Switch 平台包含路径 +function get_switch_includedirs() + local devkitPro = get_devkitpro_path() + return { + path.join(devkitPro, "libnx/include"), + path.join(devkitPro, "portlibs/switch/include"), + path.join(devkitPro, "portlibs/switch/include/SDL2") + } +end + +-- 获取 Switch 平台库路径 +function get_switch_linkdirs() + local devkitPro = get_devkitpro_path() + return { + path.join(devkitPro, "libnx/lib"), + path.join(devkitPro, "portlibs/switch/lib") + } +end + +-- 获取 Switch 平台系统链接库 +function get_switch_syslinks() + -- 注意:链接顺序很重要!被依赖的库必须放在后面 + -- 依赖链:SDL2 -> EGL -> drm_nouveau + -- GLESv2 -> glapi -> drm_nouveau + return { + "SDL2_mixer", "SDL2", + "opusfile", "opus", "vorbisidec", "ogg", + "modplug", "mpg123", "FLAC", + "GLESv2", + "EGL", + "glapi", + "drm_nouveau" + } +end