Skip to content

Latest commit

 

History

History
105 lines (84 loc) · 7.97 KB

ojm-resource-limits.md

File metadata and controls

105 lines (84 loc) · 7.97 KB

OJM 资源读取边界

更新:2026-10-13。本页是 R03 已实施的格式字节预算契约,包含 Lazy 累计与并发预留。 源身份与恢复另见源一致性方案,音频准备和 原生解码另见音频准备边界。三者分别验收。

支持范围

边界 默认范围 检查位置
不可跳转流的完整缓冲 256 MiB Read、ReadIndex 的共同缓冲入口,写入 MemoryStream 前检查,最多多读一个探测字节识别确切上限的 EOF。
单个实际采样输出 128 MiB eager 在载荷读取前,lazy 首次物化在打开载荷文件前;WAV 包含 44 字节封装,OGG 按实际载荷长度。
一次 eager 读取累计输出 512 MiB 包含 WAV 封装,完整/所选读取均在载荷读取、解密、封装及拥有副本前计入;每次调用独立。
每份 Lazy 归档的物化输出与在途预留合计 512 MiB 所有格式和采样共享同一预算,首次物化前预留;成功保留、失败回收,不依据整份索引总量提前拒绝。

这里的 MiB 为 1,048,576 字节。阈值是当前支持政策,不是 OJM 格式规定,也不是 进程峰值内存保证。源缓冲、OMC 解密数组、WAV 构建、副本、消费者的 Data 克隆及原生 PCM 仍可能同时存在。累计预算计数创建的实际输出,既不是头部 payloadSize,也不是含未加载数据的 DecodedByteLength。

seekable Read、ReadIndex 和路径 ReadLazy 不套用整包 256 MiB 上限;可以 跳过不需要的载荷。单采样保护只在实际物化时执行,因此 lazy 索引仍可登记 超限采样。不会为了判断预算而读取全部音频、Hash 全包、扫描曲库或新增 codec。

先核对五份既有文件的头部:DSong/1164(OMC)、ESong/102(M30)、1079(OJM)、 SongB/3952(M30)和 SongB/169(M30)。最大单载荷 3,756,653 字节,最大累计 输出 15,906,527 字节;只读头部共 46,840 字节,没有物化音频或计时。该小范围 不能代表全曲库,且这些文件没有非空 WAV,不能据此证明所有长 PCM 都已兼容。 128 MiB 输出约容纳 12.7 分钟的 44.1 kHz/16-bit 双声道 PCM;这是容量换算, 不是解码时长检查。以后若有有效超限实例,重新评估阈值和读取策略。

失败与兼容行为

超限以 InvalidDataException 明确拒绝,原因区分 non-seek source、single sample output 和 eager output;不截断音频冒充成功,也不更改源文件。

  • Read 超限不返回部分 archive;caller 保有输入流,缓冲由 reader 释放。
  • ReadIndex 的 seek 路径只登记 ID;不分配载荷,也不消耗输出预算。
  • ReadLazy 的超限只影响首次请求的采样;失败由 Lazy 缓存,不循环重试。 检查在源 IO 的异常捕获区外,不设置 IsSourceInvalidated,其他合规采样仍 可读。之后真实源变化/IO失败仍沿用 R02 的整个源单向失效契约。
  • Host 保留既有准备失败日志与不可用声音的处理;没有新 UI、设置开关、 sample ID 回退或将超限冒充合法 ID 0 的路径。

零长度槽、合法 ID 0、未选择的采样和不支持的 M30 codec 不消耗输出预算。 OMC 跳过的效果仍推进加密状态。M30 同 ID 继续保留首次合法条目;重复条目 直接跳过,无需为最终不会拥有的数据解密或分配。头部低报 payloadSize、高报 sampleCount 及最后一个不完整载荷的容忍继续保留,不因预算增加而改成整包拒绝。

OjmReadLimits/OjmReadBudget/OjmLazyReadBudget 在纯 O2Jam.Formats.Ojm 内部,无 osu、Core、 缓存或存储依赖。公共 reader API 保持原样,内部预算注入用于小规模边界测试。 没有增加全局资源管理器或为这次修改重写 OJN 已有的预算契约。

Lazy 的所有权与并发政策

一份 ReadLazy 结果的所有采样共用预算,包括波形、背景 OGG 与 M30。 只有第一次物化申请配额:单采样检查和累计预留都发生在载荷 IO/解密/WAV 封装前,预留后释放计数锁;不会持锁读取文件。最终输出长度必须与登记值一致。 同一个采样的并发访问仍由原生 .NET Lazy 保证只物化一次,不新增缓存机制。

成功数组留在 OjmSample 中,关闭 OpenRead 的流、停止播放、准备任务完成、 取消后续消费者或缓存逐出都不能归还它的配额。失败的读取/解密/输出校验或 取消只退回本次预留;被 Lazy 缓存的异常不会重复退回其他采样的占用。 预算随该组采样的物化闭包/在途请求存活,不是跨归档共享的全局账户,无需依靠 finalizer、强制 GC 或把 archive 变成可提前销毁的对象来回收。

未加载的索引、未选择/零长度/unsupported 采样和重复 M30 ID 不预留。 不同 ReadLazy 结果预算独立;缓存复用同一 archive 时也复用其已物化值。 OjmLazySource 只负责源身份和范围读取;预算包在 OjmSample 的整个 loader 外侧,以便覆盖解密与 WAV 封装的失败回收,并避免资源拒绝误标源失效。

超限采样立即失败而不等待其他采样释放。允许总索引大于预算,只加载需要的 部分;在实际超限的归档中,先获准物化的声音保留,后续不足配额的请求失败。 若同时发生在途失败,其他请求此前遭拒的 Lazy 异常也不会自动重试。正常总输出 不超过预算的归档不依赖先后顺序;对超大归档不保证优先保留某类 KS/BGM。 这是明确的支持范围,不靠暂停预览或等待配额营造可播放的假象。

公共 Data 仍返回独立副本,OpenRead 沿用 readonly MemoryStream,不破坏 现有所有权契约。本预算不限制外部调用者持有的克隆数量,也不约束调用者通过 公开 OjmSample(byte[]) 手工创建的资源;原生 SampleStore 还可能持有自己的 编码数据副本。原生 PCM 所有权评估见音频准备边界。

当前验证与未完成项

独立格式 212 项与宿主音频/缓存/回放键音/资源 153 项过滤测试全部通过; 本轮新增 24 项 Lazy 契约检查,并纳入既有两项格式所有权检查,不按重复执行或 旧轮次数累加。原 59 项字节预算、源变化/恢复、OMC 与 M30 兼容回归继续通过。 新检查覆盖只计一次、反向加载顺序、不同归档独立、跨 WAV/OGG 配额、在途预留、 锁外 IO、失败/取消/长度不符回收、缓存失败不重复退款以及保留 clone/stream 契约。 200 份实际正式源码分层、模块、Realm 与计分检查通过,保留原有两个 partial 例外;347 个其他源码/项目/资源输入核对不变。正式包无测试桥、计时探针或 NUnit,十份资源与当前听感 DLL 相同。

第一阶段证据保留于 .artifacts/ojm-resource-limits-20261013/,本轮证据与修改前 文件位于 .artifacts/ojm-lazy-budget-20261013/,构建独立输出。当前听感 DLL、 默认正式输出和历史候选未覆盖;没有真实曲库载荷读取、客户端操作、安装、 benchmark、提交或推送。NuGetAudit=false 是本机离线准备,不是依赖审计通过。

R03 的 OJM 格式输入/物化字节保护现已实施;消费者副本和原生 PCM 峰值仍未 受此预算约束。当前原生 store 接口未提供解码前 PCM 配额,在没有可正确预估/ 归还的接口前,不新增私有字段补丁、自写解码或一次额外的整曲预解码。 保留既有 native Track 流式策略、原生释放和准备四槽;它们也不是全局上限。 极端压缩比、多个 Skin/store/活跃归档和外部规则集仍可能占用较大内存,属于 独立剩余风险。未来出现有效实例或宿主提供合适 API 时再设计相应防护;完整 设备、超大合法曲目与性能仍独立验收,不据此宣布整个 R03 或全项目完成。