Vyntek
规格机器视觉规格

图像存储、访问与归还生命周期

www 工作副本 · M2(Arden · 2026-10-21 Asia/Shanghai)
双轨:本页已迁入 Vyntek www;mvdocs memory-management 仍在线对照。
实现边界:对拍 tip e46c042。MemoryPool 为 imaging/memory_pool.hpp 虚基类(Allocate / Deallocate / CanRent);ImageBuffer::Rent / TryRent / CanRent 接受 MemoryPool*。ReturnCompleted 实现在 src/imaging(不再是头内空体)。TakeOwnership 吃 MemoryOwner。仍 NON-STABLE / STUB;TC-MEM 未验收。可勾选见 test-contracts-tc-mem.md。Arden 不改实现代码。
关联:image-model · session-lifecycle · TC-MEM 摘录 · cpp-headers · overview · 预览 http://127.0.0.1:8792

成像权威框(Alive):存储/访问/pin/归还等内存寿命权威在 C++ vyntek::imaging(公开头 <vyntek/imaging/...>,见 cpp-headers.md)。.NET/C# 仅为绑定/显示投影;禁止「Imaging 仅依赖 .NET」或把托管引用计数写成像素真相。ImageDisplay 仅 C#,经绑定消费句柄。

编排权威见 toolflow.md(Tool / ToolFlow)。本文只负责存储、访问授予、pin 与真实归还等内存寿命;不定义工具流执行模型。句柄 ReadOnly 发布后内容稳定,已交下游输入不得改写;链接赋值不是 DAG 调度。工具流内交接可测对照见 toolflow §3.4。

语言:下文 API 示意为 C++(vyntek::imaging)。示意、非已发布 ABI。

本文负责存储、所有权、授予、状态、初始化、native pin 与真实归还。类型化临时图像的声明见图像模型,访问语义以本文为准。

关联:图像模型 · ToolFlow(编排) · 头文件 · 采集 · 停止 · 测试契约 TC-MEM-*(Arden · R11) · 场景 S12

存储工厂、生命周期与真实归还

// 示意、非已发布 ABI。<vyntek/imaging/buffer.hpp>
namespace vyntek::imaging {

enum class BufferAccess : std::uint8_t { ReadOnly = 0, ReadWrite = 1 };
enum class BufferInitialization : std::uint8_t { Uninitialized = 0, Initialized = 1 };
enum class BufferReturnState : std::uint8_t {
  Active = 0, Returning = 1, Returned = 2, ReturnFailed = 3
};

struct VersionMismatchError {
  ImageContentVersion expected;
  ImageContentVersion actual;
};

class ImageBuffer {
 public:
  static ImageBuffer Rent(int minimum_capacity, MemoryPool* pool = nullptr);

  static ImageBuffer TakeOwnership(
      std::unique_ptr<MemoryOwner> owner,
      BufferAccess access = BufferAccess::ReadWrite,
      BufferInitialization initialization = BufferInitialization::Initialized,
      std::optional<std::int64_t> retained_byte_length = std::nullopt);

  static ImageBuffer ImportNative(
      std::uintptr_t address,
      std::int64_t capacity,
      BufferAccess access,
      std::function<Status()> release,  // 恰好调用一次;完成=真实归还
      BufferInitialization initialization = BufferInitialization::Initialized,
      std::optional<std::int64_t> retained_byte_length = std::nullopt);

  BufferId buffer_id() const;
  std::int64_t capacity() const;
  std::int64_t retained_byte_length() const;
  BufferAccess access() const;
  BufferReturnState return_state() const;
  // 可重复等待的归还完成信号(实现可为 future / 回调)
  Status AwaitReturnCompleted();

  ImageBuffer Share() const;
  std::expected<ImagePixelReader, Status> OpenRead(CancelToken cancel = {}) const;
  std::expected<ImagePixelReader, Status> OpenRead(
      ImageContentVersion expected, CancelToken cancel = {}) const;
  std::expected<ImagePixelWriter, Status> OpenWrite(CancelToken cancel = {});
  std::expected<ImagePixelWriter, Status> OpenWrite(
      ImageContentVersion expected, CancelToken cancel = {});
  // 析构 / Close 只关闭当前 handle
};

}  // namespace vyntek::imaging

工厂约束

工厂初始内容接管点与失败处理
Rent未初始化、可写;首个读取对象必须在成功提交后取得成功返回后框架管理租出的 owner;失败归还内部已租出的存储
TakeOwnership默认认为调用方已完成写入,初始版本为 1成功返回才接管 owner;抛错时不释放调用方 owner
ImportNative默认认为 SDK 写入已结束,初始版本为 1成功返回才接管一份已经由调用方取得的有效 SDK/原生资源引用;抛错时不调用 releaseAsync

minimumCapacity、capacity 必须大于零,native 地址必须非零,地址与容量组合不能超出进程地址空间。是否真正可访问由导入方保证。ReadOnly + Uninitialized 无法恢复为有效内容,创建时拒绝。

Capacity 是可寻址字节范围。RetainedByteLength 是持有该输入所牵连的实际分配成本,默认取 Capacity,显式值不得小于 Capacity。SDK 给出的视图只是较大分配的一部分时,由适配器报告完整保留成本,不能仅用 ROI 长度计费。框架无法推断未声明的共享分配关系。

Rent 默认使用 进程默认 MemoryPool,Capacity 限于请求的可寻址长度,RetainedByteLength 按 owner 报告的完整租用成本计量,不把池额外容量暴露为有效图像。自定义 pool 的寿命由调用方管理,必须覆盖所有在途 owner 的归还;工厂不会销毁该 pool。

TakeOwnership 成功后调用方不再访问或释放原 owner;不要同时给 owner 和 buffer 套两个独立的 using。ImportNative 的回调只对应接管的那一份 SDK 引用,不能归还另一个消费者仍持有的引用。

native 初版导入要求地址全寿命稳定,并且 SDK 在这份资源引用结束前不再次写入或复用内存。只在回调内有效的指针使用图像复制工厂,不得以空释放回调伪装为长期借用。

handle、异步等待与清理

Share() 返回新的 handle,共用 BufferId、内容版本与访问协调。每个 handle 独立 Dispose;释放一个 handle 不关闭其他 handle。ReturnState 是底层实体状态,不是当前 handle 是否已关闭。

异步权限申请在入队时保留内部存活引用。调用方随后释放发起申请的 handle,不撤销已经登记的申请;取消等待须使用令牌。所有新方法调用与 handle 的 Dispose 竞争时由单一协调点裁定,已经关闭的 handle 不得发起新申请。资源只有在全部 handle、活动访问和排队申请结束后才进入 Returning。

Dispose() 幂等,只关闭当前 handle,不阻塞 UI 或渲染线程等待其他消费者、native 工作或 SDK 回调。元数据、ReturnState 和 ReturnCompleted 仍可查询;Share/申请权限则抛 Status::Disposed(或等价)。

releaseAsync 不接收取消令牌,恰好调用一次,其完成表示真实归还完成。框架在资源清理队列调用它,不在访问锁、UI 属性通知或 render 回调中调用;它可以将工作转发到 SDK 指定线程,并返回一个在实际归还后才完成的 异步完成信号(future/回调示意)。托管宿主没有后台线程时只支持适用的非阻塞清理,不能据此假定 Browser 具备 SDK 线程能力。

SDK 导入阶段必须保证最终清理提交不会因队列已满而丢失:由有界外借数量限制清理工作,或提前预留归还席位及投递所需对象。最后一次 Dispose 只投递,不同步等待队列容量。某个供应商回调长时间未完成不得串行阻塞其他存储的归还;清理调度采用受控并发并独立观察各项结果。

ReturnCompleted 是可重复等待的 Task:归还成功完成,归还失败 fault,绝不因某个调用方停止等待而取消。失败进入 ReturnFailed,禁止恢复访问或自动重试。调用方若要等待,先关闭自己的 handle/资源引用,再等待此 Task;持有最后一份引用并等待自身归还会形成等待环。

ReturnFailed 不等于资源已经归还,适配器保留失败诊断与隔离状态,不把该槽放回可用池,也不将未知归还成本当作成功释放。供应商明确的修复或设备关闭策略属于会话故障处置,不能由核心偷偷重试 Release。

同一有效期内一份实际分配只建立一个协调实体,其他 handle 与 ROI 从它派生,不能重复包装别名来绕过互斥。buffer 与可写 image handle 仅保持存储存活;活动读取、从读取派生的稳定图像和 DisplaySnapshot 的原图读取都保留对应访问权限。稳定图像内部的持久读取也必须计入访问账本。

读取与写入像素

OpenRead 返回 ImagePixelReader,using 结束前保持这一版像素稳定;OpenWrite 返回 ImagePixelWriter,在关闭前独占缓冲。writer.Complete 表示内容已经准备好,可以再次读取;没有 Complete 就关闭不发布可能只完成一部分的内容。

// <vyntek/imaging/buffer.hpp>(reader/writer/pin)
namespace vyntek::imaging {

class ImagePixelReader {
 public:
  std::int64_t access_id() const;
  ImageContentVersion version() const;
  std::int64_t capacity() const;
  std::int64_t retained_byte_length() const;

  ImagePixelReader Share() const;
  ImageBuffer ShareBuffer() const;  // 只保活分配,不延长读权限
  std::span<const std::uint8_t> GetSpan(std::int64_t offset, int length) const;
  std::span<const std::uint8_t> GetPixelRow(const ImageInfo& info, int row) const;
  ImageBufferPin Pin() const;
};

class ImagePixelWriter {
 public:
  std::int64_t access_id() const;
  BufferId buffer_id() const;
  std::int64_t capacity() const;

  std::span<std::uint8_t> GetSpan(std::int64_t offset, int length);
  std::span<std::uint8_t> GetPixelRow(const ImageInfo& info, int row);
  ImageBufferPin Pin();
  ImageContentVersion Complete();
  ImagePixelReader CompleteAndOpenRead();
};

class ImageBufferPin {
 public:
  std::uintptr_t address() const;
  std::int64_t capacity() const;
  BufferAccess access() const;
};

}  // namespace vyntek::imaging

访问授予

  • 读取对象绑定一个稳定版本,Share 创建同一版本的另一个读取对象,不重新排队申请读取。ShareBuffer 只保持底层分配存活,不延长当前读权限或把只读存储提升为可写。
  • 写入对象独占整份分配;同一版本的不同 ROI 也不获得并发写权限。写者排队后阻止新的独立读申请,已有读取对象仍可按需复制,因此应用主动无限传递读取对象仍可能拖延写入。
  • 已初始化且没有冲突时,权限申请可以同步完成。存在写者时读申请排队,存在读者/写者时写申请排队。轮到读申请时若内容未初始化或上次写入失败,立即抛 Status::InvalidOperation(或等价),不无限等待某个未登记的写者。
  • 期望版本为空值、Generation 小于 1 或 BufferId 不匹配时抛 Status::InvalidArgument(或等价);授予读权限时有效内容版本不匹配则抛 VersionMismatchError / Status::VersionMismatch,携带期望与实际版本。期望版本检查与授予原子完成;未初始化或 Invalid 仍按内容无效报告,不冒充另一有效版本。
  • 带 expectedVersion 的写申请同样在授予点验证版本,防止源输入脱离期间已被其他写者更新,随后算法却修改了另一阶段的内容。版本不匹配或内容已 Invalid 时不授予写权限;恢复失败写入使用不带版本的写申请。
  • 只读存储申请写入抛 Status::NotSupported。不提供隐式读升级、自动显示输入脱离或自动复制后写入。

所有读写对象的 Dispose 幂等。使用与 Dispose 不得并发,唯一例外是实现须原子裁定 Share/ShareBuffer 与 Dispose 的竞争,成功返回的子对象始终独立有效。对象关闭后 GetSpan/GetPixelRow/Pin/Complete 等操作抛 Status::Disposed(或等价);身份、版本和容量等值元数据仍可读取。

读写对象不通过终结器代替 Dispose,也不承诺丢失托管引用后 SDK 会自动归还。没有关闭的 reader、writer、pin 或 buffer 可能阻止资源复用;清理诊断以显式打开/关闭记录为准。

写入提交

Complete 原子发布新版本并结束写权限,返回包含 BufferId 与 Generation 的 ImageContentVersion;CompleteAndOpenRead 在同一原子边界提交并转为读权限,返回新版本读取对象,期间不能插入另一个写者。返回对象和必要状态必须在提交前构造完成,不能内容已提交却因返回资源引用分配失败丢失访问权。两者均不允许重复调用。

没有 Complete 就 Dispose 表示写入中止,内容变为 Invalid。其字节可能已部分修改,既不回滚,也不把旧代次重新发布为有效内容。按初始化与失败恢复的完整低层恢复才能重新读取。Generation 从 1 开始,每次提交严格增加;到 long.MaxValue 时在下次授予写入前拒绝,保持原内容可读,调用方可先复制到新存储,不等到像素已改写后才发现无法发布。

框架不追踪逐字节写入完成度,尤其无法观察 native 指针上的写入;Complete 是调用方对待发布内容已完整准备的显式声明。Invalid 后的恢复完整性由写入方保证,不能把版本验证理解成内容校验或事务回滚。

span 与 pin 的有效期

GetPixelRow(info, row) 返回指定逻辑行的有效像素,忽略行尾空隙;info 不得为 null,并使用图像模型的 ImageInfo 范围验证。GetSpan 返回分配内任意受检字节范围,不承诺是连续图像。所有返回的 span 只能在 reader/writer 有效期内使用。

Pin 返回分配基地址,ROI 起点为 Address + info.StartOffsetBytes,负步长仍按逻辑行索引推进。托管内存在 pin 期间固定,native 导入直接使用其稳定地址;两条路径都保留资源与访问权限。读取对象产生的 pin 的 Access 永远是 ReadOnly,即使底层 buffer 可写。

pin 独立保留访问有效期:父读取对象先 Dispose 时,pin 仍阻止写入和归还。父写入对象结束前必须先释放其全部 pin,Complete/CompleteAndOpenRead 在存在写 pin 时抛 Status::InvalidOperation(或等价);父写入对象先 Dispose 时内容中止,但独占权限持续到最后一个写 pin 释放,期间不授予新访问。

native 异步算法可以跨 await 持有 pin,但必须等待 native 工作实际结束后再释放。API 不能拦截保存在外部的地址,也不能因 native API 的取消等待已返回就推断设备或工作线程不再访问指针。pin 不提供裸 MemoryHandle 转移,禁止绕过其生命周期管理。

首次初始化、ROI 写入中止与恢复

未初始化图像不能发布为稳定只读句柄(不可交给工具流下游输入),也不能创建可读 ROI。类型化 Rent 的首次写入必须由根图像覆盖全部逻辑像素后 Complete;不能仅写一个 ROI 就把整图声明为有效。图像层记录这个初始根范围,未完成首次发布前拒绝登记不同范围的初始化视图。raw ImageBuffer 的高级写入路径仍由调用方对发布范围完整性负责。

正常已初始化图像允许修改 ROI;Complete 推进整份存储的 Generation,未修改区域沿用其原有有效内容。任何写入中止都使整个协调实体 Invalid,不把失败局限为一个缺少跟踪的矩形。此后类型化图像的读、写、稳定发布和 ROI 派生均拒绝;不能用另一个小 ROI 的 Complete 把整份内容重新标为有效。

工具惯例是丢弃失败输出或从稳定只读输入重新生成。高级低层恢复只能由拥有完整初始化范围的适配器执行无期望版本的 ImageBuffer 写入,完成全部必要内容后重新发布;框架不提供字节级回滚或自动覆盖检测。带期望版本的恢复始终拒绝 Invalid 内容。

逻辑像素有效不代表行尾空隙、前缀和池额外字节已初始化。GetPixelRow、Copy、序列化默认只处理逻辑像素,不输出池中残留的 padding。GetSpan、Pin 和 Create 自定义重解释是高级能力,调用方保证其访问或发布的范围有效;仅通过容量校验不能证明任意重解释字节均已写入。需要导出完整含 padding 的内存时显式初始化相应字节。

三组正交状态

状态维度内容约束
资源归还Active / Returning / Returned / ReturnFailedActive 只表示仍有资源,不保证像素可读
内容状态Uninitialized / Ready / Writing / Invalid只在 Ready 授予新读取;首次成功提交或完整恢复后回到 Ready
活动权限活动读、独占写、pin 与排队申请写与读互斥;共享稳定图像计入读;最后引用及工作退出才归还

写入授予时内容进入 Writing;Complete 发布 Generation 并恢复 Ready;未完成即关闭进入 Invalid。写 pin 仍在时即便内容已 Invalid,也继续占用独占权限,其他申请不能进入。Generation 记录最后一次成功提交,不能仅凭其数值非零跳过内容状态检查。所有状态转移由同一存储协调点原子裁定,回调和 continuation 在锁外执行。

单个 handle 已关闭与底层资源状态也不同:关闭一个 image 不表示其 Share、稳定发布、pin 或等待申请已经结束。稳定发布图像的关闭必须归还它内部持久读引用,不能只减生命周期计数;这是队列清空后写者和 SDK 回收能够恢复推进的必要条件。

可勾选契约(Arden · R11 · 薄回链)

阶段 B 散文 → 可勾选:TC-MEM-01…13(工厂接管、最后引用与等待环、pin 阻写/归还、写 pin 阻 Complete、授予/取消与 Share/Dispose 原子、未 Complete→Invalid、首次根范围/ROI 中止整实体、Invalid 恢复、releaseAsync 一次与归还隔离/ReturnFailed)。勾选表见 TC-MEM 摘录(§10);薄场景 S12。首次根范围 / padding 薄指 image-model.md。不改 toolflow v1.9 / T9 停止矩阵语义。


文档状态:设计草案(2026-10-21,Asia/Shanghai)。可勾选见 Arden · R11(TC-MEM- / S12);权威在 C++ vyntek::imaging;与 toolflow v1.9 §3.4 句柄交接对照,不以编排语义替代本文。*


本仓实现对拍(tip e46c042 · 按实)

对照 imaging/buffer.hpp · access.hpp · memory_pool.hpp · memory_owner.hpp · 便利 <vyntek/memory.hpp>。可勾选断言见 TC-MEM 摘录。缺口 → 问题单。外形 ≠ 验收。

规格概念(本文)本仓已有stub / 缺口边界
BufferAccess / BufferInitialization / BufferReturnState已有 buffer.hpp枚举齐全;状态机完整行为属实现
ImageBuffer::Rent / TryRent / CanRent已有 (minimum_capacity, MemoryPool* = nullptr)CanRent 默认乐观(bytes > 0),不是保留;池预算验收未齐
TakeOwnership已有 unique_ptr<MemoryOwner>失败路径在登记前不得释放调用方 owner
ImportNative + release 恰好一次已有清理队列不串行阻塞(TC-MEM-13)已实现且有断言:A 的 release 阻塞时 B 仍能完成归还、ReturnFailed 不自动重试且可观察;验收未勾
Share / OpenRead / OpenWrite(含 expectedVersion)已有授予排队/取消竞争已实现且有断言(EnqueueAndWaitRead/Write、grant_queue 上限 32、queue_ref 保活、SignalCancelOnQueued;smoke TC-MEM-08 覆盖取消、Dispose 不撤已入队、队满立即拒);仍受 DEBT-IMG-003 限制——CancelToken 为 by-value,跨线程改调用方 token 不可行
AwaitReturnCompleted / ReturnCompleted声明 + cpp 实现可重复等待 / 禁等自身环以测为准,未验收
ImageBufferPin已有读 pin Access 永 ReadOnly 以实现为准
ImagePixelReader / Writer类在 buffer.hpp;像素主路径在 access.hpp Viewspan API 仍薄
MemoryPool虚基类 memory_pool.hpp(非空 struct)Reed 拥有真实池;默认 CanRent 乐观
扁平 <vyntek/memory.hpp>转发 buffer + vyntek::BufferView{data,size}非协调实体

句柄 / Access / pin(交叉)

项状态
PixelImageHandle::access()已有
OpenRead/OpenWrite → ReadOnlyView/WritableView已有外形
Pin() 独立保活已有声明;父 Dispose 后行为以实现/TC-MEM-06/07 为准
Complete / CompleteAndOpenRead已有于 WritableViewBase

文档状态:www M2 工作副本(2026-10-21 Asia/Shanghai)。与 Arden · R11 / TC-MEM- 对齐;实现验收另开问题单。*

On this page