diff --git a/docs/api/XCEngine/Audio/Audio.md b/docs/api/XCEngine/Audio/Audio.md index 8f69e351..ff483372 100644 --- a/docs/api/XCEngine/Audio/Audio.md +++ b/docs/api/XCEngine/Audio/Audio.md @@ -4,27 +4,72 @@ **类型**: `module` -**描述**: 音频系统、混音器、效果器与后端接口。 +**描述**: 提供音频系统、输出后端、基础混音器、效果器和音频通用类型定义。 ## 概览 -该目录与 `XCEngine/Audio` 对应的 public headers 保持平行,用于承载唯一的 canonical API 文档入口。 +`XCEngine::Audio` 是当前引擎音频运行时的基础模块。它的核心主链路比较明确: + +- [AudioSourceComponent](../Components/AudioSourceComponent/AudioSourceComponent.md) 在组件层负责音频片段解码与源级播放状态。 +- [AudioSystem](AudioSystem/AudioSystem.md) 负责维护全局监听器状态、活跃音频源列表和每帧混音入口。 +- [IAudioBackend](IAudioBackend/IAudioBackend.md) / [WASAPIBackend](WindowsAudioBackend/WindowsAudioBackend.md) 负责把浮点混音结果交给平台输出。 + +同时,这个模块里还定义了 [AudioMixer](AudioMixer/AudioMixer.md)、[Equalizer](Equalizer/Equalizer.md)、[FFTFilter](FFTFilter/FFTFilter.md)、[HRTF](HRTF/HRTF.md)、[Reverbation](Reverbation/Reverbation.md) 等构件。但按当前源码取证,它们更多属于“基础能力和扩展点”,并没有全部接入当前的全局播放主链路。 + +## 设计理解 + +从设计方向看,这个模块明显是朝“Unity 风格的 `AudioSource + AudioListener + AudioMixer`”体系靠拢的: + +- 场景对象负责挂载和空间关系。 +- 全局音频系统负责设备与混音调度。 +- 效果器和 mixer 负责后续音频处理扩展。 + +这种组织方式的好处是脚本层和编辑器层都容易理解,也便于以后逐步扩展到更完整的商用引擎音频架构。代价是当前版本里,很多接口形状已经有了,但接线成熟度并不一致,文档必须明确区分“已在主链路生效”和“当前只存值/基础实现”。 + +## 当前主链路 + +按当前实现,比较真实的运行路径是: + +1. `AudioSourceComponent` 在 `Play()` 时向 `AudioSystem` 注册自己。 +2. `AudioSystem::Update()` 遍历活跃音频源,把它们混到一个浮点 buffer。 +3. `AudioSystem` 把结果交给当前 `IAudioBackend`。 +4. 内建 Windows 后端把浮点样本转换为 `int16` 并交给系统音频输出。 + +需要特别注意的是: + +- [AudioMixer](AudioMixer/AudioMixer.md) 当前没有被这条主链路自动调用。 +- [Equalizer](Equalizer/Equalizer.md)、[FFTFilter](FFTFilter/FFTFilter.md)、[HRTF](HRTF/HRTF.md)、[Reverbation](Reverbation/Reverbation.md) 也没有自动接到全局 playback path。 +- 所以这些类型不能直接按“已经等同 Unity Audio Mixer / 完整 DSP graph”理解。 + +## 当前实现边界 + +- 默认后端是 Windows 专用实现;`AudioSystem::Initialize()` 当前直接创建 `WASAPI::WASAPIBackend`。 +- 尽管类名叫 `WASAPIBackend`,但当前实现实际使用的是 `waveOut*` WinMM API,而不是 Core Audio WASAPI COM 接口。 +- `AudioSystem::Stats` 里只有 `activeSources` 和 `totalSources` 在当前实现中被更新,`memoryUsage` 和 `cpuUsage` 仍是占位字段。 +- 公开的 mixer / effect / spatialization 类型已经存在,但并未完整串入全局混音图。 +- 当前测试覆盖主要集中在 `Resources::AudioClip`,`Audio` 模块核心类型本身缺少直接单元测试。 ## 头文件 -- [AudioConfig](AudioConfig/AudioConfig.md) - `AudioConfig.h` -- [AudioMixer](AudioMixer/AudioMixer.md) - `AudioMixer.h` -- [AudioSystem](AudioSystem/AudioSystem.md) - `AudioSystem.h` -- [AudioTypes](AudioTypes/AudioTypes.md) - `AudioTypes.h` -- [Equalizer](Equalizer/Equalizer.md) - `Equalizer.h` -- [FFTFilter](FFTFilter/FFTFilter.md) - `FFTFilter.h` -- [HRTF](HRTF/HRTF.md) - `HRTF.h` -- [IAudioBackend](IAudioBackend/IAudioBackend.md) - `IAudioBackend.h` -- [IAudioEffect](IAudioEffect/IAudioEffect.md) - `IAudioEffect.h` -- [Reverbation](Reverbation/Reverbation.md) - `Reverbation.h` -- [WindowsAudioBackend](WindowsAudioBackend/WindowsAudioBackend.md) - `WindowsAudioBackend.h` +- [AudioConfig](AudioConfig/AudioConfig.md) - `AudioConfig.h`,输出设备初始化配置。 +- [AudioMixer](AudioMixer/AudioMixer.md) - `AudioMixer.h`,基础 mixer 容器与效果链扩展点。 +- [AudioSystem](AudioSystem/AudioSystem.md) - `AudioSystem.h`,全局音频系统入口。 +- [AudioTypes](AudioTypes/AudioTypes.md) - `AudioTypes.h`,音频通用枚举和参数结构。 +- [Equalizer](Equalizer/Equalizer.md) - `Equalizer.h`,多段均衡器效果。 +- [FFTFilter](FFTFilter/FFTFilter.md) - `FFTFilter.h`,FFT 频谱分析型处理器。 +- [HRTF](HRTF/HRTF.md) - `HRTF.h`,简化双耳空间化处理器。 +- [IAudioBackend](IAudioBackend/IAudioBackend.md) - `IAudioBackend.h`,平台输出后端接口。 +- [IAudioEffect](IAudioEffect/IAudioEffect.md) - `IAudioEffect.h`,效果器通用接口。 +- [Reverbation](Reverbation/Reverbation.md) - `Reverbation.h`,基础混响效果。 +- [WASAPIBackend](WindowsAudioBackend/WindowsAudioBackend.md) - `WindowsAudioBackend.h`,当前内建 Windows 输出后端类型。 + +## 相关指南 + +- [Audio System And Effect Chain](../../_guides/Audio/Audio-System-And-Effect-Chain.md) - 解释当前音频主链路、与 Unity 风格系统的对应关系,以及 mixer/effect 为什么暂时还没完全接入。 ## 相关文档 - [上级目录](../XCEngine.md) +- [AudioSourceComponent](../Components/AudioSourceComponent/AudioSourceComponent.md) +- [AudioListenerComponent](../Components/AudioListenerComponent/AudioListenerComponent.md) - [API 总索引](../../main.md) diff --git a/docs/api/XCEngine/Audio/AudioConfig/AudioConfig.md b/docs/api/XCEngine/Audio/AudioConfig/AudioConfig.md index a64f5fd6..a4228b95 100644 --- a/docs/api/XCEngine/Audio/AudioConfig/AudioConfig.md +++ b/docs/api/XCEngine/Audio/AudioConfig/AudioConfig.md @@ -6,30 +6,43 @@ **头文件**: `XCEngine/Audio/AudioConfig.h` -**描述**: 定义 `XCEngine/Audio` 子目录中的 `AudioConfig` public API。 +**描述**: 描述音频输出设备初始化时使用的基础采样格式和缓冲配置。 -## 概述 +## 角色概述 -`AudioConfig.h` 是 `XCEngine/Audio` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`AudioConfig` 是音频系统和平台后端之间共享的配置载体。它主要回答三个问题: -## 声明概览 +- 采样格式是什么 +- 输出声道布局是什么 +- 缓冲区如何组织 -| 声明 | 类型 | 说明 | -|------|------|------| -| `AudioConfig` | `struct` | 头文件中的公开声明。 | +当前最直接的使用点是 [AudioSystem::Initialize](../AudioSystem/Initialize.md) 和 [WASAPIBackend](../WindowsAudioBackend/WindowsAudioBackend.md) 的初始化流程。 -## 结构体成员 +## 字段说明 -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `sampleRate` | `uint32_t` | 结构体公开字段。 | `48000` | -| `channels` | `uint16` | 结构体公开字段。 | `2` | -| `bitsPerSample` | `uint16` | 结构体公开字段。 | `16` | -| `speakerMode` | `SpeakerMode` | 结构体公开字段。 | `SpeakerMode::Stereo` | -| `bufferSize` | `uint32` | 结构体公开字段。 | `8192` | -| `bufferCount` | `uint32` | 结构体公开字段。 | `2` | +- `sampleRate` + 说明: 默认 `48000`,当前内建后端会把它写入 `WAVEFORMATEX`。 +- `channels` + 说明: 默认 `2`,当前主链路主要按立体声思路实现。 +- `bitsPerSample` + 说明: 默认 `16`,Windows 后端最终输出也会转成 `int16`。 +- `speakerMode` + 说明: 逻辑层面的声道模式描述,默认 `Stereo`。 +- `bufferSize` + 说明: 默认 `8192`,当前 `AudioSystem::Update()` 和后端缓冲逻辑都会读取它,但“按帧数还是按样本数”这一语义在现有实现里还不够统一。 +- `bufferCount` + 说明: 默认 `2`,当前更像配置预留字段,没有在已取证的主链路里形成完整策略分支。 + +## 当前实现限制 + +- 当前公开字段已经足够表达基础 PCM 输出格式,但还没有更完整的设备能力协商层。 +- `bufferSize` 在 `AudioSystem` 和内建 Windows 后端里的使用语义存在一定模糊性,文档不把它写成严格稳定契约。 +- `bufferCount` 当前没有看到明显的运行时消费点。 ## 相关文档 -- [当前目录](../Audio.md) - 返回 `Audio` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Audio.md) +- [AudioSystem](../AudioSystem/AudioSystem.md) +- [IAudioBackend](../IAudioBackend/IAudioBackend.md) +- [WASAPIBackend](../WindowsAudioBackend/WindowsAudioBackend.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Audio/AudioMixer/AudioMixer.md b/docs/api/XCEngine/Audio/AudioMixer/AudioMixer.md index 5f74562f..65a2cce0 100644 --- a/docs/api/XCEngine/Audio/AudioMixer/AudioMixer.md +++ b/docs/api/XCEngine/Audio/AudioMixer/AudioMixer.md @@ -6,40 +6,101 @@ **头文件**: `XCEngine/Audio/AudioMixer.h` -**描述**: 定义 `XCEngine/Audio` 子目录中的 `AudioMixer` public API。 +**描述**: 表示一个基础 mixer 容器,公开了总音量、静音、效果链、输出 mixer 和 3D 参数等混音控制接口。 -## 概述 +## 角色概述 -`AudioMixer.h` 是 `XCEngine/Audio` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +从 API 形状看,`AudioMixer` 明显是为更完整的音频路由系统预留的核心类型。它的设计方向和 Unity `AudioMixer` 概念相似: -## 声明概览 +- 可以挂效果器 +- 可以连接到另一个 output mixer +- 可以保存 3D 参数和每声道音量 -| 声明 | 类型 | 说明 | -|------|------|------| -| `AudioMixer` | `class` | 头文件中的公开声明。 | +但按当前实现,它还处在比较早期的基础阶段。 -## 公共方法 +## 当前实现行为 -| 方法 | 描述 | -|------|------| -| [AudioMixer()](Constructor.md) | 构造对象。 | -| [~AudioMixer()](Destructor.md) | 销毁对象并释放相关资源。 | -| [SetVolume](SetVolume.md) | 设置相关状态或配置。 | -| [GetVolume](GetVolume.md) | 获取相关状态或对象。 | -| [SetMute](SetMute.md) | 设置相关状态或配置。 | -| [IsMute](IsMute.md) | 查询当前状态。 | -| [AddEffect](AddEffect.md) | 添加元素或建立关联。 | -| [RemoveEffect](RemoveEffect.md) | 移除元素或解除关联。 | -| [ClearEffects](ClearEffects.md) | 清空内部数据。 | -| [ProcessAudio](ProcessAudio.md) | 公开方法,详见头文件声明。 | -| [SetOutputMixer](SetOutputMixer.md) | 设置相关状态或配置。 | -| [GetOutputMixer](GetOutputMixer.md) | 获取相关状态或对象。 | -| [Set3DParams](Set3DParams.md) | 设置相关状态或配置。 | -| [Get3DParams](Get3DParams.md) | 获取相关状态或对象。 | -| [SetChannelVolume](SetChannelVolume.md) | 设置相关状态或配置。 | -| [GetChannelVolume](GetChannelVolume.md) | 获取相关状态或对象。 | +### 1. 真实生效的部分很少 + +`ProcessAudio(float* buffer, uint32 sampleCount, uint32 channels)` 当前实际做的事情只有: + +- 如果静音,则等效音量为 `0` +- 如果总音量接近 0,则直接返回 +- 否则对整个 buffer 乘以一个全局音量系数 + +也就是说,当前 mixer 的实际生效能力基本只有: + +- 总音量 +- 总静音 + +### 2. 效果链目前只存,不处理 + +`AddEffect()` / `RemoveEffect()` / `ClearEffects()` 会维护 `m_effects` 容器,但 `ProcessAudio()` 当前并不会遍历效果器并调用它们的 `ProcessAudio()`。 + +这意味着效果链 API 已经有了,但主处理逻辑还没接上。 + +### 3. 其他配置当前主要是存值 + +以下状态当前也只是被保存下来: + +- `m_outputMixer` +- `m_3DParams` +- `m_channelVolumes` + +按当前源码,没有看到它们在 `ProcessAudio()` 里被真正使用。 + +## 所有权语义 + +- `AudioMixer` 不拥有 `IAudioEffect*` +- `AddEffect()` 只保存裸指针 +- `ClearEffects()` 只清空容器,不负责销毁 effect 实例 + +因此,效果器实例的生命周期需要由外部系统管理。 + +## 设计理解 + +这类设计在商用引擎里通常是合理的:先把 mixer、route、effect slot 的抽象搭起来,再逐步接线具体 DSP 逻辑。当前版本的问题不在于接口方向,而在于“多数字段已经存在,但还没被处理路径消费”。 + +## 线程语义 + +- 当前实现没有内部加锁。 +- 效果链和参数更新默认应按主线程配置路径使用。 + +## 当前实现限制 + +- 效果器链当前不会被执行。 +- `outputMixer` 当前不会形成真正的级联混音图。 +- `Audio3DParams` 当前不会直接影响 mixer 输出。 +- 每声道音量只被存储,没有应用到样本处理。 +- `ChannelVolume` 结构里的 `mute` 字段当前既无 public setter,也无处理路径消费。 +- 没有发现该类型的直接单元测试。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [SetVolume](SetVolume.md) +- [GetVolume](GetVolume.md) +- [SetMute](SetMute.md) +- [IsMute](IsMute.md) +- [AddEffect](AddEffect.md) +- [RemoveEffect](RemoveEffect.md) +- [ClearEffects](ClearEffects.md) +- [ProcessAudio](ProcessAudio.md) +- [SetOutputMixer](SetOutputMixer.md) +- [GetOutputMixer](GetOutputMixer.md) +- [Set3DParams](Set3DParams.md) +- [Get3DParams](Get3DParams.md) +- [SetChannelVolume](SetChannelVolume.md) +- [GetChannelVolume](GetChannelVolume.md) + +## 相关指南 + +- [Audio System And Effect Chain](../../../_guides/Audio/Audio-System-And-Effect-Chain.md) ## 相关文档 -- [当前目录](../Audio.md) - 返回 `Audio` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Audio.md) +- [IAudioEffect](../IAudioEffect/IAudioEffect.md) +- [AudioSystem](../AudioSystem/AudioSystem.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Audio/AudioSystem/AudioSystem.md b/docs/api/XCEngine/Audio/AudioSystem/AudioSystem.md index 8b26af15..a59521dd 100644 --- a/docs/api/XCEngine/Audio/AudioSystem/AudioSystem.md +++ b/docs/api/XCEngine/Audio/AudioSystem/AudioSystem.md @@ -6,46 +6,148 @@ **头文件**: `XCEngine/Audio/AudioSystem.h` -**描述**: 定义 `XCEngine/Audio` 子目录中的 `AudioSystem` public API。 +**描述**: 全局音频系统入口,负责音频后端生命周期、监听器状态、活跃音频源列表和每帧混音调度。 -## 概述 +## 角色概述 -`AudioSystem.h` 是 `XCEngine/Audio` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`AudioSystem` 是当前引擎音频主链路的中心。它的定位类似于一个轻量级全局运行时服务: -## 声明概览 +- 管理一个当前后端 `IAudioBackend` +- 持有监听器位置、旋转和速度 +- 持有活跃 `AudioSourceComponent` 列表 +- 在 `Update()` 中把各音频源混到输出 buffer -| 声明 | 类型 | 说明 | -|------|------|------| -| `AudioSystem` | `class` | 头文件中的公开声明。 | +如果从 Unity 风格心智模型来理解,它更接近“底层音频 runtime + 设备桥接层”,而不是 Unity Audio Mixer 那样完整的音频图形系统。 -## 公共方法 +## 生命周期 -| 方法 | 描述 | -|------|------| -| [Get](Get.md) | 获取相关状态或对象。 | -| [Initialize](Initialize.md) | 初始化内部状态。 | -| [Shutdown](Shutdown.md) | 关闭并清理内部状态。 | -| [Update](Update.md) | 更新运行时状态。 | -| [SetBackend](SetBackend.md) | 设置相关状态或配置。 | -| [GetBackend](GetBackend.md) | 获取相关状态或对象。 | -| [GetCurrentDevice](GetCurrentDevice.md) | 获取相关状态或对象。 | -| [SetDevice](SetDevice.md) | 设置相关状态或配置。 | -| [GetAvailableDevices](GetAvailableDevices.md) | 获取相关状态或对象。 | -| [GetMasterVolume](GetMasterVolume.md) | 获取相关状态或对象。 | -| [SetMasterVolume](SetMasterVolume.md) | 设置相关状态或配置。 | -| [IsMuted](IsMuted.md) | 查询当前状态。 | -| [SetMuted](SetMuted.md) | 设置相关状态或配置。 | -| [ProcessAudio](ProcessAudio.md) | 公开方法,详见头文件声明。 | -| [SetListenerTransform](SetListenerTransform.md) | 设置相关状态或配置。 | -| [SetListenerVelocity](SetListenerVelocity.md) | 设置相关状态或配置。 | -| [GetListenerPosition](GetListenerPosition.md) | 获取相关状态或对象。 | -| [GetListenerRotation](GetListenerRotation.md) | 获取相关状态或对象。 | -| [GetListenerVelocity](GetListenerVelocity.md) | 获取相关状态或对象。 | -| [RegisterSource](RegisterSource.md) | 注册对象、回调或映射。 | -| [UnregisterSource](UnregisterSource.md) | 取消注册对象、回调或映射。 | -| [GetStats](GetStats.md) | 获取相关状态或对象。 | +### 初始化 + +`Initialize(const AudioConfig&)` 当前会: + +1. 如果已有后端,先 `Shutdown()` +2. 直接创建 `WASAPI::WASAPIBackend` +3. 调用后端 `Initialize(config)` +4. 成功后调用 `Start()` +5. 失败时打印日志并清空后端 + +这说明当前默认后端策略是写死的,不是从插件表或平台抽象层动态选择。 + +### 关闭 + +`Shutdown()` 会: + +- 停止并销毁当前后端 +- 清空 `m_activeSources` + +它不会额外遍历场景组件做状态回填,因此组件侧的播放状态和系统侧活跃源列表可能需要上层逻辑保持一致。 + +## 当前混音路径 + +`Update(float deltaTime)` 是当前主路径: + +1. 保存 `m_deltaTime` +2. 检查后端是否存在且正在运行 +3. 根据后端配置分配一个浮点 `mixBuffer` +4. 遍历 `m_activeSources` +5. 对每个“已启用且正在播放”的源调用 `ProcessSource()` +6. 把最终混音结果交给后端 `ProcessAudio()` +7. 更新 `Stats.activeSources` 和 `Stats.totalSources` + +`ProcessSource()` 当前只是简单转发到 `AudioSourceComponent::ProcessAudio()`。这意味着: + +- `AudioSystem` 本身不做复杂路由 +- 不会自动经过 `AudioMixer` +- 不会自动挂任何效果器链 + +## 监听器语义 + +当前系统只维护一份全局监听器状态: + +- `SetListenerTransform()` +- `SetListenerVelocity()` +- `GetListenerPosition()` +- `GetListenerRotation()` +- `GetListenerVelocity()` + +[AudioListenerComponent](../../Components/AudioListenerComponent/AudioListenerComponent.md) 的作用,就是在 `Update()` 时把宿主对象的 `Transform` 推进到这里。 + +## 设备控制 + +当前提供: + +- `GetCurrentDevice()` +- `SetDevice()` +- `GetAvailableDevices()` +- `GetMasterVolume()` +- `SetMasterVolume()` +- `IsMuted()` +- `SetMuted()` + +这些能力本质上都是对当前后端的转发。没有后端时,它们会返回空字符串、默认值或直接 no-op。 + +## Stats 语义 + +`Stats` 结构当前公开: + +- `activeSources` +- `totalSources` +- `memoryUsage` +- `cpuUsage` + +但当前实现里实际更新的只有前两个字段。`memoryUsage` 和 `cpuUsage` 仍是占位值。 + +## 线程语义 + +- 当前实现没有为 `m_activeSources` 做加锁保护。 +- `RegisterSource()` 和 `UnregisterSource()` 只是直接改 `std::vector`。 +- 后端可能有自己的线程,但 `AudioSystem` 文档本身不提供并发安全保证。 + +默认应按主线程驱动 `Update()`,并谨慎看待与后端线程的交互边界。 + +## 当前实现限制 + +- `RegisterSource()` 没有去重保护,同一 source 可能被重复注册。 +- 没有自动接入 [AudioMixer](../AudioMixer/AudioMixer.md) 或效果器链。 +- `ProcessAudio()` 这个直接转发接口当前硬编码把 sample rate 传成 `48000`。 +- `IAudioBackend::ProcessAudio()` 的 `bufferSize` 语义在当前调用方与内建 Windows 后端之间并不完全一致:`AudioSystem` 传的是样本数量,而后端按字节大小解释。 +- `Stats.memoryUsage` / `Stats.cpuUsage` 当前没有真实采集逻辑。 +- 没有看到直接覆盖该类型的单元测试。 + +## 相关方法 + +- [Get](Get.md) +- [Initialize](Initialize.md) +- [Shutdown](Shutdown.md) +- [Update](Update.md) +- [SetBackend](SetBackend.md) +- [GetBackend](GetBackend.md) +- [GetCurrentDevice](GetCurrentDevice.md) +- [SetDevice](SetDevice.md) +- [GetAvailableDevices](GetAvailableDevices.md) +- [GetMasterVolume](GetMasterVolume.md) +- [SetMasterVolume](SetMasterVolume.md) +- [IsMuted](IsMuted.md) +- [SetMuted](SetMuted.md) +- [ProcessAudio](ProcessAudio.md) +- [SetListenerTransform](SetListenerTransform.md) +- [SetListenerVelocity](SetListenerVelocity.md) +- [GetListenerPosition](GetListenerPosition.md) +- [GetListenerRotation](GetListenerRotation.md) +- [GetListenerVelocity](GetListenerVelocity.md) +- [RegisterSource](RegisterSource.md) +- [UnregisterSource](UnregisterSource.md) +- [GetStats](GetStats.md) + +## 相关指南 + +- [Audio System And Effect Chain](../../../_guides/Audio/Audio-System-And-Effect-Chain.md) ## 相关文档 -- [当前目录](../Audio.md) - 返回 `Audio` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Audio.md) +- [IAudioBackend](../IAudioBackend/IAudioBackend.md) +- [WASAPIBackend](../WindowsAudioBackend/WindowsAudioBackend.md) +- [AudioSourceComponent](../../Components/AudioSourceComponent/AudioSourceComponent.md) +- [AudioListenerComponent](../../Components/AudioListenerComponent/AudioListenerComponent.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Audio/AudioTypes/AudioTypes.md b/docs/api/XCEngine/Audio/AudioTypes/AudioTypes.md index db4b8837..52307a54 100644 --- a/docs/api/XCEngine/Audio/AudioTypes/AudioTypes.md +++ b/docs/api/XCEngine/Audio/AudioTypes/AudioTypes.md @@ -2,42 +2,70 @@ **命名空间**: `XCEngine::Audio` -**类型**: `enum class` +**类型**: `header-overview` **头文件**: `XCEngine/Audio/AudioTypes.h` -**描述**: 定义 `XCEngine/Audio` 子目录中的 `AudioTypes` public API。 +**描述**: 汇总 `Audio` 模块共享的基础整数别名、状态枚举和音频参数结构体。 -## 概述 +## 角色概述 -`AudioTypes.h` 是 `XCEngine/Audio` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`AudioTypes.h` 是 `Audio` 模块的通用类型中心。它定义了: -## 声明概览 +- 基础整数别名 +- 音频资源、格式和设备相关枚举 +- 播放状态和停止模式 +- 3D 音频、缓冲区和空间化参数结构 -| 声明 | 类型 | 说明 | -|------|------|------| -| `AudioResourceType` | `enum class` | 头文件中的公开声明。 | -| `AudioLoadState` | `enum class` | 头文件中的公开声明。 | -| `AudioFormat` | `enum class` | 头文件中的公开声明。 | -| `SpeakerMode` | `enum class` | 头文件中的公开声明。 | -| `AudioChannel` | `enum class` | 头文件中的公开声明。 | -| `PlayState` | `enum class` | 头文件中的公开声明。 | -| `StopMode` | `enum class` | 头文件中的公开声明。 | -| `PanMode` | `enum class` | 头文件中的公开声明。 | -| `VolumeSource` | `enum class` | 头文件中的公开声明。 | -| `Audio3DParams` | `struct` | 头文件中的公开声明。 | -| `AudioBufferDesc` | `struct` | 头文件中的公开声明。 | -| `SpatializerParams` | `struct` | 头文件中的公开声明。 | +这些类型被 [AudioSystem](../AudioSystem/AudioSystem.md)、[AudioSourceComponent](../../Components/AudioSourceComponent/AudioSourceComponent.md)、[AudioMixer](../AudioMixer/AudioMixer.md) 和效果器模块共同使用。 -## 枚举值 +## 当前最关键的类型 -| 枚举值 | 数值 | 描述 | -|--------|------|------| -| `AudioClip` | - | 枚举项。 | -| `AudioMixer` | - | 枚举项。 | -| `AudioBank` | - | 枚举项。 | +### 播放状态相关 + +- `PlayState` + 说明: 当前被 `AudioSourceComponent` 直接使用。 +- `StopMode` + 说明: API 已存在,但当前 `AudioSourceComponent::Stop()` 还没有区分 `Immediate` 和 `AllowFadeOut` 的实现分支。 + +### 设备与格式相关 + +- `AudioFormat` +- `SpeakerMode` +- `AudioBufferDesc` + +这些类型更多服务于资源和后端配置层。当前内建 Windows 后端主要消费的是基础 PCM 参数,而不是所有高级格式枚举。 + +### 3D 音频相关 + +- `Audio3DParams` +- `SpatializerParams` + +`Audio3DParams` 已经被 `AudioSourceComponent` 和 `AudioMixer` 暴露,但从当前实现看,真正被稳定消费的字段还很有限,例如 `maxDistance` 会影响当前音频源距离衰减,而其他字段多数仍处于预留或轻量接线状态。 + +### 其他枚举 + +- `PanMode` +- `VolumeSource` +- `AudioResourceType` +- `AudioLoadState` +- `AudioChannel` + +这些类型完善了音频 API 形状,但并不意味着当前版本已经对每一种模式都给出完整运行时实现。 + +## 文档使用建议 + +阅读 `Audio` 模块时,可以把这个头文件理解为“共享词汇表”。当具体类型页提到 `PlayState`、`Audio3DParams` 或 `SpeakerMode` 时,真正的业务语义仍然要回到各自消费它们的实现去判断。 + +## 当前实现限制 + +- 头文件里的类型覆盖面已经比当前实际落地能力更宽。 +- 某些枚举和结构主要用于预留未来扩展点,不应被误读为“当前全部生效”。 ## 相关文档 -- [当前目录](../Audio.md) - 返回 `Audio` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Audio.md) +- [AudioConfig](../AudioConfig/AudioConfig.md) +- [AudioSystem](../AudioSystem/AudioSystem.md) +- [AudioSourceComponent](../../Components/AudioSourceComponent/AudioSourceComponent.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Audio/Equalizer/Equalizer.md b/docs/api/XCEngine/Audio/Equalizer/Equalizer.md index e790474c..9ee2f79b 100644 --- a/docs/api/XCEngine/Audio/Equalizer/Equalizer.md +++ b/docs/api/XCEngine/Audio/Equalizer/Equalizer.md @@ -6,39 +6,101 @@ **头文件**: `XCEngine/Audio/Equalizer.h` -**描述**: 定义 `XCEngine/Audio` 子目录中的 `Equalizer` public API。 +**描述**: 一个多段参数均衡器效果,实现上使用按频段串行处理的 IIR 型滤波系数。 -## 概述 +## 角色概述 -`Equalizer.h` 是 `XCEngine/Audio` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Equalizer` 是当前 `IAudioEffect` 派生效果器里最像“真实音频处理器”的一个。它提供: -## 声明概览 +- 可调频段数量 +- 每段频率 +- 每段增益 +- 每段 Q 值 -| 声明 | 类型 | 说明 | -|------|------|------| -| `Equalizer` | `class` | 继承自 `IAudioEffect` 的公开声明。 | +从设计思路上,它对应的是常见商用引擎里的基础 EQ 效果,而不是完整母带级处理器。 -## 公共方法 +## 当前实现行为 -| 方法 | 描述 | -|------|------| -| [Equalizer()](Constructor.md) | 构造对象。 | -| [~Equalizer()](Destructor.md) | 销毁对象并释放相关资源。 | -| [ProcessAudio](ProcessAudio.md) | 公开方法,详见头文件声明。 | -| [SetBandCount](SetBandCount.md) | 设置相关状态或配置。 | -| [GetBandCount](GetBandCount.md) | 获取相关状态或对象。 | -| [SetBandFrequency](SetBandFrequency.md) | 设置相关状态或配置。 | -| [GetBandFrequency](GetBandFrequency.md) | 获取相关状态或对象。 | -| [SetBandGain](SetBandGain.md) | 设置相关状态或配置。 | -| [GetBandGain](GetBandGain.md) | 获取相关状态或对象。 | -| [SetBandQ](SetBandQ.md) | 设置相关状态或配置。 | -| [GetBandQ](GetBandQ.md) | 获取相关状态或对象。 | -| [SetEnabled](SetEnabled.md) | 设置相关状态或配置。 | -| [IsEnabled](IsEnabled.md) | 查询当前状态。 | -| [SetWetMix](SetWetMix.md) | 设置相关状态或配置。 | -| [GetWetMix](GetWetMix.md) | 获取相关状态或对象。 | +### 1. 会直接原地修改 buffer + +`ProcessAudio()` 当前会: + +- 检查启用状态和参数合法性 +- 按通道遍历 +- 对每个频段顺序执行滤波 +- 直接回写到输入 buffer + +因此,它是一个真正会改写音频样本的效果器。 + +### 2. 参数更新会重算滤波系数 + +以下接口都会触发 `ComputeCoefficients()`: + +- `SetBandFrequency()` +- `SetBandGain()` +- `SetBandQ()` + +当前实现里还包含基础 clamp: + +- 频率 `20Hz ~ 20000Hz` +- 增益 `-24dB ~ 24dB` +- Q 值 `0.1 ~ 10.0` + +### 3. 默认带数和初始频点 + +构造函数默认创建 4 段 EQ,并把中心频率初始化为: + +- `100Hz` +- `500Hz` +- `2000Hz` +- `8000Hz` + +## `wetMix` 的现实状态 + +虽然类型覆写了 `SetWetMix()` / `GetWetMix()`,但当前 `ProcessAudio()` 并没有按 dry/wet 混合输出,它是直接把处理结果覆盖回 buffer。 + +所以 `wetMix` 目前更像被保存下来的参数,而不是已接入的混合控制。 + +## 通道语义 + +当前实现通过 `m_bandStates[ch * m_bandCount + band]` 管理状态,但 `SetBandCount()` 里把状态数组固定 resize 为 `count * 2`。这说明当前实现默认按双声道思路写的: + +- 对单声道和立体声更合理 +- 对多于 2 个通道的音频没有看到可靠保障 + +## 线程语义 + +- 当前实现没有内部加锁。 +- 参数修改和样本处理不应默认认为可跨线程并发。 + +## 当前实现限制 + +- `wetMix` 当前不参与真正的干湿混合。 +- 样本率固定按 `48000` 计算系数,没有看到从实际流配置动态同步采样率。 +- 通道状态数组当前按双声道规模预设,多通道支持不完整。 +- 没有看到该类型的直接单元测试。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [ProcessAudio](ProcessAudio.md) +- [SetBandCount](SetBandCount.md) +- [GetBandCount](GetBandCount.md) +- [SetBandFrequency](SetBandFrequency.md) +- [GetBandFrequency](GetBandFrequency.md) +- [SetBandGain](SetBandGain.md) +- [GetBandGain](GetBandGain.md) +- [SetBandQ](SetBandQ.md) +- [GetBandQ](GetBandQ.md) +- [SetEnabled](SetEnabled.md) +- [IsEnabled](IsEnabled.md) +- [SetWetMix](SetWetMix.md) +- [GetWetMix](GetWetMix.md) ## 相关文档 -- [当前目录](../Audio.md) - 返回 `Audio` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Audio.md) +- [IAudioEffect](../IAudioEffect/IAudioEffect.md) +- [AudioMixer](../AudioMixer/AudioMixer.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Audio/FFTFilter/FFTFilter.md b/docs/api/XCEngine/Audio/FFTFilter/FFTFilter.md index 4dc1317b..cd3da0fa 100644 --- a/docs/api/XCEngine/Audio/FFTFilter/FFTFilter.md +++ b/docs/api/XCEngine/Audio/FFTFilter/FFTFilter.md @@ -6,33 +6,90 @@ **头文件**: `XCEngine/Audio/FFTFilter.h` -**描述**: 定义 `XCEngine/Audio` 子目录中的 `FFTFilter` public API。 +**描述**: 基于 kissfft 的频谱分析处理器,提供 FFT 尺寸与平滑参数控制,并维护最近一次频谱结果。 -## 概述 +## 角色概述 -`FFTFilter.h` 是 `XCEngine/Audio` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +尽管名字叫 `FFTFilter`,按当前实现它更准确的定位是“频谱分析器”而不是“会显著改变声音的滤波器”。 -## 声明概览 +它的主要职责是: -| 声明 | 类型 | 说明 | -|------|------|------| -| `FFTFilter` | `class` | 继承自 `IAudioEffect` 的公开声明。 | +- 从输入 buffer 抽取一段样本 +- 做 FFT +- 计算频谱幅度 +- 维护一个平滑后的 `m_spectrumData` -## 公共方法 +## 当前实现行为 -| 方法 | 描述 | -|------|------| -| [FFTFilter()](Constructor.md) | 构造对象。 | -| [~FFTFilter()](Destructor.md) | 销毁对象并释放相关资源。 | -| [ProcessAudio](ProcessAudio.md) | 公开方法,详见头文件声明。 | -| [SetFFTSize](SetFFTSize.md) | 设置相关状态或配置。 | -| [GetFFTSize](GetFFTSize.md) | 获取相关状态或对象。 | -| [SetSmoothingFactor](SetSmoothingFactor.md) | 设置相关状态或配置。 | -| [GetSmoothingFactor](GetSmoothingFactor.md) | 获取相关状态或对象。 | -| [GetSpectrumData](GetSpectrumData.md) | 获取相关状态或对象。 | -| [GetSpectrumSize](GetSpectrumSize.md) | 获取相关状态或对象。 | +### 1. 当前不会改写输入 buffer + +`ProcessAudio()` 虽然是 `IAudioEffect` 风格接口,但当前实现只是读取 buffer 并更新频谱数据,不会把 FFT 结果再写回输入样本。 + +因此,如果把它放在效果链语境里理解,它更接近 analyzer 节点,而不是 tone-shaping filter。 + +### 2. 处理前提 + +只有在以下条件满足时才会真正计算频谱: + +- 组件启用 +- `buffer != nullptr` +- `sampleCount > 0` +- `channels > 0` +- `sampleCount >= m_fftSize` + +否则直接返回。 + +### 3. 频谱数据带平滑 + +`ComputeFFT()` 会把新计算的 dB 结果与 `m_spectrumData` 通过 `m_smoothingFactor` 做指数平滑。 + +`SetSmoothingFactor()` 会把该值 clamp 到 `0.0f ~ 1.0f`。 + +## 输入样本提取方式 + +当前实现会先构造一个 `monoBuffer`,再从多声道输入里取值。需要注意的是: + +- 单声道时直接取第 0 声道 +- 多声道时使用 `i % channels` 选择通道 + +这不是标准的 downmix,而是一种非常轻量的取样策略。对分析准确性要求高的场景,不能把它当成严谨的多声道频谱融合实现。 + +## FFT 资源管理 + +- 构造和 `SetFFTSize()` 会调用 `InitializeFFT()` +- 内部通过 kissfft 配置对象维护 FFT 计划 +- `ComputeFFT()` 每次都会动态分配临时时域和频域数组,再在末尾释放 + +这说明当前实现更偏功能可用性,而不是高性能零分配实时分析。 + +## 线程语义 + +- 当前实现没有内部加锁。 +- 频谱数据读写默认不提供并发安全保证。 + +## 当前实现限制 + +- 名义上是 “Filter”,当前实际更像 analyzer。 +- 处理过程不会回写处理后的音频样本。 +- 多声道输入只做轻量通道抽取,不是严格 downmix。 +- `ComputeFFT()` 每次调用都有堆分配。 +- 没有看到该类型的直接单元测试。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [ProcessAudio](ProcessAudio.md) +- [SetFFTSize](SetFFTSize.md) +- [GetFFTSize](GetFFTSize.md) +- [SetSmoothingFactor](SetSmoothingFactor.md) +- [GetSmoothingFactor](GetSmoothingFactor.md) +- [GetSpectrumData](GetSpectrumData.md) +- [GetSpectrumSize](GetSpectrumSize.md) ## 相关文档 -- [当前目录](../Audio.md) - 返回 `Audio` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Audio.md) +- [IAudioEffect](../IAudioEffect/IAudioEffect.md) +- [AudioMixer](../AudioMixer/AudioMixer.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Audio/HRTF/HRTF.md b/docs/api/XCEngine/Audio/HRTF/HRTF.md index 575291ef..c2f0adea 100644 --- a/docs/api/XCEngine/Audio/HRTF/HRTF.md +++ b/docs/api/XCEngine/Audio/HRTF/HRTF.md @@ -6,40 +6,96 @@ **头文件**: `XCEngine/Audio/HRTF.h` -**描述**: 定义 `XCEngine/Audio` 子目录中的 `HRTF` public API。 +**描述**: 一个简化的双耳空间化处理器,根据声源与监听器相对方向对立体声样本做基础方位处理。 -## 概述 +## 角色概述 -`HRTF.h` 是 `XCEngine/Audio` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`HRTF` 试图提供比简单左右声像更进一步的空间听感处理。它不是 `IAudioEffect` 派生类,而是一个独立的空间处理器,要求调用方显式传入: -## 声明概览 +- `sourcePosition` +- `listenerPosition` +- `listenerRotation` -| 声明 | 类型 | 说明 | -|------|------|------| -| `HRTFParams` | `struct` | 头文件中的公开声明。 | -| `HRTF` | `class` | 头文件中的公开声明。 | +从设计方向上,它对应的是商业引擎里空间化模块的雏形。 -## 公共方法 +## 当前实现行为 -| 方法 | 描述 | -|------|------| -| [HRTF()](Constructor.md) | 构造对象。 | -| [~HRTF()](Destructor.md) | 销毁对象并释放相关资源。 | -| [ProcessAudio](ProcessAudio.md) | 公开方法,详见头文件声明。 | -| [SetEnabled](SetEnabled.md) | 设置相关状态或配置。 | -| [IsEnabled](IsEnabled.md) | 查询当前状态。 | -| [SetHRTFEnabled](SetHRTFEnabled.md) | 设置相关状态或配置。 | -| [IsHRTFEnabled](IsHRTFEnabled.md) | 查询当前状态。 | -| [SetQualityLevel](SetQualityLevel.md) | 设置相关状态或配置。 | -| [GetQualityLevel](GetQualityLevel.md) | 获取相关状态或对象。 | -| [SetCrossFeed](SetCrossFeed.md) | 设置相关状态或配置。 | -| [GetCrossFeed](GetCrossFeed.md) | 获取相关状态或对象。 | -| [SetDopplerShiftEnabled](SetDopplerShiftEnabled.md) | 设置相关状态或配置。 | -| [IsDopplerShiftEnabled](IsDopplerShiftEnabled.md) | 查询当前状态。 | -| [SetSpeedOfSound](SetSpeedOfSound.md) | 设置相关状态或配置。 | -| [GetSpeedOfSound](GetSpeedOfSound.md) | 获取相关状态或对象。 | +### 1. 处理流程是“方向计算 + 简化双耳处理” + +`ProcessAudio()` 当前会: + +1. 根据源和监听器关系计算 `azimuth` / `elevation` +2. 计算 `ITD` 和 `ILD` +3. 如果开启 HRTF,则执行 `ApplyHRTF()` +4. 否则退回到 `ApplySimplePanning()` + +### 2. 当前真正生效的是简化方向增益与延迟线 + +从源码看,当前 HRTF 路径主要依赖: + +- 方位角带来的左右增益差 +- 一个固定长度的左右延迟线 +- `interauralLevelDifference` 对一侧增益的衰减 + +这使它更像“轻量双耳近似”,而不是完整的 HRTF 卷积系统。 + +### 3. 退化路径是简单声像 + +当 `m_hrtfEnabled == false` 时,会退化成简单左右声像分配。这提供了一个比完整 HRTF 更轻的回退模式。 + +## 当前参数里哪些真的生效 + +### 已确认直接影响处理的参数 + +- `enabled` +- `hrtfEnabled` +- `speedOfSound` +- `qualityLevel` + 说明: 当前主要体现在参数保存与质量档位范围约束,不代表已经实现多档卷积质量策略。 + +### 当前主要是存值或接线不完整的参数 + +- `crossFeed` +- `dopplerEnabled` +- `m_prevSourcePosition` +- `m_prevListenerPosition` +- `m_prevDopplerShift` + +这些字段和开关已经存在,但在当前处理路径里没有看到完整消费逻辑。 + +## 当前实现限制 + +- `ComputePinnaEffect()` 当前定义了但没有接入处理主路径。 +- `ComputeITD()` 会计算 `interauralTimeDelay`,但 `ApplyHRTF()` 没有按该值动态调整读取偏移,延迟线处理仍然非常简化。 +- `dopplerEnabled` 当前没有真正参与 Doppler 计算。 +- 只对 `channels >= 2` 的输入做有意义处理。 +- 没有看到该类型的直接单元测试。 + +## 设计理解 + +对当前版本最准确的理解方式是:它已经比单纯的 pan 多一步空间方向近似,但距离完整商用双耳空间化系统还有明显差距。文档因此不会把它写成“完整 HRTF 渲染器”。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [ProcessAudio](ProcessAudio.md) +- [SetEnabled](SetEnabled.md) +- [IsEnabled](IsEnabled.md) +- [SetHRTFEnabled](SetHRTFEnabled.md) +- [IsHRTFEnabled](IsHRTFEnabled.md) +- [SetQualityLevel](SetQualityLevel.md) +- [GetQualityLevel](GetQualityLevel.md) +- [SetCrossFeed](SetCrossFeed.md) +- [GetCrossFeed](GetCrossFeed.md) +- [SetDopplerShiftEnabled](SetDopplerShiftEnabled.md) +- [IsDopplerShiftEnabled](IsDopplerShiftEnabled.md) +- [SetSpeedOfSound](SetSpeedOfSound.md) +- [GetSpeedOfSound](GetSpeedOfSound.md) ## 相关文档 -- [当前目录](../Audio.md) - 返回 `Audio` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Audio.md) +- [AudioListenerComponent](../../Components/AudioListenerComponent/AudioListenerComponent.md) +- [AudioSourceComponent](../../Components/AudioSourceComponent/AudioSourceComponent.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Audio/IAudioBackend/IAudioBackend.md b/docs/api/XCEngine/Audio/IAudioBackend/IAudioBackend.md index bdbe0f67..c06e3d5c 100644 --- a/docs/api/XCEngine/Audio/IAudioBackend/IAudioBackend.md +++ b/docs/api/XCEngine/Audio/IAudioBackend/IAudioBackend.md @@ -2,45 +2,88 @@ **命名空间**: `XCEngine::Audio` -**类型**: `class (abstract)` +**类型**: `class (interface)` **头文件**: `XCEngine/Audio/IAudioBackend.h` -**描述**: 定义 `XCEngine/Audio` 子目录中的 `IAudioBackend` public API。 +**描述**: 定义平台音频输出后端的抽象接口,让 `AudioSystem` 能以统一方式控制设备、音量、静音和样本提交。 -## 概述 +## 角色概述 -`IAudioBackend.h` 是 `XCEngine/Audio` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`IAudioBackend` 把平台相关的设备枚举、设备切换、播放线程和最终样本输出从 [AudioSystem](../AudioSystem/AudioSystem.md) 中分离出来。这样做的主要收益是: -## 声明概览 +- 音频系统可以保持统一的上层入口 +- 平台后端可以独立替换 +- Windows、未来的其他平台或测试后端都可以复用同一套调用形状 -| 声明 | 类型 | 说明 | -|------|------|------| -| `IAudioBackend` | `class` | 头文件中的公开声明。 | +## 接口契约 -## 公共方法 +当前接口主要分成四组: -| 方法 | 描述 | -|------|------| -| [~IAudioBackend()](Destructor.md) | 销毁对象并释放相关资源。 | -| [Initialize](Initialize.md) | 初始化内部状态。 | -| [Shutdown](Shutdown.md) | 关闭并清理内部状态。 | -| [GetDeviceName](GetDeviceName.md) | 获取相关状态或对象。 | -| [GetAvailableDevices](GetAvailableDevices.md) | 获取相关状态或对象。 | -| [SetDevice](SetDevice.md) | 设置相关状态或配置。 | -| [GetMasterVolume](GetMasterVolume.md) | 获取相关状态或对象。 | -| [SetMasterVolume](SetMasterVolume.md) | 设置相关状态或配置。 | -| [IsMuted](IsMuted.md) | 查询当前状态。 | -| [SetMuted](SetMuted.md) | 设置相关状态或配置。 | -| [Start](Start.md) | 公开方法,详见头文件声明。 | -| [Stop](Stop.md) | 公开方法,详见头文件声明。 | -| [Suspend](Suspend.md) | 公开方法,详见头文件声明。 | -| [Resume](Resume.md) | 公开方法,详见头文件声明。 | -| [ProcessAudio](ProcessAudio.md) | 公开方法,详见头文件声明。 | -| [IsRunning](IsRunning.md) | 查询当前状态。 | -| [GetConfig](GetConfig.md) | 获取相关状态或对象。 | +- 生命周期: `Initialize()`、`Shutdown()`、`Start()`、`Stop()`、`Suspend()`、`Resume()` +- 设备管理: `GetDeviceName()`、`GetAvailableDevices()`、`SetDevice()` +- 全局控制: `GetMasterVolume()`、`SetMasterVolume()`、`IsMuted()`、`SetMuted()` +- 音频提交: `ProcessAudio()`、`IsRunning()`、`GetConfig()` + +从设计上说,这是一层典型的 runtime backend 抽象,而不是资源加载接口或效果器接口。 + +## 当前实现语境 + +在当前代码库里,已取证到的内建实现只有 [WASAPIBackend](../WindowsAudioBackend/WindowsAudioBackend.md)。 + +因此,这个接口虽然是跨平台抽象形状,但当前落地能力仍然以 Windows 输出为中心。 + +## `ProcessAudio()` 的现实语义 + +这是当前接口里最需要谨慎理解的一个方法。 + +签名是: + +```cpp +void ProcessAudio(float* buffer, uint32 bufferSize, uint32 channels, uint32 sampleRate); +``` + +从命名上看,`bufferSize` 像是“缓冲大小”;但在当前调用路径里: + +- `AudioSystem::Update()` 把“浮点样本数量”传给它 +- `WASAPIBackend::ProcessAudio()` 却按“字节大小”去做 `bufferSize / sizeof(float)` 计算 + +这说明当前接口的 buffer size 语义还没有完全收敛成稳定契约。文档不应把它描述成已经完全明确且无歧义的规范。 + +## 线程语义 + +接口本身没有声明线程模型,也没有提供同步保证。具体线程行为完全取决于实现类。 + +例如当前 Windows 实现会自己维护后台线程和回调;但这不代表所有 `IAudioBackend` 实现都必须采用同样模型。 + +## 当前实现限制 + +- 当前只有 Windows 内建实现。 +- `ProcessAudio()` 的 `bufferSize` 语义在当前调用方和实现方之间存在不一致。 +- 文档范围内没有发现专门针对该接口契约的测试后端或 mock backend。 + +## 相关方法 + +- [Initialize](Initialize.md) +- [Shutdown](Shutdown.md) +- [GetDeviceName](GetDeviceName.md) +- [GetAvailableDevices](GetAvailableDevices.md) +- [SetDevice](SetDevice.md) +- [GetMasterVolume](GetMasterVolume.md) +- [SetMasterVolume](SetMasterVolume.md) +- [IsMuted](IsMuted.md) +- [SetMuted](SetMuted.md) +- [Start](Start.md) +- [Stop](Stop.md) +- [Suspend](Suspend.md) +- [Resume](Resume.md) +- [ProcessAudio](ProcessAudio.md) +- [IsRunning](IsRunning.md) +- [GetConfig](GetConfig.md) ## 相关文档 -- [当前目录](../Audio.md) - 返回 `Audio` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Audio.md) +- [AudioSystem](../AudioSystem/AudioSystem.md) +- [WASAPIBackend](../WindowsAudioBackend/WindowsAudioBackend.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Audio/IAudioEffect/IAudioEffect.md b/docs/api/XCEngine/Audio/IAudioEffect/IAudioEffect.md index ab33e7d4..c1174f0b 100644 --- a/docs/api/XCEngine/Audio/IAudioEffect/IAudioEffect.md +++ b/docs/api/XCEngine/Audio/IAudioEffect/IAudioEffect.md @@ -2,34 +2,77 @@ **命名空间**: `XCEngine::Audio` -**类型**: `class (abstract)` +**类型**: `class (interface)` **头文件**: `XCEngine/Audio/IAudioEffect.h` -**描述**: 定义 `XCEngine/Audio` 子目录中的 `IAudioEffect` public API。 +**描述**: 定义音频效果器的最小公共接口,包括样本处理入口和通用的启用/湿声比例控制。 -## 概述 +## 角色概述 -`IAudioEffect.h` 是 `XCEngine/Audio` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`IAudioEffect` 是 `Audio` 模块里最轻量的一层效果器抽象。它要求具体效果器至少提供: -## 声明概览 +- 一个 in-place 的 `ProcessAudio()` 入口 +- 一个启用开关 +- 一个 `wetMix` 参数 -| 声明 | 类型 | 说明 | -|------|------|------| -| `IAudioEffect` | `class` | 头文件中的公开声明。 | +从 API 形状看,这是一种很常见的 DSP effect base class 设计,适合作为 `AudioMixer` 的效果槽接口。 -## 公共方法 +## 当前实现行为 -| 方法 | 描述 | -|------|------| -| [~IAudioEffect()](Destructor.md) | 销毁对象并释放相关资源。 | -| [ProcessAudio](ProcessAudio.md) | 公开方法,详见头文件声明。 | -| [SetEnabled](SetEnabled.md) | 设置相关状态或配置。 | -| [IsEnabled](IsEnabled.md) | 查询当前状态。 | -| [SetWetMix](SetWetMix.md) | 设置相关状态或配置。 | -| [GetWetMix](GetWetMix.md) | 获取相关状态或对象。 | +基类本身非常轻: + +- `ProcessAudio()` 是纯虚函数 +- `SetEnabled()` / `IsEnabled()` 默认只读写 `m_enabled` +- `SetWetMix()` / `GetWetMix()` 默认只读写 `m_wetMix` + +它不拥有缓冲区,也不定义线程模型、样本格式转换或多通道路由规则。 + +## 当前实现语境 + +当前已取证到的派生效果器包括: + +- [Equalizer](../Equalizer/Equalizer.md) +- [FFTFilter](../FFTFilter/FFTFilter.md) +- [Reverbation](../Reverbation/Reverbation.md) + +但需要明确: + +- 这些效果器当前没有自动插入 [AudioSystem](../AudioSystem/AudioSystem.md) 主链路 +- [AudioMixer](../AudioMixer/AudioMixer.md) 虽然能保存 effect 指针,但当前也不会真正执行效果链 + +## 使用和实现上的注意点 + +- `IAudioEffect` 不约束所有权,调用方需要决定 effect 实例何时创建和销毁。 +- 不同派生类对 `wetMix` 的支持成熟度并不一致。 +- 基类并不保证派生类一定会把 `wetMix` 真正作用到样本处理结果上。 + +这也是为什么类型页需要区分“接口形状”和“具体效果器当前实现”。 + +## 线程语义 + +- 当前接口不提供线程安全保证。 +- 典型使用方式应视为同一线程内配置和处理,除非具体实现另有说明。 + +## 当前实现限制 + +- 这是最小接口,不是完整 DSP graph 节点规范。 +- `wetMix` 只是公共参数约定,具体效果器是否真实使用它,要看各自实现。 +- 没有看到该接口层的专门测试覆盖。 + +## 相关方法 + +- [ProcessAudio](ProcessAudio.md) +- [SetEnabled](SetEnabled.md) +- [IsEnabled](IsEnabled.md) +- [SetWetMix](SetWetMix.md) +- [GetWetMix](GetWetMix.md) ## 相关文档 -- [当前目录](../Audio.md) - 返回 `Audio` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Audio.md) +- [AudioMixer](../AudioMixer/AudioMixer.md) +- [Equalizer](../Equalizer/Equalizer.md) +- [FFTFilter](../FFTFilter/FFTFilter.md) +- [Reverbation](../Reverbation/Reverbation.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Audio/Reverbation/Reverbation.md b/docs/api/XCEngine/Audio/Reverbation/Reverbation.md index c1b3424e..649ee644 100644 --- a/docs/api/XCEngine/Audio/Reverbation/Reverbation.md +++ b/docs/api/XCEngine/Audio/Reverbation/Reverbation.md @@ -6,39 +6,86 @@ **头文件**: `XCEngine/Audio/Reverbation.h` -**描述**: 定义 `XCEngine/Audio` 子目录中的 `Reverbation` public API。 +**描述**: 一个基础混响效果器,内部使用 comb filter 和 all-pass filter 结构生成简化空间残响。 -## 概述 +## 角色概述 -`Reverbation.h` 是 `XCEngine/Audio` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Reverbation` 是当前 `IAudioEffect` 派生效果器里最接近传统混响器的一种实现。它采用经典的: -## 声明概览 +- 多组 comb filter +- 多组 all-pass filter -| 声明 | 类型 | 说明 | -|------|------|------| -| `Reverbation` | `class` | 继承自 `IAudioEffect` 的公开声明。 | +来生成基础 reverberation 尾音。 -## 公共方法 +## 当前实现行为 -| 方法 | 描述 | -|------|------| -| [Reverbation()](Constructor.md) | 构造对象。 | -| [~Reverbation()](Destructor.md) | 销毁对象并释放相关资源。 | -| [ProcessAudio](ProcessAudio.md) | 公开方法,详见头文件声明。 | -| [SetRoomSize](SetRoomSize.md) | 设置相关状态或配置。 | -| [GetRoomSize](GetRoomSize.md) | 获取相关状态或对象。 | -| [SetDamping](SetDamping.md) | 设置相关状态或配置。 | -| [GetDamping](GetDamping.md) | 获取相关状态或对象。 | -| [SetWetMix](SetWetMix.md) | 设置相关状态或配置。 | -| [GetWetMix](GetWetMix.md) | 获取相关状态或对象。 | -| [SetDryMix](SetDryMix.md) | 设置相关状态或配置。 | -| [GetDryMix](GetDryMix.md) | 获取相关状态或对象。 | -| [SetWidth](SetWidth.md) | 设置相关状态或配置。 | -| [GetWidth](GetWidth.md) | 获取相关状态或对象。 | -| [SetFreeze](SetFreeze.md) | 设置相关状态或配置。 | -| [IsFreeze](IsFreeze.md) | 查询当前状态。 | +### 1. 会真正改写输入样本 + +`ProcessAudio()` 会逐帧读取输入样本,经过 comb/all-pass 处理后,把结果写回 buffer。因此它不是参数占位器,而是真正参与样本处理的效果器。 + +### 2. 当前按“首通道输入 -> 全通道复制输出”工作 + +实现中每一帧只取: + +`float input = buffer[i * channels];` + +也就是只读第一个通道的输入,再把最终 `outSample` 写回所有通道。 + +这意味着当前更像是“单声道混响核心 + 多通道镜像输出”,而不是严格意义上的多声道独立混响。 + +### 3. 真实参与处理的参数 + +以下参数当前会影响处理: + +- `roomSize` +- `damping` +- `wetMix` +- `dryMix` + +其中: + +- `SetRoomSize()` 会更新 comb filter 的 feedback +- `SetDamping()` 会更新 comb filter 的阻尼系数 + +## 当前未接线或未充分使用的参数 + +- `width` +- `freeze` + +这两个 setter 当前会保存值,但在已取证的 `ProcessAudio()` 路径里没有看到实际消费逻辑。 + +## 线程语义 + +- 当前实现没有内部加锁。 +- 参数修改和样本处理默认不应视为并发安全。 + +## 当前实现限制 + +- 只读取首通道输入,再复制到所有输出通道。 +- `width` 和 `freeze` 当前未参与真实处理。 +- 没有看到该类型的直接单元测试。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [ProcessAudio](ProcessAudio.md) +- [SetRoomSize](SetRoomSize.md) +- [GetRoomSize](GetRoomSize.md) +- [SetDamping](SetDamping.md) +- [GetDamping](GetDamping.md) +- [SetWetMix](SetWetMix.md) +- [GetWetMix](GetWetMix.md) +- [SetDryMix](SetDryMix.md) +- [GetDryMix](GetDryMix.md) +- [SetWidth](SetWidth.md) +- [GetWidth](GetWidth.md) +- [SetFreeze](SetFreeze.md) +- [IsFreeze](IsFreeze.md) ## 相关文档 -- [当前目录](../Audio.md) - 返回 `Audio` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Audio.md) +- [IAudioEffect](../IAudioEffect/IAudioEffect.md) +- [AudioMixer](../AudioMixer/AudioMixer.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Audio/WindowsAudioBackend/WindowsAudioBackend.md b/docs/api/XCEngine/Audio/WindowsAudioBackend/WindowsAudioBackend.md index d00038fd..ca733569 100644 --- a/docs/api/XCEngine/Audio/WindowsAudioBackend/WindowsAudioBackend.md +++ b/docs/api/XCEngine/Audio/WindowsAudioBackend/WindowsAudioBackend.md @@ -1,4 +1,4 @@ -# WindowsAudioBackend +# WASAPIBackend **命名空间**: `XCEngine::Audio::WASAPI` @@ -6,42 +6,105 @@ **头文件**: `XCEngine/Audio/WindowsAudioBackend.h` -**描述**: 定义 `XCEngine/Audio` 子目录中的 `WindowsAudioBackend` public API。 +**描述**: 当前内建的 Windows 音频输出后端,实现 `IAudioBackend` 接口并负责设备打开、输出线程和样本提交。 -## 概述 +## 命名说明 -`WindowsAudioBackend.h` 是 `XCEngine/Audio` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +文档目录名和头文件名是 `WindowsAudioBackend`,但头文件里声明的主要类型名是 `WASAPIBackend`。 -## 声明概览 +更重要的是,虽然类名带 `WASAPI`,当前实现实际上使用的是: -| 声明 | 类型 | 说明 | -|------|------|------| -| `WASAPIBackend` | `class` | 继承自 `IAudioBackend` 的公开声明。 | +- `` +- `waveOutOpen` +- `waveOutWrite` +- `waveOutPrepareHeader` -## 公共方法 +也就是更接近 WinMM `waveOut` 路径,而不是 Core Audio WASAPI COM 接口。文档必须把这一点明确说出来,避免名称误导。 -| 方法 | 描述 | -|------|------| -| [WASAPIBackend()](Constructor.md) | 构造对象。 | -| [~WASAPIBackend()](Destructor.md) | 销毁对象并释放相关资源。 | -| [Initialize](Initialize.md) | 初始化内部状态。 | -| [Shutdown](Shutdown.md) | 关闭并清理内部状态。 | -| [GetDeviceName](GetDeviceName.md) | 获取相关状态或对象。 | -| [GetAvailableDevices](GetAvailableDevices.md) | 获取相关状态或对象。 | -| [SetDevice](SetDevice.md) | 设置相关状态或配置。 | -| [GetMasterVolume](GetMasterVolume.md) | 获取相关状态或对象。 | -| [SetMasterVolume](SetMasterVolume.md) | 设置相关状态或配置。 | -| [IsMuted](IsMuted.md) | 查询当前状态。 | -| [SetMuted](SetMuted.md) | 设置相关状态或配置。 | -| [Start](Start.md) | 公开方法,详见头文件声明。 | -| [Stop](Stop.md) | 公开方法,详见头文件声明。 | -| [Suspend](Suspend.md) | 公开方法,详见头文件声明。 | -| [Resume](Resume.md) | 公开方法,详见头文件声明。 | -| [ProcessAudio](ProcessAudio.md) | 公开方法,详见头文件声明。 | -| [IsRunning](IsRunning.md) | 查询当前状态。 | -| [GetConfig](GetConfig.md) | 获取相关状态或对象。 | +## 角色概述 + +`WASAPIBackend` 是当前唯一已取证到的 `IAudioBackend` 实现,也是 [AudioSystem](../AudioSystem/AudioSystem.md) 默认创建的后端。 + +它负责: + +- 用 `AudioConfig` 初始化 Windows 输出格式 +- 枚举 waveOut 设备 +- 维护一个后台线程 +- 把浮点混音结果转换成 `int16` 输出缓冲 + +## 当前实现行为 + +### 1. 初始化阶段 + +`Initialize()` 会: + +- 保存配置 +- 填充 `WAVEFORMATEX` +- 调用 `InitDevice()` +- 调用 `InitBuffer()` + +`InitDevice()` 当前通过 `waveOutOpen(..., WAVE_MAPPER, ...)` 打开默认设备,并把 `m_deviceName` 设成 `"Default Device"`。 + +### 2. 设备切换并不完整 + +`SetDevice(const std::string&)` 现在只是在枚举到设备名匹配时保存 `m_deviceName` 并返回 true,但没有重新打开对应设备。 + +这意味着它更像“记录想使用哪个设备”,而不是已经完成真正的设备热切换。 + +### 3. 启停与线程 + +`Start()` 会创建后台线程,`Stop()` 会关闭运行标记并 `join()` 线程。线程主函数会等待 `m_dataReady`,然后切换前后缓冲并把当前 front buffer 写给系统。 + +### 4. 样本提交 + +`ProcessAudio()` 当前会: + +- 如果静音或 buffer 为空则直接返回 +- 读取 `m_masterVolume` +- 把浮点样本 clamp 到 `[-1, 1]` +- 转成 `int16` +- 写入 back buffer +- 标记 `m_dataReady` + +## 当前实现限制 + +- 类名虽然叫 `WASAPIBackend`,实际不是 WASAPI COM 后端。 +- `SetDevice()` 不会真正重建设备句柄。 +- `PrepareBackData()` 当前是空实现。 +- `ProcessAudio()` 把传入的 `bufferSize` 按字节数解释,而当前 `AudioSystem` 调用方传的是样本数量,这会造成语义不一致。 +- 内部使用的双缓冲大小固定基于 `BufferSize = 8192` 和 `int16` 缓冲数组,和 `AudioConfig` 的关系还不完全统一。 +- 这是 Windows 专用实现,不适用于跨平台文档语义。 +- 没有看到该类型的直接单元测试。 + +## 线程语义 + +- 后端内部确实使用线程、原子变量、互斥量和条件变量。 +- 但这不意味着上层可以任意并发调用所有 public 方法;当前文档仍不提供完整并发安全承诺。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [Initialize](Initialize.md) +- [Shutdown](Shutdown.md) +- [GetDeviceName](GetDeviceName.md) +- [GetAvailableDevices](GetAvailableDevices.md) +- [SetDevice](SetDevice.md) +- [GetMasterVolume](GetMasterVolume.md) +- [SetMasterVolume](SetMasterVolume.md) +- [IsMuted](IsMuted.md) +- [SetMuted](SetMuted.md) +- [Start](Start.md) +- [Stop](Stop.md) +- [Suspend](Suspend.md) +- [Resume](Resume.md) +- [ProcessAudio](ProcessAudio.md) +- [IsRunning](IsRunning.md) +- [GetConfig](GetConfig.md) ## 相关文档 -- [当前目录](../Audio.md) - 返回 `Audio` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Audio.md) +- [IAudioBackend](../IAudioBackend/IAudioBackend.md) +- [AudioSystem](../AudioSystem/AudioSystem.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Core/Asset/Asset.md b/docs/api/XCEngine/Core/Asset/Asset.md index 1cb7dae5..5c730536 100644 --- a/docs/api/XCEngine/Core/Asset/Asset.md +++ b/docs/api/XCEngine/Core/Asset/Asset.md @@ -1,14 +1,38 @@ # Asset -**命名空间**: `XCEngine::Core::Asset` +**命名空间**: `XCEngine::Resources` **类型**: `submodule` -**描述**: 资源句柄、资源管理器、异步加载与依赖图。 +**描述**: 定义运行时资源系统的标识、句柄、缓存、加载和依赖管理基础设施。 ## 概览 -该目录与 `XCEngine/Core/Asset` 对应的 public headers 保持平行,用于承载唯一的 canonical API 文档入口。 +`Core/Asset` 是当前引擎运行时资源系统的基础层。它并不直接实现材质、着色器、网格或音频资源本身,而是提供一套更底层的通用约定: + +- 用 [ResourceTypes](ResourceTypes/ResourceTypes.md) 定义资源类型和 GUID 规则 +- 用 [IResource](IResource/IResource.md) 约束资源对象的最小公共接口 +- 用 [ImportSettings](ImportSettings/ImportSettings.md) 传递资源导入配置 +- 用 [ResourceManager](ResourceManager/ResourceManager.md) 做同步加载、加载器注册和引用计数入口 +- 用 [ResourceHandle](ResourceHandle/ResourceHandle.md) 在上层代码里传递带类型的资源引用 +- 用 [ResourceCache](ResourceCache/ResourceCache.md)、[AsyncLoader](AsyncLoader/AsyncLoader.md)、[ResourceDependencyGraph](ResourceDependencyGraph/ResourceDependencyGraph.md) 为缓存、异步加载和依赖卸载预留基础结构 + +从设计意图上看,这一层更接近商业引擎里“运行时资源服务”的雏形,而不是 Unity `AssetDatabase` 那类编辑器资产数据库。它强调的是运行时可加载、可引用、可扩展,而不是导入流水线和编辑器管理界面。 + +## 设计要点 + +- 资源身份和资源对象分离。`ResourceGUID` 负责标识,`IResource` 负责对象实例,`ResourceHandle` 负责类型化访问。 +- 资源类型分派依赖 `GetResourceType()` 模板特化,而不是 RTTI 或字符串注册。 +- `ResourceManager` 当前是主入口,但它和 `ResourceCache`、`AsyncLoader`、`ResourceDependencyGraph` 的集成还没有完全做完。 +- 当前自动注册的内建 loader 只有 `MaterialLoader` 和 `ShaderLoader`。 +- 这套接口已经有明显的“未来做流式加载和资源依赖卸载”的方向,但当前很多能力仍处于占位或半成品状态。 + +## 当前实现现状 + +- 同步加载路径是当前最可靠的使用方式。 +- `Load()` 直接把传入路径交给 loader,当前不会自动调用 `ResolvePath()`,所以 `SetResourceRoot()` 只是一个路径拼接辅助,不是统一的实际加载入口。 +- `LoadAsync()` 已经有 API 形状,但当前队列不会被后台线程消费,成功回调路径也没有闭环,不能把它当成成熟的异步流式系统。 +- `ResourceManager`、`ResourceCache` 和句柄引用计数之间还没有完全打通,文档里会明确标出这些差异,而不把它们包装成“完整资源生命周期管理”。 ## 头文件 @@ -21,6 +45,17 @@ - [ResourceManager](ResourceManager/ResourceManager.md) - `ResourceManager.h` - [ResourceTypes](ResourceTypes/ResourceTypes.md) - `ResourceTypes.h` +## 推荐阅读顺序 + +1. 先读 [ResourceTypes](ResourceTypes/ResourceTypes.md),理解 `ResourceType` 和 `ResourceGUID`。 +2. 再读 [IResource](IResource/IResource.md) 与 [ImportSettings](ImportSettings/ImportSettings.md),理解资源对象和导入配置契约。 +3. 然后读 [ResourceManager](ResourceManager/ResourceManager.md) 与 [ResourceHandle](ResourceHandle/ResourceHandle.md),这是当前实际最常用的运行时入口。 +4. 最后再读 [ResourceCache](ResourceCache/ResourceCache.md)、[AsyncLoader](AsyncLoader/AsyncLoader.md)、[ResourceDependencyGraph](ResourceDependencyGraph/ResourceDependencyGraph.md),它们更偏向扩展机制和当前限制说明。 + +## 相关指南 + +- [Resource Lifecycle And Handles](../../../_guides/Core/Asset/Resource-Lifecycle-And-Handles.md) + ## 相关文档 - [上级目录](../Core.md) diff --git a/docs/api/XCEngine/Core/Asset/AsyncLoader/AsyncLoader.md b/docs/api/XCEngine/Core/Asset/AsyncLoader/AsyncLoader.md index 749b72fa..b6805dac 100644 --- a/docs/api/XCEngine/Core/Asset/AsyncLoader/AsyncLoader.md +++ b/docs/api/XCEngine/Core/Asset/AsyncLoader/AsyncLoader.md @@ -2,41 +2,132 @@ **命名空间**: `XCEngine::Resources` -**类型**: `class (singleton)` +**类型**: `class` **头文件**: `XCEngine/Core/Asset/AsyncLoader.h` -**描述**: 定义 `XCEngine/Core/Asset` 子目录中的 `AsyncLoader` public API。 +**描述**: 为资源异步加载预留的请求队列与回调分发接口,但当前实现仍明显未完成。 -## 概述 +## 角色概述 -`AsyncLoader.h` 是 `XCEngine/Core/Asset` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`AsyncLoader` 的 API 形状表达得很清楚:它想成为资源系统里的后台加载服务,负责接收 `LoadRequest`、在工作线程里执行 loader,然后把结果回送给主线程或调用方。 -## 声明概览 +这也是商业引擎资源系统非常常见的一层设计,因为真正的资源加载通常要考虑: -| 声明 | 类型 | 说明 | -|------|------|------| -| `LoadRequest` | `struct` | 头文件中的公开声明。 | -| `AsyncLoader` | `class` | 头文件中的公开声明。 | +- 后台 I/O +- 导入配置复制 +- 回调线程切换 +- 进度统计 +- 取消与批量清理 -## 公共方法 +但当前版本还只搭好了外形,没有把执行链真正闭环。 -| 方法 | 描述 | -|------|------| -| [Get](Get.md) | 获取相关状态或对象。 | -| [Initialize](Initialize.md) | 初始化内部状态。 | -| [Shutdown](Shutdown.md) | 关闭并清理内部状态。 | -| [Submit](Submit.md) | 公开方法,详见头文件声明。 | -| [Update](Update.md) | 更新运行时状态。 | -| [IsLoading](IsLoading.md) | 查询当前状态。 | -| [GetPendingCount](GetPendingCount.md) | 获取相关状态或对象。 | -| [GetProgress](GetProgress.md) | 获取相关状态或对象。 | -| [CancelAll](CancelAll.md) | 判断当前条件下是否可执行。 | -| [Cancel](Cancel.md) | 判断当前条件下是否可执行。 | -| [~AsyncLoader()](Destructor.md) | 销毁对象并释放相关资源。 | -| [AsyncLoader()](Constructor.md) | 构造对象。 | +## 当前结构 + +`LoadRequest` 当前包含: + +- `path` +- `type` +- `callback` +- `settings` +- `requestId` + +`requestId` 通过原子自增静态计数器生成。 + +`AsyncLoader` 自身同时提供两种使用方式: + +- `AsyncLoader::Get()` 返回全局单例 +- [ResourceManager](../ResourceManager/ResourceManager.md) 在 `Initialize()` 中再拥有一个自己的 `AsyncLoader` 实例 + +这说明当前架构上已经出现“双入口”现象。真实运行时主要走的是 `ResourceManager` 持有的那一份,而不是 `AsyncLoader::Get()` 单例。 + +## 当前行为 + +### 初始化与关闭 + +- `Initialize(workerThreadCount)` 当前直接忽略参数,不创建线程。 +- `Shutdown()` 当前只是调用 `CancelAll()`。 + +也就是说,初始化并不会启动任何真正的后台执行环境。 + +### 提交请求 + +`Submit()` 当前流程是: + +1. 组装 `LoadRequest` +2. 通过 `FindLoader(type)` 查找 loader +3. 如果没有 loader,立即通过回调返回失败结果 +4. 如果有 loader,只把请求推入 `m_pendingQueue` +5. 递增 `m_pendingCount` 与 `m_totalRequested` + +关键点在第 4 步之后就结束了。当前没有工作线程,也没有任何地方会真正从 `m_pendingQueue` 取出请求并调用 `loader->Load()`。 + +### 更新与完成回调 + +`Update()` 当前只会处理 `m_completedQueue`。但现在的 `QueueCompleted()` 是空实现,所以成功加载请求根本不会进入完成队列。 + +这意味着: + +- “无 loader”的失败回调会立即触发 +- “有 loader”的请求会一直留在 pending 队列里 +- `IsLoading()` 可能长期保持 true +- `GetProgress()` 也不会自然推进到你期待的完成状态 + +## 导入配置与生命周期边界 + +`LoadRequest` 保存的是 `ImportSettings*` 裸指针。当前版本里: + +- `Submit()` 不会克隆 settings +- 队列里只是保留原始指针 +- 由于真正后台执行逻辑还没做完,这个问题暂时没有完全暴露 + +但从接口契约上讲,这已经说明未来如果异步路径做实,调用方就不能假设 settings 会被安全复制。当前文档必须把这个生命周期边界讲清楚。 + +## 设计取向 + +把异步加载单独做成服务是对的。它让资源系统可以朝商业引擎常见的“主线程驱动资源引用,后台线程完成 I/O 与解码”的方向演进。 + +但当前版本更准确的描述应该是: + +- 它是异步加载架构草图 +- 不是已经可投产的后台加载器 + +如果你需要稳定的当前行为,应该优先走 [ResourceManager::Load](../ResourceManager/Load.md) 的同步路径。 + +## 线程语义 + +- `m_pendingQueue` 和 `m_completedQueue` 各自有独立 mutex 保护。 +- `m_pendingCount` / `m_completedCount` 使用原子计数。 +- 但没有真实工作线程,也没有完整的跨线程回调时序保证。 + +## 当前实现限制 + +- `Initialize()` 忽略 `workerThreadCount`。 +- 没有后台线程处理 `m_pendingQueue`。 +- `QueueCompleted()` 是空实现。 +- `Update()` 只能消费完成队列,但当前没有成功请求会进入完成队列。 +- `Cancel(requestId)` 是 stub。 +- `AsyncLoader::Get()` 与 `ResourceManager` 自持实例并存,架构入口不统一。 +- `ImportSettings*` 当前按裸指针跨请求传递,没有复制或所有权说明。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [Get](Get.md) +- [Initialize](Initialize.md) +- [Shutdown](Shutdown.md) +- [Submit](Submit.md) +- [Update](Update.md) +- [IsLoading](IsLoading.md) +- [GetPendingCount](GetPendingCount.md) +- [GetProgress](GetProgress.md) +- [CancelAll](CancelAll.md) +- [Cancel](Cancel.md) ## 相关文档 -- [当前目录](../Asset.md) - 返回 `Asset` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Asset.md) +- [ResourceManager](../ResourceManager/ResourceManager.md) +- [ImportSettings](../ImportSettings/ImportSettings.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Asset/IResource/IResource.md b/docs/api/XCEngine/Core/Asset/IResource/IResource.md index c110b79c..05cc648f 100644 --- a/docs/api/XCEngine/Core/Asset/IResource/IResource.md +++ b/docs/api/XCEngine/Core/Asset/IResource/IResource.md @@ -6,34 +6,101 @@ **头文件**: `XCEngine/Core/Asset/IResource.h` -**描述**: 定义 `XCEngine/Core/Asset` 子目录中的 `IResource` public API。 +**描述**: 所有运行时资源对象的基础抽象接口,统一资源身份、有效性和内存统计入口。 -## 概述 +## 角色概述 -`IResource.h` 是 `XCEngine/Core/Asset` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`IResource` 是资源系统里最基础的对象契约。具体资源类型,例如纹理、网格、材质、音频片段,最终都应该能回答一组相同的问题: -## 声明概览 +- 你是什么类型的资源 +- 你的名字和路径是什么 +- 你的 GUID 是什么 +- 你当前是否有效 +- 你占了多少内存 +- 需要释放时应该怎么做 -| 声明 | 类型 | 说明 | -|------|------|------| -| `IResource` | `class` | 头文件中的公开声明。 | +这种抽象方式和商业引擎里常见的 runtime resource base class 很像。好处是 loader、缓存和管理器可以只依赖这一层;代价是更具体的资源能力仍然要靠派生类自己补充。 -## 公共方法 +## ConstructParams 与初始化 -| 方法 | 描述 | -|------|------| -| [~IResource()](Destructor.md) | 销毁对象并释放相关资源。 | -| [GetType](GetType.md) | 获取相关状态或对象。 | -| [GetName](GetName.md) | 获取相关状态或对象。 | -| [GetPath](GetPath.md) | 获取相关状态或对象。 | -| [GetGUID](GetGUID.md) | 获取相关状态或对象。 | -| [IsValid](IsValid.md) | 查询当前状态。 | -| [GetMemorySize](GetMemorySize.md) | 获取相关状态或对象。 | -| [Release](Release.md) | 释放引用或底层资源。 | -| [Initialize](Initialize.md) | 初始化内部状态。 | -| [SetInvalid](SetInvalid.md) | 设置相关状态或配置。 | +`ConstructParams` 当前包含: + +- `name` +- `path` +- `guid` +- `memorySize` + +`Initialize(const ConstructParams&)` 是一个内联辅助函数,它会直接填充: + +- `m_name` +- `m_path` +- `m_guid` +- `m_memorySize` +- `m_isValid = true` + +这意味着当前资源对象通常走的是“先默认构造,再由 loader 调 `Initialize()` 填元数据”的模式。 + +## 有效性与释放语义 + +- `IsValid()` 是纯虚函数,但大多数当前测试资源和典型实现都直接返回基类里的 `m_isValid`。 +- `SetInvalid()` 只是把 `m_isValid` 置为 false。 +- `Release()` 是纯虚函数,释放策略由派生类决定。 + +在现有代码和测试里,`Release()` 常见的两种语义是: + +- 直接 `delete this` +- 做某种自定义资源释放逻辑 + +所以 `IResource` 本身不提供统一所有权模型,它只规定“对象知道如何释放自己”。 + +## 当前数据布局特点 + +需要特别指出的是,`m_name`、`m_path`、`m_guid`、`m_isValid`、`m_memorySize` 这些字段当前都位于 `public` 区域。也就是说,这个基类不仅是接口,还是一个公开存储通用资源元数据的轻量基底。 + +这种设计的好处是简单直接,loader 和测试写起来很方便;代价是封装性较弱,调用方和派生类都更容易直接依赖内部字段布局。 + +## 设计取向 + +这层设计和 Unity 那种“所有资源都继承统一对象基类”的思路有相似点,但它更轻: + +- 没有统一对象树 +- 没有反射或序列化元数据 +- 没有自动引用计数 +- 没有线程模型 + +它更像是“资源系统最低共识层”,为 loader、cache 和 manager 提供统一接口。 + +## 线程语义 + +- `IResource` 自身没有锁。 +- `Initialize()` 和 `SetInvalid()` 都是直接写成员。 +- 线程安全要靠具体资源类型或外部资源管理流程保证。 + +## 当前实现限制 + +- 没有内建引用计数。 +- 没有内建序列化、磁盘保存或 GPU 上传契约。 +- `Initialize()` 不是虚函数,也没有防止重复初始化的保护。 +- `SetInvalid()` 只是翻转标志位,不会自动触发额外清理。 +- 公共字段布局暴露较多,封装边界比较弱。 + +## 相关方法 + +- [Destructor](Destructor.md) +- [GetType](GetType.md) +- [GetName](GetName.md) +- [GetPath](GetPath.md) +- [GetGUID](GetGUID.md) +- [IsValid](IsValid.md) +- [GetMemorySize](GetMemorySize.md) +- [Release](Release.md) +- [Initialize](Initialize.md) +- [SetInvalid](SetInvalid.md) ## 相关文档 -- [当前目录](../Asset.md) - 返回 `Asset` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Asset.md) +- [ResourceHandle](../ResourceHandle/ResourceHandle.md) +- [ResourceManager](../ResourceManager/ResourceManager.md) +- [ResourceTypes](../ResourceTypes/ResourceTypes.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Asset/ImportSettings/ImportSettings.md b/docs/api/XCEngine/Core/Asset/ImportSettings/ImportSettings.md index 49f9920c..8401f0e8 100644 --- a/docs/api/XCEngine/Core/Asset/ImportSettings/ImportSettings.md +++ b/docs/api/XCEngine/Core/Asset/ImportSettings/ImportSettings.md @@ -6,28 +6,72 @@ **头文件**: `XCEngine/Core/Asset/ImportSettings.h` -**描述**: 定义 `XCEngine/Core/Asset` 子目录中的 `ImportSettings` public API。 +**描述**: 资源导入配置的抽象基类,用于把资源类型相关的加载参数从 loader 接口中分离出来。 -## 概述 +## 角色概述 -`ImportSettings.h` 是 `XCEngine/Core/Asset` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`ImportSettings` 解决的问题不是“怎么加载资源”,而是“加载同一个资源时,调用方想附带哪些资源类型特定配置”。例如: -## 声明概览 +- 网格是否要重新计算法线 +- 纹理是否要生成 mipmaps +- 材质是否要套用额外解析选项 -| 声明 | 类型 | 说明 | -|------|------|------| -| `ImportSettings` | `class` | 头文件中的公开声明。 | +把这些配置独立成对象,在商业引擎里是很常见的做法。这样 loader 接口可以保持统一,而具体资源类型再扩展自己的配置子类。 -## 公共方法 +## 当前接口语义 -| 方法 | 描述 | -|------|------| -| [~ImportSettings()](Destructor.md) | 销毁对象并释放相关资源。 | -| [Clone](Clone.md) | 公开方法,详见头文件声明。 | -| [LoadFromJSON](LoadFromJSON.md) | 加载资源或数据。 | -| [SaveToJSON](SaveToJSON.md) | 公开方法,详见头文件声明。 | +`ImportSettings` 当前只规定了三件事: + +- `Clone()` 是纯虚函数,派生类必须能拷贝自己 +- `LoadFromJSON()` 默认返回 false +- `SaveToJSON()` 默认返回空字符串 + +这说明 JSON 序列化在这里是可选能力,而不是强制契约。只有真正需要文本化导入配置的资源类型,才需要覆盖这两个默认实现。 + +## 设计取向 + +可以把它理解成一个比 Unity importer settings 更轻量的运行时版本: + +- 它没有编辑器资产导入器生命周期 +- 没有统一反射字段系统 +- 没有自动持久化框架 +- 只是一个可选的配置对象接口 + +这种设计的好处是简单、解耦;代价是类型信息和生命周期都需要调用方与 loader 自己协调。 + +## 生命周期与所有权边界 + +当前最重要的现实问题是所有权: + +- `ResourceManager::Load()` / `LoadAsync()` 只是接收 `ImportSettings*` +- 接口层没有约定由谁负责删除它 +- `AsyncLoader` 当前也不会克隆它,只是把裸指针塞进 `LoadRequest` + +所以调用方不能假设 settings 会被系统接管,也不能假设异步路径会自动复制一份安全副本。 + +## 线程语义 + +- 基类本身没有锁。 +- `Clone()` 是否线程安全,取决于具体派生类。 +- 如果未来异步加载路径补全,settings 的线程可见性和所有权问题会变得更关键。 + +## 当前实现限制 + +- 没有统一类型 ID 或运行时查询接口。 +- 默认 JSON 接口只是占位实现。 +- 生命周期完全依赖调用方和 loader 约定。 +- 当前异步加载路径不会自动复制 settings。 + +## 相关方法 + +- [Destructor](Destructor.md) +- [Clone](Clone.md) +- [LoadFromJSON](LoadFromJSON.md) +- [SaveToJSON](SaveToJSON.md) ## 相关文档 -- [当前目录](../Asset.md) - 返回 `Asset` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Asset.md) +- [ResourceManager](../ResourceManager/ResourceManager.md) +- [AsyncLoader](../AsyncLoader/AsyncLoader.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Asset/ResourceCache/ResourceCache.md b/docs/api/XCEngine/Core/Asset/ResourceCache/ResourceCache.md index bdfff5e3..0f5dc845 100644 --- a/docs/api/XCEngine/Core/Asset/ResourceCache/ResourceCache.md +++ b/docs/api/XCEngine/Core/Asset/ResourceCache/ResourceCache.md @@ -6,40 +6,103 @@ **头文件**: `XCEngine/Core/Asset/ResourceCache.h` -**描述**: 定义 `XCEngine/Core/Asset` 子目录中的 `ResourceCache` public API。 +**描述**: 保存资源缓存条目、内存统计和一套当前仍较简化的淘汰顺序数据。 -## 概述 +## 角色概述 -`ResourceCache.h` 是 `XCEngine/Core/Asset` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`ResourceCache` 代表的是“缓存策略对象”这一层,而不是完整资源服务。它关心的是: -## 声明概览 +- 某个 `ResourceGUID` 是否对应一个缓存条目 +- 该条目的内存占用是多少 +- 当前缓存总内存是多少 +- 在内存压力下,应该尝试淘汰哪些条目 -| 声明 | 类型 | 说明 | -|------|------|------| -| `CacheEntry` | `struct` | 头文件中的公开声明。 | -| `ResourceCache` | `class` | 头文件中的公开声明。 | +这种把“资源索引”与“缓存策略”拆开的设计,在商业引擎里很常见,因为后续往往要支持多种缓存级别、内存预算、后台流送和平台差异策略。当前版本已经有这个结构轮廓,但离真正的 LRU 资源缓存还差不少实现。 -## 公共方法 +## CacheEntry 语义 -| 方法 | 描述 | -|------|------| -| [ResourceCache()](Constructor.md) | 构造对象。 | -| [~ResourceCache()](Destructor.md) | 销毁对象并释放相关资源。 | -| [Add](Add.md) | 添加元素或建立关联。 | -| [Remove](Remove.md) | 移除元素或解除关联。 | -| [Find](Find.md) | 查找并返回匹配对象。 | -| [Touch](Touch.md) | 公开方法,详见头文件声明。 | -| [GetSize](GetSize.md) | 获取相关状态或对象。 | -| [GetMemoryUsage](GetMemoryUsage.md) | 获取相关状态或对象。 | -| [SetMemoryBudget](SetMemoryBudget.md) | 设置相关状态或配置。 | -| [GetMemoryBudget](GetMemoryBudget.md) | 获取相关状态或对象。 | -| [OnMemoryPressure](OnMemoryPressure.md) | 公开方法,详见头文件声明。 | -| [OnZeroRefCount](OnZeroRefCount.md) | 公开方法,详见头文件声明。 | -| [Flush](Flush.md) | 公开方法,详见头文件声明。 | -| [Clear](Clear.md) | 清空内部数据。 | -| [GetLRUList](GetLRUList.md) | 获取相关状态或对象。 | +每个缓存项当前记录: + +- `resource` +- `guid` +- `memorySize` +- `lastAccessTime` +- `accessCount` + +`CacheEntry::GetCurrentTick()` 当前使用一个文件级静态计数器递增生成时间戳,不依赖系统时钟。 + +## 当前行为 + +### 添加与查找 + +- `Add(guid, resource)` 在键不存在时插入条目,记录内存大小,并把 GUID 追加到 `m_lruOrder`。 +- `Find(guid)` 只做查找,不会自动调用 `Touch()`。 +- `Remove(guid)` 会删除缓存项,并通过重建数组的方式从 `m_lruOrder` 中移除该 GUID。 + +### 访问更新 + +`Touch(guid)` 当前只会: + +- 更新 `lastAccessTime` +- `accessCount++` + +它不会调整 `m_lruOrder` 中的位置,所以它并没有把访问行为真实反映到淘汰顺序里。 + +### 内存压力 + +`OnMemoryPressure(requiredBytes)` 会根据当前统计调用 `Evict()`。而 `Evict()` 的实际策略是: + +- 一直从 `m_lruOrder` 的尾部取 GUID +- 找到条目后直接 `resource->Release()` +- 从缓存里移除条目并减少 `m_memoryUsage` + +这说明当前版本的淘汰行为更像“按数组尾部顺序删”,不是成熟的最近最少使用缓存。 + +## 设计取向 + +把缓存做成独立对象是合理的。它让资源管理器有机会只负责“谁被加载了”,而把“什么时候因为内存压力淘汰”下沉给缓存层。对商业引擎来说,这样的分层有两个好处: + +- 后续可以替换淘汰策略,而不推倒 `ResourceManager` +- 可以把 CPU 资源缓存、GPU 资源缓存、流送缓存做成不同实现 + +但当前版本还处在“接口骨架已搭好,策略未打磨完成”的阶段。 + +## 线程语义 + +- `Add()`、`Remove()`、`Find()`、`Touch()`、`OnMemoryPressure()`、`Flush()`、`Clear()`、`GetLRUList()` 当前都使用 `m_mutex`。 +- 头文件里还有一个 `m_cacheMutex`,但当前 `.cpp` 实现并没有实际使用它。 +- 资源对象本身的线程安全不由 `ResourceCache` 保证。 + +## 当前实现限制 + +- 不是严格的 LRU。`Touch()` 不会重排顺序,而 `Evict()` 从数组尾部弹出。 +- `Find()` 不会自动更新访问热度。 +- `OnZeroRefCount()` 是空实现。 +- `Flush()` 没有逐项释放资源对象,而是直接清空容器和统计值。 +- 当前的内存压力计算偏保守,可能比“刚好满足预算”释放更多条目。 +- 它和 [ResourceManager](../ResourceManager/ResourceManager.md) 的缓存镜像当前没有完全同步,单独释放或清空缓存时,管理器侧状态不一定同步更新。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [Add](Add.md) +- [Remove](Remove.md) +- [Find](Find.md) +- [Touch](Touch.md) +- [SetMemoryBudget](SetMemoryBudget.md) +- [GetMemoryBudget](GetMemoryBudget.md) +- [GetMemoryUsage](GetMemoryUsage.md) +- [GetSize](GetSize.md) +- [OnMemoryPressure](OnMemoryPressure.md) +- [OnZeroRefCount](OnZeroRefCount.md) +- [Flush](Flush.md) +- [Clear](Clear.md) +- [GetLRUList](GetLRUList.md) ## 相关文档 -- [当前目录](../Asset.md) - 返回 `Asset` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Asset.md) +- [ResourceManager](../ResourceManager/ResourceManager.md) +- [ResourceHandle](../ResourceHandle/ResourceHandle.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Asset/ResourceDependencyGraph/ResourceDependencyGraph.md b/docs/api/XCEngine/Core/Asset/ResourceDependencyGraph/ResourceDependencyGraph.md index c2dc59bf..6d7fb085 100644 --- a/docs/api/XCEngine/Core/Asset/ResourceDependencyGraph/ResourceDependencyGraph.md +++ b/docs/api/XCEngine/Core/Asset/ResourceDependencyGraph/ResourceDependencyGraph.md @@ -6,42 +6,127 @@ **头文件**: `XCEngine/Core/Asset/ResourceDependencyGraph.h` -**描述**: 定义 `XCEngine/Core/Asset` 子目录中的 `ResourceDependencyGraph` public API。 +**描述**: 记录资源之间的依赖关系、反向依赖关系和图节点级引用计数。 -## 概述 +## 角色概述 -`ResourceDependencyGraph.h` 是 `XCEngine/Core/Asset` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +资源依赖图是大型引擎里非常典型的一层能力。材质依赖着色器和纹理,预制体依赖网格和材质,场景依赖大量子资源。如果要做真正可靠的流式加载、批量卸载和循环依赖检查,就需要这样一张图。 -## 声明概览 +`ResourceDependencyGraph` 已经把这层的核心数据结构搭出来了: -| 声明 | 类型 | 说明 | -|------|------|------| -| `DependencyNode` | `struct` | 头文件中的公开声明。 | -| `ResourceDependencyGraph` | `class` | 头文件中的公开声明。 | +- 每个节点一个 `DependencyNode` +- 节点记录 `dependencies` +- 节点记录 `dependents` +- 节点还有独立的 `refCount` -## 公共方法 +但当前实现还更偏向“图数据容器”,不是完整的资源依赖调度系统。 -| 方法 | 描述 | -|------|------| -| [ResourceDependencyGraph()](Constructor.md) | 构造对象。 | -| [~ResourceDependencyGraph()](Destructor.md) | 销毁对象并释放相关资源。 | -| [AddNode](AddNode.md) | 添加元素或建立关联。 | -| [RemoveNode](RemoveNode.md) | 移除元素或解除关联。 | -| [AddDependency](AddDependency.md) | 添加元素或建立关联。 | -| [RemoveDependency](RemoveDependency.md) | 移除元素或解除关联。 | -| [GetDependencies](GetDependencies.md) | 获取相关状态或对象。 | -| [GetDependents](GetDependents.md) | 获取相关状态或对象。 | -| [GetAllDependencies](GetAllDependencies.md) | 获取相关状态或对象。 | -| [IncrementRefCount](IncrementRefCount.md) | 公开方法,详见头文件声明。 | -| [DecrementRefCount](DecrementRefCount.md) | 公开方法,详见头文件声明。 | -| [GetRefCount](GetRefCount.md) | 获取相关状态或对象。 | -| [HasCircularDependency](HasCircularDependency.md) | 判断是否具备指定状态或能力。 | -| [TopologicalSort](TopologicalSort.md) | 公开方法,详见头文件声明。 | -| [Unload](Unload.md) | 卸载资源或释放缓存。 | -| [Clear](Clear.md) | 清空内部数据。 | -| [HasNode](HasNode.md) | 判断是否具备指定状态或能力。 | +## 当前行为 + +### 节点管理 + +- `AddNode(guid, type)` 在节点不存在时插入。 +- `RemoveNode(guid)` 当前只是直接从 `m_nodes` 里删除该节点。 + +这里要注意:`RemoveNode()` 不会自动清理其他节点里残留的依赖或反向依赖引用,所以它不是一个“完整拓扑清理”操作。 + +### 依赖关系 + +- `AddDependency(owner, dependency)` 只有在两个节点都存在时才会生效。 +- 它会避免同一个 `owner` 重复加入同一条 `dependency`。 +- 当依赖首次建立时,会把 `owner` 追加到依赖节点的 `dependents` 列表。 +- `RemoveDependency()` 会分别从 `dependencies` 和 `dependents` 中移除对应项,移除方式是用尾元素覆盖后 `PopBack()`。 + +### 递归依赖查询 + +`GetAllDependencies(guid)` 当前采用显式栈做深度遍历式搜索: + +- 起点节点本身不会写入结果 +- 遍历顺序取决于数组压栈顺序,不承诺稳定拓扑顺序 +- 结果会避免重复 GUID + +它适合做“收集一批间接依赖”的基础用途,但不能把返回顺序当成可靠构建顺序。 + +### 节点级引用计数 + +- `IncrementRefCount()` / `DecrementRefCount()` 只修改图节点自己的 `refCount` +- 这套计数与 [ResourceManager](../ResourceManager/ResourceManager.md) 里的句柄引用计数没有自动联动 + +因此它更像“依赖图上的使用统计槽位”,不是全局唯一的资源实际引用计数。 + +## 循环依赖与卸载语义 + +### HasCircularDependency + +接口上这是循环依赖检测入口,但当前实现比较简化: + +- 使用一个只增不减的 `path` 数组 +- 遍历时只要再次看到 `path` 中已有节点,就认为发现了环 + +这会带来一个重要后果:对于“共享子依赖但并没有成环”的图,当前实现也可能给出误报。它不应被当成严格正确的生产级环检测器。 + +### TopologicalSort + +`TopologicalSort()` 当前直接返回空数组,是未完成实现。 + +### Unload + +`Unload(guid)` 当前只做“是否允许卸载”的布尔判断: + +- 节点不存在则返回 false +- 节点自身 `refCount > 0` 则返回 false +- 任一 dependent 节点 `refCount > 0` 也返回 false +- 否则返回 true + +它不会真正删除节点,也不会调用 `ResourceManager` 去释放资源对象。 + +## 设计取向 + +从架构角度看,这个类的方向是合理的。商业引擎的资源系统通常都会把“依赖关系图”和“实际加载器/缓存”拆开,因为: + +- 图本身更适合做批量分析、循环检测和卸载判定 +- 资源管理器更适合做实际对象加载和缓存落地 + +当前代码已经体现了这种分层意识,只是还没有把分析结果真正驱动到加载和卸载流程里。 + +## 线程语义 + +- 当前实现没有加锁。 +- `m_nodes` 以及其中的依赖数组都不是并发安全容器。 +- 默认应按单线程资源管理辅助结构来使用。 + +## 当前实现限制 + +- `RemoveNode()` 不会清理其他节点中的残留引用。 +- `HasCircularDependency()` 可能对共享依赖图产生误报。 +- `HasCircularDependencyInternal()` 当前未实现且未被真正使用。 +- `TopologicalSort()` 是空实现。 +- `Unload()` 只返回判定结果,不真正卸载资源或修改图结构。 +- 图节点引用计数与 `ResourceManager` 的句柄引用计数没有自动打通。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [AddNode](AddNode.md) +- [RemoveNode](RemoveNode.md) +- [AddDependency](AddDependency.md) +- [RemoveDependency](RemoveDependency.md) +- [GetDependencies](GetDependencies.md) +- [GetDependents](GetDependents.md) +- [GetAllDependencies](GetAllDependencies.md) +- [IncrementRefCount](IncrementRefCount.md) +- [DecrementRefCount](DecrementRefCount.md) +- [GetRefCount](GetRefCount.md) +- [HasCircularDependency](HasCircularDependency.md) +- [TopologicalSort](TopologicalSort.md) +- [Unload](Unload.md) +- [Clear](Clear.md) +- [HasNode](HasNode.md) ## 相关文档 -- [当前目录](../Asset.md) - 返回 `Asset` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Asset.md) +- [ResourceManager](../ResourceManager/ResourceManager.md) +- [ResourceTypes](../ResourceTypes/ResourceTypes.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Asset/ResourceHandle/ResourceHandle.md b/docs/api/XCEngine/Core/Asset/ResourceHandle/ResourceHandle.md index e2c9230a..05bbdea5 100644 --- a/docs/api/XCEngine/Core/Asset/ResourceHandle/ResourceHandle.md +++ b/docs/api/XCEngine/Core/Asset/ResourceHandle/ResourceHandle.md @@ -2,40 +2,116 @@ **命名空间**: `XCEngine::Resources` -**类型**: `class` +**类型**: `class template` **头文件**: `XCEngine/Core/Asset/ResourceHandle.h` -**描述**: 定义 `XCEngine/Core/Asset` 子目录中的 `ResourceHandle` public API。 +**描述**: 对 `IResource` 派生对象提供类型化访问,并把引用计数通知给 `ResourceManager`。 -## 概述 +## 角色概述 -`ResourceHandle.h` 是 `XCEngine/Core/Asset` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`ResourceHandle` 的目标很明确:让上层代码拿到一个“带类型的资源引用”,而不是到处传裸 `IResource*`。这和商业引擎里常见的 typed asset handle 思路一致,优点是调用端可读性好,也给未来做异步占位、流式加载和句柄稳定化留了接口空间。 -## 声明概览 +但当前版本要特别注意,它不是 `shared_ptr`,也不是一个严格拥有资源生命周期的安全句柄。它本质上只是: -| 声明 | 类型 | 说明 | -|------|------|------| -| `ResourceHandle` | `class` | 头文件中的公开声明。 | +- 保存一个 `T*` +- 在构造 / 拷贝时调用 `ResourceManager::AddRef()` +- 在析构 / `Reset()` 时调用 `ResourceManager::Release()` -## 公共方法 +所以更准确地说,它是“带类型的资源引用计数通知器”,不是“完整所有权对象”。 -| 方法 | 描述 | -|------|------| -| [ResourceHandle()](Constructor.md) | 构造对象。 | -| [~ResourceHandle()](Destructor.md) | 销毁对象并释放相关资源。 | -| [operator=](OperatorAssign.md) | 公开方法,详见头文件声明。 | -| [Get](Get.md) | 获取相关状态或对象。 | -| [operator->](OperatorArrow.md) | 公开方法,详见头文件声明。 | -| [operator*](OperatorMultiply.md) | 公开方法,详见头文件声明。 | -| [IsValid](IsValid.md) | 查询当前状态。 | -| [bool](bool.md) | 公开方法,详见头文件声明。 | -| [GetGUID](GetGUID.md) | 获取相关状态或对象。 | -| [GetResourceType](GetResourceType.md) | 获取相关状态或对象。 | -| [Reset](Reset.md) | 公开方法,详见头文件声明。 | -| [Swap](Swap.md) | 公开方法,详见头文件声明。 | +## 生命周期 + +### 创建 + +- 默认构造得到空句柄。 +- 从 `T*` 构造时,如果指针非空,会立刻对该资源 GUID 调用一次 `ResourceManager::AddRef()`。 +- 拷贝构造和拷贝赋值会再次增加引用计数。 +- 移动构造和移动赋值只转移内部指针,不增加引用计数。 + +### 释放 + +- 析构时会调用 `Reset()`。 +- `Reset()` 会对当前资源 GUID 调用 `ResourceManager::Release()`,然后把内部指针设为空。 + +这个流程看起来类似智能指针,但当前 `ResourceManager::Release()` 的零引用回收路径并没有真正完成,所以“最后一个句柄销毁后资源立刻释放”并不是现状保证。 + +## 访问与判定语义 + +- `Get()` 返回裸指针。 +- `operator->()` 和 `operator*()` 直接解引用内部指针,没有空指针保护。 +- `IsValid()` 同时要求内部指针非空且 `m_resource->IsValid()` 返回 true。 +- `operator bool()` 只是 `IsValid()` 的语法糖。 +- `GetGUID()` 在空句柄时返回 `ResourceGUID(0)`。 +- `GetResourceType()` 在空句柄时返回 `ResourceType::Unknown`。 + +这意味着: + +- “句柄不为空”和“资源逻辑上有效”是两个层次,`IsValid()` 同时检查了这两层。 +- 两个空句柄通过 `operator==` 比较时会相等,因为它们的 GUID 都是 `0`。 + +## 所有权与安全边界 + +当前版本最需要说清楚的是它的边界: + +- `ResourceHandle` 不负责删除资源对象。 +- 它依赖 `ResourceManager` 的引用计数,但当前引用计数归零并不会自动完成可靠卸载。 +- 它内部持有的是裸指针,不是稳定句柄表索引。 + +结合当前 `ResourceManager` / `ResourceCache` 的实现,这里存在一个现实风险: + +- 如果缓存层在外部条件下释放了资源对象,而某个 `ResourceHandle` 还保留着旧指针,这个句柄就可能变成悬空引用。 + +因此,当前版本的 `ResourceHandle` 更适合表达“我正在逻辑上使用这个资源”,而不适合被当作“无论发生什么都绝对安全的生命周期所有权令牌”。 + +## 设计取向 + +可以把它理解成一个比 Unity `Object` 引用更底层、比 `shared_ptr` 更轻的资源句柄: + +- 它保留了类型信息和基本有效性查询 +- 它没有引入共享控制块和重型所有权语义 +- 它把最终生命周期决策留给 `ResourceManager` + +这种设计在大型引擎里是合理方向,因为资源系统最终通常需要和缓存、流送、GPU 上传、依赖图一起协作。但当前实现距离完整句柄系统还有明显距离。 + +## 线程语义 + +- `ResourceHandle` 自身没有内部锁。 +- 构造、拷贝、析构触发的 `AddRef()` / `Release()` 会进入 `ResourceManager` 的加锁路径。 +- 资源对象本身是否可并发访问,不由 `ResourceHandle` 保证。 +- `operator->()` / `operator*()` 只是裸指针访问,不提供并发保护。 + +## 当前实现限制 + +- 不是智能指针,不拥有资源对象内存。 +- 不是稳定句柄表,不会自动防止悬空指针。 +- 最后一个句柄释放后不会自动完成可靠卸载,因为 `Release()` 的零引用路径当前没有真正闭环。 +- 可以包装一个不在 `ResourceManager` 缓存中的裸资源指针,当前系统不会自动把它纳入完整资源生命周期管理。 +- `operator->()` / `operator*()` 没有空值保护,调用方需要自己保证句柄有效。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [Get](Get.md) +- [OperatorArrow](OperatorArrow.md) +- [OperatorMultiply](OperatorMultiply.md) +- [IsValid](IsValid.md) +- [bool](bool.md) +- [GetGUID](GetGUID.md) +- [GetResourceType](GetResourceType.md) +- [Reset](Reset.md) +- [Swap](Swap.md) +- [OperatorAssign](OperatorAssign.md) + +## 相关指南 + +- [Resource Lifecycle And Handles](../../../../_guides/Core/Asset/Resource-Lifecycle-And-Handles.md) ## 相关文档 -- [当前目录](../Asset.md) - 返回 `Asset` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Asset.md) +- [ResourceManager](../ResourceManager/ResourceManager.md) +- [IResource](../IResource/IResource.md) +- [ResourceTypes](../ResourceTypes/ResourceTypes.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Asset/ResourceManager/ResourceManager.md b/docs/api/XCEngine/Core/Asset/ResourceManager/ResourceManager.md index 5b5d4c06..ea082390 100644 --- a/docs/api/XCEngine/Core/Asset/ResourceManager/ResourceManager.md +++ b/docs/api/XCEngine/Core/Asset/ResourceManager/ResourceManager.md @@ -6,50 +6,170 @@ **头文件**: `XCEngine/Core/Asset/ResourceManager.h` -**描述**: 定义 `XCEngine/Core/Asset` 子目录中的 `ResourceManager` public API。 +**描述**: 运行时资源系统的中心入口,负责 loader 注册、同步加载、引用计数和部分缓存管理。 -## 概述 +## 角色概述 -`ResourceManager.h` 是 `XCEngine/Core/Asset` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`ResourceManager` 是当前资源模块里最像“服务定位入口”的类。上层系统通常不会直接操作 `ResourceCache` 或 `AsyncLoader`,而是通过这里来做几件事: -## 声明概览 +- 以 `ResourceType` 为键注册和查找 `IResourceLoader` +- 根据路径生成 `ResourceGUID` +- 执行同步 `Load()` +- 记录句柄引用计数 +- 暴露资源根路径、内存预算和缓存查询入口 -| 声明 | 类型 | 说明 | -|------|------|------| -| `ResourceManager` | `class` | 头文件中的公开声明。 | +这种设计和商业引擎里常见的资源服务层比较接近。好处是资源调用点集中,上层 API 简洁;代价是很多子系统都要依赖这个全局单例,而且当前实现里缓存、引用计数和异步加载还没有完全闭环。 -## 公共方法 +## 生命周期 -| 方法 | 描述 | -|------|------| -| [Get](Get.md) | 获取相关状态或对象。 | -| [Initialize](Initialize.md) | 初始化内部状态。 | -| [Shutdown](Shutdown.md) | 关闭并清理内部状态。 | -| [SetResourceRoot](SetResourceRoot.md) | 设置相关状态或配置。 | -| [GetResourceRoot](GetResourceRoot.md) | 获取相关状态或对象。 | -| [Load](Load.md) | 加载资源或数据。 | -| [LoadAsync](LoadAsync.md) | 加载资源或数据。 | -| [Unload](Unload.md) | 卸载资源或释放缓存。 | -| [UnloadUnused](UnloadUnused.md) | 卸载资源或释放缓存。 | -| [UnloadAll](UnloadAll.md) | 卸载资源或释放缓存。 | -| [AddRef](AddRef.md) | 添加元素或建立关联。 | -| [Release](Release.md) | 释放引用或底层资源。 | -| [GetRefCount](GetRefCount.md) | 获取相关状态或对象。 | -| [RegisterLoader](RegisterLoader.md) | 注册对象、回调或映射。 | -| [UnregisterLoader](UnregisterLoader.md) | 取消注册对象、回调或映射。 | -| [GetLoader](GetLoader.md) | 获取相关状态或对象。 | -| [SetMemoryBudget](SetMemoryBudget.md) | 设置相关状态或配置。 | -| [GetMemoryUsage](GetMemoryUsage.md) | 获取相关状态或对象。 | -| [GetMemoryBudget](GetMemoryBudget.md) | 获取相关状态或对象。 | -| [FlushCache](FlushCache.md) | 公开方法,详见头文件声明。 | -| [Find](Find.md) | 查找并返回匹配对象。 | -| [Exists](Exists.md) | 公开方法,详见头文件声明。 | -| [ResolvePath](ResolvePath.md) | 解析并返回目标结果。 | -| [LoadGroup](LoadGroup.md) | 加载资源或数据。 | -| [GetResourcePaths](GetResourcePaths.md) | 获取相关状态或对象。 | -| [UnloadGroup](UnloadGroup.md) | 卸载资源或释放缓存。 | +### 初始化 + +`Initialize()` 当前会做三件事: + +1. 创建一个 `AsyncLoader` 实例并调用 `Initialize(2)` +2. 注册内建 `MaterialLoader` +3. 注册内建 `ShaderLoader` + +这里有两个实际语义需要注意: + +- `workerThreadCount = 2` 只是传入了 `AsyncLoader`,但当前 `AsyncLoader::Initialize()` 并不会真的创建工作线程。 +- 只有材质和着色器 loader 会自动注册,其他资源类型需要调用方自行注册,或者当前根本还没有成熟 loader。 + +### 关闭 + +`Shutdown()` 会调用: + +- `UnloadAll()` +- `m_asyncLoader->Shutdown()` +- `m_asyncLoader.reset()` + +当前没有空指针保护,所以它默认假设你已经成功调用过 `Initialize()`。把它当成“可随时重复调用的幂等 API”并不安全。 + +## 当前加载模型 + +### 同步加载 + +模板 `Load()` 是目前最可依赖的主路径。按当前实现,它会: + +1. 用原始 `path` 调用 `ResourceGUID::Generate(path)` +2. 在 `m_resourceCache` 里查缓存 +3. 用 `GetResourceType()` 找到资源类型,再从 `m_loaders` 找 loader +4. 直接调用 `loader->Load(path, settings)` +5. 加入缓存并返回 `ResourceHandle` + +这里有几个重要结论: + +- `Load()` 并不会调用 `ResolvePath()`,所以 `SetResourceRoot()` 不会自动影响同步加载路径。 +- `GetResourceType()` 只有少数显式特化;对没有特化的类型使用 `Load()`,并不是“自动推断资源类型”。 +- loader 查找失败时会记录 warning 并返回空句柄。 +- loader 加载失败时会记录 error 并返回空句柄。 + +### 异步加载 + +`LoadAsync()` 只是把请求转发给 `m_asyncLoader->Submit(...)`。当前版本中: + +- `Submit()` 只把请求压入 `m_pendingQueue` +- 没有后台线程消费这个队列 +- `QueueCompleted()` 也没有真正把成功结果放回完成队列 + +所以它的 API 形状已经存在,但不能当成成熟的异步资源流式系统来依赖。 + +## 引用计数与句柄语义 + +[ResourceHandle](../ResourceHandle/ResourceHandle.md) 的构造、拷贝、析构都会调用 `AddRef()` / `Release()`。这意味着 `ResourceManager` 记录的是“句柄引用数”,不是资源对象的真实所有权。 + +当前实现里还有两个关键现实: + +- `Release()` 在引用数归零时只会调用 `m_cache.OnZeroRefCount(guid)`,而这个函数当前是空实现。 +- `AddRef()` 发现资源不在 `m_resourceCache` 时会调用 `ReloadResource(guid)`,但 `ReloadResource()` 现在基本是 stub,而且 `m_guidToPath` 在正常加载路径里也没有被填充。 + +结果就是: + +- “最后一个句柄释放后自动卸载资源”当前并没有真正成立。 +- “句柄重新引用一个已卸载资源时自动重载”当前也没有真正成立。 + +## 缓存与内存预算 + +`ResourceManager` 内部同时维护: + +- `m_resourceCache` +- `m_memoryUsage` +- 一个独立的 `ResourceCache m_cache` + +从设计意图上看,这是想把“管理器级索引”和“缓存策略对象”分层。但按当前实现,这两层并没有完全同步: + +- `AddToCache()` 会同时写入 `m_resourceCache` 和 `m_cache` +- `Unload()` / `UnloadGroup()` 只移除 `m_resourceCache`,不会同步移除 `m_cache` +- `FlushCache()` 只调用 `m_cache.Flush()`,不会清理 `m_resourceCache` 和 `m_memoryUsage` +- `ResourceCache::Evict()` 会直接 `Release()` 资源,但不会回写 `m_resourceCache` +- `SetMemoryBudget()` 只改 `ResourceManager` 自己的预算值,没有把预算同步给 `m_cache` + +这意味着当前版本里,“缓存是否存在”“管理器是否还持有指针”“内存统计是否准确”并不总是完全一致。文档和调用方都不应把这里理解成已经严密打通的商业级缓存系统。 + +## 查询与路径语义 + +- `Find(path)` / `Find(guid)` 只查当前缓存,不查磁盘。 +- `Exists(path)` / `Exists(guid)` 也只代表“当前是否在缓存里”,不代表文件存在。 +- `ResolvePath(relativePath)` 只是简单返回 `m_resourceRoot + "/" + relativePath`。 +- `GetResourcePaths()` 读取的是 `m_guidToPath`,但当前默认加载流程没有填这个表,所以它通常不会返回你期待的完整已加载资源路径列表。 +- `LoadGroup()` 会对每个路径分别发起 `LoadAsync()`,并逐个回调,不会做“整组完成后再统一通知”的聚合。 + +## 线程语义 + +- `AddRef()`、`Release()`、缓存写入和 loader 注册等路径使用了 `m_mutex`。 +- 并不是所有只读接口都加了锁,例如 `GetRefCount()`、`GetMemoryUsage()`、`GetMemoryBudget()`、`GetLoader()`。 +- loader 本身是否线程安全、回调在哪个线程执行,当前并没有统一契约。 +- 结合 `AsyncLoader` 的现状,默认仍应按主线程驱动资源系统理解。 + +## 当前实现限制 + +- 当前自动注册的 loader 只有 `Material` 和 `Shader`。 +- `Load()` 不会自动使用 `SetResourceRoot()`。 +- `UnloadUnused()` 是空实现。 +- `UnloadAll()` 当前只是清空容器和内存计数,没有逐个释放所有资源对象。 +- `ReloadResource()` 只有路径表检查,没有真正重载逻辑。 +- `GetResourcePaths()` 依赖的 `m_guidToPath` 当前默认不会在同步加载时维护。 +- `ResourceManager` 和 `ResourceCache` 的状态同步并不完整,存在悬空指针和统计不一致风险。 +- `LoadAsync()` / `LoadGroup()` 目前不适合当成生产可用的异步加载通路。 + +## 相关方法 + +- [Get](Get.md) +- [Initialize](Initialize.md) +- [Shutdown](Shutdown.md) +- [SetResourceRoot](SetResourceRoot.md) +- [GetResourceRoot](GetResourceRoot.md) +- [Load](Load.md) +- [LoadAsync](LoadAsync.md) +- [Unload](Unload.md) +- [UnloadUnused](UnloadUnused.md) +- [UnloadAll](UnloadAll.md) +- [AddRef](AddRef.md) +- [Release](Release.md) +- [GetRefCount](GetRefCount.md) +- [RegisterLoader](RegisterLoader.md) +- [UnregisterLoader](UnregisterLoader.md) +- [GetLoader](GetLoader.md) +- [SetMemoryBudget](SetMemoryBudget.md) +- [GetMemoryUsage](GetMemoryUsage.md) +- [GetMemoryBudget](GetMemoryBudget.md) +- [FlushCache](FlushCache.md) +- [Find](Find.md) +- [Exists](Exists.md) +- [ResolvePath](ResolvePath.md) +- [LoadGroup](LoadGroup.md) +- [GetResourcePaths](GetResourcePaths.md) +- [UnloadGroup](UnloadGroup.md) + +## 相关指南 + +- [Resource Lifecycle And Handles](../../../../_guides/Core/Asset/Resource-Lifecycle-And-Handles.md) ## 相关文档 -- [当前目录](../Asset.md) - 返回 `Asset` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Asset.md) +- [ResourceHandle](../ResourceHandle/ResourceHandle.md) +- [ResourceCache](../ResourceCache/ResourceCache.md) +- [AsyncLoader](../AsyncLoader/AsyncLoader.md) +- [IResource](../IResource/IResource.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Asset/ResourceTypes/ResourceTypes.md b/docs/api/XCEngine/Core/Asset/ResourceTypes/ResourceTypes.md index d1f31e08..083c5566 100644 --- a/docs/api/XCEngine/Core/Asset/ResourceTypes/ResourceTypes.md +++ b/docs/api/XCEngine/Core/Asset/ResourceTypes/ResourceTypes.md @@ -2,52 +2,111 @@ **命名空间**: `XCEngine::Resources` -**类型**: `enum class` +**类型**: `header-overview` **头文件**: `XCEngine/Core/Asset/ResourceTypes.h` -**描述**: 定义 `XCEngine/Core/Asset` 子目录中的 `ResourceTypes` public API。 +**描述**: 定义资源系统的基础类型,包括资源类别枚举、GUID 规则和类型映射模板。 -## 概述 +## 角色概述 -`ResourceTypes.h` 是 `XCEngine/Core/Asset` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`ResourceTypes.h` 是整个资源模块最底层的身份定义头文件。它回答的是三个基础问题: -## 声明概览 +- 这是什么类别的资源 +- 这个资源的稳定标识是什么 +- 给定一个 C++ 资源类型,应该映射到哪个 `ResourceType` -| 声明 | 类型 | 说明 | -|------|------|------| -| `ResourceType` | `enum class` | 头文件中的公开声明。 | -| `ResourceGUID` | `struct` | 头文件中的公开声明。 | +这类头文件在商业引擎里通常非常核心,因为 loader、缓存、依赖图、序列化和调试输出都会依赖同一套身份系统。 -## 枚举值 +## 当前定义 -| 枚举值 | 数值 | 描述 | -|--------|------|------| -| `Unknown` | `0` | 枚举项。 | -| `Texture` | - | 枚举项。 | -| `Mesh` | - | 枚举项。 | -| `Material` | - | 枚举项。 | -| `Shader` | - | 枚举项。 | -| `AudioClip` | - | 枚举项。 | -| `Binary` | - | 枚举项。 | -| `AnimationClip` | - | 枚举项。 | -| `Skeleton` | - | 枚举项。 | -| `Font` | - | 枚举项。 | -| `ParticleSystem` | - | 枚举项。 | -| `Scene` | - | 枚举项。 | -| `Prefab` | - | 枚举项。 | +### ResourceType -## std::hash 特化 +`ResourceType` 当前包含: -```cpp -namespace std { - template<> struct hash; -} -``` +- `Unknown` +- `Texture` +- `Mesh` +- `Material` +- `Shader` +- `AudioClip` +- `Binary` +- `AnimationClip` +- `Skeleton` +- `Font` +- `ParticleSystem` +- `Scene` +- `Prefab` -`ResourceTypes.h` 同时提供 `std::hash` 特化,可直接用于哈希容器键类型。 +`GetResourceTypeName()` 是对应的 constexpr 名称映射,用于日志和调试输出。 + +### ResourceGUID + +`ResourceGUID` 本质上是一个 `uint64` 包装类型,提供: + +- `IsValid()` +- 相等 / 不等比较 +- `Generate(const char*)` +- `Generate(const Containers::String&)` +- `ToString()` + +当前 `Generate()` 的实现使用 64 位 FNV-1a 风格字符串哈希,对输入路径逐字节哈希。 + +### 类型映射模板 + +`GetResourceType()` 当前只有少数显式特化: + +- `Texture` +- `Mesh` +- `Material` +- `Shader` +- `AudioClip` +- `BinaryResource` + +这正是 `ResourceManager::Load()` 能正常分派到 loader 的基础。 + +## GUID 语义 + +当前 `ResourceGUID::Generate(path)` 的行为非常直接: + +- 只对传入字符串本身做哈希 +- 不做路径规范化 +- 不统一大小写 +- 不消除相对路径和绝对路径差异 + +因此: + +- `"textures/a.png"` 和 `"./textures/a.png"` 会生成不同 GUID +- 路径大小写不同,也会生成不同 GUID +- 调用方必须自己保证路径规范一致,否则缓存命中和资源去重都会受影响 + +`ToString()` 当前输出 16 位十六进制字符串,便于日志和调试面板展示。 + +## 设计取向 + +用路径派生 GUID 而不是维护中心注册表,是一种很轻量的资源身份策略。它的优点是: + +- 不需要额外数据库 +- 同一路径天然得到稳定结果 +- 非常适合当前这种运行时轻量资源系统 + +代价也很明确: + +- 身份稳定性完全依赖路径字符串规范 +- 理论上存在哈希碰撞可能 +- 没有单独的 GUID 到路径反查系统保证 + +## 当前实现限制 + +- `GetResourceType()` 没有通用默认实现,只能用于已显式特化的资源类型。 +- `ResourceGUID` 不做路径规范化,调用方必须自己统一路径形式。 +- 当前没有更强的 GUID 持久化或冲突检测机制。 +- `ResourceType` 已经枚举了不少类别,但并不是每一类当前都有成熟 loader 或完整资源实现。 ## 相关文档 -- [当前目录](../Asset.md) - 返回 `Asset` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Asset.md) +- [ResourceManager](../ResourceManager/ResourceManager.md) +- [ResourceHandle](../ResourceHandle/ResourceHandle.md) +- [IResource](../IResource/IResource.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Containers/Array/Array.md b/docs/api/XCEngine/Core/Containers/Array/Array.md index 93af155a..d80a11d7 100644 --- a/docs/api/XCEngine/Core/Containers/Array/Array.md +++ b/docs/api/XCEngine/Core/Containers/Array/Array.md @@ -2,47 +2,101 @@ **命名空间**: `XCEngine::Containers` -**类型**: `class` +**类型**: `class template` **头文件**: `XCEngine/Core/Containers/Array.h` -**描述**: 定义 `XCEngine/Core/Containers` 子目录中的 `Array` public API。 +**描述**: 引擎自定义动态数组,提供连续内存存储和值语义。 -## 概述 +## 角色概述 -`Array.h` 是 `XCEngine/Core/Containers` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Array` 是当前引擎代码里最接近 `std::vector` 的顺序容器。它的核心能力包括: -## 声明概览 +- 连续内存存储 +- 动态扩容 +- 值语义拷贝 / 移动 +- `PushBack` / `EmplaceBack` / `Resize` +- 原始指针迭代器 -| 声明 | 类型 | 说明 | -|------|------|------| -| `Array` | `class` | 头文件中的公开声明。 | +对上层来说,它适合承载“由当前对象拥有的一段有序元素序列”。 -## 公共方法 +## 生命周期与所有权 -| 方法 | 描述 | -|------|------| -| [Array()](Constructor.md) | 构造对象。 | -| [~Array()](Destructor.md) | 销毁对象并释放相关资源。 | -| [operator=](OperatorAssign.md) | 公开方法,详见头文件声明。 | -| [operator[]](OperatorSubscript.md) | 公开方法,详见头文件声明。 | -| [Data](Data.md) | 公开方法,详见头文件声明。 | -| [Size](Size.md) | 公开方法,详见头文件声明。 | -| [Capacity](Capacity.md) | 公开方法,详见头文件声明。 | -| [Empty](Empty.md) | 公开方法,详见头文件声明。 | -| [Clear](Clear.md) | 清空内部数据。 | -| [Reserve](Reserve.md) | 公开方法,详见头文件声明。 | -| [Resize](Resize.md) | 公开方法,详见头文件声明。 | -| [PushBack](PushBack.md) | 公开方法,详见头文件声明。 | -| [EmplaceBack](EmplaceBack.md) | 公开方法,详见头文件声明。 | -| [PopBack](PopBack.md) | 公开方法,详见头文件声明。 | -| [Front](Front.md) | 公开方法,详见头文件声明。 | -| [Back](Back.md) | 公开方法,详见头文件声明。 | -| [begin](begin.md) | 公开方法,详见头文件声明。 | -| [end](end.md) | 公开方法,详见头文件声明。 | -| [SetAllocator](SetAllocator.md) | 设置相关状态或配置。 | +- 默认构造时没有分配缓冲区 +- 指定 `capacity` 构造时只分配容量,不创建元素 +- `count, value` 构造和 initializer list 构造会实际构造元素 +- 析构时会逐个析构已构造元素,再释放整块缓冲区 +- 拷贝构造和拷贝赋值会深拷贝元素 +- 移动构造和移动赋值转移缓冲区指针 + +`Clear()` 只销毁元素并把 `m_size` 置零,不释放已有容量。这一点和常见的游戏引擎容器设计一致,目的是避免高频清空时反复分配。 + +## 当前行为 + +### 容量增长 + +- `PushBack()` / `EmplaceBack()` 在容量不足时按 `0 -> 4 -> 8 -> 16 ...` 方式扩容 +- `Reserve(capacity)` 只在请求值大于当前容量时扩容 +- `Resize(newSize)` 若需要扩容,会直接扩到 `newSize` + +### 元素构造与销毁 + +- `Resize(newSize)` 新增部分使用默认构造 +- `Resize(newSize, value)` 新增部分用给定值填充 +- `PopBack()` 在非空时销毁最后一个元素 +- `Front()` / `Back()` 假定数组非空,不做保护 + +### 迭代器 + +- `begin()` / `end()` 本质上就是裸指针 +- 任何重新分配都会使已有迭代器、`Data()` 返回值和引用失效 + +## 设计取向 + +自定义数组容器在商业引擎里非常常见,因为它是很多更高层系统的基石。`Array` 当前已经具备最基础的值类型容器能力,也很适合在头文件模板里内联使用。 + +但它当前更偏“简单可靠的基础实现”,不是完整 STL 等价物。 + +## 线程语义 + +- 没有内部锁。 +- 所有修改操作都直接作用于内部缓冲。 +- 默认应按普通非线程安全容器使用。 + +## 当前实现限制 + +- `operator[]` 不做边界检查。 +- `Front()` / `Back()` 对空数组没有保护。 +- `SetAllocator()` 当前只是保存指针,底层分配仍然使用全局 `operator new/delete`。 +- 扩容时 `Reallocate()` 通过拷贝构造迁移元素,而不是优先移动。 +- 因为扩容依赖拷贝构造,当前容器对 move-only 类型并不友好。 +- 没有 `Insert`、`Erase(iterator)`、`ShrinkToFit` 等更完整接口。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [OperatorAssign](OperatorAssign.md) +- [OperatorSubscript](OperatorSubscript.md) +- [Data](Data.md) +- [Size](Size.md) +- [Capacity](Capacity.md) +- [Empty](Empty.md) +- [Clear](Clear.md) +- [Reserve](Reserve.md) +- [Resize](Resize.md) +- [PushBack](PushBack.md) +- [EmplaceBack](EmplaceBack.md) +- [PopBack](PopBack.md) +- [Front](Front.md) +- [Back](Back.md) +- [begin](begin.md) +- [end](end.md) +- [SetAllocator](SetAllocator.md) ## 相关文档 -- [当前目录](../Containers.md) - 返回 `Containers` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Containers.md) +- [String](../String/String.md) +- [HashMap](../HashMap/HashMap.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Containers/Containers.md b/docs/api/XCEngine/Core/Containers/Containers.md index 06ec0ad5..1423e27e 100644 --- a/docs/api/XCEngine/Core/Containers/Containers.md +++ b/docs/api/XCEngine/Core/Containers/Containers.md @@ -1,22 +1,68 @@ # Containers -**命名空间**: `XCEngine::Core::Containers` +**命名空间**: `XCEngine::Containers` **类型**: `submodule` -**描述**: 字符串、数组和哈希表等核心容器。 +**头文件**: `XCEngine/Core/Containers/Containers.h` + +**描述**: 定义引擎自定义字符串、动态数组和哈希表等基础容器。 ## 概览 -该目录与 `XCEngine/Core/Containers` 对应的 public headers 保持平行,用于承载唯一的 canonical API 文档入口。 +`Core/Containers` 是当前引擎最底层的数据结构层之一。它提供的不是一整套 STL 替代品,而是一组在引擎内部频繁出现、接口风格统一的基础容器: + +- [String](String/String.md) +- [Array](Array/Array.md) +- [HashMap](HashMap/HashMap.md) + +从设计方向看,这和商业引擎里常见的“自定义基础容器层”很像。这样做的好处是: + +- 对外 API 风格统一 +- 可以逐步引入引擎自己的分配器和调试支持 +- 跨模块时不必把标准库类型直接暴露到所有接口里 + +但按当前实现,它们仍然是轻量容器,而不是 fully-featured STL 等价物。 + +## umbrella header + +`Containers.h` 当前只是一个聚合头文件,负责包含: + +- `Array.h` +- `String.h` +- `HashMap.h` + +它自身没有新增独立类型,所以这里直接把它并入模块页说明,不再保留一份重复的 `Containers/Containers.md` 类型页。 + +## 设计要点 + +- `Array` 和 `HashMap` 当前都是头文件模板实现。 +- `String` 采用单独 `.cpp`,用堆分配维护可变字符缓冲。 +- 三个容器都使用值语义,但当前都偏“简单直接实现”,而不是“最大化性能和异常安全”的成熟版本。 +- `Array::SetAllocator()` 和 `HashMap::SetAllocator()` 当前只是记录指针,没有真正接管底层分配。 + +## 当前实现现状 + +- `String` 已覆盖最常用的拼接、子串、前后缀和大小写转换能力。 +- `Array` 已可覆盖大部分顺序容器用途,但没有边界检查,也没有完整 allocator 接入。 +- `HashMap` 已可用于基本查找和插入,但迭代器语义与 `operator[]` 的扩容返回路径都还存在当前实现问题。 ## 头文件 - [Array](Array/Array.md) - `Array.h` -- [Containers](Containers/Containers.md) - `Containers.h` - [HashMap](HashMap/HashMap.md) - `HashMap.h` - [String](String/String.md) - `String.h` +## 推荐阅读顺序 + +1. 先读 [String](String/String.md),理解当前项目里最常见的自定义字符串约定。 +2. 再读 [Array](Array/Array.md),这是其他模块最常用的顺序容器。 +3. 最后读 [HashMap](HashMap/HashMap.md),注意它当前的迭代和扩容边界。 + +## 相关指南 + +- [Custom Containers, Value Semantics, And Interop](../../../_guides/Core/Containers/Custom-Containers-Value-Semantics-And-Interop.md) + ## 相关文档 - [上级目录](../Core.md) diff --git a/docs/api/XCEngine/Core/Containers/Containers/Containers.md b/docs/api/XCEngine/Core/Containers/Containers/Containers.md deleted file mode 100644 index 6ba8b50d..00000000 --- a/docs/api/XCEngine/Core/Containers/Containers/Containers.md +++ /dev/null @@ -1,18 +0,0 @@ -# Containers - -**命名空间**: `XCEngine` - -**类型**: `header` - -**头文件**: `XCEngine/Core/Containers/Containers.h` - -**描述**: 定义 `XCEngine/Core/Containers` 子目录中的 `Containers` public API。 - -## 概述 - -`Containers.h` 是 `XCEngine/Core/Containers` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 - -## 相关文档 - -- [当前目录](../Containers.md) - 返回 `Containers` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 diff --git a/docs/api/XCEngine/Core/Containers/HashMap/HashMap.md b/docs/api/XCEngine/Core/Containers/HashMap/HashMap.md index 78b6a34a..2e99b3b4 100644 --- a/docs/api/XCEngine/Core/Containers/HashMap/HashMap.md +++ b/docs/api/XCEngine/Core/Containers/HashMap/HashMap.md @@ -2,42 +2,106 @@ **命名空间**: `XCEngine::Containers` -**类型**: `class` +**类型**: `class template` **头文件**: `XCEngine/Core/Containers/HashMap.h` -**描述**: 定义 `XCEngine/Core/Containers` 子目录中的 `HashMap` public API。 +**描述**: 基于桶数组和线性桶内查找实现的轻量哈希表。 -## 概述 +## 角色概述 -`HashMap.h` 是 `XCEngine/Core/Containers` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`HashMap` 是当前容器层提供的关联容器实现。它的基本结构是: -## 声明概览 +- 外层一个 `Array` +- 每个 bucket 内部再保存一个 `Array` +- 通过 `std::hash` 选择 bucket -| 声明 | 类型 | 说明 | -|------|------|------| -| `HashMap` | `class` | 头文件中的公开声明。 | +这种实现方式简单直接,适合项目早期快速提供可用的键值查找能力,也便于与自定义 `String` 和 `Array` 生态打通。 -## 公共方法 +## 当前行为 -| 方法 | 描述 | -|------|------| -| [HashMap()](Constructor.md) | 构造对象。 | -| [~HashMap()](Destructor.md) | 销毁对象并释放相关资源。 | -| [operator=](OperatorAssign.md) | 公开方法,详见头文件声明。 | -| [operator[]](OperatorSubscript.md) | 公开方法,详见头文件声明。 | -| [Find](Find.md) | 查找并返回匹配对象。 | -| [Contains](Contains.md) | 公开方法,详见头文件声明。 | -| [Insert](Insert.md) | 公开方法,详见头文件声明。 | -| [Erase](Erase.md) | 公开方法,详见头文件声明。 | -| [Clear](Clear.md) | 清空内部数据。 | -| [Size](Size.md) | 公开方法,详见头文件声明。 | -| [Empty](Empty.md) | 公开方法,详见头文件声明。 | -| [begin](begin.md) | 公开方法,详见头文件声明。 | -| [end](end.md) | 公开方法,详见头文件声明。 | -| [SetAllocator](SetAllocator.md) | 设置相关状态或配置。 | +### 插入与查找 + +- 默认 bucket 数量是 `16` +- `Insert(key, value)` 如果 key 已存在,会覆盖旧值并返回 `false` +- `Find(key)` 返回值指针,不存在时返回 `nullptr` +- `Contains(key)` 基于同一套 bucket 查找逻辑 +- `operator[]` 在 key 不存在时会插入一个默认构造的 `Value` + +### 扩容 + +当前负载因子阈值是 `0.75f`。超过后会调用 `Resize()`,并把所有 pair 重新散列到新的 bucket 数组中。 + +### 擦除 + +`Erase(key)` 当前会: + +- 在 bucket 内线性查找 +- 若找到目标,则用 bucket 内最后一个元素覆盖当前位置 +- 再 `PopBack()` + +这意味着同一 bucket 内 pair 的相对顺序不稳定。 + +## 设计取向 + +商业引擎常会提供自定义哈希表,不只是为了控制接口风格,也为了未来对内存分配、调试、序列化和平台兼容做统一约束。`HashMap` 当前已经承担了这类基础职责,尤其适合: + +- 少量到中等规模的引擎内查找表 +- 与 `Containers::String` 组合使用 + +但它离成熟标准容器还有一段距离。 + +## 迭代器语义 + +当前最重要、也最容易被误用的点,是 `begin()` / `end()`: + +- `begin()` 返回的是第一个 bucket 的 `pairs.begin()` +- `end()` 返回的是最后一个 bucket 的 `pairs.end()` + +这并不能形成“跨所有 bucket 的完整连续迭代区间”。也就是说,当前 `HashMap` 的 `begin()` / `end()` 不是可靠的整表遍历接口。 + +如果按标准容器心智模型去做范围 for 或手写全表迭代,行为并不成立。 + +## 一个当前实现风险 + +`operator[]` 在插入新 key 后,如果触发了 `Resize()`,当前实现会在扩容之后继续返回扩容前 bucket 引用上的元素引用。按源码来看,这里存在返回悬空引用的风险。 + +因此,当前版本里不应把“缺失 key 时通过 `operator[]` 插入并立即持有返回引用”当成绝对安全路径,尤其是在表接近扩容阈值时。 + +## 线程语义 + +- 没有内部锁。 +- 所有读写都直接操作 bucket 数组。 +- 默认按非线程安全容器使用。 + +## 当前实现限制 + +- bucket 内查找是线性的。 +- `begin()` / `end()` 当前不是可靠的整表迭代器语义。 +- `operator[]` 在触发扩容时存在返回悬空引用风险。 +- `SetAllocator()` 当前只是保存指针,底层分配仍依赖容器默认实现。 +- 没有 `const operator[]`、没有 iterator erase、没有稳定遍历顺序。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [OperatorAssign](OperatorAssign.md) +- [OperatorSubscript](OperatorSubscript.md) +- [Find](Find.md) +- [Contains](Contains.md) +- [Insert](Insert.md) +- [Erase](Erase.md) +- [Clear](Clear.md) +- [Size](Size.md) +- [Empty](Empty.md) +- [begin](begin.md) +- [end](end.md) +- [SetAllocator](SetAllocator.md) ## 相关文档 -- [当前目录](../Containers.md) - 返回 `Containers` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Containers.md) +- [String](../String/String.md) +- [Array](../Array/Array.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Containers/String/String.md b/docs/api/XCEngine/Core/Containers/String/String.md index 5d1c7d6b..d03d6e1c 100644 --- a/docs/api/XCEngine/Core/Containers/String/String.md +++ b/docs/api/XCEngine/Core/Containers/String/String.md @@ -6,53 +6,111 @@ **头文件**: `XCEngine/Core/Containers/String.h` -**描述**: 定义 `XCEngine/Core/Containers` 子目录中的 `String` public API。 +**描述**: 引擎自定义可变字符串类型,提供基本值语义、拼接和常见文本处理能力。 -## 概述 +## 角色概述 -`String.h` 是 `XCEngine/Core/Containers` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`String` 是当前引擎代码里最常见的基础文本容器之一。它承担的职责比较明确: -## 声明概览 +- 保存 UTF-8 风格的字节字符串内容 +- 对外暴露 C 风格字符串接口 `CStr()` +- 提供最常用的拼接、查找、截取和大小写转换功能 -| 声明 | 类型 | 说明 | -|------|------|------| -| `String` | `class` | 头文件中的公开声明。 | +它更接近一个轻量版 `std::string`,而不是功能完全对齐的完整替代品。 -## 公共方法 +## 生命周期与所有权 -| 方法 | 描述 | -|------|------| -| [String()](Constructor.md) | 构造对象。 | -| [~String()](Destructor.md) | 销毁对象并释放相关资源。 | -| [operator=](OperatorAssign.md) | 公开方法,详见头文件声明。 | -| [operator+=](OperatorPlusAssign.md) | 公开方法,详见头文件声明。 | -| [Substring](Substring.md) | 公开方法,详见头文件声明。 | -| [Trim](Trim.md) | 公开方法,详见头文件声明。 | -| [ToLower](ToLower.md) | 公开方法,详见头文件声明。 | -| [ToUpper](ToUpper.md) | 公开方法,详见头文件声明。 | -| [Find](Find.md) | 查找并返回匹配对象。 | -| [StartsWith](StartsWith.md) | 公开方法,详见头文件声明。 | -| [EndsWith](EndsWith.md) | 公开方法,详见头文件声明。 | -| [CStr](CStr.md) | 公开方法,详见头文件声明。 | -| [Length](Length.md) | 公开方法,详见头文件声明。 | -| [Capacity](Capacity.md) | 公开方法,详见头文件声明。 | -| [Empty](Empty.md) | 公开方法,详见头文件声明。 | -| [operator[]](OperatorSubscript.md) | 公开方法,详见头文件声明。 | -| [Clear](Clear.md) | 清空内部数据。 | -| [Reserve](Reserve.md) | 公开方法,详见头文件声明。 | -| [Resize](Resize.md) | 公开方法,详见头文件声明。 | +- 默认构造会分配一个长度为 1 的缓冲区,并写入 `'\0'` +- 从 `const char*` 构造时会复制输入内容 +- 拷贝构造和拷贝赋值执行深拷贝 +- 移动构造和移动赋值直接转移内部缓冲区指针 +- 析构时用 `delete[]` 释放内部字符缓冲 + +当前实现里最需要注意的点是移动后的对象状态: + +- move 之后,源对象的 `m_data` 会被置为 `nullptr` +- 这意味着“被移动后的 `String` 仍像空字符串一样安全可读”并不是当前实现保证 + +析构和再次赋值仍然安全,但不要假设对 moved-from 对象调用 `CStr()` 一定还能得到 `""`。 + +## 当前行为 + +### 拼接与修改 + +- `operator+=(String)` 和 `operator+=(const char*)` 会在容量不足时重新分配到“刚好够用”的新缓冲 +- `operator+=(char)` 的扩容策略则是 `newLength * 2` +- `Clear()` 只把长度归零并写入终止符,不释放容量 +- `Reserve()` 只增不减 +- `Resize()` 会补齐字符并保持尾部 `'\0'` + +### 文本处理 + +- `Substring(pos, len)` 越界起点会返回空字符串 +- `Trim()` 只裁剪空格、`\t`、`\n`、`\r` +- `ToLower()` / `ToUpper()` 只处理 ASCII 英文字母 +- `StartsWith()` / `EndsWith()` 支持 `String` 和 `const char*` 两种形式 + +### 查找 + +`Find(const char* str, SizeType pos)` 当前有一个和 `std::string` 不同的语义: + +- 当 `str` 为空字符串时,它返回 `npos` + +也就是说,当前实现并没有采用“空模式匹配当前位置”的标准库习惯。 ## std::hash 特化 -```cpp -namespace std { - template<> struct hash; -} -``` +`String.h` 当前提供了 `std::hash` 特化,因此 [HashMap](../HashMap/HashMap.md) 可以直接把 `String` 作为 key 使用。哈希算法当前是经典的 djb2 风格实现。 -`String.h` 同时提供 `std::hash` 特化,可直接用于哈希容器键类型。 +## 设计取向 + +商业引擎经常保留自己的字符串类型,不只是为了“重复造轮子”,而是为了: + +- 统一公共 API 形状 +- 未来接 allocator、profile、serialization hook +- 避免在所有模块边界直接暴露标准库类型 + +`String` 当前已经承担了这种基础角色,但仍然偏简单实现,没有做复杂的小字符串优化或完整字符集语义处理。 + +## 线程语义 + +- 类本身没有锁。 +- 所有修改操作都直接写内部缓冲。 +- 默认应按普通单对象值类型理解,不提供并发安全保证。 + +## 当前实现限制 + +- 没有 small string optimization。 +- 大小写转换只覆盖 ASCII。 +- `Find("")` 返回 `npos`,不同于 `std::string`。 +- moved-from 对象不保证仍然表现为空字符串。 +- 没有自定义 allocator 接入路径。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [OperatorAssign](OperatorAssign.md) +- [OperatorPlusAssign](OperatorPlusAssign.md) +- [Substring](Substring.md) +- [Trim](Trim.md) +- [ToLower](ToLower.md) +- [ToUpper](ToUpper.md) +- [Find](Find.md) +- [StartsWith](StartsWith.md) +- [EndsWith](EndsWith.md) +- [CStr](CStr.md) +- [Length](Length.md) +- [Capacity](Capacity.md) +- [Empty](Empty.md) +- [OperatorSubscript](OperatorSubscript.md) +- [Clear](Clear.md) +- [Reserve](Reserve.md) +- [Resize](Resize.md) ## 相关文档 -- [当前目录](../Containers.md) - 返回 `Containers` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Containers.md) +- [Array](../Array/Array.md) +- [HashMap](../HashMap/HashMap.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Core.md b/docs/api/XCEngine/Core/Core.md index e269ad7e..f25b8e23 100644 --- a/docs/api/XCEngine/Core/Core.md +++ b/docs/api/XCEngine/Core/Core.md @@ -4,29 +4,73 @@ **类型**: `module` -**描述**: 基础类型、事件、IO、容器与数学基础设施。 +**描述**: 提供基础类型别名、智能指针约定、引用计数基类、事件分发、层系统、文件写入以及若干基础子模块。 ## 概览 -该目录与 `XCEngine/Core` 对应的 public headers 保持平行,用于承载唯一的 canonical API 文档入口。 +`XCEngine::Core` 是引擎最底层的公共基础设施模块。它和业务功能相比更偏“支撑层”,为上层系统提供一组统一约定: + +- 基础整数和字节类型 +- 所有权和对象生命周期工具 +- 事件分发机制 +- Layer / LayerStack 这种运行时扩展组织方式 +- 简单文件写入封装 +- 更细分的 `Asset`、`Containers`、`IO`、`Math` 子模块 + +很多其他模块都会直接依赖它。例如: + +- `Scene` 和 `Input` 直接使用 [Event](Event/Event.md) +- `ResourceManager` 使用 [SmartPtr](SmartPtr/SmartPtr.md) 里的 `MakeUnique` + +所以这层文档最重要的不是列函数名,而是把“当前基础约定到底是什么”讲清楚。 + +## 设计要点 + +- `Core.h` 是 umbrella header,只聚合 `Types.h`、`RefCounted.h`、`SmartPtr.h`、`Event.h`,本身不引入新的运行时类型。 +- `RefCounted` 和 `SmartPtr` 代表两套不同的 ownership 思路,当前实现并没有把它们自动打通。 +- `Event` 是模板化的轻量事件广播器,强调易用性和复制回调安全,而不是高性能无分配事件总线。 +- `Layer` / `LayerStack` 提供了一种类似应用框架栈的组织方式,但当前在代码库里的真实使用还很少。 + +## umbrella header + +**头文件**: `XCEngine/Core/Core.h` + +`Core.h` 当前只是一个 convenience include: + +- `Types.h` +- `RefCounted.h` +- `SmartPtr.h` +- `Event.h` + +它自己的命名空间体是空的,没有新增 class / struct / function。因此这里不再单独保留一页重复的“`Core` 类型页”,而是把它并入模块页说明。 ## 子目录 -- [Asset](Asset/Asset.md) -- [Containers](Containers/Containers.md) -- [IO](IO/IO.md) -- [Math](Math/Math.md) +- [Asset](Asset/Asset.md) - 资源句柄、异步加载与资源管理基础设施。 +- [Containers](Containers/Containers.md) - 引擎自定义容器和字符串类型。 +- [IO](IO/IO.md) - 路径与文件系统相关基础能力。 +- [Math](Math/Math.md) - 向量、矩阵、四元数、颜色等数学基础设施。 -## 头文件 +## 顶层头文件 -- [Core](Core/Core.md) - `Core.h` -- [Event](Event/Event.md) - `Event.h` -- [FileWriter](FileWriter/FileWriter.md) - `FileWriter.h` -- [Layer](Layer/Layer.md) - `Layer.h` -- [LayerStack](LayerStack/LayerStack.md) - `LayerStack.h` -- [RefCounted](RefCounted/RefCounted.md) - `RefCounted.h` -- [SmartPtr](SmartPtr/SmartPtr.md) - `SmartPtr.h` -- [Types](Types/Types.md) - `Types.h` +- [Event](Event/Event.md) - `Event.h`,模板化事件分发器。 +- [FileWriter](FileWriter/FileWriter.md) - `FileWriter.h`,简单文件写入封装。 +- [Layer](Layer/Layer.md) - `Layer.h`,Layer 基类。 +- [LayerStack](LayerStack/LayerStack.md) - `LayerStack.h`,Layer 容器与调度顺序。 +- [RefCounted](RefCounted/RefCounted.md) - `RefCounted.h`,侵入式原子引用计数基类。 +- [SmartPtr](SmartPtr/SmartPtr.md) - `SmartPtr.h`,`shared_ptr` / `unique_ptr` 别名与工厂函数。 +- [Types](Types/Types.md) - `Types.h`,基础整数别名。 + +## 推荐阅读顺序 + +1. 先读 [Core Foundations: Ownership, Events, And Layers](../../_guides/Core/Core-Foundations-Ownership-Events-And-Layers.md)。 +2. 再读 [SmartPtr](SmartPtr/SmartPtr.md) 和 [RefCounted](RefCounted/RefCounted.md),理解当前代码库里的 ownership 分层。 +3. 然后读 [Event](Event/Event.md),这是上层模块依赖最广的顶层类型之一。 +4. 只有在需要应用框架栈语义时,再读 [Layer](Layer/Layer.md) 和 [LayerStack](LayerStack/LayerStack.md)。 + +## 相关指南 + +- [Core Foundations: Ownership, Events, And Layers](../../_guides/Core/Core-Foundations-Ownership-Events-And-Layers.md) ## 相关文档 diff --git a/docs/api/XCEngine/Core/Core/Core.md b/docs/api/XCEngine/Core/Core/Core.md deleted file mode 100644 index d523ade2..00000000 --- a/docs/api/XCEngine/Core/Core/Core.md +++ /dev/null @@ -1,18 +0,0 @@ -# Core - -**命名空间**: `XCEngine` - -**类型**: `header` - -**头文件**: `XCEngine/Core/Core.h` - -**描述**: 定义 `XCEngine/Core` 子目录中的 `Core` public API。 - -## 概述 - -`Core.h` 是 `XCEngine/Core` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 - -## 相关文档 - -- [当前目录](../Core.md) - 返回 `Core` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 diff --git a/docs/api/XCEngine/Core/Event/Event.md b/docs/api/XCEngine/Core/Event/Event.md index ff7e1f12..f2615abe 100644 --- a/docs/api/XCEngine/Core/Event/Event.md +++ b/docs/api/XCEngine/Core/Event/Event.md @@ -2,35 +2,117 @@ **命名空间**: `XCEngine::Core` -**类型**: `class` +**类型**: `class template` **头文件**: `XCEngine/Core/Event.h` -**描述**: 定义 `XCEngine/Core` 子目录中的 `Event` public API。 +**描述**: 一个轻量模板事件广播器,支持订阅、延迟取消订阅和参数化回调调用。 -## 概述 +## 角色概述 -`Event.h` 是 `XCEngine/Core` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Event` 是当前代码库里最常用的基础广播机制之一。它的定位很明确: -## 声明概览 +- 用模板参数表达回调签名 +- 用 `Subscribe()` 返回监听器 ID +- 用 `Invoke()` 把参数广播给所有当前监听器 -| 声明 | 类型 | 说明 | -|------|------|------| -| `Event` | `class` | 头文件中的公开声明。 | +和一些商业引擎里的事件系统相比,它更偏轻量 C++ 容器封装,而不是完整的消息总线、typed dispatcher 或 editor event graph。 -## 公共方法 +## 当前真实使用 -| 方法 | 描述 | -|------|------| -| [Subscribe](Subscribe.md) | 公开方法,详见头文件声明。 | -| [Unsubscribe](Unsubscribe.md) | 公开方法,详见头文件声明。 | -| [ProcessUnsubscribes](ProcessUnsubscribes.md) | 公开方法,详见头文件声明。 | -| [Invoke](Invoke.md) | 公开方法,详见头文件声明。 | -| [Clear](Clear.md) | 清空内部数据。 | -| [begin](begin.md) | 公开方法,详见头文件声明。 | -| [end](end.md) | 公开方法,详见头文件声明。 | +当前已能在多个核心模块里看到它的直接使用,例如: + +- `Scene::OnGameObjectCreated()` +- `Scene::OnGameObjectDestroyed()` +- `SceneManager` 的场景切换事件 +- `InputManager` 的键鼠输入事件 + +这说明它不是“理论上的基础设施”,而是已经进入主代码路径的常用类型。 + +## 当前实现行为 + +### 1. `Subscribe()` 会分配递增 ID + +- 监听器以 `(uint64_t id, Callback)` 形式保存在 `m_listeners` 中 +- ID 由 `m_nextId` 递增生成 +- `tests/core/test_core.cpp` 已验证不同订阅返回的 ID 不同 + +### 2. `Unsubscribe()` 是延迟取消订阅 + +`Unsubscribe(id)` 当前不会立刻从 `m_listeners` 删除,而是先把 ID 放进 `m_pendingUnsubscribes`。 + +真正删除发生在两种时机: + +- 显式调用 [ProcessUnsubscribes](ProcessUnsubscribes.md) +- 下一次 [Invoke](Invoke.md) 开始前 + +这意味着 API 语义更接近“标记取消订阅”,而不是“立刻从容器抹掉”。 + +### 3. `Invoke()` 会在锁外执行回调 + +`Invoke()` 的当前流程是: + +1. 加锁 +2. 先处理 `m_pendingUnsubscribes` +3. 复制 `m_listeners` 到本地 `listenersCopy` +4. 释放锁 +5. 逐个执行回调 + +这套做法的关键收益是: + +- 回调执行期间不会长期占着互斥锁 +- 回调内部即使再次订阅/取消订阅,也不直接破坏当前这次遍历 + +代价是会有一次监听器数组复制,且它不是为极端高频无分配场景优化的实现。 + +## 线程语义 + +### 已有保护 + +- `Subscribe()`、`Unsubscribe()`、`ProcessUnsubscribes()`、`Invoke()`、`Clear()` 都会访问 `m_mutex` + +### 不能误解的地方 + +- [begin](begin.md) / [end](end.md) 直接返回内部 `std::vector` 迭代器,没有加锁包装 +- 如果你跨线程持有这些迭代器,同时别的线程订阅/取消订阅,就没有安全保证 + +所以可以说:它对常规订阅和调用路径做了基础互斥保护,但并不是“所有访问方式都线程安全”的容器。 + +## 当前实现限制 + +- `begin()` / `end()` 暴露的是内部容器迭代器,不适合并发或长时间持有。 +- 回调列表在 `Invoke()` 时会拷贝一份,适合易用性,但不适合把它当成极致性能事件系统。 +- 没有 listener priority、事件消费中止或过滤器机制。 +- 没有 scoped subscription / RAII subscription token。 + +## 测试覆盖 + +`tests/core/test_core.cpp` 已覆盖: + +- 订阅与回调调用 +- 带参数事件 +- 取消订阅 +- 清空监听器 +- 多监听器广播 +- 返回 ID 唯一性 + +## 相关方法 + +- [Subscribe](Subscribe.md) +- [Unsubscribe](Unsubscribe.md) +- [ProcessUnsubscribes](ProcessUnsubscribes.md) +- [Invoke](Invoke.md) +- [Clear](Clear.md) +- [begin](begin.md) +- [end](end.md) + +## 相关指南 + +- [Core Foundations: Ownership, Events, And Layers](../../../_guides/Core/Core-Foundations-Ownership-Events-And-Layers.md) ## 相关文档 -- [当前目录](../Core.md) - 返回 `Core` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Core.md) +- [Scene](../../Scene/Scene/Scene.md) +- [InputManager](../../Input/InputManager/InputManager.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Core/FileWriter/FileWriter.md b/docs/api/XCEngine/Core/FileWriter/FileWriter.md index b6f149b5..484fbe36 100644 --- a/docs/api/XCEngine/Core/FileWriter/FileWriter.md +++ b/docs/api/XCEngine/Core/FileWriter/FileWriter.md @@ -6,31 +6,92 @@ **头文件**: `XCEngine/Core/FileWriter.h` -**描述**: 定义 `XCEngine/Core` 子目录中的 `FileWriter` public API。 +**描述**: 一个基于 `FILE*` 的轻量文件写入封装,提供打开、关闭、写入和刷新能力。 -## 概述 +## 角色概述 -`FileWriter.h` 是 `XCEngine/Core` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`FileWriter` 是当前 `Core` 顶层里非常朴素的 I/O 工具类。它没有做复杂的路径解析、目录创建或异步写入,只是把一组常见的 C 文件写入操作包装成 RAII 风格接口。 -## 声明概览 +这类工具在引擎里常用于: -| 声明 | 类型 | 说明 | -|------|------|------| -| `FileWriter` | `class` | 头文件中的公开声明。 | +- 简单二进制导出 +- 文本或日志写出 +- 不需要完整文件系统抽象时的底层落盘 -## 公共方法 +## 当前实现行为 -| 方法 | 描述 | -|------|------| -| [FileWriter()](Constructor.md) | 构造对象。 | -| [~FileWriter()](Destructor.md) | 销毁对象并释放相关资源。 | -| [Open](Open.md) | 公开方法,详见头文件声明。 | -| [Close](Close.md) | 公开方法,详见头文件声明。 | -| [Write](Write.md) | 公开方法,详见头文件声明。 | -| [Flush](Flush.md) | 公开方法,详见头文件声明。 | -| [IsOpen](IsOpen.md) | 查询当前状态。 | +### 1. 构造和析构遵循 RAII + +- 默认构造不会自动打开文件 +- 带路径构造会立刻尝试 `Open()` +- 析构函数会自动 `Close()` + +### 2. `Open()` 会先关闭旧文件 + +当前 `Open(const char* filePath, bool append)` 的第一步就是调用 `Close()`。这意味着: + +- 可以重复复用同一个 `FileWriter` +- 打开新文件前,旧句柄会先被安全关闭 + +### 3. 始终以二进制模式打开 + +当前模式字符串是: + +- `append == false` -> `"wb"` +- `append == true` -> `"ab"` + +也就是说,它没有文本模式分支,始终按二进制写入处理。 + +### 4. `Write()` 的失败条件很直接 + +`Write(const char* data, size_t length)` 在以下情况返回 `false`: + +- 文件没打开 +- `data == nullptr` +- `length == 0` +- `fwrite` 未完整写入指定长度 + +这意味着“空写入”也会被视为失败,而不是 no-op success。 + +### 5. `Write(const Containers::String&)` 只是便利重载 + +它会转发到字节写入版本,使用: + +- `str.CStr()` +- `str.Length()` + +当前没有额外编码转换或换行处理。 + +## 当前代码库中的使用情况 + +在当前取证范围内,没有看到 `FileWriter` 在 `engine/src` 里形成广泛调用链。因此它当前更像底层通用工具,而不是高频核心主线类型。 + +## 线程语义 + +- 当前实现没有加锁。 +- `FILE*` 句柄由对象内部独占持有。 +- 默认应按单线程顺序使用。 + +## 当前实现限制 + +- 不会自动创建父目录。 +- 只提供写入,不提供读取。 +- 打开模式固定为二进制写/追加。 +- `Write(length == 0)` 会返回 `false`。 +- 没有看到直接单元测试覆盖。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [Open](Open.md) +- [Close](Close.md) +- [Write](Write.md) +- [Flush](Flush.md) +- [IsOpen](IsOpen.md) ## 相关文档 -- [当前目录](../Core.md) - 返回 `Core` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Core.md) +- [IO](../IO/IO.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Core/IO/FileArchive/FileArchive.md b/docs/api/XCEngine/Core/IO/FileArchive/FileArchive.md index 2f8540c4..603b1f02 100644 --- a/docs/api/XCEngine/Core/IO/FileArchive/FileArchive.md +++ b/docs/api/XCEngine/Core/IO/FileArchive/FileArchive.md @@ -6,34 +6,93 @@ **头文件**: `XCEngine/Core/IO/FileArchive.h` -**描述**: 定义 `XCEngine/Core/IO` 子目录中的 `FileArchive` public API。 +**描述**: `IArchive` 的一个当前非常轻量的实现,按“目录前缀 + 相对文件名”方式读取普通文件。 -## 概述 +## 角色概述 -`FileArchive.h` 是 `XCEngine/Core/IO` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +从接口命名上看,`FileArchive` 像是“文件归档读取器”或“包挂载器”。但按当前实现,它实际上更接近一个目录适配层: -## 声明概览 +- `Open(path)` 保存一个基础路径 +- `Read(fileName)` 把 `fileName` 拼接到这个路径后面 +- 再直接用 `fopen` 读取普通文件 -| 声明 | 类型 | 说明 | -|------|------|------| -| `FileArchive` | `class` | 继承自 `IArchive` 的公开声明。 | +所以当前版本不要把它理解成 zip、pak 或自定义压缩包读取器。它更像是给 [ResourceFileSystem](../ResourceFileSystem/ResourceFileSystem.md) 准备的统一归档接口占位实现。 -## 公共方法 +## 生命周期 -| 方法 | 描述 | -|------|------| -| [FileArchive()](Constructor.md) | 构造对象。 | -| [~FileArchive()](Destructor.md) | 销毁对象并释放相关资源。 | -| [Open](Open.md) | 公开方法,详见头文件声明。 | -| [Close](Close.md) | 公开方法,详见头文件声明。 | -| [Read](Read.md) | 公开方法,详见头文件声明。 | -| [GetSize](GetSize.md) | 获取相关状态或对象。 | -| [Exists](Exists.md) | 公开方法,详见头文件声明。 | -| [Enumerate](Enumerate.md) | 公开方法,详见头文件声明。 | -| [IsValid](IsValid.md) | 查询当前状态。 | -| [GetPath](GetPath.md) | 获取相关状态或对象。 | +- 默认构造时 `m_isValid = false` +- `Open(path)` 会直接写入 `m_archivePath` 并把 `m_isValid = true` +- 析构时会调用 `Close()` +- `Close()` 会清空路径并把对象标记为无效 + +这里有个很重要的现实语义: + +- `Open(path)` 当前不会检查路径是否存在,也不会验证它是不是目录或真实归档文件 +- 只要调用了 `Open()`,对象就会返回 true 并进入“有效”状态 + +测试也明确覆盖了这一点:即使传入无效路径,`Open()` 依然返回 true。 + +## 当前读取行为 + +### Read + +`Read(fileName, buffer, size, offset)` 当前会: + +1. 检查 `m_isValid` +2. 用 `m_archivePath + "/" + fileName` 组装完整路径 +3. 用 `fopen(..., "rb")` 打开普通文件 +4. 可选 `fseek` 到偏移位置 +5. 读取指定字节数 + +只有当实际读取字节数和 `size` 完全相等时才返回 true。 + +### GetSize / Exists + +- `GetSize(fileName)` 也是对拼接后的真实文件路径做 `fopen` 和 `ftell` +- `Exists(fileName)` 则以能否成功打开拼接后的文件为准 + +也就是说,`FileArchive` 的“归档”语义当前完全建立在宿主文件系统之上。 + +### Enumerate + +`Enumerate(pattern, outFiles)` 当前只会 `outFiles.Clear()`,然后结束。pattern 还没有真正实现。 + +## 设计取向 + +把目录读取和真正归档读取统一到一个 `IArchive` 接口下面,是合理的商业引擎方向。这样上层虚拟文件系统只关心“挂载点能不能读某个相对路径”,不关心背后到底是目录、打包文件还是远程内容。 + +但当前 `FileArchive` 只是把这个接口先站住,还没有进入真正的 archive reader 阶段。 + +## 线程语义 + +- 类本身没有锁。 +- 读取调用每次都重新打开一次文件,没有共享文件句柄缓存。 +- 默认应按简单同步 I/O 组件理解。 + +## 当前实现限制 + +- `Open()` 不检查路径存在性。 +- 当前不是压缩归档格式读取器,只是目录前缀适配器。 +- `Enumerate()` 还是 TODO。 +- 不缓存句柄,也不做批量 I/O 优化。 +- 不提供校验、压缩、索引表或挂载优先级能力。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [Open](Open.md) +- [Close](Close.md) +- [Read](Read.md) +- [GetSize](GetSize.md) +- [Exists](Exists.md) +- [Enumerate](Enumerate.md) +- [IsValid](IsValid.md) +- [GetPath](GetPath.md) ## 相关文档 -- [当前目录](../IO.md) - 返回 `IO` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../IO.md) +- [ResourceFileSystem](../ResourceFileSystem/ResourceFileSystem.md) +- [ResourcePackage](../ResourcePackage/ResourcePackage.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/IO/IO.md b/docs/api/XCEngine/Core/IO/IO.md index 371de1af..fcd12c05 100644 --- a/docs/api/XCEngine/Core/IO/IO.md +++ b/docs/api/XCEngine/Core/IO/IO.md @@ -1,14 +1,38 @@ # IO -**命名空间**: `XCEngine::Core::IO` +**命名空间**: `XCEngine::Resources` **类型**: `submodule` -**描述**: 资源路径、归档与虚拟文件系统。 +**描述**: 定义资源路径辅助、资源加载器接口、包文件与虚拟文件系统相关基础设施。 ## 概览 -该目录与 `XCEngine/Core/IO` 对应的 public headers 保持平行,用于承载唯一的 canonical API 文档入口。 +`Core/IO` 这个目录在物理结构上属于 `Core`,但逻辑命名空间已经落在 `XCEngine::Resources`。它承担的是“资源系统和底层文件访问之间的桥梁层”: + +- [ResourcePath](ResourcePath/ResourcePath.md) 负责轻量路径拆分和 GUID 派生 +- [IResourceLoader](IResourceLoader/IResourceLoader.md) 负责把磁盘数据转换成运行时 `IResource` +- [FileArchive](FileArchive/FileArchive.md) 和 [ResourcePackage](ResourcePackage/ResourcePackage.md) 负责资源容器抽象 +- [ResourceFileSystem](ResourceFileSystem/ResourceFileSystem.md) 试图把目录、归档和包文件统一到一个虚拟资源访问入口 + +从设计方向看,这一层很像商业引擎里的 runtime file access / resource loading substrate。它不是完整的编辑器资产数据库,更接近运行时资源读取和格式装载层。 + +## 设计要点 + +- `IResourceLoader` 是当前最关键的接口,因为 [ResourceManager](../Asset/ResourceManager/ResourceManager.md) 最终就是靠它真正加载资源。 +- `ResourcePath` 只是字符串辅助类,不负责真实文件系统查询、标准化或路径规范化。 +- `FileArchive`、`ResourcePackage`、`ResourceFileSystem` 已经搭好了虚拟文件系统方向的 API 轮廓,但当前实现普遍还比较早期。 +- 当前这层里存在一些需要明确写进文档的实现差异,例如: + - `ResourcePath::GetExtension()` 返回带点扩展名,如 `.png` + - `IResourceLoader::GetExtension()` 返回不带点扩展名,如 `png` + +## 当前实现现状 + +- 真正稳定可用的主路径仍然是“具体 loader + [ResourceManager](../Asset/ResourceManager/ResourceManager.md) 同步加载”。 +- `ResourcePath` 当前可用于做名称拆分和 GUID 生成,但不应被理解成跨平台标准化路径工具。 +- `FileArchive` 当前更像“目录前缀适配器”,不是真正的压缩归档读取器。 +- `ResourcePackage` 的 builder 和 reader 当前还没有形成闭环。 +- `ResourceFileSystem` 的虚拟文件系统层当前仍有明显占位实现和行为缺口。 ## 头文件 @@ -18,7 +42,19 @@ - [ResourcePackage](ResourcePackage/ResourcePackage.md) - `ResourcePackage.h` - [ResourcePath](ResourcePath/ResourcePath.md) - `ResourcePath.h` +## 推荐阅读顺序 + +1. 先读 [ResourcePath](ResourcePath/ResourcePath.md),理解当前路径字符串约定和 GUID 派生方式。 +2. 再读 [IResourceLoader](IResourceLoader/IResourceLoader.md),这是连接 IO 层和 Asset 层的关键接口。 +3. 然后读 [ResourceFileSystem](ResourceFileSystem/ResourceFileSystem.md),了解当前虚拟文件系统层的设计方向和限制。 +4. 最后再读 [FileArchive](FileArchive/FileArchive.md) 和 [ResourcePackage](ResourcePackage/ResourcePackage.md),理解当前容器格式支持做到哪一步。 + +## 相关指南 + +- [Resource Paths, Packages, And Virtual File System](../../../_guides/Core/IO/Resource-Paths-Packages-And-Virtual-FileSystem.md) + ## 相关文档 - [上级目录](../Core.md) +- [Asset](../Asset/Asset.md) - [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Core/IO/IResourceLoader/IResourceLoader.md b/docs/api/XCEngine/Core/IO/IResourceLoader/IResourceLoader.md index 8765f6c3..f6d3d0ca 100644 --- a/docs/api/XCEngine/Core/IO/IResourceLoader/IResourceLoader.md +++ b/docs/api/XCEngine/Core/IO/IResourceLoader/IResourceLoader.md @@ -2,37 +2,122 @@ **命名空间**: `XCEngine::Resources` -**类型**: `class (abstract)` +**类型**: `header-overview` **头文件**: `XCEngine/Core/IO/IResourceLoader.h` -**描述**: 定义 `XCEngine/Core/IO` 子目录中的 `IResourceLoader` public API。 +**描述**: 定义资源加载器抽象接口、加载结果结构和静态注册宏。 -## 概述 +## 角色概述 -`IResourceLoader.h` 是 `XCEngine/Core/IO` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`IResourceLoader` 是当前资源系统真正把“文件或字节流”变成“运行时资源对象”的关键接口。[ResourceManager](../../Asset/ResourceManager/ResourceManager.md) 的同步和异步加载入口,最终都依赖它完成类型分派和具体装载。 -## 声明概览 +这种设计和商业引擎里常见的 importer / loader 分层很接近。好处是: -| 声明 | 类型 | 说明 | -|------|------|------| -| `LoadResult` | `struct` | 头文件中的公开声明。 | -| `IResourceLoader` | `class` | 头文件中的公开声明。 | -| `loaderType` | `struct` | 头文件中的公开声明。 | +- `ResourceManager` 不需要知道具体文件格式细节 +- 新资源格式可以通过新增 loader 扩展 +- 资源类型、扩展名和导入配置可以在各自 loader 内部收敛 -## 公共方法 +## LoadResult 语义 -| 方法 | 描述 | -|------|------| -| [~IResourceLoader()](Destructor.md) | 销毁对象并释放相关资源。 | -| [GetResourceType](GetResourceType.md) | 获取相关状态或对象。 | -| [GetSupportedExtensions](GetSupportedExtensions.md) | 获取相关状态或对象。 | -| [CanLoad](CanLoad.md) | 判断当前条件下是否可执行。 | -| [Load](Load.md) | 加载资源或数据。 | -| [LoadAsync](LoadAsync.md) | 加载资源或数据。 | -| [GetDefaultSettings](GetDefaultSettings.md) | 获取相关状态或对象。 | +`LoadResult` 当前公开暴露: + +- `resource` +- `success` +- `errorMessage` + +它有一个需要特别说明的实现细节: + +- `operator bool()` 的判定条件是 `success && resource != nullptr` + +这意味着: + +- `LoadResult(resourcePtr)` 只有在 `resourcePtr != nullptr` 时才会转成 true +- `LoadResult("some error")` 一定是 false +- `LoadResult(true)` 会把 `success` 设成 true,但由于 `resource` 仍然是 `nullptr`,`operator bool()` 依然是 false + +测试当前只验证了 `success` 字段本身,没有把 `LoadResult(true)` 当成“完整成功结果”使用。 + +## IResourceLoader 契约 + +派生类当前需要实现: + +- `GetResourceType()` +- `GetSupportedExtensions()` +- `CanLoad(path)` +- `Load(path, settings)` +- `GetDefaultSettings()` + +这套契约本身很清楚:先声明自己属于什么资源类型、支持什么扩展名,再真正执行同步加载。 + +## 默认异步行为 + +`LoadAsync()` 在基类里的默认实现并不是后台线程加载。当前实现只是: + +1. 直接调用同步 `Load(path, settings)` +2. 如果有回调,则在同一调用栈里立刻回调结果 + +因此,单独从 `IResourceLoader` 视角看,`LoadAsync()` 只是“异步接口形状的同步默认实现”,不是并发加载能力。 + +## 受保护辅助函数 + +### ReadFileData + +- 以二进制方式一次性读完整个文件 +- 打不开文件时返回空数组 +- 读取过程中失败也返回空数组 + +### GetExtension + +`IResourceLoader::GetExtension(path)` 当前返回的是不带点的扩展名,例如: + +- `a.png` -> `png` +- `shader.hlsl` -> `hlsl` + +这和 [ResourcePath](../ResourcePath/ResourcePath.md) 的 `GetExtension()` 不同,后者返回带点扩展名,例如 `.png`。这是当前 IO 层一个很容易踩坑的细节差异。 + +## REGISTER_RESOURCE_LOADER 宏 + +`REGISTER_RESOURCE_LOADER(loaderType)` 提供了一种静态注册便捷方式。按当前展开逻辑,它会: + +- 定义一个匿名命名空间下的 registrar +- 在静态初始化阶段执行 `ResourceManager::Get().RegisterLoader(new loaderType())` + +这带来两个现实后果: + +- loader 注册时机是静态初始化时,而不是显式运行时配置时 +- 它会堆分配一个 `loaderType` 实例,而当前 [ResourceManager](../../Asset/ResourceManager/ResourceManager.md) 不负责统一销毁注册进来的 raw pointer + +所以宏注册更像“进程生命周期内常驻 loader”的便捷入口,而不是严格托管的插件系统。 + +## 线程语义 + +- 接口本身没有锁。 +- 默认 `LoadAsync()` 不切线程。 +- 具体 loader 是否线程安全,完全取决于派生实现。 + +## 当前实现限制 + +- `LoadAsync()` 默认只是同步转发。 +- `LoadResult(true)` 的 `success` 与 `operator bool()` 语义并不完全等价。 +- 扩展名格式与 [ResourcePath](../ResourcePath/ResourcePath.md) 不一致。 +- 宏注册路径当前没有明确对象销毁闭环。 +- 当前测试主要覆盖 `LoadResult`,对 loader 抽象层本身的契约覆盖有限。 + +## 相关方法 + +- [Destructor](Destructor.md) +- [GetResourceType](GetResourceType.md) +- [GetSupportedExtensions](GetSupportedExtensions.md) +- [CanLoad](CanLoad.md) +- [Load](Load.md) +- [LoadAsync](LoadAsync.md) +- [GetDefaultSettings](GetDefaultSettings.md) ## 相关文档 -- [当前目录](../IO.md) - 返回 `IO` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../IO.md) +- [IResource](../../Asset/IResource/IResource.md) +- [ImportSettings](../../Asset/ImportSettings/ImportSettings.md) +- [ResourceManager](../../Asset/ResourceManager/ResourceManager.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/IO/ResourceFileSystem/ResourceFileSystem.md b/docs/api/XCEngine/Core/IO/ResourceFileSystem/ResourceFileSystem.md index 608d2f25..07b2faae 100644 --- a/docs/api/XCEngine/Core/IO/ResourceFileSystem/ResourceFileSystem.md +++ b/docs/api/XCEngine/Core/IO/ResourceFileSystem/ResourceFileSystem.md @@ -2,43 +2,159 @@ **命名空间**: `XCEngine::Resources` -**类型**: `class (singleton)` +**类型**: `header-overview` **头文件**: `XCEngine/Core/IO/ResourceFileSystem.h` -**描述**: 定义 `XCEngine/Core/IO` 子目录中的 `ResourceFileSystem` public API。 +**描述**: 定义归档抽象接口、资源基础信息结构以及当前虚拟资源文件系统入口。 -## 概述 +## 角色概述 -`ResourceFileSystem.h` 是 `XCEngine/Core/IO` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`ResourceFileSystem.h` 当前同时定义了三层概念: -## 声明概览 +- `IArchive`:抽象归档挂载点接口 +- `ResourceInfo`:资源基础信息结构 +- `ResourceFileSystem`:统一的资源查找与读取入口 -| 声明 | 类型 | 说明 | -|------|------|------| -| `IArchive` | `class` | 头文件中的公开声明。 | -| `ResourceInfo` | `struct` | 头文件中的公开声明。 | -| `ResourceFileSystem` | `class` | 头文件中的公开声明。 | +从设计方向看,这非常像商业引擎里的虚拟文件系统起点:上层只给相对路径,底层决定它来自根目录、附加目录、归档还是包文件。 -## 公共方法 +但当前实现仍然很早期。接口方向是对的,行为闭环还没有完成。 -| 方法 | 描述 | -|------|------| -| [ResourceFileSystem()](Constructor.md) | 构造对象。 | -| [~ResourceFileSystem()](Destructor.md) | 销毁对象并释放相关资源。 | -| [Initialize](Initialize.md) | 初始化内部状态。 | -| [Shutdown](Shutdown.md) | 关闭并清理内部状态。 | -| [AddArchive](AddArchive.md) | 添加元素或建立关联。 | -| [AddDirectory](AddDirectory.md) | 添加元素或建立关联。 | -| [RemoveArchive](RemoveArchive.md) | 移除元素或解除关联。 | -| [FindResource](FindResource.md) | 查找并返回匹配对象。 | -| [ReadResource](ReadResource.md) | 公开方法,详见头文件声明。 | -| [Exists](Exists.md) | 公开方法,详见头文件声明。 | -| [GetResourceInfo](GetResourceInfo.md) | 获取相关状态或对象。 | -| [EnumerateResources](EnumerateResources.md) | 公开方法,详见头文件声明。 | -| [Get](Get.md) | 获取相关状态或对象。 | +## IArchive 语义 + +`IArchive` 抽象了挂载点最基本的几件事: + +- 打开 / 关闭 +- 按相对路径读取字节 +- 查询大小和存在性 +- 枚举匹配项 +- 查询挂载点是否有效 + +[FileArchive](../FileArchive/FileArchive.md) 是它当前唯一的轻量实现。 + +## ResourceInfo 语义 + +`ResourceInfo` 当前公开包含: + +- `path` +- `size` +- `modifiedTime` +- `inArchive` +- `archivePath` + +从结构设计上看,它想承载“虚拟资源查询结果”的统一元数据。但当前实现里只有少数字段会被真正填充。 + +## ResourceFileSystem 当前行为 + +### 初始化与单例 + +- `Initialize(rootPath)` 当前只是记录根路径 +- `Shutdown()` 会清空 archives、directories、info cache 和根路径 +- `Get()` 提供了一个全局单例入口 + +### 目录与归档挂载 + +- `AddDirectory(directoryPath)` 会把目录字符串加入 `m_directories` +- `AddArchive(archivePath)` 当前也只是把这个路径塞进 `m_directories` +- `RemoveArchive(archivePath)` 也只是从 `m_directories` 里删同名字符串 + +这说明当前“archive 挂载”其实还没有进入 `m_archives` 这条真实归档通路。接口叫 `AddArchive()`,实际行为更像“再加一个目录搜索路径”。 + +### 查找语义 + +`FindResource(relativePath, outAbsolutePath)` 依次尝试: + +1. `FindInDirectories` +2. `FindInArchives` + +但 `FindInDirectories()` 当前有个非常重要的实现特征: + +- 只要 `m_rootPath` 非空,就直接返回 `m_rootPath + "/" + relativePath` +- 或者只要 `m_directories` 非空,就直接返回第一个目录拼接结果 +- 它不会真的检查目标文件是否存在 + +这会直接影响 `Exists()` 的语义,因为 `Exists()` 只是调用 `FindResource()`。因此当前版本里: + +- 只要已经初始化了根路径,`Exists("xxx")` 很可能就会返回 true +- 即使这个文件在磁盘上根本不存在 + +测试里也明确依赖了这一点。 + +### 读取与资源信息 + +按接口意图,`ReadResource()` 和 `GetResourceInfo()` 应该是当前类最重要的实际功能。但按当前实现,它们都有一个严重问题: + +- 这两个函数先对 `m_mutex` 加锁 +- 然后又调用 `FindResource()` +- `FindResource()` 内部再次对同一个 `m_mutex` 加锁 + +而 `Threading::Mutex` 当前底层封装的是 `std::mutex`,不是递归锁。按当前源码,这两条调用路径存在自死锁风险。 + +另外,`GetResourceInfo()` 即使忽略这个问题,当前也只会: + +- 设置 `path` +- 设置 `inArchive = false` +- 把结果塞入 cache + +真实 `size` 和 `modifiedTime` 仍是 TODO。 + +### 归档查找 + +`FindInArchives()` 会遍历 `m_archives` 调用 `Exists()`。但当前 `AddArchive()` 并没有往 `m_archives` 塞任何对象,所以这条归档查找路径实际上默认不会生效。 + +### 枚举 + +`EnumerateResources(pattern, outResources)` 当前只清空输出数组,没有真正枚举逻辑。 + +## 设计取向 + +把资源文件系统做成独立服务是合理方向。商业引擎通常都会把: + +- 普通目录 +- 打包文件 +- patch 包 +- 远程流送资源 + +统一抽象成一个虚拟文件系统层,再让 loader 基于它读取数据。这样资源加载器就不必知道内容到底来自哪里。 + +`ResourceFileSystem` 当前已经具备这套分层的基本轮廓,只是实现深度还明显不够。 + +## 线程语义 + +- 当前大部分公开方法都会加 `m_mutex` +- 但 `ReadResource()` 与 `GetResourceInfo()` 因重复加锁存在自死锁风险 +- 默认不应把当前版本当成可放心跨线程调用的成熟 VFS + +## 当前实现限制 + +- `AddArchive()` 没有真正创建或挂载 `IArchive` +- `RemoveArchive()` 也只移除目录字符串,不处理 `m_archives` +- `Exists()` 只代表“当前能否拼出一个候选路径”,不代表真实文件存在 +- `ReadResource()` / `GetResourceInfo()` 当前存在自死锁风险 +- `GetResourceInfo()` 不会真实填充 `size` 和 `modifiedTime` +- `EnumerateResources()` 是空实现 +- 归档路径当前默认不会真正参与查找 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [Initialize](Initialize.md) +- [Shutdown](Shutdown.md) +- [AddArchive](AddArchive.md) +- [AddDirectory](AddDirectory.md) +- [RemoveArchive](RemoveArchive.md) +- [FindResource](FindResource.md) +- [ReadResource](ReadResource.md) +- [Exists](Exists.md) +- [GetResourceInfo](GetResourceInfo.md) +- [EnumerateResources](EnumerateResources.md) +- [Get](Get.md) ## 相关文档 -- [当前目录](../IO.md) - 返回 `IO` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../IO.md) +- [FileArchive](../FileArchive/FileArchive.md) +- [ResourcePackage](../ResourcePackage/ResourcePackage.md) +- [IResourceLoader](../IResourceLoader/IResourceLoader.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/IO/ResourcePackage/ResourcePackage.md b/docs/api/XCEngine/Core/IO/ResourcePackage/ResourcePackage.md index 9aaf8d7b..8c858c36 100644 --- a/docs/api/XCEngine/Core/IO/ResourcePackage/ResourcePackage.md +++ b/docs/api/XCEngine/Core/IO/ResourcePackage/ResourcePackage.md @@ -2,40 +2,149 @@ **命名空间**: `XCEngine::Resources` -**类型**: `class` +**类型**: `header-overview` **头文件**: `XCEngine/Core/IO/ResourcePackage.h` -**描述**: 定义 `XCEngine/Core/IO` 子目录中的 `ResourcePackage` public API。 +**描述**: 定义资源包格式的构建器、运行时读取器以及相关公开结构。 -## 概述 +## 角色概述 -`ResourcePackage.h` 是 `XCEngine/Core/IO` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`ResourcePackage.h` 当前同时暴露了三类东西: -## 声明概览 +- `PackageFileEntry` +- `ResourcePackageBuilder` +- `ResourcePackage` -| 声明 | 类型 | 说明 | -|------|------|------| -| `PackageFileEntry` | `struct` | 头文件中的公开声明。 | -| `ResourcePackageBuilder` | `class` | 头文件中的公开声明。 | -| `ResourcePackage` | `class` | 头文件中的公开声明。 | +它的设计方向很明确,就是朝“自定义资源包格式”走:先把多个资源文件打包,再由运行时统一读取。这是商业引擎里非常常见的思路,因为它能带来: -## 公共方法 +- 更少的散文件 +- 更统一的发布格式 +- 更容易接入虚拟文件系统与挂载层 -| 方法 | 描述 | -|------|------| -| [ResourcePackage()](Constructor.md) | 构造对象。 | -| [~ResourcePackage()](Destructor.md) | 销毁对象并释放相关资源。 | -| [Open](Open.md) | 公开方法,详见头文件声明。 | -| [Close](Close.md) | 公开方法,详见头文件声明。 | -| [IsValid](IsValid.md) | 查询当前状态。 | -| [Exists](Exists.md) | 公开方法,详见头文件声明。 | -| [Read](Read.md) | 公开方法,详见头文件声明。 | -| [GetSize](GetSize.md) | 获取相关状态或对象。 | -| [Enumerate](Enumerate.md) | 公开方法,详见头文件声明。 | -| [GetInfo](GetInfo.md) | 获取相关状态或对象。 | +但按当前源码,这套打包格式还处在早期阶段,builder 与 reader 并没有形成完整闭环。 + +## 当前包头格式 + +当前实现里写出的头部字段包括: + +- `magic = "XCRP"` +- `version = 1` +- `manifestSize` +- `fileCount` +- `dataOffset` + +其中 `ReadHeader()` 会读取并校验 magic 和 version,然后把这些信息写入 `PackageInfo`。 + +## ResourcePackageBuilder 当前行为 + +### AddFile + +- 会尝试用 `fopen` 打开源文件 +- 成功后记录源路径、相对路径和文件大小 +- 当前阶段先不算 checksum,真正 checksum 在 `Build()` 的数据写入阶段计算 + +### AddDirectory + +`AddDirectory()` 当前直接返回 true,没有递归扫描目录,也不会往 `m_files` 里加入任何文件。这是一个明确的占位实现。 + +### Build + +`Build()` 当前会: + +1. 检查输出路径是否已设置 +2. 检查是否至少添加了一个文件 +3. 创建输出文件 +4. 写包头 +5. 调用 `WriteManifest()` +6. 调用 `WriteData()` + +其中: + +- `WriteManifest()` 当前直接返回 true,没有写任何 manifest 内容 +- `WriteData()` 会顺序写入原始文件字节,并更新 `m_progress` + +如果中途失败,会删除输出文件。 + +## ResourcePackage 当前行为 + +### Open / Close + +- `Open(packagePath)` 会读取头部并调用 `ReadManifest()` +- 当前 `ReadManifest()` 直接返回 true,不会填充 `m_entries` +- 只要头部合法,`Open()` 就会把包标记为有效 + +### Exists / Read / GetSize / Enumerate + +运行时读取几乎完全依赖 `m_entries`。但由于 `ReadManifest()` 当前不填充任何条目: + +- `Exists()` 通常返回 false +- `Read()` 通常返回空数组 +- `GetSize()` 通常返回 0 +- `Enumerate()` 通常得不到任何文件 + +即使 builder 成功写出了一个包文件,当前 reader 也不能可靠地把它重新索引出来。 + +### GetInfo + +`GetInfo()` 当前能稳定提供的主要是: + +- `path` +- `version` +- `fileCount` + +但 `totalSize` 当前在 `ReadHeader()` 里被固定写成 0,没有真实统计逻辑。 + +## 公开结构与实现落差 + +- 头文件里的 `PackageFileEntry` 当前没有成为实际读写路径里的核心结构 +- builder 内部使用自己的 `FileEntry` +- reader 内部使用自己的 `FileEntryInternal` + +这说明公开数据结构和当前实现内部结构还没有真正收敛。 + +## 设计取向 + +把“构建包文件”和“运行时读包”放在同一个头文件里,在项目早期是合理的,因为格式演进和运行时消费往往需要一起调整。商业引擎后续一般会继续把它拆成: + +- 编辑器或构建期的 package builder +- 运行时的 package reader / mount point + +当前 XCEngine 已经能看出这个方向,但还没完成闭环。 + +## 线程语义 + +- 当前实现没有加锁。 +- builder 和 reader 都按同步串行 I/O 组件实现。 +- 默认应按构建期或主线程调用工具理解。 + +## 当前实现限制 + +- `AddDirectory()` 是空实现。 +- `WriteManifest()` 是空实现。 +- `ReadManifest()` 是空实现。 +- builder 与 reader 当前不能形成可靠闭环。 +- `PackageFileEntry` 公开存在,但没有成为当前主要运行时读写结构。 +- `Enumerate()` 当前忽略 pattern,只是遍历条目列表。 +- 没有校验 checksum 的运行时逻辑。 +- `PackageInfo.totalSize` 当前不会真实计算。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [Open](Open.md) +- [Close](Close.md) +- [IsValid](IsValid.md) +- [Exists](Exists.md) +- [Read](Read.md) +- [GetSize](GetSize.md) +- [Enumerate](Enumerate.md) +- [GetInfo](GetInfo.md) ## 相关文档 -- [当前目录](../IO.md) - 返回 `IO` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../IO.md) +- [FileArchive](../FileArchive/FileArchive.md) +- [ResourceFileSystem](../ResourceFileSystem/ResourceFileSystem.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/IO/ResourcePath/ResourcePath.md b/docs/api/XCEngine/Core/IO/ResourcePath/ResourcePath.md index afcb68a0..b3069bde 100644 --- a/docs/api/XCEngine/Core/IO/ResourcePath/ResourcePath.md +++ b/docs/api/XCEngine/Core/IO/ResourcePath/ResourcePath.md @@ -6,37 +6,97 @@ **头文件**: `XCEngine/Core/IO/ResourcePath.h` -**描述**: 定义 `XCEngine/Core/IO` 子目录中的 `ResourcePath` public API。 +**描述**: 针对资源路径字符串提供轻量拆分、扩展名判断和 GUID 派生功能。 -## 概述 +## 角色概述 -`ResourcePath.h` 是 `XCEngine/Core/IO` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`ResourcePath` 当前不是一个真正的跨平台路径库,也不是文件系统查询对象。它更像资源系统里一个很轻的路径字符串辅助器,用来回答几类最常见的问题: -## 声明概览 +- 文件扩展名是什么 +- 文件名、目录名和 stem 是什么 +- 这条路径对应的资源 GUID 是什么 -| 声明 | 类型 | 说明 | -|------|------|------| -| `ResourcePath` | `class` | 头文件中的公开声明。 | +这种轻量 helper 在商业引擎里很常见,因为 loader、缓存和导入流程经常只需要做路径拆分,而不需要每次都引入完整文件系统抽象。 -## 公共方法 +## 当前行为 -| 方法 | 描述 | -|------|------| -| [ResourcePath()](Constructor.md) | 构造对象。 | -| [GetExtension](GetExtension.md) | 获取相关状态或对象。 | -| [GetStem](GetStem.md) | 获取相关状态或对象。 | -| [GetFullPath](GetFullPath.md) | 获取相关状态或对象。 | -| [GetFileName](GetFileName.md) | 获取相关状态或对象。 | -| [GetDirectory](GetDirectory.md) | 获取相关状态或对象。 | -| [GetRelativePath](GetRelativePath.md) | 获取相关状态或对象。 | -| [ToGUID](ToGUID.md) | 公开方法,详见头文件声明。 | -| [HasExtension](HasExtension.md) | 判断是否具备指定状态或能力。 | -| [HasAnyExtension](HasAnyExtension.md) | 判断是否具备指定状态或能力。 | -| [IsValid](IsValid.md) | 查询当前状态。 | -| [GetPath](GetPath.md) | 获取相关状态或对象。 | -| [SetPath](SetPath.md) | 设置相关状态或配置。 | +### 路径拆分 + +- `GetExtension()` 返回带点扩展名,例如 `.png` +- `GetStem()` 返回文件名去掉扩展名后的部分 +- `GetFileName()` 返回最后一级文件名 +- `GetDirectory()` 返回最后一级目录之前的前缀 + +`GetStem()` 和 `GetDirectory()` 同时支持正斜杠 `/` 与反斜杠 `\\`。 + +### 路径原样返回 + +- `GetFullPath()` 当前直接返回 `m_path` +- `GetRelativePath()` 当前也直接返回 `m_path` + +也就是说,当前类并不会区分“完整路径”和“相对路径”,只是保留了两个语义上可能扩展的访问口。 + +### GUID 派生 + +`ToGUID()` 当前直接调用 [ResourceGUID::Generate](../../Asset/ResourceTypes/ResourceTypes.md) 对 `m_path` 做哈希,因此它完全继承了资源 GUID 的路径敏感性: + +- 不做规范化 +- 不统一大小写 +- 相对路径和绝对路径会得到不同 GUID + +## 扩展名语义 + +`HasExtension(ext)` 和 `HasAnyExtension(...)` 当前基于 `GetExtension()` 实现,所以调用时应传入带点扩展名,例如: + +- `.png` +- `.fbx` + +这和 [IResourceLoader](../IResourceLoader/IResourceLoader.md) 里的受保护辅助函数 `GetExtension()` 不同,后者返回不带点扩展名。当前 IO 层这两套约定并不统一。 + +## 设计取向 + +可以把 `ResourcePath` 理解成资源系统里的“小刀”,不是“工具箱”: + +- 它擅长快速拆路径 +- 不负责真实磁盘查询 +- 不负责标准化和规范化 +- 不负责路径合法性校验 + +这种取舍让它很轻,也很适合被 loader 和资源工具频繁内联使用;代价是调用方必须自己理解它不承担更重的文件系统职责。 + +## 线程语义 + +- 类本身没有锁。 +- 内部只保存一份 `Containers::String`。 +- 普通只读调用是纯字符串处理。 + +## 当前实现限制 + +- 不是文件系统路径规范化工具。 +- `GetFullPath()` 和 `GetRelativePath()` 当前没有语义差别。 +- `IsValid()` 只检查字符串是否为空,不检查路径是否真的存在。 +- `ToGUID()` 不会做路径规范化。 +- 与 [IResourceLoader](../IResourceLoader/IResourceLoader.md) 的扩展名格式约定不一致。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [GetExtension](GetExtension.md) +- [GetStem](GetStem.md) +- [GetFullPath](GetFullPath.md) +- [GetFileName](GetFileName.md) +- [GetDirectory](GetDirectory.md) +- [GetRelativePath](GetRelativePath.md) +- [ToGUID](ToGUID.md) +- [HasExtension](HasExtension.md) +- [HasAnyExtension](HasAnyExtension.md) +- [IsValid](IsValid.md) +- [GetPath](GetPath.md) +- [SetPath](SetPath.md) ## 相关文档 -- [当前目录](../IO.md) - 返回 `IO` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../IO.md) +- [ResourceTypes](../../Asset/ResourceTypes/ResourceTypes.md) +- [IResourceLoader](../IResourceLoader/IResourceLoader.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Layer/Layer.md b/docs/api/XCEngine/Core/Layer/Layer.md index 1349793c..1eba90f5 100644 --- a/docs/api/XCEngine/Core/Layer/Layer.md +++ b/docs/api/XCEngine/Core/Layer/Layer.md @@ -6,32 +6,85 @@ **头文件**: `XCEngine/Core/Layer.h` -**描述**: 定义 `XCEngine/Core` 子目录中的 `Layer` public API。 +**描述**: 一个轻量 Layer 基类,定义 attach、detach、update、event 和 ImGui 渲染钩子。 -## 概述 +## 角色概述 -`Layer.h` 是 `XCEngine/Core` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Layer` 代表一种经典应用框架式组织单元。它和 Unity 的 `MonoBehaviour` 或场景组件不是一回事,更像是: -## 声明概览 +- 应用层 / 编辑器层 / overlay 层的逻辑片段 +- 由 [LayerStack](../LayerStack/LayerStack.md) 排序和驱动 -| 声明 | 类型 | 说明 | -|------|------|------| -| `Layer` | `class` | 头文件中的公开声明。 | +这种模式在编辑器、工具程序和引擎壳层中很常见,因为它把“功能模块的生命周期”和“场景对象组件系统”分离开了。 -## 公共方法 +## 当前实现行为 -| 方法 | 描述 | -|------|------| -| [Layer()](Constructor.md) | 构造对象。 | -| [~Layer()](Destructor.md) | 销毁对象并释放相关资源。 | -| [onAttach](onAttach.md) | 公开方法,详见头文件声明。 | -| [onDetach](onDetach.md) | 公开方法,详见头文件声明。 | -| [onUpdate](onUpdate.md) | 公开方法,详见头文件声明。 | -| [onEvent](onEvent.md) | 公开方法,详见头文件声明。 | -| [onImGuiRender](onImGuiRender.md) | 公开方法,详见头文件声明。 | -| [getName](getName.md) | 公开方法,详见头文件声明。 | +### 1. 这是一个纯虚拟钩子基类 + +除构造、析构和 `getName()` 外,所有行为方法都是默认 no-op: + +- `onAttach()` +- `onDetach()` +- `onUpdate(float dt)` +- `onEvent(void* event)` +- `onImGuiRender()` + +这说明它本质上是一个扩展接口,而不是自带逻辑的运行时类型。 + +### 2. 事件接口当前是弱类型 + +`onEvent(void* event)` 只接收 `void*`。这意味着: + +- 当前 Layer 系统没有自己的强类型事件层 +- 使用者需要自己知道传入的 event 指针语义 + +和现代商业引擎里更强类型的事件包装相比,这是一种更轻但也更脆弱的做法。 + +### 3. 命名风格有历史痕迹 + +这些接口使用的是 lowerCamelCase: + +- `onAttach` +- `onDetach` +- `onUpdate` + +而项目里很多别的模块更偏 PascalCase 风格。这通常意味着它来自更偏应用框架/编辑器壳层的一套约定,而不是当前全局统一风格的代表。 + +## 当前代码库中的使用情况 + +在当前取证范围内,没有看到 `Layer` / `LayerStack` 在 `engine/src` 里形成大规模实际调用链。也就是说: + +- API 形状已经明确 +- 但它目前更像基础框架预留能力,而不是已广泛深入主运行时的核心机制 + +## 线程语义 + +- 当前基类不提供线程安全保证。 +- 所有钩子的典型使用方式都应视为由外部主循环串行驱动。 + +## 当前实现限制 + +- `onEvent(void*)` 是弱类型接口。 +- 基类不提供 enabled/disabled、priority 或 attach state 管理。 +- 没有看到直接测试覆盖。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [onAttach](onAttach.md) +- [onDetach](onDetach.md) +- [onUpdate](onUpdate.md) +- [onEvent](onEvent.md) +- [onImGuiRender](onImGuiRender.md) +- [getName](getName.md) + +## 相关指南 + +- [Core Foundations: Ownership, Events, And Layers](../../../_guides/Core/Core-Foundations-Ownership-Events-And-Layers.md) ## 相关文档 -- [当前目录](../Core.md) - 返回 `Core` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Core.md) +- [LayerStack](../LayerStack/LayerStack.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Core/LayerStack/LayerStack.md b/docs/api/XCEngine/Core/LayerStack/LayerStack.md index 2d7e3ece..a852dfb3 100644 --- a/docs/api/XCEngine/Core/LayerStack/LayerStack.md +++ b/docs/api/XCEngine/Core/LayerStack/LayerStack.md @@ -6,43 +6,120 @@ **头文件**: `XCEngine/Core/LayerStack.h` -**描述**: 定义 `XCEngine/Core` 子目录中的 `LayerStack` public API。 +**描述**: 一个按“普通层 + overlay”分段组织的 `Layer` 容器,负责遍历和基础生命周期分发。 -## 概述 +## 角色概述 -`LayerStack.h` 是 `XCEngine/Core` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`LayerStack` 负责管理多个 [Layer](../Layer/Layer.md) 实例的顺序关系。它把容器分成两段: -## 声明概览 +- 普通 layers +- overlays -| 声明 | 类型 | 说明 | -|------|------|------| -| `LayerStack` | `class` | 头文件中的公开声明。 | +这是一种常见的应用壳层设计,适合把“主功能层”和“悬浮 UI / 调试覆盖层”分开组织。 -## 公共方法 +## 当前实现行为 -| 方法 | 描述 | -|------|------| -| [LayerStack()](Constructor.md) | 构造对象。 | -| [~LayerStack()](Destructor.md) | 销毁对象并释放相关资源。 | -| [pushLayer](pushLayer.md) | 公开方法,详见头文件声明。 | -| [pushOverlay](pushOverlay.md) | 公开方法,详见头文件声明。 | -| [popLayer](popLayer.md) | 公开方法,详见头文件声明。 | -| [popOverlay](popOverlay.md) | 公开方法,详见头文件声明。 | -| [onUpdate](onUpdate.md) | 公开方法,详见头文件声明。 | -| [onEvent](onEvent.md) | 公开方法,详见头文件声明。 | -| [onImGuiRender](onImGuiRender.md) | 公开方法,详见头文件声明。 | -| [onAttach](onAttach.md) | 公开方法,详见头文件声明。 | -| [onDetach](onDetach.md) | 公开方法,详见头文件声明。 | -| [begin](begin.md) | 公开方法,详见头文件声明。 | -| [end](end.md) | 公开方法,详见头文件声明。 | -| [rbegin](rbegin.md) | 公开方法,详见头文件声明。 | -| [rend](rend.md) | 公开方法,详见头文件声明。 | -| [cbegin](cbegin.md) | 公开方法,详见头文件声明。 | -| [cend](cend.md) | 公开方法,详见头文件声明。 | -| [crbegin](crbegin.md) | 公开方法,详见头文件声明。 | -| [crend](crend.md) | 公开方法,详见头文件声明。 | +### 1. 所有权由 `std::unique_ptr` 表达 + +`LayerStack` 内部直接持有: + +`std::vector> m_layers` + +因此: + +- stack 拥有 layer 生命周期 +- `pushLayer()` / `pushOverlay()` 都会转移 unique ownership +- `popLayer()` / `popOverlay()` 删除元素时会触发对象析构 + +### 2. 普通层和 overlay 的分界靠 `m_layerInsertIndex` + +`pushLayer()` 会把新层插入 `m_layers.begin() + m_layerInsertIndex`,然后递增索引;`pushOverlay()` 则直接追加到尾部。 + +这意味着当前排序规则是: + +- 普通层保持在前半段 +- overlay 永远位于后半段 + +### 3. `push*` 不会自动调用 `onAttach()` + +这是一个非常重要的当前行为。很多引擎框架会在 push 时立刻 attach,但当前实现不会: + +- `pushLayer()` 只做插入 +- `pushOverlay()` 只做插入 + +真正的 attach 分发只发生在显式调用 [onAttach](onAttach.md) 时。 + +### 4. `pop*` 会调用 `onDetach()` + +相对地,`popLayer()` 和 `popOverlay()` 在删除元素前都会先调用对应 layer 的 `onDetach()`。 + +### 5. 遍历顺序是正向遍历 + +以下分发方法当前都按 `m_layers` 的正向顺序调用: + +- `onUpdate()` +- `onEvent()` +- `onImGuiRender()` +- `onAttach()` +- `onDetach()` + +这尤其意味着 `onEvent()` 不是“从 top-most overlay 逆向往下传”的模式,而是从前到后正向调用。对有事件吞噬需求的系统来说,这一点必须明确。 + +## 迭代器暴露 + +`LayerStack` 同时暴露了: + +- `begin()` / `end()` +- `rbegin()` / `rend()` +- `cbegin()` / `cend()` +- `crbegin()` / `crend()` + +这让外部可以按需要自定义遍历顺序,但也说明它没有封装成强约束的调度器。 + +## 当前代码库中的使用情况 + +在当前取证范围内,没有看到它在 `engine/src` 里形成广泛调用链。因此这更像基础应用框架能力,而不是当前引擎主运行时的核心主线。 + +## 线程语义 + +- 当前实现没有加锁。 +- 所有修改和遍历都应按单线程外部驱动理解。 + +## 当前实现限制 + +- `pushLayer()` / `pushOverlay()` 不会自动 `onAttach()`。 +- `onDetach()` 在 `pop*` 时会调用,但析构函数本身是默认实现,不会自动遍历 detach 剩余层。 +- `onEvent()` 采用正向遍历,不是常见的逆向 overlay 优先分发。 +- 没有看到直接测试覆盖。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [pushLayer](pushLayer.md) +- [pushOverlay](pushOverlay.md) +- [popLayer](popLayer.md) +- [popOverlay](popOverlay.md) +- [onUpdate](onUpdate.md) +- [onEvent](onEvent.md) +- [onImGuiRender](onImGuiRender.md) +- [onAttach](onAttach.md) +- [onDetach](onDetach.md) +- [begin](begin.md) +- [end](end.md) +- [rbegin](rbegin.md) +- [rend](rend.md) +- [cbegin](cbegin.md) +- [cend](cend.md) +- [crbegin](crbegin.md) +- [crend](crend.md) + +## 相关指南 + +- [Core Foundations: Ownership, Events, And Layers](../../../_guides/Core/Core-Foundations-Ownership-Events-And-Layers.md) ## 相关文档 -- [当前目录](../Core.md) - 返回 `Core` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Core.md) +- [Layer](../Layer/Layer.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/AABB/AABB.md b/docs/api/XCEngine/Core/Math/AABB/AABB.md index 5d18fc73..ae74a852 100644 --- a/docs/api/XCEngine/Core/Math/AABB/AABB.md +++ b/docs/api/XCEngine/Core/Math/AABB/AABB.md @@ -2,31 +2,90 @@ **命名空间**: `XCEngine::Math` -**类型**: `struct` +**类型**: `header-group` **头文件**: `XCEngine/Core/Math/AABB.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `AABB` public API。 +**描述**: 名称上是 `AABB.h`,但当前头文件实际导出的是 `OBB`,用于表达带姿态的盒体辅助类型。 -## 概述 +## 角色概述 -`AABB.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`AABB.h` 当前没有定义经典意义上的 axis-aligned bounding box 类型,真正暴露出来的是: -## 声明概览 +- `struct OBB` -| 声明 | 类型 | 说明 | +也就是说,这个头文件名和实际公开类型并不一致。文档必须按源码现状说明,不能把它写成一个已经完备的 AABB 系统。 + +`OBB` 当前试图承担的角色是“带方向的盒体”: + +- `center` +- `extents` +- `transform` + +这和商业引擎里常见的 oriented bounding box 方向一致,但当前实现明显还不成熟。 + +## 当前行为 + +- 默认 `center = Zero()`、`extents = One()`、`transform = Identity()`。 +- `GetAxis(index)` 直接读取 `transform` 的第 `index` 列前三个元素。 +- `GetMin()` / `GetMax()` 只基于 `center` 和 `extents` 计算,不考虑 `transform`。 +- `Contains(point)` 会对点应用 `transform.Inverse().MultiplyPoint()` 后,再按 `extents` 做局部盒体判断。 +- `Intersects(const Sphere&)` 也走相同的“先逆变换到局部空间,再 clamp 到 extents”路径。 +- `Intersects(const OBB&)` 使用一套 SAT-like 检测,但只检查了 6 条面法线方向,没有覆盖完整 OBB SAT 所需的叉积分离轴。 + +## 生命周期 + +- 纯值类型。 +- 没有外部资源和缓存。 + +## 线程语义 + +- 无共享状态。 +- 并发读写语义等同于普通值对象。 + +## 所有权与资源管理 + +- 不持有碰撞系统、BVH 或物理对象。 +- 只是一个轻量几何结构体。 + +## 公开数据 + +| 成员 | 类型 | 说明 | |------|------|------| -| `OBB` | `struct` | 头文件中的公开声明。 | +| `center` | `Vector3` | 盒体中心。 | +| `extents` | `Vector3` | 半尺寸。 | +| `transform` | `Matrix4` | 当前姿态矩阵。 | -## 结构体成员 +## 关键 API -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `center` | `Vector3` | 结构体公开字段。 | - | -| `extents` | `Vector3` | 结构体公开字段。 | - | -| `transform` | `Matrix4` | 结构体公开字段。 | - | +- `GetAxis()` - 从 `transform` 中读出局部轴。 +- `Contains()` - 进行点包含测试。 +- `Intersects(OBB)` - 做 OBB 间重叠测试。 +- `Intersects(Sphere)` - 做 OBB 与球体测试。 + +## 设计取向 + +把 OBB 放在数学层是合理的,因为它本质上是碰撞与裁剪算法的基础几何体。但如果要做到商业级可靠,通常至少需要: + +- 统一姿态与中心的单一数据源 +- 完整 SAT 轴集 +- 更明确的局部空间与世界空间约定 + +当前 `OBB` 还没有达到这个程度。 + +## 当前实现限制 + +- 头文件名和公开类型不一致,使用时容易产生预期偏差。 +- `center` 和 `transform` 都在描述空间位置,但不同方法对这两者的使用并不一致。 +- `GetMin()` / `GetMax()` 忽略 `transform`,只能反映未旋转盒体的轴对齐极值。 +- `GetAxis()` 不做归一化;如果 `transform` 含缩放,得到的轴长度会被带进去。 +- `Intersects(const OBB&)` 当前不是完整 SAT,不能把它当成成熟稳健的 OBB-OBB 求交实现。 +- 当前 `tests/math` 没有 `OBB` 专门测试覆盖。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Matrix4](../Matrix4/Matrix4.md) +- [Sphere](../Sphere/Sphere.md) +- [Box](../Box/Box.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Bounds/Bounds.md b/docs/api/XCEngine/Core/Math/Bounds/Bounds.md index fa1bc72f..43cf9b2a 100644 --- a/docs/api/XCEngine/Core/Math/Bounds/Bounds.md +++ b/docs/api/XCEngine/Core/Math/Bounds/Bounds.md @@ -6,26 +6,78 @@ **头文件**: `XCEngine/Core/Math/Bounds.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Bounds` public API。 +**描述**: 轴对齐包围盒类型,使用 `center + extents` 表示,并提供包裹、扩张和最近点等常见操作。 -## 概述 +## 角色概述 -`Bounds.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Bounds` 是当前数学层里最标准、也最成熟的 AABB 值对象。它适合承担: -## 声明概览 +- 场景物体的粗粒度包围盒 +- 视锥裁剪输入 +- 包围体合并与扩张 +- 最近点和体积等简单辅助计算 -| 声明 | 类型 | 说明 | +这和商业引擎里常见的 `Bounds` 心智模型非常接近。 + +## 当前行为 + +- 存储格式是 `center + extents`,不是 `min + max`。 +- 构造函数第二个参数名为 `size`,内部会自动乘 `0.5f` 变成 `extents`。 +- `GetMin()` / `GetMax()` 分别返回 `center - extents` 和 `center + extents`。 +- `SetMinMax()` 会反推出 `center` 和 `extents`。 +- `Contains(point)` 对边界使用闭区间,即边界点也算包含。 +- `Intersects(other)` 通过比较两个盒子在三个轴上的中心距与半尺寸和判断相交。 +- `Encapsulate(point)` 与 `Encapsulate(bounds)` 都会重建新的包围范围。 +- `Expand(float)` 会把传入值视为最终尺寸增量,因此只给 `extents` 增加一半。 +- `GetClosestPoint(point)` 会把点 clamp 到包围盒内部。 +- `GetVolume()` 返回完整尺寸乘积,而不是 `extents` 乘积。 + +按 `tests/math/test_geometry.cpp`,当前 `Bounds` 的默认构造、参数构造、最值、包含、相交、包裹、扩张、体积和最近点都有测试覆盖。 + +## 生命周期 + +- 纯值类型。 +- 适合大量临时创建和按值返回。 + +## 线程语义 + +- 没有共享状态。 +- 并发语义等同于普通值对象。 + +## 所有权与资源管理 + +- 不持有变换、层级或加速结构。 +- 不负责同步 [Frustum](../Frustum/Frustum.md) 或 [Box](../Box/Box.md) 数据。 + +## 公开数据 + +| 成员 | 类型 | 说明 | |------|------|------| -| `Bounds` | `struct` | 头文件中的公开声明。 | +| `center` | `Vector3` | 包围盒中心。 | +| `extents` | `Vector3` | 半尺寸。 | -## 结构体成员 +## 关键 API -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `center` | `Vector3` | 结构体公开字段。 | `Vector3::Zero()` | -| `extents` | `Vector3` | 结构体公开字段。 | `Vector3::Zero()` | +- `GetMin()` / `GetMax()` - 取得轴对齐极值点。 +- `SetMinMax()` - 从极值重建 `center + extents`。 +- `Encapsulate()` - 扩大包围盒。 +- `Expand()` - 扩张尺寸。 +- `GetClosestPoint()` - 取得包围盒上的最近点。 + +## 设计取向 + +相比 [Box](../Box/Box.md) 那种同时带变换和尺寸的类型,`Bounds` 当前更纯粹,也更适合做“稳定 AABB”。在商业引擎里,绝大多数 broad-phase、可见性裁剪和层级包围体缓存都会优先建立在这种结构上。 + +## 当前实现限制 + +- 构造函数和字段语义不同: 传入的是 `size`,内部保存的是 `extents`,使用时需要区分。 +- 没有与矩阵或旋转的直接结合能力,本质上只能表达轴对齐包围盒。 +- 没有球体、平面或射线的成员级求交接口。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Frustum](../Frustum/Frustum.md) +- [Box](../Box/Box.md) +- [Sphere](../Sphere/Sphere.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Box/Box.md b/docs/api/XCEngine/Core/Math/Box/Box.md index 8f3d327d..ff5626a6 100644 --- a/docs/api/XCEngine/Core/Math/Box/Box.md +++ b/docs/api/XCEngine/Core/Math/Box/Box.md @@ -6,27 +6,74 @@ **头文件**: `XCEngine/Core/Math/Box.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Box` public API。 +**描述**: 盒体几何类型,当前同时暴露 `center / extents / transform`,但不同查询路径对这三者的使用并不一致。 -## 概述 +## 角色概述 -`Box.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Box` 直觉上看像一个可以表达姿态的盒体类型,因为它同时带有: -## 声明概览 +- `center` +- `extents` +- `transform` -| 声明 | 类型 | 说明 | +但按源码核对,它当前并不是一个语义完全统一的“成熟 OBB”。不同方法对 `center` 和 `transform` 的依赖方式不同,因此更适合把它理解为“仍在演进中的盒体查询结构”。 + +## 当前行为 + +- 默认 `center = Zero()`、`extents = Zero()`、`transform = Identity()`。 +- `GetMin()` / `GetMax()` 只基于 `center +/- extents`,不考虑 `transform`。 +- `Contains(point)` 会把世界点变换到 `transform` 的局部空间,再按 `extents` 做包围测试。 +- `Intersects(const Sphere&)` 也使用同样的逆变换局部空间路径。 +- `Intersects(const Box&)` 只是比较双方 `GetMin()` / `GetMax()` 的轴对齐重叠,因此不考虑 `transform`。 +- `Intersects(const Ray&, float& t)` 只是转调 [Ray](../Ray/Ray.md) 的 `Intersects(Box)`,而后者同样基于 `GetMin()` / `GetMax()`,因此也不考虑 `transform`。 + +## 生命周期 + +- 纯值类型。 +- 不持有外部资源。 + +## 线程语义 + +- 无共享状态。 +- 并发写同一实例需要调用方自行同步。 + +## 所有权与资源管理 + +- 不管理碰撞系统或加速结构。 +- 不缓存局部矩阵逆或世界空间极值。 + +## 公开数据 + +| 成员 | 类型 | 说明 | |------|------|------| -| `Box` | `struct` | 头文件中的公开声明。 | +| `center` | `Vector3` | 盒体中心。 | +| `extents` | `Vector3` | 半尺寸。 | +| `transform` | `Matrix4x4` | 盒体当前姿态矩阵。 | -## 结构体成员 +## 关键 API -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `center` | `Vector3` | 结构体公开字段。 | `Vector3::Zero()` | -| `extents` | `Vector3` | 结构体公开字段。 | `Vector3::Zero()` | -| `transform` | `Matrix4x4` | 结构体公开字段。 | `Matrix4x4::Identity()` | +- `GetMin()` / `GetMax()` - 获取轴对齐极值。 +- `Contains()` - 点包含测试。 +- `Intersects(Sphere)` - 盒体与球体测试。 +- `Intersects(Box)` - 盒体与盒体测试。 +- `Intersects(Ray)` - 盒体与射线测试。 + +## 设计取向 + +商业引擎通常会把“真正的 OBB”与“真正的 AABB”做得语义非常稳定,因为后续 broad-phase、physics 和 culling 都要依赖它们。`Box` 当前还没有达到这个阶段,更像一个正在收敛 API 形状的中间态类型。 + +## 当前实现限制 + +- `center` 与 `transform` 都在描述位置/姿态,但不同方法使用它们的方式不一致。 +- `Contains()` / `Intersects(Sphere)` 会看 `transform`,而 `GetMin()` / `GetMax()` / `Intersects(Box)` / `Intersects(Ray)` 当前都忽略 `transform`。 +- 因此如果调用方只更新 `transform` 或只更新 `center`,不同查询会得到不一致结果。 +- 当前没有测试覆盖 `Box` 自身的全部查询路径。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Ray](../Ray/Ray.md) +- [Sphere](../Sphere/Sphere.md) +- [Bounds](../Bounds/Bounds.md) +- [AABB](../AABB/AABB.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Color/Color.md b/docs/api/XCEngine/Core/Math/Color/Color.md index a9886ef5..54ba9fa4 100644 --- a/docs/api/XCEngine/Core/Math/Color/Color.md +++ b/docs/api/XCEngine/Core/Math/Color/Color.md @@ -6,28 +6,81 @@ **头文件**: `XCEngine/Core/Math/Color.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Color` public API。 +**描述**: 轻量线性 RGBA 颜色值类型,用于在运行时携带和转换颜色分量。 -## 概述 +## 角色概述 -`Color.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Color` 是数学层里最直接的颜色承载对象。它的定位很像商业引擎里常见的基础颜色值类型: -## 声明概览 +- 公开 `r/g/b/a` +- 一组常用静态颜色工厂 +- 基础插值和算术运算 +- 与向量格式之间的轻量互转 -| 声明 | 类型 | 说明 | +它不是完整的颜色管理系统,而更像渲染、材质、调试绘制和简单序列化路径上的“颜色容器”。 + +## 当前行为 + +- 默认构造结果是白色,即 `(1, 1, 1, 1)`。 +- 静态颜色工厂覆盖 `White / Black / Red / Green / Blue / Yellow / Cyan / Magenta / Clear`。 +- `Lerp()` 会先把 `t` clamp 到 `[0, 1]`。 +- `ToVector3()` 只返回 `rgb`。 +- `ToVector4()` 返回完整 `rgba`。 +- `operator+/-/*//` 都是逐分量运算,其中乘除只接受标量。 + +## 生命周期 + +- 纯值类型。 +- 适合按值传递、序列化和临时构造。 +- 没有外部资源或注册流程。 + +## 线程语义 + +- 无共享状态。 +- 并发读写语义等同于普通值对象。 + +## 所有权与资源管理 + +- 不持有 GPU 资源。 +- 不管理颜色空间配置、调色板或贴图对象。 + +## 公开数据 + +| 成员 | 类型 | 说明 | |------|------|------| -| `Color` | `struct` | 头文件中的公开声明。 | +| `r` | `float` | 红色分量,默认 `1.0f`。 | +| `g` | `float` | 绿色分量,默认 `1.0f`。 | +| `b` | `float` | 蓝色分量,默认 `1.0f`。 | +| `a` | `float` | Alpha 分量,默认 `1.0f`。 | -## 结构体成员 +## 关键 API -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `r` | `float` | 结构体公开字段。 | `1.0f` | -| `g` | `float` | 结构体公开字段。 | `1.0f` | -| `b` | `float` | 结构体公开字段。 | `1.0f` | -| `a` | `float` | 结构体公开字段。 | `1.0f` | +- `Lerp()` - 在线性空间里做颜色插值。 +- `ToRGBA()` - 试图打包为 8-bit RGBA。 +- `ToVector3()` - 转成 `rgb` 向量。 +- `ToVector4()` - 转成 `rgba` 向量。 + +## 设计取向 + +商业引擎通常都会在基础数学层或 Core 层放一个最小可用的颜色类型,而不是让高层模块直接到处传 `Vector4`。这样做的好处是: + +- 语义更明确 +- 可以单独扩展打包、色彩空间或序列化工具 +- 不会把“颜色”和“纯四维向量”混成一回事 + +XCEngine 当前也明显是在走这条路,但它目前仍然是一个非常薄的基础类型。 + +## 当前实现限制 + +- `ToRGBA()` 按 `engine/src/Core/Math/Color.cpp` 的当前实现,局部变量名与成员名相同,形成自初始化式的遮蔽问题,不能把它当成可靠的打包输出接口。 +- 没有颜色空间转换,默认就是裸 `float` 分量。 +- 没有 `Clamp()`、`HSV`/`HSL` 互转、Gamma/Linear 转换等更完整工具。 +- `operator/` 不检查除零。 +- 当前 `tests/math` 没有独立的 `Color` 单元测试覆盖。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Vector3](../Vector3/Vector3.md) +- [Vector4](../Vector4/Vector4.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Frustum/Frustum.md b/docs/api/XCEngine/Core/Math/Frustum/Frustum.md index 57d829ef..544c9fc6 100644 --- a/docs/api/XCEngine/Core/Math/Frustum/Frustum.md +++ b/docs/api/XCEngine/Core/Math/Frustum/Frustum.md @@ -6,26 +6,83 @@ **头文件**: `XCEngine/Core/Math/Frustum.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Frustum` public API。 +**描述**: 轻量视锥体裁剪类,当前本质上是一个公开六平面数组上的查询包装器。 -## 概述 +## 角色概述 -`Frustum.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Frustum` 在当前引擎里承担视锥裁剪的基础容器角色,但它的实现非常薄: -## 声明概览 +- 公开 `planes[6]` +- 提供 `Contains()` / `Intersects()` 查询 +- 不负责从相机矩阵自动提取平面 -| 声明 | 类型 | 说明 | +这意味着它更像“视锥判定工具类”,而不是一个完整的相机视锥系统。 + +## 当前行为 + +- `planes[6]` 由调用方自行填充。 +- `PlaneIndex` 只是一组索引常量,不附带提取逻辑。 +- `Contains(point)` 要求点对所有平面都在非负侧。 +- `Contains(sphere)` 与 `Intersects(sphere)` 当前都使用同一套判定逻辑: 只要任一平面距离小于 `-radius` 就判定失败。 +- `Contains(bounds)` 会检查 8 个角点是否对某个平面全部落在负侧;只要不存在这种“整盒都在某平面外侧”的情况,就返回 `true`。 +- `Intersects(bounds)` 也基本是同一思路;当前实现里 `allPositive` 变量没有实质作用。 + +按 `tests/math/test_geometry.cpp`,当前有对点、球体和 `Bounds` 的基础测试,但测试构造都非常轻量,通常只手动设置一个 near plane。 + +## 生命周期 + +- 纯值语义容器。 +- 默认构造不自动初始化六个平面的语义状态。 + +## 线程语义 + +- 没有共享状态。 +- 如果多个线程共享同一个实例写入 `planes`,仍需调用方同步。 + +## 所有权与资源管理 + +- 不拥有相机对象。 +- 不管理矩阵缓存或可见性结果缓存。 + +## 公开数据 + +| 成员 | 类型 | 说明 | |------|------|------| -| `Frustum` | `class` | 头文件中的公开声明。 | +| `planes` | `Plane[6]` | 六个裁剪平面,由调用方负责填充。 | -## 公共方法 +## 公开枚举 -| 方法 | 描述 | -|------|------| -| [Contains](Contains.md) | 公开方法,详见头文件声明。 | -| [Intersects](Intersects.md) | 公开方法,详见头文件声明。 | +| 枚举值 | 说明 | +|--------|------| +| `PlaneIndex::Left` | 左平面索引。 | +| `PlaneIndex::Right` | 右平面索引。 | +| `PlaneIndex::Bottom` | 下平面索引。 | +| `PlaneIndex::Top` | 上平面索引。 | +| `PlaneIndex::Near` | 近平面索引。 | +| `PlaneIndex::Far` | 远平面索引。 | + +## 关键 API + +- [Contains](Contains.md) - 做包含测试。 +- [Intersects](Intersects.md) - 做相交测试。 + +## 设计取向 + +这种“由六个平面组成的轻量视锥类”非常常见,因为很多裁剪算法本质上只关心平面测试本身。把它保持轻量,有利于和不同相机、渲染管线或脚本层解耦。 + +## 当前实现限制 + +- 没有从 [Matrix4](../Matrix4/Matrix4.md) 自动提取视锥平面的构造辅助。 +- `Contains(Sphere)` 与 `Intersects(Sphere)` 当前语义相同。 +- `Contains(Bounds)` 当前更接近“没有被任一平面完整剔除”,这在语义上更像相交测试而不是严格包含测试。 +- `Intersects(Bounds)` 的实现与 `Contains(Bounds)` 非常接近,区分度有限。 +- 当前没有针对 [Box](../Box/Box.md) 或 `OBB` 的裁剪接口。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Plane](../Plane/Plane.md) +- [Bounds](../Bounds/Bounds.md) +- [Sphere](../Sphere/Sphere.md) +- [Matrix4](../Matrix4/Matrix4.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Math.md b/docs/api/XCEngine/Core/Math/Math.md index 34ca49ff..9d3b5e93 100644 --- a/docs/api/XCEngine/Core/Math/Math.md +++ b/docs/api/XCEngine/Core/Math/Math.md @@ -1,14 +1,39 @@ # Math -**命名空间**: `XCEngine::Core::Math` +**命名空间**: `XCEngine::Math` **类型**: `submodule` -**描述**: 向量、矩阵、几何体与变换数学类型。 +**描述**: 定义向量、矩阵、四元数、颜色和基础几何体等运行时数学类型。 ## 概览 -该目录与 `XCEngine/Core/Math` 对应的 public headers 保持平行,用于承载唯一的 canonical API 文档入口。 +`Core/Math` 是引擎运行时的基础数学层。它承载了场景变换、摄像机矩阵、包围体计算、颜色处理和几何查询依赖的核心类型。 + +从结构上看,它大致分成几层: + +- 标量常量与角度辅助:[Math](Math/Math.md) +- 向量:[Vector2](Vector2/Vector2.md)、[Vector3](Vector3/Vector3.md)、[Vector4](Vector4/Vector4.md) +- 旋转与矩阵:[Quaternion](Quaternion/Quaternion.md)、[Matrix3](Matrix3/Matrix3.md)、[Matrix4](Matrix4/Matrix4.md) +- 颜色、变换和几何体:`Color`、`Transform`、`Plane`、`Ray`、`Bounds`、`Frustum` 等 + +这套 API 的设计方向很接近商业引擎常见的运行时 math layer,但当前不同类型成熟度并不完全一致。向量、`Matrix4` 和 `Quaternion` 已经比较可用;部分几何类型和个别矩阵构造路径仍然需要结合源码行为理解。 + +## 设计要点 + +- 当前所有基础数学类型都采用公开字段和轻量值类型设计。 +- 大部分函数是静态工厂或内联运算,倾向直接可读而不是极限泛型化。 +- 若和 Unity 风格类比: + - `Vector3` / `Quaternion` / `Matrix4` 的心智模型比较接近 Unity 同名类型 + - 但具体行为仍应以当前源码为准,而不是默认等同 Unity +- 当前数学层默认不做运行时边界检查,例如向量和矩阵的 `operator[]` 都没有保护。 + +## 当前实现现状 + +- `Lerp()` 在当前向量实现里会 clamp `t` 到 `[0, 1]`,不是无界线性插值。 +- `MultiplyPoint()` 当前只是把点乘以齐次矩阵并直接取 `x/y/z`,不会做透视除法。 +- `Matrix4::Inverse()` 与 `Matrix3::Inverse()` 在奇异矩阵情况下都会返回 `Identity()`,不会报错。 +- `Matrix3::RotationX/Y/Z()` 当前实现从零矩阵开始填值,未设置的主对角项保持为 `0`,这会让结果和标准旋转矩阵不同。 ## 头文件 @@ -30,7 +55,19 @@ - [Vector3](Vector3/Vector3.md) - `Vector3.h` - [Vector4](Vector4/Vector4.md) - `Vector4.h` +## 推荐阅读顺序 + +1. 先读 [Math](Math/Math.md),确认常量、角度换算和 `EPSILON` 约定。 +2. 再读 [Vector3](Vector3/Vector3.md) 与 [Quaternion](Quaternion/Quaternion.md),这是变换和空间计算最常用的两类。 +3. 然后读 [Matrix4](Matrix4/Matrix4.md),理解当前矩阵乘法、投影和分解约定。 +4. 需要二维或齐次向量时再补 [Vector2](Vector2/Vector2.md) 与 [Vector4](Vector4/Vector4.md)。 + +## 相关指南 + +- [Vectors, Quaternions, And Matrices In Runtime Transforms](../../../_guides/Core/Math/Vectors-Quaternions-And-Matrices-In-Runtime-Transforms.md) + ## 相关文档 - [上级目录](../Core.md) +- [TransformComponent](../../Components/TransformComponent/TransformComponent.md) - [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Math/Math.md b/docs/api/XCEngine/Core/Math/Math/Math.md index 595cde93..1a5f7d36 100644 --- a/docs/api/XCEngine/Core/Math/Math/Math.md +++ b/docs/api/XCEngine/Core/Math/Math/Math.md @@ -1,18 +1,96 @@ # Math -**命名空间**: `XCEngine` +**命名空间**: `XCEngine::Math` **类型**: `header` **头文件**: `XCEngine/Core/Math/Math.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Math` public API。 +**描述**: 数学层公共常量与角度换算入口,定义 `PI`、`EPSILON` 和弧度/角度互转辅助。 -## 概述 +## 角色概述 -`Math.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Math.h` 是整个 `Core/Math` 模块的基础约定头。它不定义向量或矩阵类型,而是提供所有数学类型都会依赖的最小公共语义: + +- 常用圆周率常量 +- 弧度与角度换算常量 +- 浮点比较阈值 `EPSILON` +- 一个显式的 `FLOAT_MAX` +- `Radians()` / `Degrees()` 两个内联换算函数 + +这类头文件在商业引擎里非常重要,因为它决定了整个数学层对“角度单位”和“浮点容差”的统一理解。 + +## 当前行为 + +- `PI`、`TWO_PI`、`HALF_PI` 都是 `constexpr float`。 +- `DEG_TO_RAD = PI / 180.0f` +- `RAD_TO_DEG = 180.0f / PI` +- `EPSILON = 1e-6f` +- `FLOAT_MAX = 3.402823466e+38f` +- `Radians(degrees)` 只是简单乘 `DEG_TO_RAD` +- `Degrees(radians)` 只是简单乘 `RAD_TO_DEG` + +这意味着: + +- 当前没有额外的角度包装类型 +- 没有运行时范围规整 +- 没有 double 精度版本 + +整个数学层大量逻辑都直接依赖这些约定。例如: + +- 向量归一化的零长度判断 +- 矩阵求逆的奇异矩阵判断 +- 四元数插值与退化输入判断 +- 组件层角度制 API 到数学层弧度制 API 的转换 + +## 生命周期 + +- 纯头文件常量与内联函数。 +- 没有初始化、销毁或运行时状态。 + +## 线程语义 + +- 全部是只读 `constexpr` / 内联计算。 +- 不涉及共享可变状态。 + +## 所有权与资源管理 + +- 无资源管理语义。 + +## 公开声明 + +| 声明 | 类型 | 说明 | +|------|------|------| +| `PI` | `constexpr float` | 圆周率。 | +| `TWO_PI` | `constexpr float` | 两倍圆周率。 | +| `HALF_PI` | `constexpr float` | 半圆周率。 | +| `DEG_TO_RAD` | `constexpr float` | 角度转弧度因子。 | +| `RAD_TO_DEG` | `constexpr float` | 弧度转角度因子。 | +| `EPSILON` | `constexpr float` | 当前数学层统一浮点容差。 | +| `FLOAT_MAX` | `constexpr float` | 浮点最大值常量。 | +| `Radians()` | `inline float` | 角度转弧度。 | +| `Degrees()` | `inline float` | 弧度转角度。 | + +## 设计取向 + +把这些常量集中在 `Math.h`,而不是让每个类型各自定义一遍,是很典型的商业引擎基础层做法。它的收益是: + +- 所有数学类型共享同一套单位约定 +- 容差判断行为一致 +- 组件、脚本和渲染层都能复用相同常量 + +## 当前实现限制 + +- 只有 `float` 版本,没有 `double` 版本。 +- `Radians()` / `Degrees()` 只是线性换算,不做任何角度归一化。 +- `FLOAT_MAX` 是手写常量,不是 `std::numeric_limits::max()` 包装。 +- 当前 `tests/math` 没有针对本头文件的独立单元测试,可靠性主要来自其被大量下游代码间接使用。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Vector3](../Vector3/Vector3.md) +- [Quaternion](../Quaternion/Quaternion.md) +- [Matrix4](../Matrix4/Matrix4.md) +- [Vectors, Quaternions, And Matrices In Runtime Transforms](../../../../_guides/Core/Math/Vectors-Quaternions-And-Matrices-In-Runtime-Transforms.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Matrix3/Matrix3.md b/docs/api/XCEngine/Core/Math/Matrix3/Matrix3.md index eed1bb4b..bed5fda5 100644 --- a/docs/api/XCEngine/Core/Math/Matrix3/Matrix3.md +++ b/docs/api/XCEngine/Core/Math/Matrix3/Matrix3.md @@ -6,36 +6,97 @@ **头文件**: `XCEngine/Core/Math/Matrix3.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Matrix3` public API。 +**描述**: 轻量 3x3 线性变换矩阵,当前主要用于纯旋转、缩放和基向量级计算。 -## 概述 +## 角色概述 -`Matrix3.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Matrix3.h` 暴露的实际类型名是 `Matrix3x3`,并通过 `using Matrix3 = Matrix3x3;` 提供常用别名。它代表不带平移的线性变换部分,适合表达: -## 声明概览 +- 旋转基 +- 逐轴缩放 +- 法线或方向向量的线性变换 -| 声明 | 类型 | 说明 | +从设计方向上,它很像商业引擎里常见的“小矩阵类型”: 比 [Matrix4](../Matrix4/Matrix4.md) 更轻,但也只覆盖线性部分,不负责平移和投影。 + +不过当前实现成熟度明显低于 `Vector3`、`Quaternion` 和 `Matrix4`。尤其是三个旋转工厂函数,当前源码里有实质性行为偏差,文档必须按现状说明,而不能按标准线性代数预期来写。 + +## 当前行为 + +- 数据按 `m[row][column]` 存储。 +- `operator*(const Vector3&)` 按“矩阵乘列向量”方式计算结果。 +- 默认构造函数把所有元素初始化为 `0.0f`。 +- `Identity()` 会把三个主对角元素设为 `1.0f`。 +- `Scale()` 会从零矩阵开始,仅填入三个缩放对角项。 +- `Transpose()` 和 `Determinant()` 行为直接、符合常见 3x3 矩阵公式。 +- `Inverse()` 在行列式绝对值小于 `EPSILON` 时直接返回 `Identity()`,不会报错,也不会返回失败状态。 + +当前最需要注意的差异在于: + +- `RotationX()` +- `RotationY()` +- `RotationZ()` + +这三个函数当前都从零矩阵开始填值,没有补上未触及的单位对角项。结果是: + +- `RotationX()` 的 `m[0][0]` 仍然是 `0` +- `RotationY()` 的 `m[1][1]` 仍然是 `0` +- `RotationZ()` 的 `m[2][2]` 仍然是 `0` + +因此它们当前返回的并不是标准意义上的纯旋转矩阵。把它们直接当作正交旋转基使用,会得到错误结果。 + +## 生命周期 + +- 纯值类型。 +- 没有初始化器、句柄或外部资源。 +- 适合按值返回、栈上创建和短生命周期传递。 + +## 线程语义 + +- 没有共享全局状态。 +- 读写安全性等同于普通 POD 风格值对象。 +- 并发场景下是否安全,取决于调用方是否共享同一个实例。 + +## 所有权与资源管理 + +- 不拥有堆内存。 +- 不管理 GPU、文件或句柄类资源。 + +## 公开数据 + +| 成员 | 类型 | 说明 | |------|------|------| -| `Matrix3x3` | `struct` | 头文件中的公开声明。 | +| `m` | `float[3][3]` | 原始矩阵存储,按 `m[row][column]` 访问。 | -## 公共方法 +## 关键 API -| 方法 | 描述 | -|------|------| -| [Matrix3x3()](Constructor.md) | 构造对象。 | -| [Identity](Identity.md) | 公开方法,详见头文件声明。 | -| [Zero](Zero.md) | 公开方法,详见头文件声明。 | -| [RotationX](RotationX.md) | 公开方法,详见头文件声明。 | -| [RotationY](RotationY.md) | 公开方法,详见头文件声明。 | -| [RotationZ](RotationZ.md) | 公开方法,详见头文件声明。 | -| [Scale](Scale.md) | 公开方法,详见头文件声明。 | -| [operator*](OperatorMultiply.md) | 公开方法,详见头文件声明。 | -| [Transpose](Transpose.md) | 公开方法,详见头文件声明。 | -| [Inverse](Inverse.md) | 公开方法,详见头文件声明。 | -| [Determinant](Determinant.md) | 公开方法,详见头文件声明。 | -| [operator[]](OperatorSubscript.md) | 公开方法,详见头文件声明。 | +- [Identity](Identity.md) - 构造单位矩阵。 +- [Zero](Zero.md) - 构造全零矩阵。 +- [Scale](Scale.md) - 构造逐轴缩放矩阵。 +- [Transpose](Transpose.md) - 返回转置矩阵。 +- [Inverse](Inverse.md) - 计算逆矩阵,奇异矩阵时退回单位矩阵。 + +## 设计取向 + +如果按商业级引擎的常见分层看,`Matrix3` 通常承担“局部线性代数工具”的角色,而不是主变换格式。主路径一般是: + +- 方向和旋转逻辑用 [Quaternion](../Quaternion/Quaternion.md) +- 带平移的组合变换用 [Matrix4](../Matrix4/Matrix4.md) +- `Matrix3` 只在需要纯线性部分时介入 + +XCEngine 当前也在朝这个方向走,但 `Matrix3` 这一层显然还没有完全打磨完,尤其不适合在当前版本里作为“可靠旋转构造器”直接使用。 + +## 当前实现限制 + +- `RotationX/Y/Z()` 当前实现不返回标准旋转矩阵,这是最重要的现实限制。 +- `Inverse()` 对奇异矩阵静默返回 `Identity()`,可能掩盖上游数据错误。 +- `operator[]` 不做边界检查。 +- 当前 `tests/math` 没有专门的 `Matrix3` 单元测试覆盖,可靠性判断主要来自源码核对,而不是测试闭环。 +- 没有与 `Quaternion` 的直接互转接口,也没有法线矩阵、伴随矩阵等更高层辅助函数。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Quaternion](../Quaternion/Quaternion.md) +- [Matrix4](../Matrix4/Matrix4.md) +- [Vectors, Quaternions, And Matrices In Runtime Transforms](../../../../_guides/Core/Math/Vectors-Quaternions-And-Matrices-In-Runtime-Transforms.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Matrix4/Matrix4.md b/docs/api/XCEngine/Core/Math/Matrix4/Matrix4.md index 011b02c5..772d3c18 100644 --- a/docs/api/XCEngine/Core/Math/Matrix4/Matrix4.md +++ b/docs/api/XCEngine/Core/Math/Matrix4/Matrix4.md @@ -6,48 +6,133 @@ **头文件**: `XCEngine/Core/Math/Matrix4.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Matrix4` public API。 +**描述**: 当前运行时主矩阵类型,承担 TRS 组合、摄像机视图和投影矩阵等核心职责。 -## 概述 +## 角色概述 -`Matrix4.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Matrix4.h` 暴露的真实类型名是 `Matrix4x4`,并通过 `using Matrix4 = Matrix4x4;` 提供常用别名。它是当前数学层里最关键的矩阵类型,直接连接: -## 声明概览 +- [Transform](../Transform/Transform.md) 的 `ToMatrix()` +- `TransformComponent` 的 local-to-world / world-to-local 路径 +- 摄像机 `LookAt()`、`Perspective()`、`Orthographic()` 这种渲染前矩阵构造 -| 声明 | 类型 | 说明 | +如果做商业引擎类比,它的位置非常像 Unity `Matrix4x4` 或 Unreal 一类运行时 4x4 变换矩阵: 既是场景层和渲染层之间的边界格式,也是很多空间变换工具的最终落点。 + +## 当前行为 + +- 数据按 `m[row][column]` 存储。 +- 变换按 `Matrix * Vector` 计算。 +- 平移写在最后一列,即 `m[0][3] / m[1][3] / m[2][3]`。 +- 默认构造函数返回全零矩阵。 +- `Identity()` 只设置四个主对角元素为 `1.0f`。 +- `Translation()`、`RotationX/Y/Z()` 和 `Scale()` 都可直接用于构造基础仿射矩阵。 +- `Rotation(const Quaternion&)` 只是转调 [Quaternion](../Quaternion/Quaternion.md) 的 `ToMatrix4x4()`。 +- `TRS()` 当前按 `Translation * Rotation * Scale` 顺序组合。 +- `MultiplyPoint()` 会把输入当作 `w = 1` 的点参与计算。 +- `MultiplyVector()` 会把输入当作 `w = 0` 的方向向量参与计算,因此不受平移影响。 + +按当前源码和 `tests/math/test_matrix.cpp`,下面几项行为是有测试支撑的: + +- 基础平移、缩放、绕轴旋转 +- `TRS()` 组合 +- `LookAt()` +- `Perspective()` 和 `Orthographic()` +- `Inverse()`、`Determinant()` +- `GetTranslation()`、`GetScale()`、`Decompose()` + +## 视图与投影约定 + +`Matrix4` 当前既用来表达仿射变换,也用来表达相机矩阵,但这两类矩阵的使用边界需要明确区分。 + +### `LookAt()` + +当前实现构造的是 view-style 矩阵: + +- `z = Normalize(eye - target)` +- `x = Normalize(Cross(up, z))` +- `y = Cross(z, x)` + +当输入退化时,源码会做最小兜底: + +- `eye == target` 时把 `z` 退回 `Vector3::Forward()` +- `up` 与 `z` 平行或接近平行时把 `x` 退回 `Vector3::Right()` + +### `Perspective()` + +当前透视矩阵实现采用源码和测试都明确验证过的系数形式: + +- `m[2][2] = far / (far - near)` +- `m[2][3] = -far * near / (far - near)` +- `m[3][2] = 1.0f` + +这意味着它已经带有明确的引擎内部投影约定,不能简单按别的数学库默认模板去理解。 + +### `Orthographic()` + +当前实现把深度映射写成: + +- `m[2][2] = 1 / (far - near)` +- `m[2][3] = -near / (far - near)` + +它和 `Perspective()` 一样,应被视为“当前引擎约定”的一部分。 + +## 生命周期 + +- 纯值类型。 +- 没有隐藏堆分配,也没有外部句柄。 +- 适合按值返回和临时拼装。 + +## 线程语义 + +- 没有共享静态状态。 +- 并发读安全性等同于普通值对象。 +- 并发写同一实例仍由调用方自行同步。 + +## 所有权与资源管理 + +- 不管理 GPU 资源。 +- 不拥有外部对象。 +- 不涉及文件、锁或句柄生命周期。 + +## 公开数据 + +| 成员 | 类型 | 说明 | |------|------|------| -| `Matrix4x4` | `struct` | 头文件中的公开声明。 | +| `m` | `float[4][4]` | 原始矩阵存储,按 `m[row][column]` 访问。 | -## 公共方法 +## 关键 API -| 方法 | 描述 | -|------|------| -| [Matrix4x4()](Constructor.md) | 构造对象。 | -| [Identity](Identity.md) | 公开方法,详见头文件声明。 | -| [Zero](Zero.md) | 公开方法,详见头文件声明。 | -| [Translation](Translation.md) | 公开方法,详见头文件声明。 | -| [Rotation](Rotation.md) | 公开方法,详见头文件声明。 | -| [Scale](Scale.md) | 公开方法,详见头文件声明。 | -| [TRS](TRS.md) | 公开方法,详见头文件声明。 | -| [LookAt](LookAt.md) | 公开方法,详见头文件声明。 | -| [Perspective](Perspective.md) | 公开方法,详见头文件声明。 | -| [Orthographic](Orthographic.md) | 公开方法,详见头文件声明。 | -| [RotationX](RotationX.md) | 公开方法,详见头文件声明。 | -| [RotationY](RotationY.md) | 公开方法,详见头文件声明。 | -| [RotationZ](RotationZ.md) | 公开方法,详见头文件声明。 | -| [operator*](OperatorMultiply.md) | 公开方法,详见头文件声明。 | -| [MultiplyPoint](MultiplyPoint.md) | 公开方法,详见头文件声明。 | -| [MultiplyVector](MultiplyVector.md) | 公开方法,详见头文件声明。 | -| [Transpose](Transpose.md) | 公开方法,详见头文件声明。 | -| [Inverse](Inverse.md) | 公开方法,详见头文件声明。 | -| [Determinant](Determinant.md) | 公开方法,详见头文件声明。 | -| [GetTranslation](GetTranslation.md) | 获取相关状态或对象。 | -| [GetRotation](GetRotation.md) | 获取相关状态或对象。 | -| [GetScale](GetScale.md) | 获取相关状态或对象。 | -| [Decompose](Decompose.md) | 公开方法,详见头文件声明。 | -| [operator[]](OperatorSubscript.md) | 公开方法,详见头文件声明。 | +- [TRS](TRS.md) - 组合平移、旋转和缩放。 +- [LookAt](LookAt.md) - 构造视图风格矩阵。 +- [Perspective](Perspective.md) - 构造当前投影约定下的透视矩阵。 +- [MultiplyPoint](MultiplyPoint.md) - 变换点。 +- [Decompose](Decompose.md) - 从矩阵中提取平移、旋转和缩放。 + +## 与变换系统的关系 + +在当前引擎里,`Matrix4` 更像是“边界格式”而不是“最舒适的旋转编辑格式”: + +- 方向与姿态编辑优先用 [Quaternion](../Quaternion/Quaternion.md) +- 位置、旋转、缩放状态可用 [Transform](../Transform/Transform.md) 保存 +- 到了层级合成、相机或渲染边界,再转成 `Matrix4` + +这种分工和商业引擎常见实践一致,因为四元数更适合表达旋转状态,而 4x4 矩阵更适合一次性打包完整空间变换。 + +## 当前实现限制 + +- `MultiplyPoint()` 不做透视除法。把投影矩阵乘上点之后,结果不会自动除以 `w`。不要按 Unity `Matrix4x4.MultiplyPoint()` 的直觉假定它会完成齐次归一化。 +- `Inverse()` 在奇异矩阵情况下静默返回 `Identity()`。 +- `GetScale()` 通过前三列长度估算缩放。 +- `GetRotation()` 直接把当前矩阵交给 `Quaternion::FromRotationMatrix()`,没有先显式去除缩放。 +- `Decompose()` 只是依次调用 `GetTranslation()`、`GetScale()` 和 `GetRotation()`,因此对非正交矩阵、带剪切矩阵、复杂非均匀缩放矩阵并不稳健。 +- `operator[]` 不做边界检查。 +- 没有区分“仿射快速逆”和“通用 4x4 逆”的 API,也没有显式矩阵布局互转辅助函数。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Quaternion](../Quaternion/Quaternion.md) +- [Transform](../Transform/Transform.md) +- [TransformComponent](../../../Components/TransformComponent/TransformComponent.md) +- [Vectors, Quaternions, And Matrices In Runtime Transforms](../../../../_guides/Core/Math/Vectors-Quaternions-And-Matrices-In-Runtime-Transforms.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Plane/Plane.md b/docs/api/XCEngine/Core/Math/Plane/Plane.md index 77d35106..17f2d828 100644 --- a/docs/api/XCEngine/Core/Math/Plane/Plane.md +++ b/docs/api/XCEngine/Core/Math/Plane/Plane.md @@ -6,26 +6,82 @@ **头文件**: `XCEngine/Core/Math/Plane.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Plane` public API。 +**描述**: 平面几何类型,用于点侧判断、最近点查询和与球体、射线、视锥体的基础空间关系计算。 -## 概述 +## 角色概述 -`Plane.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Plane` 是当前几何层里最基础的无限平面表示。它主要服务于: -## 声明概览 +- [Ray](../Ray/Ray.md) 与平面的相交测试 +- [Frustum](../Frustum/Frustum.md) 的六平面裁剪 +- 球体和平面的快速关系判断 -| 声明 | 类型 | 说明 | +这类类型在商业引擎里通常是很多裁剪、碰撞和相机算法的底层积木,XCEngine 当前也是这个定位。 + +## 当前行为 + +- 默认平面法线是 `Vector3::Up()`,`distance` 为 `0.0f`。 +- 构造函数会先归一化 `normal`,再原样保存 `distance`。 +- 当前距离公式是 `dot(normal, point) + distance`。 +- `GetClosestPoint()` 通过上面的距离公式把点投影回平面。 +- `GetSide()` 以距离是否大于 `0.0f` 判断点位于平面哪一侧。 +- `Intersects(const Sphere&)` 以 `abs(distanceToCenter) <= radius` 判断球体是否与平面相交。 + +按 `tests/math/test_geometry.cpp`,以下路径有测试覆盖: + +- 默认构造 +- 参数构造 +- `FromPoints()` +- `GetDistanceToPoint()` +- `GetClosestPoint()` +- `GetSide()` +- `Intersects(Sphere)` + +## 生命周期 + +- 纯值类型。 +- 没有外部资源。 +- 适合按值传递和作为视锥平面数组元素。 + +## 线程语义 + +- 无共享状态。 +- 可安全用于只读并发场景。 + +## 所有权与资源管理 + +- 不持有其他几何对象。 +- 不管理内存或句柄。 + +## 公开数据 + +| 成员 | 类型 | 说明 | |------|------|------| -| `Plane` | `struct` | 头文件中的公开声明。 | +| `normal` | `Vector3` | 平面法线,默认 `Vector3::Up()`。 | +| `distance` | `float` | 当前参与公式 `dot(normal, point) + distance` 的常数项。 | -## 结构体成员 +## 关键 API -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `normal` | `Vector3` | 结构体公开字段。 | `Vector3::Up()` | -| `distance` | `float` | 结构体公开字段。 | `0.0f` | +- `FromPoints()` - 由三个点构造平面。 +- `GetDistanceToPoint()` - 计算点到平面的有符号距离表达式结果。 +- `GetClosestPoint()` - 计算平面上的最近点。 +- `GetSide()` - 判断点在法线正侧还是负侧。 + +## 设计取向 + +`Plane` 当前做得非常薄,只承担基础几何关系,不承担“完整碰撞平面系统”的职责。这种做法是合理的,因为平面更适合作为很多高层算法的内部数学对象,而不是高层模块直接长期持有的大状态对象。 + +## 当前实现限制 + +- 当前类的符号约定是 `dot(normal, point) + distance`,这和不少引擎常见的 `dot(normal, point) - distance` 直觉不同。 +- `FromPoints()` 当前把 `distance` 写成 `dot(normal, a)`,这与 `GetDistanceToPoint()` 使用的公式在一般情况下符号并不一致;测试只覆盖了平面经过原点的情形。 +- 没有与 [Box](../Box/Box.md)、[Bounds](../Bounds/Bounds.md) 或 [Ray](../Ray/Ray.md) 的成员级双向完整交互接口,虽然头文件里有部分前向声明。 +- 没有保证法线输入非零;零向量法线会被 `Vector3::Normalize()` 退回零向量。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Ray](../Ray/Ray.md) +- [Sphere](../Sphere/Sphere.md) +- [Frustum](../Frustum/Frustum.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Quaternion/Quaternion.md b/docs/api/XCEngine/Core/Math/Quaternion/Quaternion.md index 617f0018..df63afd0 100644 --- a/docs/api/XCEngine/Core/Math/Quaternion/Quaternion.md +++ b/docs/api/XCEngine/Core/Math/Quaternion/Quaternion.md @@ -6,28 +6,105 @@ **头文件**: `XCEngine/Core/Math/Quaternion.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Quaternion` public API。 +**描述**: 运行时旋转主类型,用于稳定表示朝向,并与 `Vector3`、`Matrix4` 和 `Transform` 形成变换主路径。 -## 概述 +## 角色概述 -`Quaternion.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Quaternion` 是当前数学层里表达旋转状态的核心类型。默认值就是单位四元数,因此它天然适合作为: -## 声明概览 +- 物体朝向 +- 相机姿态 +- 层级旋转组合 +- 方向向量旋转的中间格式 -| 声明 | 类型 | 说明 | +如果和 Unity 风格类比,它的心智模型和 Unity `Quaternion` 很接近:运行时内部优先保存四元数,Euler 角更多作为编辑入口或展示层辅助格式。 + +## 当前行为 + +- 公开字段为 `x / y / z / w`。 +- 默认构造结果是单位四元数 `(0, 0, 0, 1)`。 +- `Identity()` 明确返回单位四元数。 +- `FromEulerAngles()` 和 `ToEulerAngles()` 都使用弧度制。 +- `tests/math/test_quaternion.cpp` 验证了 `FromEulerAngles(x, y, z)` 与 `ToEulerAngles()` 在单轴场景下保持 `x/y/z` 轴顺序一致。 +- `FromRotationMatrix()` 会在返回前做一次 `Normalized()`,因此输出总会被归一化。 +- `Slerp()` 会先把 `t` clamp 到 `[0, 1]`。 +- `LookRotation()` 对 `Up / Down / Right / Left` 这几条精确方向做了硬编码快捷分支,而不是完全统一走一般路径。 +- `Inverse()` 在范数为零时返回 `Identity()`。 +- `Normalized()` 在模长过小时也返回 `Identity()`。 + +## 与向量和矩阵的关系 + +当前旋转链路大致是: + +- 用 `Quaternion` 保存姿态 +- 用 `Quaternion * Quaternion` 做旋转组合 +- 用 `Quaternion * Vector3` 旋转方向 +- 在需要矩阵边界时通过 `ToMatrix4x4()` 变成 [Matrix4](../Matrix4/Matrix4.md) + +其中 `Quaternion * Vector3` 不是成员函数,而是定义在 `Quaternion.cpp`、声明在 [Vector3](../Vector3/Vector3.md) 头文件里的自由函数。这一层设计意味着: + +- `Vector3` 不内建旋转实现 +- 旋转责任仍然归 `Quaternion` + +## 生命周期 + +- 纯值类型。 +- 适合按值传递和复制。 +- 没有初始化器或外部资源绑定。 + +## 线程语义 + +- 没有共享可变全局状态。 +- 并发场景下和普通值对象一致。 + +## 所有权与资源管理 + +- 不拥有外部资源。 +- 不涉及句柄、堆对象或文件生命周期。 + +## 公开数据 + +| 成员 | 类型 | 说明 | |------|------|------| -| `Quaternion` | `struct` | 头文件中的公开声明。 | +| `x` | `float` | 虚部 X 分量。 | +| `y` | `float` | 虚部 Y 分量。 | +| `z` | `float` | 虚部 Z 分量。 | +| `w` | `float` | 实部。 | -## 结构体成员 +## 关键 API -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `x` | `float` | 结构体公开字段。 | `0.0f` | -| `y` | `float` | 结构体公开字段。 | `0.0f` | -| `z` | `float` | 结构体公开字段。 | `0.0f` | -| `w` | `float` | 结构体公开字段。 | `1.0f` | +- `FromAxisAngle()` - 从轴角构造四元数。 +- `FromEulerAngles()` - 从欧拉角构造四元数。 +- `FromRotationMatrix()` - 从 4x4 矩阵提取旋转。 +- `Slerp()` - 做球面插值。 +- `LookRotation()` - 从前方向量构造朝向。 + +## 设计取向 + +商业级引擎通常不会长期以 Euler 角作为内部旋转状态,因为那会让组合、插值和方向推导都变得脆弱。`Quaternion` 的存在就是把这件事从一开始做对: + +- 组合旋转更自然 +- 插值更平滑 +- 可直接旋转方向向量 +- 到矩阵边界时再统一转换 + +XCEngine 当前这层设计已经具备这个骨架,而且 `TransformComponent` 也是围绕这套思路组织的。 + +## 当前实现限制 + +- `FromAxisAngle()` 不会归一化 `axis`。如果传入的轴不是单位向量,结果四元数也不会自动成为单位四元数。 +- `LookRotation()` 没有显式保护零长度 `forward`。退化输入会落入一般路径并生成非直观结果,因此调用前应保证方向非零。 +- `LookRotation()` 对少数精确方向使用硬编码分支,行为更偏“工程兜底”,而不是完整稳健的通用构造算法。 +- `Slerp()` 在 `sinHalfTheta` 过小时会走一个线性退化分支;当前源码在这个分支里使用了与构造函数参数顺序不一致的分量排列,不能把这条分支当成严格可靠的插值保证。 +- `ToEulerAngles()` 适合做展示或轻量编辑桥接,但文档不应把它写成“永远无歧义的旋转真值表示”。 +- 当前没有 `AngleAxis` 提取、`Nlerp`、`FromToRotation` 等更完整的商业引擎工具族。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Vector3](../Vector3/Vector3.md) +- [Matrix4](../Matrix4/Matrix4.md) +- [Transform](../Transform/Transform.md) +- [TransformComponent](../../../Components/TransformComponent/TransformComponent.md) +- [Vectors, Quaternions, And Matrices In Runtime Transforms](../../../../_guides/Core/Math/Vectors-Quaternions-And-Matrices-In-Runtime-Transforms.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Ray/Ray.md b/docs/api/XCEngine/Core/Math/Ray/Ray.md index 9c4c6b8c..e6248427 100644 --- a/docs/api/XCEngine/Core/Math/Ray/Ray.md +++ b/docs/api/XCEngine/Core/Math/Ray/Ray.md @@ -6,26 +6,79 @@ **头文件**: `XCEngine/Core/Math/Ray.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Ray` public API。 +**描述**: 基础射线查询类型,当前覆盖点采样以及与球体、盒体和平面的相交测试。 -## 概述 +## 角色概述 -`Ray.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Ray` 在当前数学层里承担典型的空间查询入口角色。它适合表达: -## 声明概览 +- 从某点沿某方向发出的查询线 +- 鼠标拾取、视线测试这类轻量查询 +- 几何求交的统一输入 -| 声明 | 类型 | 说明 | +这和商业引擎里常见的 `Ray` 心智模型基本一致。 + +## 当前行为 + +- 默认构造函数不做额外初始化,`origin` 和 `direction` 都保持各自默认值。 +- 参数构造函数会对传入 `direction` 做一次 `Vector3::Normalize()`。 +- `GetPoint(t)` 返回 `origin + direction * t`。 +- `Intersects(const Sphere&, float& t)` 使用标准二次方程判定,并优先返回最近的正向交点。 +- `Intersects(const Plane&, float& t)` 在分母接近 `0` 时视为平行不相交;只接受 `t >= 0` 的命中。 +- `Intersects(const Box&, float& t)` 使用 slab 风格的轴向区间裁剪。 + +按 `tests/math/test_geometry.cpp`,当前有测试覆盖: + +- 默认构造 / 参数构造 +- `GetPoint()` +- 与球体的命中和未命中 +- 与盒体的命中和未命中 +- 与平面的命中和未命中 + +## 生命周期 + +- 纯值类型。 +- 适合短生命周期查询对象。 + +## 线程语义 + +- 无共享状态。 +- 并发使用安全性等同于普通值对象。 + +## 所有权与资源管理 + +- 不拥有目标几何体。 +- 不缓存最近命中结果。 + +## 公开数据 + +| 成员 | 类型 | 说明 | |------|------|------| -| `Ray` | `struct` | 头文件中的公开声明。 | +| `origin` | `Vector3` | 射线起点。 | +| `direction` | `Vector3` | 射线方向。参数构造时会被归一化。 | -## 结构体成员 +## 关键 API -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `origin` | `Vector3` | 结构体公开字段。 | - | -| `direction` | `Vector3` | 结构体公开字段。 | - | +- `GetPoint()` - 取得参数化位置。 +- `Intersects(Sphere)` - 与球体求交。 +- `Intersects(Box)` - 与盒体求交。 +- `Intersects(Plane)` - 与平面求交。 + +## 设计取向 + +当前 `Ray` 很适合作为“几何层统一查询输入”。这也是商业引擎里最常见的设计,因为拾取、碰撞预判、视锥辅助计算都很自然地围绕射线展开。 + +## 当前实现限制 + +- 构造函数会归一化方向,但不会拒绝零向量方向。零方向会让后续查询进入退化路径。 +- `Intersects(const Box&, float& t)` 会直接做 `1.0f / direction[i]`,没有零分量保护。 +- `Intersects(const Box&, float& t)` 依赖 `Box::GetMin()` / `GetMax()`,因此当前实际只按轴对齐包围盒思路工作,不会考虑 `Box::transform`。 +- 没有 `maxDistance`、层过滤或命中法线等更高层查询信息。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Sphere](../Sphere/Sphere.md) +- [Plane](../Plane/Plane.md) +- [Box](../Box/Box.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Rect/Rect.md b/docs/api/XCEngine/Core/Math/Rect/Rect.md index 53848b8e..a7ca6acd 100644 --- a/docs/api/XCEngine/Core/Math/Rect/Rect.md +++ b/docs/api/XCEngine/Core/Math/Rect/Rect.md @@ -2,34 +2,85 @@ **命名空间**: `XCEngine::Math` -**类型**: `struct` +**类型**: `header-group` **头文件**: `XCEngine/Core/Math/Rect.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Rect` public API。 +**描述**: 二维矩形与视口辅助头文件,当前同时定义 `Rect`、`RectInt` 和 `Viewport`。 -## 概述 +## 角色概述 -`Rect.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Rect.h` 不是单一类型头文件,而是一组 2D 区域辅助类型的集合: -## 声明概览 +- `Rect` - 浮点矩形 +- `RectInt` - 整数矩形 +- `Viewport` - 带深度范围的视口描述 -| 声明 | 类型 | 说明 | -|------|------|------| -| `Rect` | `struct` | 头文件中的公开声明。 | -| `RectInt` | `struct` | 头文件中的公开声明。 | -| `Viewport` | `struct` | 头文件中的公开声明。 | +这类分组在商业引擎里很常见,因为它们经常一起出现在 UI、窗口、渲染目标和裁剪区域逻辑中。 -## 结构体成员 +## 当前行为 -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `x` | `float` | 结构体公开字段。 | `0.0f` | -| `y` | `float` | 结构体公开字段。 | `0.0f` | -| `width` | `float` | 结构体公开字段。 | `0.0f` | -| `height` | `float` | 结构体公开字段。 | `0.0f` | +### `Rect` + +- 存储 `x / y / width / height` +- `GetRight()` 和 `GetBottom()` 分别返回 `x + width` 与 `y + height` +- `Contains()` 对右边界和下边界采用半开区间,即 `< x + width`、`< y + height` +- `Intersects()` 使用 `<` / `>` 判断分离,因此边界恰好接触也会被视为相交 +- `Intersect()` 会把负宽高裁成 `0` +- `Union()` 直接取包络范围 + +### `RectInt` + +- 与 `Rect` 语义类似,但字段改为 `int32_t` +- `GetPosition()`、`GetSize()`、`GetCenter()` 返回的是 [Vector2](../Vector2/Vector2.md),即浮点表示 + +### `Viewport` + +- 在 `x / y / width / height` 之外,还带 `minDepth / maxDepth` +- 默认深度范围是 `0.0f` 到 `1.0f` +- `GetAspectRatio()` 在 `height <= 0` 时返回 `0.0f` +- `GetRect()` 会丢弃深度信息,只返回 `Rect` + +## 生命周期 + +- 纯值类型集合。 +- 没有独立初始化、析构或外部资源绑定。 + +## 线程语义 + +- 无共享状态。 +- 适合在渲染和 UI 代码中按值传递。 + +## 所有权与资源管理 + +- 不管理窗口、纹理、帧缓冲或命令列表资源。 +- 只是轻量数据载体。 + +## 公开类型 + +| 类型 | 说明 | +|------|------| +| `Rect` | 浮点矩形。 | +| `RectInt` | 整数矩形。 | +| `Viewport` | 带深度范围的视口结构。 | + +## 设计取向 + +把 `Rect`、`RectInt` 和 `Viewport` 放在同一个基础头文件里,是很典型的“基础图形/界面几何层”组织方式。这样做的好处是: + +- 2D 区域概念集中 +- 上层模块不需要到处重复定义小结构体 +- 可以在不引入渲染后端依赖的前提下共享基础数据形状 + +## 当前实现限制 + +- 没有对负宽高做统一规范化;很多函数默认调用方传入的是正常矩形。 +- `Rect::Contains()` 用半开区间,但 `Rect::Intersects()` 对边界接触视为相交,这两种边界语义并不完全一致。 +- `RectInt::GetCenter()` 使用整数除法后再转浮点,奇数尺寸会截断到靠左上半格。 +- 当前 `tests/math` 没有独立的 `Rect` / `Viewport` 单元测试覆盖。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Vector2](../Vector2/Vector2.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Sphere/Sphere.md b/docs/api/XCEngine/Core/Math/Sphere/Sphere.md index fee2bb26..eabd8a0f 100644 --- a/docs/api/XCEngine/Core/Math/Sphere/Sphere.md +++ b/docs/api/XCEngine/Core/Math/Sphere/Sphere.md @@ -6,26 +6,68 @@ **头文件**: `XCEngine/Core/Math/Sphere.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Sphere` public API。 +**描述**: 基础球体几何类型,当前提供点包含和球体相交两类最常用查询。 -## 概述 +## 角色概述 -`Sphere.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Sphere` 是最简单的三维包围体之一。它适合表达: -## 声明概览 +- 简单范围检测 +- 粗粒度包围体 +- 与平面、射线、盒体的低成本求交输入 -| 声明 | 类型 | 说明 | +在当前引擎里,它和 [Ray](../Ray/Ray.md)、[Plane](../Plane/Plane.md)、[Box](../Box/Box.md)、[Frustum](../Frustum/Frustum.md) 一起构成几何查询的基础积木。 + +## 当前行为 + +- 默认中心点是 `Vector3::Zero()`,默认半径是 `0.0f`。 +- `Contains(point)` 通过比较平方距离与 `radius * radius` 判断点是否在球内或球面上。 +- `Intersects(other)` 通过比较球心平方距离和半径和的平方判断两球是否相交。 + +按 `tests/math/test_geometry.cpp`,默认构造、参数构造、包含判定和球球相交都已有测试覆盖。 + +## 生命周期 + +- 纯值类型。 +- 适合作为包围体和查询参数按值传递。 + +## 线程语义 + +- 无共享状态。 +- 普通并发读安全。 + +## 所有权与资源管理 + +- 不持有外部对象。 +- 不管理加速结构或碰撞缓存。 + +## 公开数据 + +| 成员 | 类型 | 说明 | |------|------|------| -| `Sphere` | `struct` | 头文件中的公开声明。 | +| `center` | `Vector3` | 球心。 | +| `radius` | `float` | 半径。 | -## 结构体成员 +## 关键 API -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `center` | `Vector3` | 结构体公开字段。 | `Vector3::Zero()` | -| `radius` | `float` | 结构体公开字段。 | `0.0f` | +- `Contains()` - 判断点是否位于球体内部或边界上。 +- `Intersects()` - 判断两个球体是否相交。 + +## 设计取向 + +`Sphere` 当前被设计成极薄的基础包围体。这种取向很合理,因为球体最常见的价值就是以极低成本提供“够用”的空间近似,而不是承载复杂状态。 + +## 当前实现限制 + +- 构造函数不会验证半径是否为非负数。 +- 没有体积、最近点、包围球合并等更高层工具。 +- 当前只提供与自身的相交测试;和其他几何体的关系判断分散在别的类型里实现。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Ray](../Ray/Ray.md) +- [Plane](../Plane/Plane.md) +- [Box](../Box/Box.md) +- [Frustum](../Frustum/Frustum.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Transform/Transform.md b/docs/api/XCEngine/Core/Math/Transform/Transform.md index 6e768479..f6c69b74 100644 --- a/docs/api/XCEngine/Core/Math/Transform/Transform.md +++ b/docs/api/XCEngine/Core/Math/Transform/Transform.md @@ -6,28 +6,119 @@ **头文件**: `XCEngine/Core/Math/Transform.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Transform` public API。 +**描述**: 轻量值语义 TRS 容器,把位置、旋转和缩放打包成一个可组合的运行时变换对象。 -## 概述 +## 角色概述 -`Transform.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Math::Transform` 是纯数学层的变换对象,不是场景图组件。它的职责非常明确: -## 声明概览 +- 保存 `position` +- 保存 `rotation` +- 保存 `scale` +- 支持把这三者组合成矩阵 +- 支持值语义的变换组合和点/方向转换 -| 声明 | 类型 | 说明 | +如果和商业引擎思路类比,它更接近“runtime TRS value object”,而不是 Unity `Transform` 那种直接挂在场景对象上的层级组件。XCEngine 里真正承担场景层职责的是 [TransformComponent](../../../Components/TransformComponent/TransformComponent.md)。 + +## 当前行为 + +- 默认状态是原点位置、单位旋转、单位缩放。 +- `ToMatrix()` 直接返回 `Matrix4x4::TRS(position, rotation, scale)`。 +- `operator*` 按父子组合思路计算: + - `position = position + rotation * (scale * other.position)` + - `rotation = rotation * other.rotation` + - `scale = scale * other.scale` +- `TransformPoint()` 会同时应用缩放、旋转和平移。 +- `TransformDirection()` 会应用缩放和旋转,但不应用平移。 +- `InverseTransformPoint()` 与 `InverseTransformDirection()` 都使用逐分量除法恢复局部坐标。 + +`Inverse()` 的当前实现也很直接: + +- `rotation = rotation.Inverse()` +- `scale = Vector3::One() / scale` +- `position = inverseRotation * (-position) * inverseScale` + +这条路径适合标准 TRS 变换,但并不是通用矩阵级逆分解。 + +## 生命周期 + +- 纯值类型。 +- 适合按值复制、返回和短期组合。 +- 没有注册、初始化或销毁阶段。 + +## 线程语义 + +- 没有共享状态。 +- 和普通数学值对象一样,由调用方决定是否跨线程共享实例。 + +## 所有权与资源管理 + +- 不持有外部对象。 +- 不管理层级、父子关系或缓存。 +- 不负责 dirty 标记或矩阵缓存。 + +## 公开数据 + +| 成员 | 类型 | 说明 | |------|------|------| -| `Space` | `enum class` | 头文件中的公开声明。 | -| `Transform` | `struct` | 头文件中的公开声明。 | +| `position` | `Vector3` | 平移分量,默认 `Vector3::Zero()`。 | +| `rotation` | `Quaternion` | 旋转分量,默认 `Quaternion::Identity()`。 | +| `scale` | `Vector3` | 缩放分量,默认 `Vector3::One()`。 | -## 结构体成员 +## 公开枚举 -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `position` | `Vector3` | 结构体公开字段。 | `Vector3::Zero()` | -| `rotation` | `Quaternion` | 结构体公开字段。 | `Quaternion::Identity()` | -| `scale` | `Vector3` | 结构体公开字段。 | `Vector3::One()` | +| 枚举值 | 说明 | +|--------|------| +| `Space::Self` | 局部空间。 | +| `Space::World` | 世界空间。 | + +`Space` 这个枚举在 `Transform.h` 中有声明,但当前 `Math::Transform` 自己的方法并不接收该枚举。它更像是和组件层空间语义并列的基础定义。 + +## 与 TransformComponent 的区别 + +这两者很容易被误认为一回事,但当前源码里职责不同: + +- `Math::Transform` 是无状态缓存、无层级的值对象 +- `TransformComponent` 是场景组件,负责父子层级、dirty 传播和 local-to-world 缓存 + +还有一个很重要的行为差异: + +- `Math::Transform::TransformDirection()` 会应用缩放和旋转 +- `TransformComponent::TransformDirection()` 当前只应用旋转,不应用缩放 + +这说明不能简单把两者的同名 API 当成完全等价。 + +## 关键 API + +- `ToMatrix()` - 转成 `Matrix4`。 +- `Inverse()` - 返回逆变换。 +- `operator*` - 组合两个 TRS 变换。 +- `TransformPoint()` - 变换点。 +- `InverseTransformPoint()` - 从世界点恢复局部点。 + +## 设计取向 + +把 TRS 先作为纯值对象独立出来,是商业引擎里非常常见的做法,因为它能把“数学变换”与“场景系统状态”解耦: + +- 数学层可直接复用 +- 不需要引入组件依赖 +- 更适合序列化、临时计算和函数式组合 + +XCEngine 当前已经有这层边界,后续即使组件层继续扩展 hierarchy、scripting 或 editor,也不需要把这些复杂性反灌回基础数学对象里。 + +## 当前实现限制 + +- `InverseTransformPoint()` 和 `InverseTransformDirection()` 依赖逐分量除法;如果某个缩放分量为 `0`,会直接进入普通浮点除零语义,没有保护。 +- `TransformDirection()` 会带上缩放,这和很多引擎里“方向只旋转不缩放”的高层 API 直觉不同。 +- `Space` 枚举当前没有被 `Math::Transform` 自身方法消费。 +- 当前没有直接的矩阵缓存、脏标记或层级合成支持,这些能力都属于组件层而不是本类型。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Vector3](../Vector3/Vector3.md) +- [Quaternion](../Quaternion/Quaternion.md) +- [Matrix4](../Matrix4/Matrix4.md) +- [TransformComponent](../../../Components/TransformComponent/TransformComponent.md) +- [Vectors, Quaternions, And Matrices In Runtime Transforms](../../../../_guides/Core/Math/Vectors-Quaternions-And-Matrices-In-Runtime-Transforms.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Vector2/Vector2.md b/docs/api/XCEngine/Core/Math/Vector2/Vector2.md index 460bdb1d..6e30e2dc 100644 --- a/docs/api/XCEngine/Core/Math/Vector2/Vector2.md +++ b/docs/api/XCEngine/Core/Math/Vector2/Vector2.md @@ -6,26 +6,50 @@ **头文件**: `XCEngine/Core/Math/Vector2.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Vector2` public API。 +**描述**: 轻量二维向量类型,提供常见方向常量、长度计算、归一化和线性插值。 -## 概述 +## 角色概述 -`Vector2.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Vector2` 用于表达二维位置、方向、UV、屏幕偏移等最基础的二维数学量。当前实现保持了非常直接的值类型风格: -## 声明概览 +- 两个公开 `float` 字段 +- 常见静态方向工厂 +- 若干纯函数式数学工具 +- 基本算术运算符 -| 声明 | 类型 | 说明 | -|------|------|------| -| `Vector2` | `struct` | 头文件中的公开声明。 | +它的使用体验比较接近 Unity `Vector2`,但具体行为仍以当前源码实现为准。 + +## 当前行为 + +- `Normalize()` 在向量长度小于等于 `EPSILON` 时返回 `Zero()` +- `Lerp(a, b, t)` 会把 `t` clamp 到 `[0, 1]` +- `MoveTowards()` 若距离已小于 `maxDistance` 或距离极小,会直接返回目标值 +- `operator==` 使用 `EPSILON` 做近似比较,而不是逐位精确比较 ## 结构体成员 -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `x` | `float` | 结构体公开字段。 | `0.0f` | -| `y` | `float` | 结构体公开字段。 | `0.0f` | +| 成员 | 类型 | 说明 | +|------|------|------| +| `x` | `float` | X 分量 | +| `y` | `float` | Y 分量 | + +## 设计取向 + +`Vector2` 当前更强调“足够常用、足够轻”,而不是做成完整二维数学库。它适合做运行时频繁传值的小对象。 + +## 线程语义 + +- 没有内部状态共享。 +- 普通值类型,无需额外同步。 + +## 当前实现限制 + +- 没有角度、旋转、投影到线段等更高层二维工具。 +- `operator/` 和 `operator/=` 不检查除零。 +- 没有 `operator[]`,只有命名字段访问。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Vector3](../Vector3/Vector3.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Vector3/Vector3.md b/docs/api/XCEngine/Core/Math/Vector3/Vector3.md index 46385dff..551d51d9 100644 --- a/docs/api/XCEngine/Core/Math/Vector3/Vector3.md +++ b/docs/api/XCEngine/Core/Math/Vector3/Vector3.md @@ -6,27 +6,71 @@ **头文件**: `XCEngine/Core/Math/Vector3.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Vector3` public API。 +**描述**: 三维空间里最核心的基础向量类型,覆盖方向、投影、反射和插值等常用计算。 -## 概述 +## 角色概述 -`Vector3.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Vector3` 是当前数学层里使用最广的基础类型之一。场景位置、缩放、方向、法线、速度、包围体中心点、相机向量几乎都会经过它。 -## 声明概览 +从心智模型上,它和 Unity `Vector3` 很接近: -| 声明 | 类型 | 说明 | -|------|------|------| -| `Vector3` | `struct` | 头文件中的公开声明。 | +- 公开 `x/y/z` +- 一组常见方向常量 +- 静态数学工具 + 成员快捷入口 +- 运算符重载 + +但也有一些当前实现差异需要明确写进文档。 + +## 当前行为 + +- `Normalize()` 在长度过小时返回 `Zero()` +- `Lerp()` 会 clamp `t` +- `Project(onNormal)` 在法向量平方长度过小时返回 `Zero()` +- `ProjectOnPlane(planeNormal)` 在法向量平方长度过小时返回原始 `vector` +- `Angle(from, to)` 返回角度制,使用 `RAD_TO_DEG` +- `Reflect()` 使用标准镜面反射公式 + +当前还提供了两个容易忽略的分量级运算: + +- `Vector3 operator*(const Vector3&)` 是逐分量乘法 +- `Vector3 operator/(const Vector3&)` 是逐分量除法 + +它们不是点乘,也不是叉乘。 + +## 与旋转系统的关系 + +头文件里只前向声明了 `Quaternion`,真正的 `Quaternion * Vector3` 自由函数定义在 `Quaternion.cpp`。因此: + +- `Vector3` 自身不承担旋转逻辑 +- 向量旋转是由 [Quaternion](../Quaternion/Quaternion.md) 那一层补上的 ## 结构体成员 -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `x` | `float` | 结构体公开字段。 | `0.0f` | -| `y` | `float` | 结构体公开字段。 | `0.0f` | -| `z` | `float` | 结构体公开字段。 | `0.0f` | +| 成员 | 类型 | 说明 | +|------|------|------| +| `x` | `float` | X 分量 | +| `y` | `float` | Y 分量 | +| `z` | `float` | Z 分量 | + +## 设计取向 + +`Vector3` 当前已经相当接近“运行时基础数学公约”。商业引擎通常都会围绕它去构建变换、包围体、碰撞和渲染数据流。XCEngine 当前也是这个方向。 + +## 线程语义 + +- 纯值类型。 +- 没有共享状态。 + +## 当前实现限制 + +- `operator[]` 不做边界检查。 +- `operator/` 不检查除零。 +- `Lerp()` 是 clamp 版,不适合表达无界外插。 +- 没有 `Distance()`、`Min()`、`Max()`、`ClampMagnitude()` 等更完整工具族。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Quaternion](../Quaternion/Quaternion.md) +- [Matrix4](../Matrix4/Matrix4.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/Math/Vector4/Vector4.md b/docs/api/XCEngine/Core/Math/Vector4/Vector4.md index d10956d4..0e41dafa 100644 --- a/docs/api/XCEngine/Core/Math/Vector4/Vector4.md +++ b/docs/api/XCEngine/Core/Math/Vector4/Vector4.md @@ -6,28 +6,53 @@ **头文件**: `XCEngine/Core/Math/Vector4.h` -**描述**: 定义 `XCEngine/Core/Math` 子目录中的 `Vector4` public API。 +**描述**: 四维向量与齐次坐标辅助类型,用于矩阵乘法和需要 `w` 分量的数学路径。 -## 概述 +## 角色概述 -`Vector4.h` 是 `XCEngine/Core/Math` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Vector4` 在当前引擎里的定位比较明确: -## 声明概览 +- 作为 `Matrix4` 的乘法输入输出 +- 承载齐次点 / 齐次向量 +- 在需要四分量数据时充当轻量值类型 -| 声明 | 类型 | 说明 | -|------|------|------| -| `Vector4` | `struct` | 头文件中的公开声明。 | +它不像 `Vector3` 那样是大量 gameplay / transform API 的中心,而更偏向矩阵和渲染数学辅助类型。 + +## 当前行为 + +- 可以从 `Vector3` + `w` 构造 +- `Project(vector, onNormal)` 在法向量平方长度过小时返回 `Zero()` +- `ToVector3()` 只是直接丢弃 `w` +- `operator[]` 提供按索引访问,但没有边界检查 ## 结构体成员 -| 成员 | 类型 | 描述 | 默认值 | -|------|------|------|--------| -| `x` | `float` | 结构体公开字段。 | `0.0f` | -| `y` | `float` | 结构体公开字段。 | `0.0f` | -| `z` | `float` | 结构体公开字段。 | `0.0f` | -| `w` | `float` | 结构体公开字段。 | `0.0f` | +| 成员 | 类型 | 说明 | +|------|------|------| +| `x` | `float` | X 分量 | +| `y` | `float` | Y 分量 | +| `z` | `float` | Z 分量 | +| `w` | `float` | W 分量 | + +## 设计取向 + +`Vector4` 当前是一个非常轻量的齐次向量容器,重点是和 [Matrix4](../Matrix4/Matrix4.md) 的配合,而不是单独发展成大而全的四维数学类型。 + +## 线程语义 + +- 纯值类型。 +- 无共享状态。 + +## 当前实现限制 + +- 只提供了较少的一组运算接口。 +- 没有 `Magnitude()`、`Normalize()`、`Lerp()` 等更完整工具。 +- `ToVector3()` 不做齐次除法。 +- `operator[]` 不做边界检查。 ## 相关文档 -- [当前目录](../Math.md) - 返回 `Math` 平行目录 -- [API 总索引](../../../../main.md) - 返回顶层索引 +- [当前模块](../Math.md) +- [Vector3](../Vector3/Vector3.md) +- [Matrix4](../Matrix4/Matrix4.md) +- [API 总索引](../../../../main.md) diff --git a/docs/api/XCEngine/Core/RefCounted/RefCounted.md b/docs/api/XCEngine/Core/RefCounted/RefCounted.md index 651a3943..47538f62 100644 --- a/docs/api/XCEngine/Core/RefCounted/RefCounted.md +++ b/docs/api/XCEngine/Core/RefCounted/RefCounted.md @@ -6,29 +6,108 @@ **头文件**: `XCEngine/Core/RefCounted.h` -**描述**: 定义 `XCEngine/Core` 子目录中的 `RefCounted` public API。 +**描述**: 一个侵入式原子引用计数基类,通过 `AddRef()` / `Release()` 管理对象生命周期。 -## 概述 +## 角色概述 -`RefCounted.h` 是 `XCEngine/Core` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`RefCounted` 代表的是经典 intrusive reference counting 模式: -## 声明概览 +- 引用计数字段存放在对象内部 +- 调用者手工执行 `AddRef()` / `Release()` +- 计数归零时对象自己 `delete this` -| 声明 | 类型 | 说明 | -|------|------|------| -| `RefCounted` | `class` | 头文件中的公开声明。 | +这和 [SmartPtr](../SmartPtr/SmartPtr.md) 里的 `Ref = std::shared_ptr` 是两套完全不同的所有权模型。当前代码里没有自动桥接它们,这一点非常重要。 -## 公共方法 +## 当前实现行为 -| 方法 | 描述 | -|------|------| -| [RefCounted()](Constructor.md) | 构造对象。 | -| [~RefCounted()](Destructor.md) | 销毁对象并释放相关资源。 | -| [AddRef](AddRef.md) | 添加元素或建立关联。 | -| [Release](Release.md) | 释放引用或底层资源。 | -| [GetRefCount](GetRefCount.md) | 获取相关状态或对象。 | +### 1. 初始引用计数是 1 + +构造函数会把 `m_refCount` 初始化为 `1`。`tests/core/test_core.cpp` 已验证这一点。 + +这意味着默认语义是: + +- `new DerivedFromRefCounted()` 返回的对象立刻拥有一个初始引用 +- 你不需要在创建后先手工 `AddRef()` + +### 2. `Release()` 在归零时直接 `delete this` + +当前实现是: + +```cpp +if (--m_refCount == 0) { + delete this; +} +``` + +因此: + +- 一旦 `Release()` 让计数归零,对象就立即自删除 +- 之后原来的裸指针立刻失效 + +这是 intrusive RC 最典型也最危险的一点。文档必须把它明确写出来。 + +### 3. 计数字段是原子变量 + +`m_refCount` 类型是 `std::atomic`,所以单次增减操作具备基础原子性。 + +但这不等于“整个对象生命周期管理完全线程安全”。对象自己的其他状态并没有额外同步保护。 + +## 与 SmartPtr 的关系 + +当前项目里同时存在: + +- `RefCounted` +- `Ref` / `UniqueRef` / `MakeRef()` / `MakeUnique()` + +这很容易让人误以为 `RefCounted` 会自动和 `Ref` 协同工作,但当前事实是: + +- `Ref` 只是 `std::shared_ptr` +- `RefCounted` 只是手工 intrusive RC 基类 +- 两者当前没有自定义 deleter、适配器或统一 ownership 框架 + +所以使用时必须明确选一套模型,而不是混用后想当然。 + +## 使用边界 + +- 这种类型只适合堆分配对象。 +- 不应把栈对象或外部所有权对象拿来调用 `Release()`。 +- 如果对象已经由别的 RAII 所有权工具接管,再手工 `Release()` 可能导致双重销毁。 + +## 测试覆盖 + +`tests/core/test_core.cpp` 已覆盖: + +- 初始引用计数 +- `AddRef()` +- 多次链式加减引用 +- `Release()` 归零后自销毁路径 + +## 线程语义 + +- 引用计数增减是原子的。 +- 对象其他成员状态没有自动同步。 +- 释放最后一个引用的线程会执行 `delete this`。 + +## 当前实现限制 + +- 没有 weak reference 机制。 +- 没有和 `std::shared_ptr` / `Ref` 的自动互操作。 +- 只适合明确采用 intrusive RC 的对象模型。 + +## 相关方法 + +- [Constructor](Constructor.md) +- [Destructor](Destructor.md) +- [AddRef](AddRef.md) +- [Release](Release.md) +- [GetRefCount](GetRefCount.md) + +## 相关指南 + +- [Core Foundations: Ownership, Events, And Layers](../../../_guides/Core/Core-Foundations-Ownership-Events-And-Layers.md) ## 相关文档 -- [当前目录](../Core.md) - 返回 `Core` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Core.md) +- [SmartPtr](../SmartPtr/SmartPtr.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Core/SmartPtr/SmartPtr.md b/docs/api/XCEngine/Core/SmartPtr/SmartPtr.md index e4645bca..67198575 100644 --- a/docs/api/XCEngine/Core/SmartPtr/SmartPtr.md +++ b/docs/api/XCEngine/Core/SmartPtr/SmartPtr.md @@ -1,18 +1,88 @@ # SmartPtr -**命名空间**: `XCEngine` +**命名空间**: `XCEngine::Core` -**类型**: `header` +**类型**: `header-overview` **头文件**: `XCEngine/Core/SmartPtr.h` -**描述**: 定义 `XCEngine/Core` 子目录中的 `SmartPtr` public API。 +**描述**: 定义 `std::shared_ptr` / `std::unique_ptr` 的引擎内别名,以及 `MakeRef()` / `MakeUnique()` 工厂函数。 -## 概述 +## 角色概述 -`SmartPtr.h` 是 `XCEngine/Core` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`SmartPtr.h` 是当前代码库里最直接、也最现代的一层所有权约定: + +- `Ref` = `std::shared_ptr` +- `UniqueRef` = `std::unique_ptr` +- `MakeRef()` = `std::make_shared()` +- `MakeUnique()` = `std::make_unique()` + +这让代码在保持引擎命名风格的同时,仍然直接复用标准库语义。 + +## 当前实现行为 + +### 1. 这是薄封装,不是自定义智能指针系统 + +当前头文件里没有自定义控制块、intrusive hook、调试统计或特殊 deleter 逻辑。它只是别名与便捷工厂函数。 + +这意味着: + +- `Ref` 的行为完全等同 `std::shared_ptr` +- `UniqueRef` 的行为完全等同 `std::unique_ptr` + +### 2. `MakeRef()` / `MakeUnique()` 只是便捷入口 + +当前实现直接转发到标准库: + +- `MakeRef()` -> `std::make_shared` +- `MakeUnique()` -> `std::make_unique` + +`tests/core/test_core.cpp` 已覆盖: + +- 默认构造 +- 拷贝共享 +- 移动语义 +- `nullptr` 检查 + +### 3. 已有真实调用点 + +当前源码里至少能看到: + +- `ResourceManager` 使用 `Core::MakeUnique()` + +这说明它不是纯风格占位,而是已经进入实际实现。 + +## 与 RefCounted 的关系 + +这是这个头文件最容易被误解的地方。 + +当前 [RefCounted](../RefCounted/RefCounted.md) 是侵入式手工引用计数基类,而 `Ref` 是标准库 `shared_ptr`。两者当前没有自动打通。 + +最安全的理解方式是: + +- `Ref` / `UniqueRef` 是当前更常规的 C++ RAII 工具 +- `RefCounted` 是单独存在的一套 intrusive RC 机制 + +如果没有明确适配层,不要把继承自 `RefCounted` 的对象直接当成 `Ref` 体系的一部分来想当然混用。 + +## 线程语义 + +- 线程语义沿用标准库智能指针本身的语义。 +- 文档不额外扩展任何引擎层线程保证。 + +## 当前实现限制 + +- 没有自定义内存统计、调试名或对象池集成。 +- 没有和 `RefCounted` 的统一 ownership 框架。 +- 头文件本身不提供额外行为,只提供命名和风格统一。 + +## 相关指南 + +- [Core Foundations: Ownership, Events, And Layers](../../../_guides/Core/Core-Foundations-Ownership-Events-And-Layers.md) ## 相关文档 -- [当前目录](../Core.md) - 返回 `Core` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Core.md) +- [RefCounted](../RefCounted/RefCounted.md) +- [Types](../Types/Types.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/XCEngine/Core/Types/Types.md b/docs/api/XCEngine/Core/Types/Types.md index f76d1f46..5b1ab797 100644 --- a/docs/api/XCEngine/Core/Types/Types.md +++ b/docs/api/XCEngine/Core/Types/Types.md @@ -1,18 +1,44 @@ # Types -**命名空间**: `XCEngine` +**命名空间**: `XCEngine::Core` -**类型**: `header` +**类型**: `header-overview` **头文件**: `XCEngine/Core/Types.h` -**描述**: 定义 `XCEngine/Core` 子目录中的 `Types` public API。 +**描述**: 定义 `Core` 模块共享的基础定宽整数别名与 `byte` 类型别名。 -## 概述 +## 角色概述 -`Types.h` 是 `XCEngine/Core` 子目录 下的 public header,当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。 +`Types.h` 是当前引擎代码里最基础的一层命名统一头文件。它把标准库 `` 的固定宽度整数别名映射到项目自己的命名风格,例如: + +- `int8` +- `uint32` +- `uint64` +- `byte` + +这种做法在商业引擎里很常见,主要目的是让跨模块接口的类型命名统一、可读且稳定。 + +## 当前定义 + +- `int8`, `int16`, `int32`, `int64` +- `uint8`, `uint16`, `uint32`, `uint64` +- `byte` + +其中 `byte` 当前只是 `uint8_t` 的别名,不是 `std::byte`。 + +## 使用建议 + +- 当 API 需要表达固定宽度整数时,优先使用这里的别名,而不是裸 `int` / `unsigned int`。 +- 当需要与标准库或平台 API 精确对接时,再回到对应底层类型确认边界。 + +## 当前实现限制 + +- 这里只提供别名,不提供额外的字节序、序列化或平台适配逻辑。 +- `byte` 是整数别名,不具有 `std::byte` 那样的更强类型语义。 ## 相关文档 -- [当前目录](../Core.md) - 返回 `Core` 平行目录 -- [API 总索引](../../../main.md) - 返回顶层索引 +- [当前模块](../Core.md) +- [SmartPtr](../SmartPtr/SmartPtr.md) +- [API 总索引](../../../main.md) diff --git a/docs/api/_guides/Audio/Audio-System-And-Effect-Chain.md b/docs/api/_guides/Audio/Audio-System-And-Effect-Chain.md new file mode 100644 index 00000000..1f2fe080 --- /dev/null +++ b/docs/api/_guides/Audio/Audio-System-And-Effect-Chain.md @@ -0,0 +1,130 @@ +# Audio System And Effect Chain + +## 这篇指南解决什么问题 + +只看 `Audio` 模块的单个 API 页,很容易知道“有哪些类”,但不容易知道: + +- 当前真正的播放主链路是什么 +- `AudioMixer` 和各种效果器现在到底有没有接入 +- `AudioSourceComponent`、`AudioListenerComponent` 和 `AudioSystem` 分别该怎么理解 +- 这个设计和 Unity 风格音频体系的关系是什么 + +这篇指南专门解释这些问题。 + +## 先建立正确心智模型 + +当前引擎音频系统更适合理解成: + +**场景组件层 + 全局混音系统层 + 平台输出后端层** + +其中: + +- [AudioSourceComponent](../../XCEngine/Components/AudioSourceComponent/AudioSourceComponent.md) 负责每个声源的播放状态和样本生成。 +- [AudioListenerComponent](../../XCEngine/Components/AudioListenerComponent/AudioListenerComponent.md) 负责把监听器 transform 推送到系统。 +- [AudioSystem](../../XCEngine/Audio/AudioSystem/AudioSystem.md) 负责收集活跃声源、混音并提交给输出后端。 +- [IAudioBackend](../../XCEngine/Audio/IAudioBackend/IAudioBackend.md) 负责真正的设备和平台输出。 + +这和 Unity 的 `AudioSource + AudioListener + AudioMixer` 心智模型有明显对应关系,但当前版本还没有 Unity 那种完整的 mixer graph、bus、snapshot 和 effect routing 体系。 + +## 当前真实播放链路 + +按当前代码,主链路可以简化成: + +1. `AudioSourceComponent::Play()` 把自己注册到 `AudioSystem` +2. `Scene` 更新期间,`AudioListenerComponent` 更新监听器 transform +3. `AudioSystem::Update()` 为当前帧分配一个浮点混音 buffer +4. `AudioSystem` 遍历活跃 `AudioSourceComponent` +5. 每个 `AudioSourceComponent::ProcessAudio()` 直接把自己的样本叠加到同一个 buffer +6. `AudioSystem` 调用后端 `ProcessAudio()` +7. Windows 后端把浮点样本转成 `int16` 并交给系统播放 + +这条链路已经能支撑基础声音播放,但它仍然是比较直接的“源混到总线,再送后端”模型。 + +## 为什么 `AudioMixer` 现在不能按 Unity AudioMixer 理解 + +从接口形状上看,[AudioMixer](../../XCEngine/Audio/AudioMixer/AudioMixer.md) 很像未来会成长为完整路由中枢的类型: + +- 有效果器槽 +- 有输出 mixer +- 有 3D 参数 +- 有每声道音量 + +但按当前实现,它真正处理的只有总音量和静音。效果器数组、输出 mixer、3D 参数和每声道音量现在都只是被记录,没有自动进入主链路。 + +所以最准确的理解方式是: + +- 它是架构方向正确的扩展点 +- 但还不是完整 mixer graph + +## 效果器链当前的现实状态 + +### 已有类型 + +- [Equalizer](../../XCEngine/Audio/Equalizer/Equalizer.md) +- [FFTFilter](../../XCEngine/Audio/FFTFilter/FFTFilter.md) +- [Reverbation](../../XCEngine/Audio/Reverbation/Reverbation.md) +- [HRTF](../../XCEngine/Audio/HRTF/HRTF.md) + +### 当前成熟度差异 + +- `Equalizer` 和 `Reverbation` 会真实改写音频样本。 +- `FFTFilter` 当前更像 analyzer,不会回写处理结果。 +- `HRTF` 是独立空间化处理器,不是 `IAudioEffect` 派生效果器。 + +### 当前没接上的地方 + +- `AudioMixer` 不会自动遍历 effect slots +- `AudioSystem` 也不会自动经过 `AudioMixer` +- `AudioSourceComponent::outputMixer` 当前没有形成实际路由 + +这就是为什么文档会一再强调“接口存在”和“主链路已生效”不是一回事。 + +## 当前 Windows 后端到底是什么 + +类名叫 `WASAPIBackend`,但当前实现实际上依赖的是 `waveOut*` 这套 WinMM API,而不是 Core Audio WASAPI COM 接口。 + +为什么要特别说明这点? + +- 因为名字会让人以为这是现代 Windows 音频栈的标准 WASAPI 后端 +- 但当前实现的设备模型、线程模型和缓冲策略其实更接近传统 waveOut 风格 + +这不一定是问题,但文档必须准确。 + +## 为什么要保留这些“还没完全接上”的接口 + +从引擎设计角度看,这其实是合理的演进路径: + +1. 先打通最基础的播放链路 +2. 再把 mixer、effects、spatialization 的抽象搭出来 +3. 之后逐步把这些节点正式接入主混音图 + +这样做的好处是 API 方向能尽早稳定,编辑器和脚本层也能先围绕目标架构设计。代价是文档必须明确当前成熟度,否则用户会把“未来形状”误读成“当前能力”。 + +## 你现在最该注意的限制 + +### 1. `AudioSourceComponent` 不是完整商用级 AudioSource + +它已经能解码、播放和基础混音,但 pause/resume、3D 参数、output mixer 和序列化都还不完整。 + +### 2. `AudioListenerComponent` 主要只做监听器 transform 和 master volume / mute + +其他高级参数现在大多是存值或预留位。 + +### 3. `AudioMixer` 还没有成为主链路中枢 + +不要把它当成已经具备完整 bus routing 的 Unity AudioMixer 替代品。 + +### 4. `AudioSystem` 和内建后端之间的 buffer size 语义还需要收敛 + +当前调用方和实现方对 `bufferSize` 的理解并不完全一致。 + +### 5. 这个模块当前缺少足够的直接单元测试 + +很多行为需要以源码而不是测试保证为准。 + +## 推荐阅读顺序 + +1. 先读 [Audio](../../XCEngine/Audio/Audio.md) +2. 再读 [AudioSystem](../../XCEngine/Audio/AudioSystem/AudioSystem.md) 和 [IAudioBackend](../../XCEngine/Audio/IAudioBackend/IAudioBackend.md) +3. 然后对照 [AudioSourceComponent](../../XCEngine/Components/AudioSourceComponent/AudioSourceComponent.md) 与 [AudioListenerComponent](../../XCEngine/Components/AudioListenerComponent/AudioListenerComponent.md) +4. 最后再看 [AudioMixer](../../XCEngine/Audio/AudioMixer/AudioMixer.md) 和各个效果器页,理解哪些能力已经落地、哪些还在预留阶段 diff --git a/docs/api/_guides/Core/Asset/Resource-Lifecycle-And-Handles.md b/docs/api/_guides/Core/Asset/Resource-Lifecycle-And-Handles.md new file mode 100644 index 00000000..16a8c56a --- /dev/null +++ b/docs/api/_guides/Core/Asset/Resource-Lifecycle-And-Handles.md @@ -0,0 +1,144 @@ +# Resource Lifecycle And Handles + +## 这篇指南解决什么问题 + +单看 `Core/Asset` 里的 API 名称,用户很容易以为这里已经有一套完整成熟的商业级资源系统:有 GUID、有句柄、有缓存、有异步加载、有依赖图。源码层面确实已经把这些角色都摆出来了,但当前实现成熟度并不均衡。 + +这篇指南的目标不是重复 API 参考,而是把下面几件事讲清楚: + +- 当前资源系统真正可靠的主路径是什么 +- 每个类在设计上想承担什么职责 +- 哪些能力已经能用,哪些还只是架子 +- 为什么这种分层在商业引擎里是合理方向 + +## 整体心智模型 + +当前资源系统可以按五层来理解: + +### 1. 身份层 + +- [ResourceTypes](../../../XCEngine/Core/Asset/ResourceTypes/ResourceTypes.md) + +这层定义 `ResourceType` 和 `ResourceGUID`。它回答“这是哪类资源”和“它是谁”。 + +### 2. 对象契约层 + +- [IResource](../../../XCEngine/Core/Asset/IResource/IResource.md) +- [ImportSettings](../../../XCEngine/Core/Asset/ImportSettings/ImportSettings.md) + +这层定义资源对象本身最少要暴露什么,以及加载时可附带什么配置。 + +### 3. 管理入口层 + +- [ResourceManager](../../../XCEngine/Core/Asset/ResourceManager/ResourceManager.md) + +这层是当前最主要的使用入口,负责 loader 注册、同步加载和句柄引用计数记录。 + +### 4. 访问包装层 + +- [ResourceHandle](../../../XCEngine/Core/Asset/ResourceHandle/ResourceHandle.md) + +这层让上层代码拿到的是带类型的资源引用,而不是裸 `IResource*`。 + +### 5. 扩展机制层 + +- [ResourceCache](../../../XCEngine/Core/Asset/ResourceCache/ResourceCache.md) +- [AsyncLoader](../../../XCEngine/Core/Asset/AsyncLoader/AsyncLoader.md) +- [ResourceDependencyGraph](../../../XCEngine/Core/Asset/ResourceDependencyGraph/ResourceDependencyGraph.md) + +这层是为商业引擎常见的高级能力预留的:缓存淘汰、异步流送、依赖卸载。 + +## 为什么这样分层 + +这套分层很像商业引擎的标准做法,因为资源系统最终往往要同时处理几类不同问题: + +- 加载器和文件格式扩展 +- 资源身份和缓存去重 +- 运行时对象访问 +- 后台加载和主线程回调 +- 依赖图分析与卸载决策 + +如果把这些逻辑全部塞进一个 `Load()` 函数,很快就会不可维护。把它们拆开,可以让架构朝更成熟的方向发展。 + +从理念上看,这比 Unity `AssetDatabase` 更底层,也更偏运行时。它不像编辑器资产数据库那样负责导入器生命周期、资产元数据面板和编辑器工作流,而更像运行时资源服务的骨架。 + +## 当前最可靠的使用路径 + +按当前源码,最稳妥的主路径其实很简单: + +1. 初始化 [ResourceManager](../../../XCEngine/Core/Asset/ResourceManager/ResourceManager.md) +2. 确认目标资源类型已经有对应 loader +3. 使用 `Load()` 做同步加载 +4. 用 [ResourceHandle](../../../XCEngine/Core/Asset/ResourceHandle/ResourceHandle.md) 在上层传递引用 +5. 在明确的时机手动做卸载或整体清理 + +这条路径的优点是最贴近当前已经完成的源码行为。它不像异步加载和依赖图那样依赖未来补完部分。 + +## 需要立刻知道的现实限制 + +### 1. 路径就是身份的一部分 + +`ResourceGUID::Generate()` 当前直接对传入路径字符串哈希,不会做规范化。 + +这意味着你需要自己统一路径风格,否则: + +- 同一个文件可能被视为两个不同资源 +- 缓存命中会失败 +- 句柄和查找结果会变得不稳定 + +### 2. 资源根路径不是统一加载入口 + +`SetResourceRoot()` 看起来像统一资源根目录配置,但当前 `ResourceManager::Load()` 并不会自动调用 `ResolvePath()`。 + +也就是说,实际传给 loader 的仍然是你传入的原始路径字符串。 + +### 3. 句柄不是强所有权 + +[ResourceHandle](../../../XCEngine/Core/Asset/ResourceHandle/ResourceHandle.md) 当前不是 `shared_ptr`。它只是: + +- 保存裸指针 +- 通知 `ResourceManager` 增减引用数 + +零引用后并不会可靠自动卸载,缓存层释放也不一定会同步回句柄系统。因此它更像“逻辑引用”,不是“绝对安全的资源所有权”。 + +### 4. 异步加载还不能当成正式能力 + +[AsyncLoader](../../../XCEngine/Core/Asset/AsyncLoader/AsyncLoader.md) 目前没有真正的工作线程消费 pending 队列,成功完成队列也没有闭环。 + +如果你现在需要稳定行为,应优先使用同步加载。 + +### 5. 缓存和管理器没有完全打通 + +[ResourceManager](../../../XCEngine/Core/Asset/ResourceManager/ResourceManager.md) 和 [ResourceCache](../../../XCEngine/Core/Asset/ResourceCache/ResourceCache.md) 当前存在镜像状态不同步的问题。不要把它理解成已经具备严格一致性保证的商业级缓存系统。 + +## 为什么仍然值得保留这套结构 + +尽管当前实现还有空洞,这套结构本身是合理的,因为它已经把未来商业引擎资源系统最关键的边界分出来了: + +- 资源身份是独立的 +- 资源对象契约是独立的 +- 管理入口是独立的 +- 缓存和依赖分析是独立的 +- 异步加载服务也是独立的 + +当后续补全时,通常只需要在既有边界内增强实现,而不必推翻整个 API 形状。这就是“先把接口边界做对”的价值。 + +## 推荐使用建议 + +- 当前优先采用同步加载路径。 +- 对传入路径做统一规范,避免同一文件生成多个 GUID。 +- 不要把 `ResourceHandle` 当成强所有权对象。 +- 在引入异步加载之前,先明确 `ImportSettings` 的所有权和复制策略。 +- 如果你打算扩展缓存或卸载逻辑,先检查 `ResourceManager` 与 `ResourceCache` 的同步问题,再往上叠功能。 + +## 相关 API + +- [Asset](../../../XCEngine/Core/Asset/Asset.md) +- [ResourceTypes](../../../XCEngine/Core/Asset/ResourceTypes/ResourceTypes.md) +- [IResource](../../../XCEngine/Core/Asset/IResource/IResource.md) +- [ImportSettings](../../../XCEngine/Core/Asset/ImportSettings/ImportSettings.md) +- [ResourceManager](../../../XCEngine/Core/Asset/ResourceManager/ResourceManager.md) +- [ResourceHandle](../../../XCEngine/Core/Asset/ResourceHandle/ResourceHandle.md) +- [ResourceCache](../../../XCEngine/Core/Asset/ResourceCache/ResourceCache.md) +- [AsyncLoader](../../../XCEngine/Core/Asset/AsyncLoader/AsyncLoader.md) +- [ResourceDependencyGraph](../../../XCEngine/Core/Asset/ResourceDependencyGraph/ResourceDependencyGraph.md) diff --git a/docs/api/_guides/Core/Containers/Custom-Containers-Value-Semantics-And-Interop.md b/docs/api/_guides/Core/Containers/Custom-Containers-Value-Semantics-And-Interop.md new file mode 100644 index 00000000..1e4a9149 --- /dev/null +++ b/docs/api/_guides/Core/Containers/Custom-Containers-Value-Semantics-And-Interop.md @@ -0,0 +1,101 @@ +# Custom Containers, Value Semantics, And Interop + +## 这篇指南解决什么问题 + +引擎为什么要有自己的 `String`、`Array`、`HashMap`,而不是直接把 `std::string`、`std::vector`、`std::unordered_map` 到处公开出去?以及,当前这套容器做到哪一步了,哪些地方还不能按标准库直觉来用? + +这篇指南就是回答这两个问题。 + +## 为什么商业引擎常有自己的基础容器 + +这不是单纯为了“重写一遍标准库”,而是因为引擎公共 API 通常希望控制几件事: + +- 跨模块接口的类型统一 +- 未来 allocator / profiler / serializer 的接入位置 +- 平台边界和 ABI 风险 +- 调试、日志、反射系统里的统一数据形状 + +所以保留一层自定义容器,在商业引擎里是很常见的做法。 + +## XCEngine 当前这层的定位 + +按当前源码,`Core/Containers` 更准确的定位是: + +- 一组轻量、可用、值语义优先的基础容器 +- 不是完整 STL 等价层 + +这一点很重要,因为如果你用标准库心智模型去要求它们的全部边界行为,很容易踩到当前实现差异。 + +## 三个核心类型怎么理解 + +### `String` + +- 适合做引擎公共接口里的文本参数和返回值 +- 当前是堆分配可变字符串 +- 支持基本拼接、截取、大小写与前后缀判断 + +它最像轻量版 `std::string`,但不提供小字符串优化,也不是完整字符语义库。 + +### `Array` + +- 适合做“由当前对象拥有的一段有序元素” +- 当前最接近 `std::vector` +- 支持顺序存储和基本扩容 + +它适合当默认顺序容器,但当前不要拿它去承载太复杂的 move-only / allocator-heavy 场景。 + +### `HashMap` + +- 适合做键到值的快速查找 +- 和 `String` 配合很好,因为已有 `std::hash` 特化 +- 当前更适合查找用途,不适合依赖成熟迭代器语义 + +## 当前最重要的实践建议 + +### 1. 把它们当值类型,不要脑补共享所有权 + +这套容器默认都是值语义设计。拷贝意味着复制内容,移动意味着转移底层缓冲。 + +### 2. 不要过度相信 allocator 钩子已经接通 + +`Array::SetAllocator()` 和 `HashMap::SetAllocator()` 当前只是保存指针,底层实际分配仍然走默认路径。 + +也就是说,当前不要把这两个接口当成“已经接入商业级内存系统”的保证。 + +### 3. `HashMap` 现在优先拿来查,不要拿来遍历 + +`Find()`、`Contains()`、`Insert()` 是当前相对可靠的主路径。 + +`begin()` / `end()` 还不是完整整表迭代语义,先不要把它当标准关联容器使用。 + +### 4. `Array` 目前更适合 copy-friendly 类型 + +因为扩容时当前实现是拷贝迁移,不是优先移动迁移,所以它对 move-only 类型并不友好。 + +### 5. moved-from `String` 不要继续当空字符串用 + +当前 move 之后源对象的 `m_data` 会变成 `nullptr`。析构和赋值没问题,但不应依赖它继续像普通空字符串那样工作。 + +## 和 Unity / 商业引擎思路的关系 + +如果做类比,这层更像商业引擎里的“Core Foundation Containers”: + +- 它不是玩法层 API +- 也不是高层资源系统 +- 它是所有模块共同依赖的一层基础契约 + +这种层一旦稳定下来,收益非常大,因为更上层的日志、资源、序列化、编辑器和运行时系统都能共享同一套容器约定。 + +## 当前应该怎么用 + +- 公共接口里的字符串优先使用 [String](../../../XCEngine/Core/Containers/String/String.md) +- 顺序容器优先使用 [Array](../../../XCEngine/Core/Containers/Array/Array.md) +- 查找表可用 [HashMap](../../../XCEngine/Core/Containers/HashMap/HashMap.md),但当前避免依赖整表迭代 +- 如果你真的需要成熟 allocator 接入、复杂异常安全或完整 STL 语义,先确认当前实现是否已经覆盖 + +## 相关 API + +- [Containers](../../../XCEngine/Core/Containers/Containers.md) +- [String](../../../XCEngine/Core/Containers/String/String.md) +- [Array](../../../XCEngine/Core/Containers/Array/Array.md) +- [HashMap](../../../XCEngine/Core/Containers/HashMap/HashMap.md) diff --git a/docs/api/_guides/Core/Core-Foundations-Ownership-Events-And-Layers.md b/docs/api/_guides/Core/Core-Foundations-Ownership-Events-And-Layers.md new file mode 100644 index 00000000..f86efda4 --- /dev/null +++ b/docs/api/_guides/Core/Core-Foundations-Ownership-Events-And-Layers.md @@ -0,0 +1,152 @@ +# Core Foundations: Ownership, Events, And Layers + +## 这篇指南解决什么问题 + +`Core` 模块的单个 API 页都很小,但真正难的是理解它们之间的关系: + +- 为什么同时有 `RefCounted` 和 `SmartPtr` +- 事件系统到底是立即移除还是延迟取消订阅 +- `Layer` 和 `GameObject` / `Component` 是什么关系 +- `Core.h` 为什么不该被当成一个“类型页” + +这篇指南专门把这些基础约定讲清楚。 + +## 先建立正确心智模型 + +`XCEngine::Core` 不是一个业务模块,而是底层支撑层。它更像商业引擎里最底层那层基础框架: + +- 统一基础类型 +- 统一所有权约定 +- 提供事件广播器 +- 提供应用壳层风格的 layer 组织方式 +- 给其他模块打基础 + +所以读 `Core` 时,重点不是“它能直接做什么功能”,而是“它给整个代码库定义了什么规则”。 + +## `Core.h` 为什么只是 umbrella header + +`Core.h` 当前只 `#include` 了: + +- `Types.h` +- `RefCounted.h` +- `SmartPtr.h` +- `Event.h` + +而且它自己的命名空间体是空的。 + +这意味着它的价值只是“方便包含一组常用基础头文件”,而不是引入一个新的运行时概念。把这种 header 单独写成一页“类型文档”,信息量其实是重复的,所以更合理的做法是把它并进模块页说明。 + +## 两套 ownership 模型为什么要分开理解 + +这是当前 `Core` 模块最值得强调的一点。 + +### 1. `SmartPtr` + +- `Ref` 是 `std::shared_ptr` +- `UniqueRef` 是 `std::unique_ptr` +- `MakeRef()` / `MakeUnique()` 只是标准库工厂函数薄封装 + +这是现代 C++ 常规 RAII ownership 路线。 + +### 2. `RefCounted` + +- 计数在对象内部 +- 手工 `AddRef()` / `Release()` +- 归零时 `delete this` + +这是 intrusive reference counting 路线。 + +### 3. 当前它们没有自动打通 + +这不是推测,而是当前代码事实: + +- `Ref` 没有自定义 deleter 去调用 `Release()` +- `RefCounted` 也没有返回 `shared_ptr` 的适配器 + +因此最安全的工程规则是: + +- 选一套 ownership 模型就按那套用到底 +- 不要把 `RefCounted` 当成 `Ref` 的基座想当然混用 + +## `Event` 的关键语义是什么 + +`Event` 当前提供的是“轻量广播器”,不是“复杂消息总线”。 + +最关键的三个现实语义: + +### 1. 取消订阅是延迟的 + +`Unsubscribe(id)` 只是把 ID 放进待删除列表,不立即移除。 + +### 2. `Invoke()` 会在真正回调前先清理待删除监听器 + +这让“取消订阅后,下一次触发不再收到回调”成立。 + +### 3. 回调执行在锁外进行 + +这样做更容易避免回调重入把锁长期持有,但代价是会拷贝一份监听器数组。 + +如果你期待的是高频零分配事件总线,这个实现并不是那个方向。它追求的是简洁和足够安全的基础广播语义。 + +## `Layer` / `LayerStack` 和组件系统是什么关系 + +最容易混淆的是: + +- `Layer` +- `GameObject` +- `Component` + +它们不是同一层抽象。 + +### `GameObject` / `Component` + +这是场景对象模型,适合描述世界里的对象、相机、灯光、音频源等。 + +### `Layer` / `LayerStack` + +这是应用壳层 / 编辑器壳层模型,适合描述: + +- 主逻辑层 +- UI overlay +- 调试 overlay +- 编辑器子系统层 + +这种设计在很多引擎里都存在,因为“应用外壳组织”和“场景对象组织”本来就是两套不同问题。 + +## 当前 `LayerStack` 真正的行为 + +如果你带着 Hazel、Cocos 或别的框架经验来看,很容易以为 push 时就会自动 attach。但当前实现不是: + +- `pushLayer()` / `pushOverlay()` 只插入容器 +- 不会自动调用 `onAttach()` +- `popLayer()` / `popOverlay()` 倒是会调用 `onDetach()` + +另外,事件分发也是正向遍历,不是常见的“从最顶层 overlay 逆向往下传”。 + +这类差异如果不写清楚,用户很容易按既有经验误用。 + +## 推荐工程实践 + +### ownership + +1. 常规新代码优先用 `Ref` / `UniqueRef` 这套标准 RAII 语义。 +2. 只有在明确需要 intrusive RC 的旧接口或特定对象模型里,才使用 `RefCounted`。 + +### event + +1. 把 `Event` 当成轻量广播器,而不是高阶消息系统。 +2. 不要跨线程长期持有 `begin()` / `end()` 返回的迭代器。 + +### layer + +1. 把 `Layer` 用于应用壳层和工具层组织,不要拿它替代场景组件系统。 +2. 如果你需要 push 后立刻 attach,当前需要显式调用对应生命周期逻辑。 + +## 从这里继续读什么 + +- [Core](../../XCEngine/Core/Core.md) +- [SmartPtr](../../XCEngine/Core/SmartPtr/SmartPtr.md) +- [RefCounted](../../XCEngine/Core/RefCounted/RefCounted.md) +- [Event](../../XCEngine/Core/Event/Event.md) +- [Layer](../../XCEngine/Core/Layer/Layer.md) +- [LayerStack](../../XCEngine/Core/LayerStack/LayerStack.md) diff --git a/docs/api/_guides/Core/IO/Resource-Paths-Packages-And-Virtual-FileSystem.md b/docs/api/_guides/Core/IO/Resource-Paths-Packages-And-Virtual-FileSystem.md new file mode 100644 index 00000000..ef6631dc --- /dev/null +++ b/docs/api/_guides/Core/IO/Resource-Paths-Packages-And-Virtual-FileSystem.md @@ -0,0 +1,127 @@ +# Resource Paths, Packages, And Virtual File System + +## 这篇指南解决什么问题 + +`Core/IO` 这一层看起来像是在做很多事:路径拆分、loader、归档、包文件、虚拟文件系统。单看 API 名称,很容易把它想象成一套已经成熟的商业引擎内容访问系统。 + +当前源码的真实状态更准确的理解应该是: + +- 分层方向已经对了 +- 但只有部分能力进入了可依赖状态 + +这篇指南的目的,是把这套分层的角色关系和当前可用边界讲清楚。 + +## 一条更清晰的心智模型 + +可以把 `Core/IO` 理解成四层: + +### 1. 路径字符串层 + +- [ResourcePath](../../../XCEngine/Core/IO/ResourcePath/ResourcePath.md) + +这层只负责路径拆分和 GUID 派生,不负责真实文件系统。 + +### 2. 格式装载层 + +- [IResourceLoader](../../../XCEngine/Core/IO/IResourceLoader/IResourceLoader.md) + +这层负责把字节数据解释成运行时资源对象,是当前 IO 层最关键、也最实用的一层。 + +### 3. 容器格式层 + +- [FileArchive](../../../XCEngine/Core/IO/FileArchive/FileArchive.md) +- [ResourcePackage](../../../XCEngine/Core/IO/ResourcePackage/ResourcePackage.md) + +这层想解决“资源是否来自目录、归档或打包文件”的问题。 + +### 4. 虚拟文件系统层 + +- [ResourceFileSystem](../../../XCEngine/Core/IO/ResourceFileSystem/ResourceFileSystem.md) + +这层试图把前面几种来源统一起来,让上层按相对资源路径访问内容。 + +## 为什么商业引擎都喜欢这么分 + +因为资源系统最终总要面对这些现实问题: + +- 开发阶段想直接读散文件 +- 发行阶段想改成打包格式 +- patch 阶段想叠加新的挂载点 +- loader 不想关心内容来自真实目录还是包文件 + +把路径、容器、加载器和虚拟文件系统拆开,后续替换任何一层都更容易。这也是这套结构本身的价值。 + +## 当前最可靠的使用路径 + +按当前实现,最可靠的路径其实不是包文件或虚拟文件系统,而是: + +1. 用 [ResourcePath](../../../XCEngine/Core/IO/ResourcePath/ResourcePath.md) 做轻量路径拆分 +2. 用具体 loader 实现 [IResourceLoader](../../../XCEngine/Core/IO/IResourceLoader/IResourceLoader.md) +3. 通过 [ResourceManager](../../../XCEngine/Core/Asset/ResourceManager/ResourceManager.md) 做同步 `Load()` + +这条路径已经是当前资源系统最接近可依赖运行时通路的部分。 + +## 当前必须知道的几个差异点 + +### 1. 路径扩展名约定不统一 + +- `ResourcePath::GetExtension()` 返回 `.png` +- `IResourceLoader::GetExtension()` 返回 `png` + +这不是文档风格问题,而是源码里的真实差异。写 loader 或做路径判断时,必须先确认自己用的是哪一套约定。 + +### 2. `ResourcePath` 不是标准化路径工具 + +它不会: + +- 折叠 `.` / `..` +- 统一大小写 +- 做绝对路径解析 +- 检查目标是否存在 + +所以它更像字符串助手,不像 `std::filesystem::path` 的资源版替代。 + +### 3. `FileArchive` 现在更像目录适配器 + +它并不会真的解析归档格式,而是把“archive path + relative path”拼起来直接读普通文件。 + +### 4. `ResourcePackage` 还不能当发布格式闭环 + +builder 和 reader 当前没有形成可靠闭环。现在去依赖它做正式资源打包,会碰到 manifest 缺失、条目为空、读回失败这类问题。 + +### 5. `ResourceFileSystem` 还不是成熟 VFS + +按当前实现: + +- `AddArchive()` 没有真正挂载 archive 对象 +- `Exists()` 可能对不存在文件返回 true +- `ReadResource()` 和 `GetResourceInfo()` 还有自死锁风险 + +所以它目前更像 VFS 架构草图,而不是已完成的运行时文件系统。 + +## 和 Unity 风格概念的关系 + +如果一定要做类比,这一层更接近: + +- Unity 运行时内容读取层 +- `StreamingAssets`、资源包和内部 loader 之间的底层桥接 + +它并不等价于 Unity `AssetDatabase`。`AssetDatabase` 是编辑器资产数据库和导入管线入口,而这里更多是运行时访问层的骨架。 + +## 推荐使用建议 + +- 目前优先把 `Core/IO` 当成“loader 支撑层”,而不是“成熟打包与 VFS 层”。 +- 写 loader 时先统一扩展名格式约定,避免一边带点一边不带点。 +- 用 `ResourcePath` 做拆分,不要把它当成路径规范化器。 +- 在正式依赖 `ResourceFileSystem` 之前,先补上真实存在性检查和重复加锁问题。 +- 在正式依赖 `ResourcePackage` 之前,先补完 manifest 写入和读取闭环。 + +## 相关 API + +- [IO](../../../XCEngine/Core/IO/IO.md) +- [ResourcePath](../../../XCEngine/Core/IO/ResourcePath/ResourcePath.md) +- [IResourceLoader](../../../XCEngine/Core/IO/IResourceLoader/IResourceLoader.md) +- [FileArchive](../../../XCEngine/Core/IO/FileArchive/FileArchive.md) +- [ResourcePackage](../../../XCEngine/Core/IO/ResourcePackage/ResourcePackage.md) +- [ResourceFileSystem](../../../XCEngine/Core/IO/ResourceFileSystem/ResourceFileSystem.md) +- [Asset](../../../XCEngine/Core/Asset/Asset.md) diff --git a/docs/api/_guides/Core/Math/Vectors-Quaternions-And-Matrices-In-Runtime-Transforms.md b/docs/api/_guides/Core/Math/Vectors-Quaternions-And-Matrices-In-Runtime-Transforms.md new file mode 100644 index 00000000..a93c22fd --- /dev/null +++ b/docs/api/_guides/Core/Math/Vectors-Quaternions-And-Matrices-In-Runtime-Transforms.md @@ -0,0 +1,188 @@ +# Vectors, Quaternions, And Matrices In Runtime Transforms + +## 这篇指南解决什么问题 + +很多引擎都会同时提供 `Vector3`、`Quaternion`、`Matrix4` 和 `Transform`。第一次接触时,最常见的疑问不是“这些类型叫什么”,而是: + +- 到底应该把哪一个当成旋转的主表示 +- 什么时候该用矩阵,什么时候只该用向量和四元数 +- 这套 API 和 Unity 一类商业引擎的心智模型有哪些相似和不同 +- 当前 XCEngine 的源码里,哪些行为已经可靠,哪些地方还需要避坑 + +这篇指南就是把这条主线讲清楚。 + +## 一条更接近商业引擎的心智模型 + +可以把 `Core/Math` 当前的运行时变换路径理解成四层。 + +### 1. 向量层 + +- [Vector3](../../../XCEngine/Core/Math/Vector3/Vector3.md) +- [Vector4](../../../XCEngine/Core/Math/Vector4/Vector4.md) + +这层负责位置、方向、法线、速度、齐次坐标等最基础的数据承载。 + +### 2. 旋转层 + +- [Quaternion](../../../XCEngine/Core/Math/Quaternion/Quaternion.md) + +这层负责“姿态”本身。商业引擎通常不会长期用 Euler 角做内部旋转状态,因为旋转组合、插值和方向推导都更适合四元数。 + +### 3. TRS 值对象层 + +- [Transform](../../../XCEngine/Core/Math/Transform/Transform.md) + +这层把位置、旋转、缩放打包成一个轻量运行时对象,适合做临时组合、数学计算和序列化边界。 + +### 4. 矩阵边界层 + +- [Matrix4](../../../XCEngine/Core/Math/Matrix4/Matrix4.md) +- [Matrix3](../../../XCEngine/Core/Math/Matrix3/Matrix3.md) + +这层负责把变换打包成适合层级合成、相机、渲染和批量空间变换的格式。 + +## 为什么商业引擎都喜欢这样分 + +因为这四类问题本质上不是一回事: + +- 向量表达的是数据 +- 四元数表达的是旋转状态 +- TRS 表达的是结构化变换 +- 矩阵表达的是最终可组合、可批量乘法的边界格式 + +如果把这些职责都塞进同一个类型,接口会很快变得混乱。拆开以后,上层系统就能按最合适的表示工作: + +- gameplay 逻辑更容易围绕 `Vector3 + Quaternion` 写 +- hierarchy 和 camera 更容易围绕 `Matrix4` 写 +- 序列化和临时运算更容易围绕 `Transform` 写 + +## 推荐使用顺序 + +按当前 XCEngine 源码,最稳妥的使用方式是: + +1. 位置和方向先用 [Vector3](../../../XCEngine/Core/Math/Vector3/Vector3.md) +2. 旋转状态优先用 [Quaternion](../../../XCEngine/Core/Math/Quaternion/Quaternion.md) +3. 需要一次性携带平移、旋转、缩放时用 [Transform](../../../XCEngine/Core/Math/Transform/Transform.md) +4. 只有到了层级合成、视图矩阵、投影矩阵或渲染边界时,再落到 [Matrix4](../../../XCEngine/Core/Math/Matrix4/Matrix4.md) + +这和 Unity / 商业引擎里的最佳实践非常接近:编辑器界面可以展示 Euler 角,但运行时主状态最好还是四元数和矩阵。 + +## 当前必须知道的几个真实差异 + +### 1. 数学层基本使用弧度 + +[Quaternion](../../../XCEngine/Core/Math/Quaternion/Quaternion.md) 的 `FromAxisAngle()`、`FromEulerAngles()` 和 `ToEulerAngles()` 都是弧度语义。 + +但组件层 [TransformComponent](../../../XCEngine/Components/TransformComponent/TransformComponent.md) 的 `Rotate()`、`SetLocalEulerAngles()` 公开接口使用的是角度制,再在内部乘 `DEG_TO_RAD` 转给数学层。 + +这意味着: + +- 原始数学 API 要按弧度理解 +- 组件层编辑式 API 要按角度理解 + +不要混用这两套心智模型。 + +### 2. 旋转状态优先用四元数,不要长期停留在 Euler 角 + +[Quaternion](../../../XCEngine/Core/Math/Quaternion/Quaternion.md) 当前已经覆盖了: + +- 轴角构造 +- Euler 构造 +- 旋转矩阵提取 +- 球面插值 +- 方向向量旋转 + +这是当前最接近“商业引擎可依赖旋转主路径”的一层。 + +### 3. `Matrix4::MultiplyPoint()` 不做透视除法 + +[Matrix4](../../../XCEngine/Core/Math/Matrix4/Matrix4.md) 的 `MultiplyPoint()` 当前只是把点扩成 `w = 1` 做矩阵乘法,然后直接返回 `x/y/z`。 + +这和很多人对 Unity `Matrix4x4.MultiplyPoint()` 的直觉不一样。当前 XCEngine 里: + +- 它适合仿射变换 +- 不应把它当成“投影后自动做齐次除法”的通用入口 + +### 4. `Matrix4::Decompose()` 是轻量分解,不是鲁棒分解器 + +当前分解流程只是: + +- `translation = GetTranslation()` +- `scale = GetScale()` +- `rotation = GetRotation()` + +它没有先显式去掉缩放或剪切。因此: + +- 纯 TRS 路径下通常够用 +- 非均匀缩放、剪切或退化矩阵下不应脑补成商业级稳健分解 + +### 5. `Matrix3` 当前不是可靠的旋转构造入口 + +[Matrix3](../../../XCEngine/Core/Math/Matrix3/Matrix3.md) 的 `RotationX/Y/Z()` 当前从零矩阵开始填值,没有补齐单位对角项。 + +这不是文档问题,而是源码现状。所以: + +- 当前不要把 `Matrix3::RotationX/Y/Z()` 当成标准旋转矩阵工厂 +- 需要可靠旋转时优先走 `Quaternion` 或 [Matrix4](../../../XCEngine/Core/Math/Matrix4/Matrix4.md) + +### 6. `Math::Transform` 和 `TransformComponent` 的同名方向变换并不完全等价 + +- `Math::Transform::TransformDirection()` 当前会应用缩放和旋转 +- `TransformComponent::TransformDirection()` 当前只应用旋转 + +这说明数学层值对象和场景组件层 API 虽然名字相近,但语义边界并不完全相同。 + +## 一条推荐的运行时工作流 + +### 物体姿态 + +- 存储位置: `Vector3` +- 存储旋转: `Quaternion` +- 存储缩放: `Vector3` + +### 本地变换到矩阵 + +- 需要矩阵时,再通过 [Transform](../../../XCEngine/Core/Math/Transform/Transform.md) 或 [Matrix4::TRS](../../../XCEngine/Core/Math/Matrix4/TRS.md) 生成 + +### 方向旋转 + +- 优先使用 `Quaternion * Vector3` +- 不要自己手搓 Euler 角旋转顺序,除非你非常清楚当前轴顺序和单位 + +### 相机与投影 + +- 视图矩阵走 [LookAt](../../../XCEngine/Core/Math/Matrix4/LookAt.md) +- 投影矩阵走 [Perspective](../../../XCEngine/Core/Math/Matrix4/Perspective.md) 或 [Orthographic](../../../XCEngine/Core/Math/Matrix4/Orthographic.md) +- 需要屏幕空间或裁剪空间解释时,记住当前 `MultiplyPoint()` 不负责齐次除法 + +## 为什么这种设计是合理的 + +从商业引擎架构角度看,这套分层的收益很明确: + +- 旋转逻辑不会被 Euler 角长期污染 +- 矩阵只在真正需要时生成 +- 数学值对象和场景层状态可以解耦 +- 渲染、层级、脚本桥接都能共享同一套基础类型 + +这类边界一旦建立正确,后续补 editor、animation、physics 或 scripting 时,基础数学层不会轻易被拖乱。 + +## 当前版本的务实建议 + +- 旋转状态优先用四元数保存。 +- Euler 角更适合作为 UI 或脚本侧输入输出桥接。 +- `Matrix4` 主要当作边界格式使用。 +- `MultiplyPoint()` 只在仿射场景下直接使用。 +- 对 `LookRotation()` 传入非零 `forward`,并尽量避免退化 `up`。 +- 对 `FromAxisAngle()` 先自己规范化轴向量。 +- 当前避免把 `Matrix3::RotationX/Y/Z()` 当成可靠旋转工厂。 + +## 相关 API + +- [Math](../../../XCEngine/Core/Math/Math.md) +- [Vector3](../../../XCEngine/Core/Math/Vector3/Vector3.md) +- [Vector4](../../../XCEngine/Core/Math/Vector4/Vector4.md) +- [Quaternion](../../../XCEngine/Core/Math/Quaternion/Quaternion.md) +- [Transform](../../../XCEngine/Core/Math/Transform/Transform.md) +- [Matrix3](../../../XCEngine/Core/Math/Matrix3/Matrix3.md) +- [Matrix4](../../../XCEngine/Core/Math/Matrix4/Matrix4.md) +- [TransformComponent](../../../XCEngine/Components/TransformComponent/TransformComponent.md) diff --git a/docs/api/_meta/rebuild-status.md b/docs/api/_meta/rebuild-status.md index c6116d41..a0b8ad94 100644 --- a/docs/api/_meta/rebuild-status.md +++ b/docs/api/_meta/rebuild-status.md @@ -1,14 +1,14 @@ # API 文档重构状态 -**生成时间**: `2026-03-27 16:00:15` +**生成时间**: `2026-03-27 19:11:24` **来源**: `docs/api/_tools/audit_api_docs.py` ## 摘要 -- Markdown 页面数(全部): `2625` -- Markdown 页面数(canonical): `2612` -- Public headers 数: `221` +- Markdown 页面数(全部): `2629` +- Markdown 页面数(canonical): `2610` +- Public headers 数: `227` - 有效头文件引用数(全部): `197` - 有效头文件引用数(canonical): `197` - 无效头文件引用数: `0` @@ -36,8 +36,8 @@ | `Input` | `5` | `5` | `0` | | `Memory` | `5` | `5` | `0` | | `Platform` | `11` | `11` | `0` | -| `RHI` | `83` | `68` | `15` | -| `Rendering` | `11` | `10` | `1` | +| `RHI` | `87` | `68` | `19` | +| `Rendering` | `13` | `10` | `3` | | `Resources` | `13` | `13` | `0` | | `Scene` | `3` | `2` | `1` | | `Scripting` | `7` | `0` | `7` | @@ -47,9 +47,9 @@ | 字段 | 页面数 | |------|--------| -| `命名空间` | `307` | -| `类型` | `307` | -| `描述` | `307` | +| `命名空间` | `305` | +| `类型` | `305` | +| `描述` | `305` | | `头文件` | `197` | ## 缺失的平行目录总览页 @@ -68,13 +68,19 @@ - `XCEngine/RHI/Vulkan/VulkanDescriptorSet.h` - `XCEngine/RHI/Vulkan/VulkanDevice.h` - `XCEngine/RHI/Vulkan/VulkanFence.h` +- `XCEngine/RHI/Vulkan/VulkanFramebuffer.h` - `XCEngine/RHI/Vulkan/VulkanPipelineLayout.h` - `XCEngine/RHI/Vulkan/VulkanPipelineState.h` +- `XCEngine/RHI/Vulkan/VulkanRenderPass.h` - `XCEngine/RHI/Vulkan/VulkanResourceView.h` - `XCEngine/RHI/Vulkan/VulkanSampler.h` - `XCEngine/RHI/Vulkan/VulkanScreenshot.h` +- `XCEngine/RHI/Vulkan/VulkanShader.h` +- `XCEngine/RHI/Vulkan/VulkanShaderCompiler.h` - `XCEngine/RHI/Vulkan/VulkanSwapChain.h` - `XCEngine/RHI/Vulkan/VulkanTexture.h` +- `XCEngine/Rendering/CameraRenderRequest.h` +- `XCEngine/Rendering/CameraRenderer.h` - `XCEngine/Rendering/RenderMaterialUtility.h` - `XCEngine/Scene/SceneRuntime.h` - `XCEngine/Scripting/IScriptRuntime.h`