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:
parent
9ef2d67236
commit
8b28f3c6df
|
|
@ -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% |
|
||||
|
||||
---
|
||||
|
||||
请确认此计划后,我将开始实施具体的代码修改。
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
// 使用插入排序优化小范围更新场景
|
||||
// 插入排序在大部分已有序的情况下性能接近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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
std::vector<StackItem> stack;
|
||||
stack.reserve(32);
|
||||
stack.push_back({node, 0, 0});
|
||||
|
||||
// 祖先对象列表,用于检测跨节点碰撞
|
||||
collisionBuffer_.clear();
|
||||
|
||||
while (!stack.empty()) {
|
||||
StackItem item = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
const QuadTreeNode *current = item.node;
|
||||
if (!current)
|
||||
return;
|
||||
continue;
|
||||
|
||||
// 检测当前节点对象与祖先对象的碰撞
|
||||
for (const auto &[obj, bounds] : current->objects) {
|
||||
for (const auto &[ancestorObj, ancestorBounds] : ancestors) {
|
||||
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 = ancestors.size();
|
||||
ancestors.insert(ancestors.end(), current->objects.begin(),
|
||||
// 记录当前节点的对象作为祖先
|
||||
size_t oldSize = collisionBuffer_.size();
|
||||
collisionBuffer_.insert(collisionBuffer_.end(), current->objects.begin(),
|
||||
current->objects.end());
|
||||
|
||||
// 将子节点压入栈(逆序以保持遍历顺序)
|
||||
if (current->children[0]) {
|
||||
for (const auto &child : current->children) {
|
||||
visit(child.get());
|
||||
for (int i = 3; i >= 0; --i) {
|
||||
if (current->children[i]) {
|
||||
stack.push_back({current->children[i].get(), oldSize,
|
||||
collisionBuffer_.size()});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ancestors.resize(oldSize);
|
||||
};
|
||||
|
||||
visit(node);
|
||||
// 恢复祖先列表(模拟递归返回)
|
||||
if (stack.empty() ||
|
||||
(stack.back().ancestorStart != oldSize &&
|
||||
stack.back().ancestorEnd != collisionBuffer_.size())) {
|
||||
collisionBuffer_.resize(oldSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QuadTree::clear() {
|
||||
|
|
|
|||
|
|
@ -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_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<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,26 +113,38 @@ 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) {
|
||||
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
321
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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue