feat(node): 添加变换矩阵脏标记传播机制

refactor(quadtree): 使用扫描线算法优化碰撞检测性能

refactor(spatial_hash): 重构内存布局提高缓存友好性

refactor(texture_pool): 使用侵入式LRU链表优化缓存管理

refactor(frame_property): 优化属性存储结构减少内存占用

fix(node): 修复循环引用潜在的内存泄漏问题

docs: 添加数据结构和算法优化计划文档

ci: 添加Nintendo Switch平台构建配置
This commit is contained in:
ChestnutYueyue 2026-02-10 03:06:30 +08:00
parent 9ef2d67236
commit 8b28f3c6df
17 changed files with 1252 additions and 518 deletions

View File

@ -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% |
---
请确认此计划后,我将开始实施具体的代码修改。

View File

@ -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<std::string, WeakPtr<Node>> nameIndex_`
- 添加 `std::unordered_map<int, WeakPtr<Node>> 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<int, Ptr<Action>>` 替代 `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<Ptr<Node>>&& children)` 批量添加
- 优化 `removeAllChildren` 使用批量处理
- **预期收益**: 减少多次操作的开销
---
**预计总工作量**: 4-6 小时
**优先级**: 高1.1, 1.2> 中2.1, 3.1> 低3.2, 4.1, 4.2

View File

@ -49,10 +49,72 @@ enum class FramePropertyKey : uint32 {
};
// ============================================================================
// 帧属性值 - variant 多态值
// 帧属性值 - variant 多态值(优化版本)
// 使用紧凑存储,常用小类型直接内联,大类型使用索引引用
// ============================================================================
using FramePropertyValue =
std::variant<bool, int, float, std::string, Vec2, Color, std::vector<int>>;
// 前向声明
struct FramePropertyValue;
// 属性存储类型枚举
enum class PropertyValueType : uint8_t {
Empty = 0,
Bool = 1,
Int = 2,
Float = 3,
Vec2 = 4,
Color = 5,
String = 6, // 字符串使用索引引用
IntVector = 7, // vector<int> 使用索引引用
};
// 紧凑的属性值结构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<int>& 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 <typename T> std::optional<T> get(FramePropertyKey key) const {
auto it = properties_.find(key);
if (it == properties_.end())
return std::nullopt;
if (auto *val = std::get_if<T>(&it->second)) {
return *val;
}
return std::nullopt;
}
template <typename T> std::optional<T> get(FramePropertyKey key) const;
template <typename T>
T getOr(FramePropertyKey key, const T &defaultValue) const {
@ -97,37 +154,18 @@ public:
return result.value_or(defaultValue);
}
std::optional<std::any> getCustom(const std::string &key) const {
auto it = customProperties_.find(key);
if (it == customProperties_.end())
return std::nullopt;
return it->second;
}
std::optional<std::any> 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<FramePropertyKey, FramePropertyValue,
@ -135,49 +173,38 @@ public:
const PropertyMap &properties() const { return properties_; }
// ------ 链式 API ------
FramePropertySet &withSetFlag(int index) {
set(FramePropertyKey::SetFlag, index);
return *this;
}
FramePropertySet &withPlaySound(const std::string &path) {
set(FramePropertyKey::PlaySound, path);
return *this;
}
FramePropertySet &withImageRate(const Vec2 &scale) {
set(FramePropertyKey::ImageRate, scale);
return *this;
}
FramePropertySet &withImageRotate(float degrees) {
set(FramePropertyKey::ImageRotate, degrees);
return *this;
}
FramePropertySet &withColorTint(const Color &color) {
set(FramePropertyKey::ColorTint, color);
return *this;
}
FramePropertySet &withInterpolation(bool enabled = true) {
set(FramePropertyKey::Interpolation, enabled);
return *this;
}
FramePropertySet &withBlendLinearDodge(bool enabled = true) {
set(FramePropertyKey::BlendLinearDodge, enabled);
return *this;
}
FramePropertySet &withLoop(bool enabled = true) {
set(FramePropertyKey::Loop, enabled);
return *this;
}
FramePropertySet &withSetFlag(int index);
FramePropertySet &withPlaySound(const std::string &path);
FramePropertySet &withImageRate(const Vec2 &scale);
FramePropertySet &withImageRotate(float degrees);
FramePropertySet &withColorTint(const Color &color);
FramePropertySet &withInterpolation(bool enabled = true);
FramePropertySet &withBlendLinearDodge(bool enabled = true);
FramePropertySet &withLoop(bool enabled = true);
private:
PropertyMap properties_;
std::unordered_map<std::string, std::any> customProperties_;
// 字符串池和vector池用于存储大对象
mutable std::vector<std::string> stringPool_;
mutable std::vector<std::vector<int>> vectorPool_;
mutable uint32_t nextStringIndex_ = 0;
mutable uint32_t nextVectorIndex_ = 0;
uint32_t allocateString(const std::string& str);
uint32_t allocateVector(const std::vector<int>& vec);
const std::string* getString(uint32_t index) const;
const std::vector<int>* getVector(uint32_t index) const;
};
// 模板特化声明
template <> std::optional<bool> FramePropertySet::get<bool>(FramePropertyKey key) const;
template <> std::optional<int> FramePropertySet::get<int>(FramePropertyKey key) const;
template <> std::optional<float> FramePropertySet::get<float>(FramePropertyKey key) const;
template <> std::optional<Vec2> FramePropertySet::get<Vec2>(FramePropertyKey key) const;
template <> std::optional<Color> FramePropertySet::get<Color>(FramePropertyKey key) const;
template <> std::optional<std::string> FramePropertySet::get<std::string>(FramePropertyKey key) const;
template <> std::optional<std::vector<int>> FramePropertySet::get<std::vector<int>>(FramePropertyKey key) const;
} // namespace extra2d

View File

@ -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> texture;
size_t size; // 纹理大小(字节)
float lastAccessTime; // 最后访问时间
uint32_t accessCount; // 访问次数
uint32_t lruIndex; // LRU节点索引
};
// LRU列表最近使用的在前面
using LRUList = std::list<std::string>;
using LRUIterator = LRUList::iterator;
mutable std::mutex mutex_;
TexturePoolConfig config_;
// 缓存存储
std::unordered_map<std::string, CacheEntry> cache_;
std::unordered_map<std::string, LRUIterator> lruMap_;
LRUList lruList_;
// 侵入式LRU链表 - 使用数组索引代替指针,提高缓存局部性
std::vector<LRUNode> lruNodes_;
uint32_t lruHead_ = 0; // 最近使用
uint32_t lruTail_ = 0; // 最久未使用
uint32_t freeList_ = 0; // 空闲节点链表
// 统计
size_t totalSize_ = 0;
@ -176,6 +185,13 @@ private:
};
std::vector<AsyncLoadTask> 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();

View File

@ -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_;

View File

@ -50,9 +50,21 @@ private:
std::vector<std::pair<Node *, Node *>> &collisions) const;
bool removeFromNode(QuadTreeNode *node, Node *object);
/**
* @brief 使线
* @param objects
* @param collisions
*/
void detectCollisionsInNode(
const std::vector<std::pair<Node *, Rect>> &objects,
std::vector<std::pair<Node *, Node *>> &collisions) const;
std::unique_ptr<QuadTreeNode> root_;
Rect worldBounds_;
size_t objectCount_ = 0;
// 碰撞检测用的临时缓冲区,避免重复分配
mutable std::vector<std::pair<Node *, Rect>> collisionBuffer_;
};
} // namespace extra2d

View File

@ -2,10 +2,14 @@
#include <extra2d/spatial/spatial_index.h>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace extra2d {
/**
* @brief -
* 使
*/
class SpatialHash : public ISpatialIndex {
public:
using CellKey = std::pair<int64_t, int64_t>;
@ -38,15 +42,34 @@ public:
float getCellSize() const { return cellSize_; }
private:
/**
* @brief - 使vector代替unordered_set减少内存开销
*/
struct Cell {
std::vector<Node *> 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<CellKey> &cells) const;
void insertIntoCells(Node *node, const Rect &bounds);
void removeFromCells(Node *node, const Rect &bounds);
float cellSize_;
std::unordered_map<CellKey, std::unordered_set<Node *>, CellKeyHash> grid_;
// 使用vector存储对象列表代替unordered_set内存更紧凑
std::unordered_map<CellKey, Cell, CellKeyHash> grid_;
std::unordered_map<Node *, Rect> objectBounds_;
size_t objectCount_ = 0;
// 查询用的临时缓冲区,避免重复分配
mutable std::vector<Node *> queryBuffer_;
mutable std::vector<std::pair<Node *, Node *>> collisionBuffer_;
};
} // namespace extra2d

View File

@ -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<int>& 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<std::any> 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<uint32_t>(stringPool_.size());
stringPool_.push_back(str);
return index;
}
uint32_t FramePropertySet::allocateVector(const std::vector<int>& vec) {
// 查找是否已存在相同vector
for (uint32_t i = 0; i < vectorPool_.size(); ++i) {
if (vectorPool_[i] == vec) {
return i;
}
}
// 分配新vector
uint32_t index = static_cast<uint32_t>(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<int>* FramePropertySet::getVector(uint32_t index) const {
if (index < vectorPool_.size()) {
return &vectorPool_[index];
}
return nullptr;
}
// ============================================================================
// 模板特化实现
// ============================================================================
template <> std::optional<bool> FramePropertySet::get<bool>(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<int> FramePropertySet::get<int>(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<float> FramePropertySet::get<float>(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<Vec2> FramePropertySet::get<Vec2>(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<Color> FramePropertySet::get<Color>(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<std::string> FramePropertySet::get<std::string>(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<std::vector<int>> FramePropertySet::get<std::vector<int>>(FramePropertyKey key) const {
auto it = properties_.find(key);
if (it == properties_.end()) return std::nullopt;
if (it->second.type == PropertyValueType::IntVector) {
const std::vector<int>* 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

View File

@ -156,6 +156,86 @@ Ptr<Texture> 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<uint32_t>(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> texture) {
if (!texture || !texture->isValid()) {
return;
@ -171,19 +251,22 @@ void TexturePool::add(const std::string &key, Ptr<Texture> 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<std::mutex> 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<std::mutex> 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);
}

View File

@ -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<Node> 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> 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<Node> &a, const Ptr<Node> &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<int>(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<Node> &a, const Ptr<Node> &b) {
return a->getZOrder() < b->getZOrder();
});
}
childrenOrderDirty_ = false;
}

View File

@ -180,52 +180,109 @@ std::vector<std::pair<Node *, Node *>> QuadTree::queryCollisions() const {
return collisions;
}
void QuadTree::detectCollisionsInNode(
const std::vector<std::pair<Node *, Rect>> &objects,
std::vector<std::pair<Node *, Node *>> &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<std::pair<Node *, Node *>> &collisions) const {
if (!node)
return;
std::vector<std::pair<Node *, Rect>> ancestors;
ancestors.reserve(objectCount_);
// 使用迭代而非递归,避免深层树栈溢出
struct StackItem {
const QuadTreeNode *node;
size_t ancestorStart;
size_t ancestorEnd;
};
std::function<void(const QuadTreeNode *)> visit =
[&](const QuadTreeNode *current) {
if (!current)
return;
std::vector<StackItem> 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() {

View File

@ -1,10 +1,36 @@
#include <algorithm>
#include <cstdint>
#include <extra2d/scene/node.h>
#include <extra2d/spatial/spatial_hash.h>
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_backO(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<int64_t>(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<Node *> SpatialHash::query(const Rect &area) const {
std::vector<Node *> results;
std::unordered_set<Node *> found;
queryBuffer_.clear();
std::vector<CellKey> 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<Node *> 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<Node *> 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<Node *> SpatialHash::query(const Vec2 &point) const {
}
std::vector<std::pair<Node *, Node *>> SpatialHash::queryCollisions() const {
std::vector<std::pair<Node *, Node *>> collisions;
struct PairHash {
size_t operator()(const std::pair<Node *, Node *> &p) const noexcept {
auto a = reinterpret_cast<std::uintptr_t>(p.first);
auto b = reinterpret_cast<std::uintptr_t>(p.second);
return std::hash<std::uintptr_t>{}(a) ^
(std::hash<std::uintptr_t>{}(b) << 1);
}
};
auto makeOrdered = [](Node *a, Node *b) -> std::pair<Node *, Node *> {
return a < b ? std::make_pair(a, b) : std::make_pair(b, a);
};
collisionBuffer_.clear();
std::unordered_set<std::pair<Node *, Node *>, PairHash> seen;
seen.reserve(objectCount_ * 2);
// 使用排序+唯一性检查代替unordered_set
std::vector<std::pair<Node *, Node *>> tempCollisions;
tempCollisions.reserve(objectCount_ * 2);
for (const auto &[cell, objects] : grid_) {
std::vector<Node *> 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() {

321
xmake.lua
View File

@ -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.0mesa和 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()

37
xmake.lua.backup Normal file
View File

@ -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()

115
xmake/targets/examples.lua Normal file
View File

@ -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

107
xmake/targets/extra2d.lua Normal file
View File

@ -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.0mesa和 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

View File

@ -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