示例代码场景规划(Tool / ToolFlow · v1.9)
www 工作副本(2026-10-21 Asia/Shanghai) 双轨:正文迁自 mvdocs examples-scenarios。场景是验收目录,不是已通过的测试。 实现边界:本仓可编译示例只有 examples 的
headless_run(贴近 S07 的无 UI 入口,不是 S07 全文)。tests/smoke_headers.cpp与Vyntek.Controls.Tests覆盖部分骨架。勾选仍空。 关联:toolflow · test-contracts · host-integration · image-display
示例代码场景规划(Tool / ToolFlow · v1.9)
目标设计与示意骨架,非可编译工程。总体边界见设计概述;编排权威见 toolflow.md( v1.9);成像/显示契约片段见 examples.md。
定调(勿回退):Tool / ToolFlow;命名空间
vyntek::toolflow/vyntek::imaging; ToolId; DiagnosticCode。不混 MPF / 旧 DAG(dag-dataflow.md已删除,勿链)。编排与成像核心 = C++; ImageDisplay 仅 C#。API 名与 toolflow v1.9 / cpp-headers.md / csharp-binding.md 一致。fake 替身见 toolflow §7.6;句柄交接见 §3.4;Diagnostic 见 §7.4.6;.avtf见 §6.6。
本文是成体系场景目录:每个场景给出目的、前置、步骤骨架与可测断言,供实现期单测 / headless 验收对照。详细契约片段仍落在 examples.md 与 toolflow §9,避免把契约文撑爆。
关联: examples.md · toolflow.md §9 / §10.1 · test-contracts.md(R4 可勾选契约;R6 含 S09; Arden · R9 TC-OUT-* / S10; Arden · R10 TC-ACQ-* / TC-SESS-* / S11; Arden · R11 TC-MEM-* / S12; Arden · R12 TC-DISP-* / S13) · host-integration.md( R7 · S07 同核;两端都停;TC-CTL-05 交叉) · cpp-headers.md · sketch/README.md(R5 伪树·示意实现对照,非工程) · csharp-binding.md · glossary.md( R8;Arden · R9 / R10 / R11 / R12 撞号) · output-inspection.md( T10) · camera-acquisition.md · session-lifecycle.md · memory-management.md( Arden · R11) · image-display.md( Arden · R12;仅 C#) · validation-and-roadmap.md
场景目录
| ID | 标题 | 主路径 | 文档回链 |
|---|---|---|---|
| S01 | 单工具 Run | 注册 → Create/AddTool → RunSession.Run → Outputs | toolflow §7.4 / §9.4;cpp-headers |
| S02 | 多工具 ToolFlow 顺序链 | 列表序 Link + Validate + 自上而下 Run | toolflow §4.1 / §4.2 / §5 / §9.1 |
| S03 | 采集→对齐→检测→输出观察(fake) | fake_acq → fake_pm_align → passthrough/constant;观察 LastRun | toolflow §7.6.3 / §9.1;output-inspection |
| S04 | .avtf Load + Validate 失败 | UnknownTypeId / MissingRequiredProperty | toolflow §6.5–6.6 / §7.4.5–7.4.6 |
| S05 | 句柄交接与交显示 | Complete→ReadOnly、链接赋值、H1–H10;显示段 仅 C# | toolflow §3.4;test-contracts §6;examples;csharp-binding;image-display |
| S06 | Cancel / abort / Busy | 协作取消、abort_on_tool_failure、同 session 重入拒绝 | toolflow §4.3 / §7.4.3 / §7.4.6;session-lifecycle |
| S07 | headless 批跑 vs 带显示宿主 | 无 UI 循环 Run vs 绑定层 + ImageDisplay | toolflow §7.2–7.3; host-integration.md( R7);csharp-binding;examples T12 |
| S08 | Tool 作者:fake 注册 + 最小自定义 Tool | register_fake_tools + ITool 最小契约 | toolflow §7.6;cpp-headers |
| S09a | 嵌套 ToolFlow 合法 + Encapsulation | 内层终端对外;私有属性 → Encapsulation | toolflow §9.3 / §7.4.6 / §10.1 |
| S09b | SourceDisabled + InvalidBinding | 源禁用拉值失败;端点/方向非法主断言 | toolflow §4.3 / §7.4.6 / §10.1 |
| S09c | IoFailure + 停相机 ≠ 自动 Cancel | 截断/不可读;停止矩阵独立步骤 | toolflow §7.4.3 / §7.4.6;session-lifecycle |
| S09d | §9.2 两流终端串联(可选) | Locate→Defect 终端链;极薄正路径 | toolflow §9.2 / §4.1 |
| S10 | 输出观察 T10 事件闭集(fake) | SnapshotReplaced / EmptySnapshot / Unavailable / Evicted;失败保留;Subscribe+replay;查看者 vs Evict;KeepLatest vs ReleaseAfterRouting;观察永不驱动 Run | output-inspection(T10);test-contracts TC-OUT-*( Arden · R9);TC-RUN-05 |
| S11 | 采集接入 + 会话停流(fake · 阶段 D) | ImportNative vs 回调 Copy;TryPostOwned;满池/入口预算;归还泵与 ReturnCompleted;Latest 预览;回调不堵归还;停流结束条件;外保留≠假归还;超时续清;ReturnFailed/断线可观察;两端都停须显式 Cancel+停采集 | camera-acquisition;session-lifecycle;test-contracts TC-ACQ-* / TC-SESS-*( Arden · R10);TC-CTL-04 |
| S12 | 存储 / pin / 归还(fake · 阶段 B) | Rent/TakeOwnership/ImportNative 工厂接管;最后引用→Returning;禁等自身 ReturnCompleted;pin 阻写/归还;写 pin 阻 Complete;授予/取消与 Share/Dispose 原子;未 Complete→Invalid;首次根范围/ROI 中止整实体;Invalid 恢复;releaseAsync 一次与归还隔离/ReturnFailed | memory-management;test-contracts TC-MEM-*( Arden · R11);image-model(根范围薄指) |
| S13 | 显示集成(仅 C# · 阶段 F) | 登记≠授予;Share 不接管;四回执;失败保留;四格式;ROI;卸载;预算;显示只观察 | image-display;TC-DISP-*(Arden · R12);≠ T15 R10 |
目的
验证最小闭环:Registry 创建单一 Tool → 放入 ToolFlow → RunSession::Run → 读 Outputs / 工具属性;断言成功路径与 ToolId 可定位。
前置
ToolRegistry已register_fake_tools(registry)(或至少注册fake_constant)。- 无
.avtf;内存建流即可。 - 无图像句柄要求(标量输出即可)。
步骤骨架(C++)
// 示意、非可编译;命名空间与头文件对齐 toolflow §7.4 / cpp-headers
#include <vyntek/toolflow/toolflow.hpp>
#include <vyntek/toolflow/registry.hpp>
#include <vyntek/toolflow/run_session.hpp>
using namespace vyntek::toolflow;
ToolRegistry registry;
register_fake_tools(registry);
auto flow = ToolFlow::Create("S01_Single");
auto* c = flow->AddTool(registry.Create("fake_constant", "Const"));
// 配置必填参数(示意):输出固定 int32
c->SetProperty("Value", Value::Int32(42));
flow->AddOutput("Out", TypeDesc{Type::Int32});
flow->Link(c, "Output", flow->Output("Out"));
std::vector<Diagnostic> diags;
if (!flow->Validate(diags)) { /* 期望空 Error */ return; }
RunSession session(flow.get());
RunResult r = session.Run();
// 断言见下
int out = session.GetOutput<int>("Out");
期望断言
| 项 | 期望 |
|---|---|
Validate | 无 Severity::Error |
r.ok | true |
| Outputs | Out == 42 |
ToolId | c->id() 非空且流内稳定;失败时可写入 Diagnostic.tool_id |
| 诊断 | 成功路径 diagnostics 可空或仅 Info |
| 契约映射 | TC-RUN-01 / TC-REG-04 / TC-LNK-08 |
对应文档章节
toolflow §3.1 / §7.4.1 / §7.4.3 / §7.4.4 / §9.4;cpp-headers(公开头);§7.6.3(fake_constant)
目的
验证列表序权威调度:多工具 AddTool → Link(源须在目标之上)→ Validate → 一次 Run 自上而下执行;链接不是 DAG 边调度。
前置
register_fake_tools:fake_acq、fake_passthrough、fake_constant(或等价)。- 可选:故意建「源在目标之下」的坏链,另测
OrderViolation(本场景主路径为合法链)。
步骤骨架(C++)
auto flow = ToolFlow::Create("S02_Chain");
auto* acq = flow->AddTool(registry.Create("fake_acq", "Acq"));
auto* pass = flow->AddTool(registry.Create("fake_passthrough", "Pass"));
auto* flag = flow->AddTool(registry.Create("fake_constant", "Flag"));
flow->AddOutput("Image", TypeDesc{Type::ImageGray8});
flow->AddOutput("Flag", TypeDesc{Type::Int32});
flow->Link(acq, "OutputImage", pass, "InputImage");
flow->Link(pass, "OutputImage", flow->Output("Image"));
flow->Link(flag, "Output", flow->Output("Flag"));
// 坏链对照(勿留在成功配方):flow->Link(pass, "OutputImage", acq, "InputImage");
// → Validate 期望 DiagnosticCode::OrderViolation
std::vector<Diagnostic> diags;
assert(flow->Validate(diags));
RunSession session(flow.get());
RunResult r = session.Run();
期望断言
| 项 | 期望 |
|---|---|
| 执行序 | Acq → Pass → Flag(按 tools[]);无「边就绪即跑」 |
r.ok | true |
| H6 | Outputs.Image 为 Access=ReadOnly;对输入 OpenWrite 立即拒绝 |
| 坏链 | Validate 失败且含 OrderViolation;不留下半条边 |
TypeMismatch | 图像链到 Int32 终端 → Error |
| 禁用源 | 源 disabled 时下游 Run 拉值 → DiagnosticCode::SourceDisabled(MVP 严格;主场景见 S09b) |
| 契约映射 | 至少覆盖 test-contracts TC-RUN-02 / TC-LNK-01 / TC-LNK-03 |
对应文档章节
toolflow §4.1 / §4.2 / §5.4 / §7.4.2 / §9.1;§3.4 H6
目的
用 MVP fake 替身复现 §9.1 流水线心智(无需真实相机/PMAlign):fake_acq → fake_pm_align → passthrough/constant;Run 后只读观察 Outputs / LastRun(观察不驱动 Run)。
前置
register_fake_tools:fake_acq、fake_pm_align、fake_passthrough或fake_constant。- 无真实 SDK;可选输出观察订阅(见 output-inspection,T10)。
步骤骨架(C++)
auto flow = ToolFlow::Create("S03_InspectFake");
auto* acq = flow->AddTool(registry.Create("fake_acq", "Acq"));
auto* align = flow->AddTool(registry.Create("fake_pm_align", "Align"));
auto* pass = flow->AddTool(registry.Create("fake_passthrough", "OutImg"));
auto* cnt = flow->AddTool(registry.Create("fake_constant", "Count"));
flow->AddOutput("PoseX", TypeDesc{Type::Float64}); // 示意:从 Pose 最小字段拆出
flow->AddOutput("DefectCount", TypeDesc{Type::Int32});
flow->AddOutput("Preview", TypeDesc{Type::ImageGray8});
flow->Link(acq, "OutputImage", align, "InputImage");
flow->Link(align, "OutputImage", pass, "InputImage"); // 或 Share 输入
flow->Link(pass, "OutputImage", flow->Output("Preview"));
flow->Link(cnt, "Output", flow->Output("DefectCount"));
// Pose → 终端:按 §5.5 最小字段路径(示意)
// flow->Link(align, "Pose.X", flow->Output("PoseX"));
assert(flow->Validate(/*diags*/));
RunSession session(flow.get());
RunResult r = session.Run();
// 输出观察:只读 LastRun / Outputs;不得 session.Run 由观察回调触发
auto preview = session.GetOutput<vyntek::imaging::PixelImageHandle>("Preview");
// 可选:OutputStore 订阅 EmptySnapshot / Unavailable(T10)— 见 output-inspection
期望断言
| 项 | 期望 |
|---|---|
r.ok | true |
| H3/H4/H6 | Preview / 下游输入 access()==ReadOnly;同 BufferId 语义(passthrough Share) |
| Pose / Count | Outputs 可读;类型符合 TypeDesc |
| 观察 | 订阅变更不触发第二次 Run;失败保留策略见 output-inspection |
| 无设备 | 全程无相机 SDK 调用 |
| 契约映射 | TC-RUN-03 / TC-H6 / TC-REG-01 / TC-RUN-05(薄);细项 S10 / TC-OUT-*( Arden · R9) |
对应文档章节
toolflow §7.6.3 / §9.1 / §3.4;output-inspection(T10);examples(成像读写片段)
目的
钉住持久化失败分轨:未知 type_id → Load UnknownTypeId(严格失败,无占位成功);已知 type 缺必填配置属性 → Validate MissingRequiredProperty(禁止静默默认糊弄)。对照 §6.5 / §6.6 反样例。
前置
- 合法正样例
.avtf可作对照(toolflow §6.6.2)。 - Registry 仅含 fake/builtin;不含
type_id_not_registered。 - 另备一份「已知
fake_constant但缺必填Value」的配方或 Load 后删属性。
步骤骨架(C++)
#include <vyntek/toolflow/serialize.hpp>
// --- A) 未知 type_id ---
auto bad_unknown = ToolFlow::Load("fixtures/unknown_type.avtf", registry);
// fixtures 内 tools[].type_id = "type_id_not_registered"
assert(!bad_unknown);
// bad_unknown.error() 诊断列表含 DiagnosticCode::UnknownTypeId(Severity::Error)
// --- B) 已知 type、缺必填属性 ---
auto loaded = ToolFlow::Load("fixtures/missing_required.avtf", registry);
assert(loaded); // Load 成功(type 已知;component_version 不拦)
std::vector<Diagnostic> diags;
bool ok = (*loaded)->Validate(diags);
assert(!ok);
assert(std::any_of(diags.begin(), diags.end(), [](const Diagnostic& d) {
return d.code == DiagnosticCode::MissingRequiredProperty
&& d.severity == Severity::Error;
}));
// 不得出现「静默填默认后 Validate 成功」
反样例要点(文档级,与 §6.6.4 对齐):
| 失败类 | 触发 | 码 |
|---|---|---|
| 未知工具 | tools[].type_id 不在 Registry | UnknownTypeId(Load) |
| 缺必填 | 已知 type,缺配置属性 | MissingRequiredProperty(Validate) |
| schema 过高 | schema_version > 加载器 | SchemaTooNew(Load;本场景可作附带用例) |
期望断言
| 项 | 期望 |
|---|---|
| A | Load 失败;≥1 条 UnknownTypeId Error;无半初始化成功流 |
| B | Load 成功;Validate 失败;MissingRequiredProperty;与 UnknownTypeId 分轨 |
| 附带 | 过高 schema → SchemaTooNew;component_version 差异不单独拦 Load |
| 契约映射 | TC-SER-02 / TC-SER-03 / TC-LNK-07 / TC-SER-04(见 test-contracts) |
对应文档章节
toolflow §6.5 / §6.6 / §7.4.5 / §7.4.6 / §10.1(Load 复载);validation-and-roadmap(T8)
目的
覆盖 §3.4 主路径:工具内 Complete / CompleteAndOpenRead + ShareImage → 链接拉值写入下游(H6)→ headless 持 ReadOnly 句柄;上屏仅经绑定层 + C# ImageDisplay(禁止 C++ UI)。
前置
- fake:
fake_acq+fake_clone_write(或最小自定义写工具)+fake_passthrough。 - 显示宿主:PInvoke 绑定可用;C# 侧有 ImageDisplay 实例(见 csharp-binding / image-display)。
步骤骨架(C++ · 编排与成像)
#include <vyntek/imaging/handle.hpp>
#include <vyntek/imaging/factory.hpp>
#include <vyntek/toolflow/toolflow.hpp>
#include <vyntek/toolflow/run_session.hpp>
// 流:Acq → CloneThenWrite → Passthrough → Output 终端
auto flow = ToolFlow::Create("S05_Handle");
auto* acq = flow->AddTool(registry.Create("fake_acq", "Acq"));
auto* wr = flow->AddTool(registry.Create("fake_clone_write", "Write"));
auto* pass = flow->AddTool(registry.Create("fake_passthrough", "Share"));
flow->AddOutput("DisplayImage", TypeDesc{Type::ImageGray8});
flow->Link(acq, "OutputImage", wr, "InputImage");
flow->Link(wr, "OutputImage", pass, "InputImage");
flow->Link(pass, "OutputImage", flow->Output("DisplayImage"));
RunSession session(flow.get());
RunResult r = session.Run();
auto handle = session.GetOutput<vyntek::imaging::PixelImageHandle>("DisplayImage");
assert(handle.access() == vyntek::imaging::Access::ReadOnly);
// headless:到此为止;句柄经绑定交给 UI 进程/线程(示意)
步骤骨架(C# · 仅显示段)
// 示意、非可编译;仅 ImageDisplay;像素来自 C++ 句柄经 PInvoke 绑定
// 对齐 csharp-binding / examples.md T12 / image-display
using var bound = ImagingBindings.AdoptReadOnly(nativeHandle); // opaque + Dispose
imageDisplay.SetImage(bound); // 或 SetImage(projection)
// 取样 / Depth 选项等见 examples.md;不得在此启动 ToolFlow.Run / Cancel 相机
期望断言
| 项 | 期望 |
|---|---|
| H1/H2 | Complete 后再写失败;CompleteAndOpenRead 得 ReadOnly;未 Complete 不得链给下游 |
| H3/H4 | ShareImage / ReadOnly Share → OpenWrite 拒绝;多句柄独立关闭 |
| H6 | 链接赋值后目标输入 ReadOnly;列表序不变 |
| H7/H8 | 已交下游不可改;Clone 新 BufferId 可写后再发布 |
| 显示 | C# SetImage 成功;控件不承担编排 Cancel / 停相机(停止矩阵 §7.4.3) |
| 语言 | 无 C++ UI / Avalonia include |
| H9 | ReadOnly ROI OpenWrite 拒绝;不得把根退化为可写 |
| H10 | 失败/未 Complete 输出不可假成功交给下游(与 S06 abort 联测) |
| 契约映射 | test-contracts TC-H1…TC-H9(主路径);显示段 TC-CTL-05 |
对应文档章节
toolflow §3.4(H1–H10)/ §7.4.3; test-contracts §6; examples.md(T12);csharp-binding;image-display;memory-management / image-model
目的
验证执行控制三件套:(1)协作式 RequestCancel → Cancelled,后续工具不启动;(2)abort_on_tool_failure=true + 失败工具 → Aborted,后续不 Run(H10);(3)同 RunSession 重入 → Busy(MVP 拒绝,不排队)。
前置
- fake:
fake_cancel_check、fake_fail_once/fake_fail_always、可选fake_busy_probe。 abort_on_tool_failure默认 true(§4.3)。
步骤骨架(C++)
// --- Cancel ---
auto flow_c = ToolFlow::Create("S06_Cancel");
auto* slow = flow_c->AddTool(registry.Create("fake_cancel_check", "C"));
auto* after = flow_c->AddTool(registry.Create("fake_constant", "After"));
flow_c->Link(/*…*/); // After 在 C 之后
RunSession s1(flow_c.get());
// 另一线程/测试钩:s1.RequestCancel();
RunResult rc = s1.Run();
// 期望:rc.ok==false;diagnostics 含 Cancelled;After 未 Run(LastRun 仍为未跑/跳过)
// --- abort_on_tool_failure ---
auto flow_a = ToolFlow::Create("S06_Abort");
flow_a->set_abort_on_tool_failure(true);
auto* ok1 = flow_a->AddTool(registry.Create("fake_acq", "A"));
auto* fail = flow_a->AddTool(registry.Create("fake_fail_always", "F"));
auto* ok2 = flow_a->AddTool(registry.Create("fake_constant", "Never"));
// Link A→…;F 失败;Never 在 F 后
RunResult ra = RunSession(flow_a.get()).Run();
// 期望:Aborted;Never 未 Run;不得把 F 的 Invalid 图假成功交给下游(H10)
// --- Busy ---
auto flow_b = ToolFlow::Create("S06_Busy");
auto* probe = flow_b->AddTool(registry.Create("fake_busy_probe", "Slow"));
RunSession s2(flow_b.get());
// 线程1:s2.Run() 进行中
// 线程2:RunResult rb = s2.Run();
// 期望:rb 失败;DiagnosticCode::Busy
期望断言
| 项 | 期望 |
|---|---|
| Cancel | Cancelled;当前工具合作返回后不再启动后续;r.ok==false |
| Abort | Aborted;failed_tool_id 指向失败工具;后续 LastRun 未执行;H10 |
| Busy | 同 session 第二次 Run → Busy Error;首次 Run 不受损 |
| 停止矩阵 | Cancel ≠ 停相机;停相机 ≠ 自动 Cancel(§7.4.3;独立步骤见 S09c) |
| 契约映射 | TC-CTL-01 / TC-CTL-02 / TC-CTL-03 / TC-H10 |
对应文档章节
toolflow §4.3 / §7.4.3 / §7.4.6 / §3.4 H10 / §10.1;session-lifecycle;validation-and-roadmap(T9)
目的
对照两种宿主形态:纯编排(循环 Run、断言 Outputs、无 UI)与 显示宿主(同 C++ 核心 + PInvoke + 仅 C# ImageDisplay)。证明不存在第二执行引擎,也不写 C++ UI。
前置
- 同一
.avtf或内存流(建议 S03 fake 流水线)。 - headless:测试进程 / CLI runner。
- 显示:C# 应用引用绑定包。
步骤骨架(C++ · headless 批跑)
auto loaded = ToolFlow::Load("recipes/inspect_fake.avtf", registry);
assert(loaded); // 未知 type / SchemaTooNew 等见 S04
std::unique_ptr<ToolFlow> flow = std::move(*loaded);
RunSession session(flow.get()); // 与 §7.4.5 一致:session 不抢 flow 所有权
for (int i = 0; i < N; ++i) {
// 可选:注入帧 / 依赖 fake_acq 自产
RunResult r = session.Run();
assert(r.ok);
auto n = session.GetOutput<int>("DefectCount");
// 记录 n;无 SetImage / 无窗口
}
步骤骨架(C# · 显示宿主,编排仍可经绑定调 C++)
// 示意薄 PInvoke 入口(对齐 csharp-binding);编排仍在 C++,非第二套 ToolFlow
ToolFlowNative.RunOnce(sessionHandle); // opaque session handle;不在托管侧重写执行器
using var img = ToolFlowNative.GetOutputImage(sessionHandle, "Preview");
imageDisplay.SetImage(img);
// Cancel / 停相机按停止矩阵分派到编排/采集 API,不进 ImageDisplay
期望断言
| 项 | 期望 |
|---|---|
| headless | N 次 Run 稳定;无 UI 依赖;Diagnostic 可收集到日志 |
| 显示宿主 | 同一配方结果一致;SetImage 只读快照;控件卸载不 Abort 编排 |
| 边界 | 无第二套 ToolFlow;无 C++ ImageDisplay |
| 会话 | 批跑结束按 session-lifecycle 排空/释放;观察引用不阻塞归还契约 |
| 契约映射 | TC-RUN-04 / TC-SER-01 / TC-CTL-05 |
对应文档章节
toolflow §7.2 / §7.3 / §9.4; host-integration.md( R7 同核 / 停止分派);csharp-binding; examples.md T12;session-lifecycle;image-display; test-contracts.md TC-RUN-04 / TC-SER-01 / TC-CTL-05
目的
落实 §7.6:测试包 register_fake_tools;作者实现最小 ITool(type_id、属性袋、Run、失败/取消、成像纪律);与 Create/Load 共用同一 Registry;重复 type_id 拒绝。
前置
- 公开头:
registry.hpp/tool.hpp(示意);成像公开句柄头。 - 自定义工具 不要默认链进生产
register_builtin_tools。
步骤骨架(C++)
#include <vyntek/toolflow/registry.hpp>
#include <vyntek/toolflow/tool.hpp>
#include <vyntek/imaging/handle.hpp>
// 最小自定义 Tool(示意)
class ScaleGrayTool final : public ITool {
public:
std::string_view type_id() const override { return "demo_scale_gray"; }
ToolRunStatus Run(RunContext& ctx) override {
auto in = GetProperty("InputImage"); // ReadOnly 句柄
// Clone → OpenWrite → 缩放填充 → CompleteAndOpenRead → ShareImage → SetProperty OutputImage
// 定期 ctx.cancel_requested();失败勿交出未 Complete 句柄
return ToolRunStatus::Ok;
}
// id/name/enabled/properties/Get/Set … 按 §7.6.1
};
void register_demo_tools(ToolRegistry& r) {
r.Register("demo_scale_gray", [](std::string_view name) {
return std::make_unique<ScaleGrayTool>(name);
});
// 重复 Register 同 type_id → 拒绝
}
// 测试入口
ToolRegistry registry;
register_fake_tools(registry); // §7.6.3 清单
register_demo_tools(registry);
auto* t = registry.Create("demo_scale_gray", "S");
assert(t);
auto missing = registry.Create("no_such_type", "X");
// 期望失败:UnknownTypeId
期望断言
| 项 | 期望 |
|---|---|
| 注册 | fake + demo 可 Create;未知 → UnknownTypeId |
| 共用 | .avtf 含 demo_scale_gray 时 Load 走同一工厂;禁止反序列化旁路 |
| 成像纪律 | 输入只读;输出稳定发布;H1/H2/H8 可单测 |
| 失败 | 未 Complete 输出不可被下游拉成成功 ReadOnly(H10) |
| 重复注册 | 同 type_id 第二次 Register 失败(生产默认) |
| 契约映射 | TC-REG-01…TC-REG-06;成像 TC-H2 / TC-H8 / TC-H10 |
对应文档章节
toolflow §7.6 / §7.4.1 / §3.4;cpp-headers; examples.md(工具读写像素片段)
目的
补齐 §9.3 / §10.1「嵌套仅经终端对外」:内层先组好再 AddTool 进外层;外层只链内层 Outputs/Inputs 终端;企图绑内层私有工具属性 → Encapsulation。证明嵌套 Run = 子流完整顺序表,对外仍是一个 ITool::Run。
前置
register_fake_tools:fake_acq、fake_passthrough、fake_constant(或等价)。- 无
.avtf亦可;内存建内层/外层即可(.avtf嵌套 schema 见 §6.6,本场景主路径为内存)。
步骤骨架(C++)
// 示意、非可编译;对齐 toolflow §9.3 / §9.4 嵌套示意 / §7.4.6 Encapsulation
auto inner = ToolFlow::Create("InnerLocate");
auto* acq = inner->AddTool(registry.Create("fake_acq", "Acq"));
auto* pass = inner->AddTool(registry.Create("fake_passthrough", "Share"));
inner->AddOutput("FixturedImage", TypeDesc{Type::ImageGray8});
inner->Link(acq, "OutputImage", pass, "InputImage");
inner->Link(pass, "OutputImage", inner->Output("FixturedImage"));
assert(inner->Validate(/*diags*/));
auto outer = ToolFlow::Create("OuterMeasure");
auto* locate = outer->AddTool(std::move(inner)); // 嵌套;所有权转入 outer
auto* flag = outer->AddTool(registry.Create("fake_constant", "WidthFlag"));
outer->AddOutput("Preview", TypeDesc{Type::ImageGray8});
outer->AddOutput("Flag", TypeDesc{Type::Int32});
// 合法:外层只链内层终端
outer->Link(locate->Output("FixturedImage"), outer->Output("Preview"));
// 或:outer->Link(outer->Tool("InnerLocate")->Output("FixturedImage"), …);
flag->SetProperty("Value", Value::Int32(1));
outer->Link(flag, "Output", outer->Output("Flag"));
std::vector<Diagnostic> diags;
assert(outer->Validate(diags));
RunResult r = RunSession(outer.get()).Run();
// 期望:r.ok;Preview / Flag 可读;内层私有 Acq 属性对外不可见
// --- Encapsulation 负例(勿留在成功配方)---
// outer->Link(…, /* 指向 */ "InnerLocate.Acq.OutputImage", …);
// 或 Validate 时发现外层 link 写内层私有路径
// → DiagnosticCode::Encapsulation(Severity::Error);不留下半条边
期望断言
| 项 | 期望 |
|---|---|
| 合法嵌套 | Validate 无 Error;r.ok==true;外层 Outputs 可读 |
| 封装 | 外层仅经内层终端链接;内层私有工具属性不可被外层 Link |
| Encapsulation | 绑内层私有路径 → DiagnosticCode::Encapsulation;无残链 |
| 嵌套 Run | 子流按自身列表序完整执行;对外一次 ITool::Run |
| 契约映射 | TC-RUN-06 / TC-LNK-04 / TC-REG-05 |
对应文档章节
toolflow §4.3(嵌套 Run)/ §9.3 / §9.4 嵌套示意 / §6.6 / §7.4.6 / §10.1(终端封装)
目的
将已进 §10.1 / Diagnostic 但 S01–S08 未独立成文的两项升格为清晰主断言:(1)源工具 disabled → 下游 Run 拉值 SourceDisabled(MVP 严格);(2)端点缺失 / 方向非法 → InvalidBinding(从 S04 附带升格,不与 UnknownTypeId / MissingRequiredProperty 混轨)。
前置
- fake:
fake_acq+fake_passthrough(或fake_constant)。 - 可内存建流;InvalidBinding 亦可经残缺
.avtflinks 触发(§6.6.4)。
步骤骨架(C++)
// --- A) SourceDisabled ---
auto flow_d = ToolFlow::Create("S09b_Disabled");
auto* acq = flow_d->AddTool(registry.Create("fake_acq", "Acq"));
auto* pass = flow_d->AddTool(registry.Create("fake_passthrough", "Pass"));
flow_d->AddOutput("Image", TypeDesc{Type::ImageGray8});
flow_d->Link(acq, "OutputImage", pass, "InputImage");
flow_d->Link(pass, "OutputImage", flow_d->Output("Image"));
acq->set_enabled(false); // 或 flow 级 disabled 集合;语义对齐 §4.3
assert(flow_d->Validate(/*diags*/)); // 结构合法;禁用本身不必然 Validate 失败
RunResult rd = RunSession(flow_d.get()).Run();
// 期望:rd.ok==false;diagnostics 含 DiagnosticCode::SourceDisabled;
// Pass 不得假成功交出 Image;下游不得读到「假装有效」的帧
// --- B) InvalidBinding(主断言)---
auto flow_b = ToolFlow::Create("S09b_BadBind");
auto* c = flow_b->AddTool(registry.Create("fake_constant", "C"));
flow_b->AddOutput("Out", TypeDesc{Type::Int32});
// 端点缺失:属性名不存在 / 方向非法(输出←输出、输入→输入)/ 残缺 link
// flow_b->Link(c, "NoSuchProp", flow_b->Output("Out"));
// 或 Load("fixtures/invalid_binding.avtf") 后 Validate
std::vector<Diagnostic> diags;
bool ok = flow_b->Validate(diags); // 或 Link 立即失败
assert(!ok);
assert(std::any_of(diags.begin(), diags.end(), [](const Diagnostic& d) {
return d.code == DiagnosticCode::InvalidBinding
&& d.severity == Severity::Error;
}));
// 不留下半条边;与 TypeMismatch / OrderViolation / Encapsulation 分码
期望断言
| 项 | 期望 |
|---|---|
| SourceDisabled | Run 拉值失败;码 SourceDisabled;下游无假成功 Outputs |
| 禁用目标 | 以其为目标的链接本轮不写入(§4.3);本场景主断言在「源禁用」 |
| InvalidBinding | 端点缺失/方向非法/残链 → InvalidBinding Error;无残链 |
| 分轨 | 不与 UnknownTypeId(Load)/ MissingRequiredProperty(Validate 缺配置)/ TypeMismatch / Encapsulation 混用同一码 |
| 契约映射 | TC-LNK-06 / TC-LNK-05 |
对应文档章节
toolflow §4.3 / §7.4.2 / §7.4.6 / §6.6.4 / §10.1(禁用源);S02 负例指针升格至此
目的
(1)文件不可读 / 截断 → Load/Save IoFailure(补齐 Diagnostic 表有码、场景正文缺席)。(2)停止矩阵独立步骤:宿主停相机 不自动 RequestCancel 在途 Run(可并入本场景;与 S06 Cancel/Busy 分测,避免只写在断言附注里)。
前置
- 夹具:截断/权限错误的
.avtf路径;或 Save 到不可写路径(示意)。 - 停相机:采集入口可用 fake/桩(无真实相机 SDK 证据要求);Run 在途可用
fake_busy_probe/fake_cancel_check。
步骤骨架(C++)
// --- A) IoFailure ---
auto truncated = ToolFlow::Load("fixtures/truncated.avtf", registry);
assert(!truncated);
// truncated.error() 诊断含 DiagnosticCode::IoFailure(Severity::Error)
// 对照:未知 type → UnknownTypeId;过高 schema → SchemaTooNew;本项为 IO/截断
// auto save_fail = flow->Save("/root/no_write/out.avtf"); // 示意
// 期望:失败;IoFailure
// --- B) 停相机 ≠ 自动 Cancel(停止矩阵独立步骤)---
auto flow = ToolFlow::Create("S09c_StopCam");
auto* slow = flow->AddTool(registry.Create("fake_busy_probe", "Slow"));
// … 合法最小链 …
RunSession session(flow.get());
// 线程1:RunResult r = session.Run(); // 在途
// 宿主:StopCamera() / CloseAcquisition(); // 采集层;见 camera-acquisition / session-lifecycle
// 期望:session **未**被自动 RequestCancel;r 可跑至工具边界正常结束(或宿主再显式 Cancel)
// 诊断:不得仅因停相机而出现 Cancelled(除非宿主显式既停采集又 RequestCancel)
期望断言
| 项 | 期望 |
|---|---|
| IoFailure | 截断/不可读 Load 或不可写 Save → IoFailure;无半初始化成功流 |
| 分轨 | IoFailure ≠ SchemaTooNew ≠ UnknownTypeId |
| 停相机 | 不自动 RequestCancel;在途 Run 继续至边界或自行 Cancel |
| 显式双停 | 宿主若要两端都停,须显式停采集 + RequestCancel(文档断言,非隐式) |
| 与显示 | 显示关闭仍不停编排(TC-CTL-05;本场景主钉停相机条) |
| 契约映射 | TC-SER-07 / TC-CTL-04 |
对应文档章节
toolflow §7.4.3 停止矩阵 / §7.4.6 / §10.1(T9);session-lifecycle;camera-acquisition(薄);S06 附注升格
目的
可选补齐 §9.2 心智:外层 Job/流内两个子 ToolFlow(Locate → Defect)仅经 终端 串联;列表序先 Locate 再 Defect;非嵌套私有属性、非 DAG。与 S09a(真嵌套封装)对照:本场景是「两流并列 + 终端链接」。
前置
- fake 足以冒充 Locate/Defect 对外终端(如内部分别用
fake_acq+passthrough /fake_constant)。 - 可全部内存组装;不要求编辑 UI。
步骤骨架(C++)
// 示意:两子流先各自 Validate,再放入外层列表并链终端(§9.2)
auto locate = ToolFlow::Create("Locate");
// … AddTool fake_acq → passthrough;AddOutput FixturedImage / Pose(示意)…
assert(locate->Validate(/*…*/));
auto defect = ToolFlow::Create("Defect");
defect->AddInput("Image", TypeDesc{Type::ImageGray8});
// … 内部 fake 检测;AddOutput OkNg / DefectCount …
assert(defect->Validate(/*…*/));
auto job = ToolFlow::Create("Job");
auto* loc = job->AddTool(std::move(locate));
auto* def = job->AddTool(std::move(defect));
job->Link(loc->Output("FixturedImage"), def->Input("Image"));
// 可选:Pose 终端同理
job->AddOutput("DefectCount", TypeDesc{Type::Int32});
// job->Link(def->Output("DefectCount"), job->Output("DefectCount"));
assert(job->Validate(/*…*/));
RunResult r = RunSession(job.get()).Run();
// 期望:执行序 Locate → Defect;r.ok;终端赋值生效;无环边
期望断言
| 项 | 期望 |
|---|---|
| 序 | 外层列表:Locate 在 Defect 之上;先 Locate.Run 再 Defect.Run |
| 终端 | 只链子流 Inputs/Outputs;不绑子流内部私有工具属性(否则走 S09a Encapsulation) |
r.ok | true;Outputs 符合链接 |
| 非 DAG | 链接不是边就绪调度 |
| 契约映射 | TC-RUN-07 / TC-LNK-01 / TC-REG-05 |
对应文档章节
toolflow §9.2 / §4.1 / §10.1;与 S09a(§9.3 嵌套)对照
目的
将 output-inspection.md T10 已拍板集合事件与观察不变量收成可跑骨架(fake tools 即可):SnapshotReplaced / EmptySnapshot(禁静默)/ Unavailable / Evicted;失败保留上次成功;Subscribe + replay;查看者寿命 vs Evict;KeepLatest vs ReleaseAfterRouting;观察永不驱动 Run(对齐 TC-RUN-05 / TC-OUT-09)。不改事件闭集与 toolflow v1.9 语义。
前置
register_fake_tools:至少fake_constant/fake_passthrough/fake_fail_once(或等价空输出 / 失败替身)。- 会话可配置
KeepLatest与ReleaseAfterRouting(示意开关;非 UI)。 - 观察面示意 API 见 output-inspection(
SubscribeOutputs/TryOpenLatest);非已发布 ABI;无观察 UI 专文。
步骤骨架(C++)
// 示意、非可编译;对齐 output-inspection T10 / test-contracts TC-OUT-*(Arden · R9)
using namespace vyntek::toolflow;
ToolRegistry registry;
register_fake_tools(registry);
// --- A: KeepLatest ---
auto flow = ToolFlow::Create("S10_OutObserve");
auto* c = flow->AddTool(registry.Create("fake_constant", "Const"));
c->SetProperty("Value", Value::Int32(1));
flow->AddOutput("Out", TypeDesc{Type::Int32});
flow->Link(c, "Output", flow->Output("Out"));
// session_opts.retain = KeepLatest; // 示意
assert(flow->Validate(/*…*/));
RunSession session(flow.get());
std::vector<OutputSetEventKind> seen;
auto sub = session.SubscribeOutputs(
[&](const OutputSetEvent& e) {
seen.push_back(e.kind);
// 禁止:在回调内 session.Run() / 重新采集 / 隐式链接赋值
},
/*replay_latest=*/true);
RunResult r1 = session.Run(); // 非空成功 → 期望 SnapshotReplaced
// 配置空成功替身(示意):c->SetProperty("EmitEmpty", true);
RunResult r_empty = session.Run(); // 空成功 → 必须 EmptySnapshot(禁静默)
// 失败保留:先成功再 fail_once
auto* f = flow->AddTool(registry.Create("fake_fail_once", "Fail"));
// … 使本轮失败 …
RunResult r_fail = session.Run(); // 不替换上次成功快照;可与失败状态并存
// Unavailable:模拟保留/配额失败(观察侧注入,示意)
// expect Unavailable;业务路由仍可 ok,但不得把旧集合冒充本轮新结果
// Evict + 查看者寿命
auto opened = session.TryOpenLatest(/*tool/port 示意*/);
session.EvictRetained(/*…*/); // 或预算回收 → Evicted
// opened 仍可用至自行关闭;TryOpenLatest 对新身份不可用
sub.reset(); // 断开只释放订阅,不清空运行时最近输出(KeepLatest)
// --- B: ReleaseAfterRouting ---
// session_opts.retain = ReleaseAfterRouting;
// Run 后路由完成即不保留;再 Subscribe 无法补取已回收历史
期望断言
| 项 | 期望 |
|---|---|
| SnapshotReplaced | 非空成功 → 发该事件;当前集合身份可打开 |
| EmptySnapshot | 空成功 → 必须发事件;禁止静默;不得把上次非空标为当前 |
| Unavailable | 保留失败 → 该事件;不冒充本轮新结果 |
| Evicted | 淘汰 → 该事件;其后 TryOpen 不可用 |
| 失败保留 | 工具失败不替换上次成功快照(TC-OUT-05 / TC-RUN-05) |
| Subscribe+replay | replay_latest 重放当前保留(含空);之后不漏更新 |
| 查看者 vs Evict | 已打开查看者不被强制关;新 Open 不可用 |
| KeepLatest vs ReleaseAfterRouting | 前者可重放;后者不冒充可恢复已回收历史 |
| 永不驱动 Run | 观察回调/查询不隐式 Run(TC-OUT-09) |
| 契约映射 | TC-OUT-01…TC-OUT-09;交叉 TC-RUN-05 |
对应文档章节
output-inspection( T10); test-contracts.md §7 TC-OUT-*( Arden · R9);toolflow §7.4.3 / §10.1 非编排驱动;S03 薄指针
目的
将 camera-acquisition.md / session-lifecycle.md 已拍板散文收成可跑骨架( fake SDK / 假入口即可):持帧 ImportNative vs 回调内 Copy;TryPostOwned 接管/拒绝/抛错;满池与入口预算;归还泵与 ReturnCompleted;Latest 预览;回调不堵 SDK 归还;停流状态机结束条件;外保留 ≠ 假称已归还;超时未排空但底层续清;ReturnFailed / 断线可观察;两端都停须显式 Cancel + 停采集。不改 Diagnostic / T9 停止矩阵语义与 toolflow v1.9。
前置
- fake 采集适配:可切换「持帧稳定」与「回调临时指针」两种模式;假池容量与入口预算可注入满载。
- 假归还泵:可延迟确认、注入
ReturnFailed、模拟断线。 - 可选:
fake_acq仅作 ToolFlow 侧对照(本场景主断言在采集/会话层,不扩编排语义)。 - 示意 API 非已发布 ABI;无真实供应商驱动要求。
步骤骨架(C++)
// 示意、非可编译;对齐 camera-acquisition / session-lifecycle / test-contracts TC-ACQ-* · TC-SESS-*(Arden · R10)
using namespace vyntek::imaging;
// --- A: 持帧 ImportNative ---
FakeSdkSession sdk(/*hold_frame=*/true);
auto img = adapter.ImportNativeReadOnly(sdk.CurrentFrame()); // TC-ACQ-01
bool ok = input.TryPostOwned(std::move(img)); // TC-ACQ-03
assert(ok);
// Share 不重复 AddRef SDK;关闭最后句柄 → releaseAsync → 泵上 ReturnCompleted(TC-ACQ-05)
// --- B: 回调内 Copy ---
FakeSdkSession sdk2(/*hold_frame=*/false);
sdk2.OnFrame([](void* p, size_t n) {
// 先取池+入口预算(TC-ACQ-04);满则拒绝,禁止先拷再发现无容量
auto owned = PixelImage::CopyFrom(p, n);
if (!input.TryPostOwned(std::move(owned))) { /* 关闭未接管 */ }
// 按协议立即归还临时 SDK 缓冲(TC-ACQ-02)
// 禁止在回调内等 UI / 算法 / 同线程 ReturnCompleted(TC-ACQ-07)
});
// --- C: Latest 预览 ---
for (int i = 0; i < 10; ++i) { /* 连续 Post */ }
// Latest 槽只保留最新;被替换项立即关闭;禁止无限 Post 带图 dispatcher(TC-ACQ-06)
// --- D: 停流结束条件 ---
session.BeginStop(/*drain=*/true); // Quiescing → … → Stopped(TC-SESS-01)
// 无新回调、队列空、会话拥有引用已关、SDK 归还完成
// --- E: 外保留 ---
auto exported = host.ExportToExternalWindow(latest); // 会话外保留
auto r = session.BeginStop(/*destroy_sdk=*/true);
// expect:报告仍有外部保留;不得假称设备已归还(TC-SESS-02)
// --- F: 超时续清 ---
auto r_to = session.BeginStop(/*timeout=*/50ms);
// 返回未排空/故障摘要;底层清理仍继续(TC-SESS-03)
// --- G: ReturnFailed / 断线 ---
sdk.InjectReturnFailed(); // 或 InjectDisconnect()
// 可观察故障;不自动再 Release;泵继续其他项;停止结果汇总(TC-SESS-04)
// --- H: 两端都停 ---
// 宿主要相机与 Run 一起停:必须显式
acquisition.StopAccepting(); // 停采集
run_session.RequestCancel(); // 显式 Cancel(TC-SESS-05 / TC-CTL-04)
// 禁止假设一次 SDK.Stop = 三层全停
期望断言
| 项 | 期望 |
|---|---|
| ImportNative | 持帧路径稳定 ReadOnly;Share 不重复 AddRef;一次 Release(TC-ACQ-01) |
| 回调 Copy | 临时指针路径必须 Copy;回调后不持原地址(TC-ACQ-02) |
| TryPostOwned | 成功接管 / 拒绝关闭 / 抛错未接管(TC-ACQ-03) |
| 满池预算 | 先预算再拷;满则拒绝可观察(TC-ACQ-04) |
| 归还泵 | ReturnCompleted 仅 SDK 确认后;失败不吞、不自动重试(TC-ACQ-05) |
| Latest | 有界最新槽;替换即关;无无限带图 Post(TC-ACQ-06) |
| 回调不堵 | 回调不等 UI/算法/同线程归还(TC-ACQ-07) |
| 停流结束 | 状态机到 Stopped 的联合条件满足(TC-SESS-01) |
| 外保留 | 不得假称已归还(TC-SESS-02) |
| 超时续清 | 超时返回未排空;底层继续(TC-SESS-03) |
| 故障可观察 | ReturnFailed/断线汇总;泵不卡死(TC-SESS-04) |
| 两端都停 | 显式 Cancel + 停采集(TC-SESS-05) |
| 契约映射 | TC-ACQ-01…07;TC-SESS-01…05;交叉 TC-CTL-04 |
对应文档章节
camera-acquisition.md; session-lifecycle.md; test-contracts.md §8–§9( Arden · R10); host-integration.md §4「两端都停」;toolflow §7.4.3( T9,不改语义);S09c 薄指针
目的
将 memory-management.md 已拍板散文收成可跑骨架( fake buffer 即可):工厂接管点;最后引用与 Returning;禁止持最后引用等自身 ReturnCompleted;pin 延寿与写 pin 阻 Complete;读 pin 永不提升为可写;OpenRead/OpenWrite 取消与 Share/Dispose 原子裁定;未 Complete→Invalid;首次根范围与 ROI 中止整实体;Invalid 仅无期望版本完整恢复;releaseAsync 恰好一次、一项卡住不堵其他、ReturnFailed 不自动重试不回池。不改 Diagnostic / T9 停止矩阵与 toolflow v1.9。
前置
- fake
MemoryPool/ fake nativerelease回调;可注入抛错、延迟归还、ReturnFailed。 - 可构造多 handle、排队读/写申请、读/写 pin。
- 类型化首次根范围断言可薄指 image-model.md;本场景主断言在存储协调层。
- 示意 API 非已发布 ABI;无真实供应商驱动要求。
步骤骨架(C++)
// 示意、非可编译;对齐 memory-management / test-contracts TC-MEM-*(Arden · R11)
using namespace vyntek::imaging;
// --- A: 工厂接管 ---
auto rented = ImageBuffer::Rent(4096); // TC-MEM-01 成功接管
// TakeOwnership 抛错路径:owner 仍由调用方释放(TC-MEM-02)
// ImportNative 抛错路径:不调 release(TC-MEM-03)
// --- B: 最后引用 ---
auto h1 = rented.Share();
auto h2 = rented.Share();
h1.Dispose(); // 仍 Active(TC-MEM-04)
// 关闭全部 handle + 访问 + 排队申请后才 Returning
// 禁止:持最后引用边 AwaitReturnCompleted(TC-MEM-05)
// --- C: pin ---
auto reader = rented.OpenRead().value();
auto pin = reader.Pin();
reader.Dispose(); // pin 仍阻写/归还;pin.access()==ReadOnly(TC-MEM-06)
pin.Dispose();
auto writer = rented.OpenWrite().value();
auto wpin = writer.Pin();
// writer.Complete() → InvalidOperation(写 pin 未放)(TC-MEM-07)
wpin.Dispose();
writer.Complete();
// --- D: 授予/取消与 Share/Dispose ---
// OpenRead/OpenWrite + CancelToken;发起 handle Dispose 不撤已入队申请(TC-MEM-08)
// Share 与 Dispose 竞争:成功子对象独立有效(TC-MEM-09)
// --- E: 初始化 / Invalid ---
{
auto w = ImageBuffer::Rent(4096).OpenWrite().value();
w.Dispose(); // 未 Complete → Invalid(TC-MEM-10)
}
// 类型化首次仅写 ROI → 拒绝;ROI 中止 → 整实体 Invalid(TC-MEM-11)
// Invalid 后带 expectedVersion 的 OpenWrite 拒绝;无期望版本完整写可恢复(TC-MEM-12)
// --- F: 归还隔离 ---
// 两实体并行归还;其一 releaseAsync 卡住/失败
// expect:恰好一次 release;不串行堵另一项;ReturnFailed 不自动重试、不回池(TC-MEM-13)
期望断言
| 项 | 期望 |
|---|---|
| Rent | 成功接管;失败归还内部;ReadOnly+Uninitialized 拒绝(TC-MEM-01) |
| TakeOwnership | 成功才接管;抛错不释放调用方 owner(TC-MEM-02) |
| ImportNative | 成功才接管;抛错不调 release(TC-MEM-03) |
| 最后引用 | 全 handle+访问+排队结束才 Returning(TC-MEM-04) |
| 等待环 | 禁持最后引用等自身 ReturnCompleted(TC-MEM-05) |
| 读 pin | 父 reader Dispose 后仍阻写/归还;Access 永 ReadOnly(TC-MEM-06) |
| 写 pin | 未放则 Complete 拒绝;父先 Dispose→Invalid 但独占至末 pin(TC-MEM-07) |
| 取消/排队 | cancel 可撤等待;发起 handle Dispose 不撤已入队(TC-MEM-08) |
| Share/Dispose | 原子裁定;成功子对象独立有效(TC-MEM-09) |
| 未 Complete | Dispose→Invalid;不重发旧代次(TC-MEM-10) |
| 根范围/ROI | 首次须根范围;ROI 中止整实体 Invalid(TC-MEM-11) |
| 恢复 | 无期望版本完整写可恢复;带 expected 拒绝 Invalid(TC-MEM-12) |
| 归还隔离 | release 一次;卡住不堵其他;ReturnFailed 不重试不回池(TC-MEM-13) |
| 契约映射 | TC-MEM-01…13 |
对应文档章节
memory-management.md; image-model.md(首次根范围 / padding 薄指); test-contracts.md §10( Arden · R11);S05 句柄交接薄指针(H1–H10 不替代本表)
目的
将 image-display.md 已拍板显示契约收成可勾选路径:登记≠授予、Share 不接管、Retain/Snapshot/Detach、四回执互不等价、失败保留旧图、四格式取样、ROI/负步长、卸载/重挂/context、预算失败不丢旧图、显示只观察。仅 C# 显示宿主;像素经绑定自 C++ vyntek::imaging。勿写成 C++ UI;≠ T15 开放风险 R10(C++ UI·已关闭)。
前置
- 绑定层可将 C++
PixelImageHandle投影为IPixelImage(及 Gray8/Rgb24/Bgr24/Depth16)。 - UI 线程可用;C#
ImageDisplay实例已创建(见 csharp-binding / examples 显示段)。 - fake / 测试图像可提供四格式、ROI、负步长与可写/只读两态(无需真机相机)。
步骤骨架(C# · 仅显示段)
// 示意、非可编译;对齐 image-display / test-contracts TC-DISP-*(Arden · R12)
// 像素已由绑定层从 C++ 句柄投影为 IPixelImage
var update = viewer.SetImage(image); // TC-DISP-01:登记+Share;未承诺已 OpenRead
// 调用方随后可关闭自有 handle;控件不接管(TC-DISP-02)
var retain = viewer.SetImage(rwImage, ImageInputRetention.RetainInput);
// 原地写须等读者退出(TC-DISP-03)
var snap = viewer.SetImage(rwImage, ImageInputRetention.Snapshot);
await snap.Completion; await snap.InputReleased; // 释放后原图可写;屏幕可仍旧图(TC-DISP-04)
var detach = await retain.DetachInputAsync(); // Released 或 CpuBudgetExceeded 不毁当前(TC-DISP-05)
// Completion / InputReleased / ImageRendered / ReturnCompleted 分别断言,互不等价(TC-DISP-06)
// 注入 VersionMismatch / UnsupportedImage → Failed,旧画面保留(TC-DISP-07)
viewer.SetImage(gray); pixel.TryGetGray8(...); // 仅相符格式成功(TC-DISP-08)
viewer.SetImage(rgb); pixel.TryGetRgb24(...); // 无隐式转换
viewer.SetImage(bgr); pixel.TryGetBgr24(...);
viewer.SetImage(depth); pixel.TryGetDepth16(...);
viewer.TryGetPixel(viewPos, out var p); // ROI/负步长→逻辑坐标(TC-DISP-09)
// 卸载 → 取消候选;重挂载仍有效 Source = 新请求(TC-DISP-10)
// 预算不足 → Failed + 保留旧图(TC-DISP-11)
// Dispose / ClearImageAsync → 不停 Run、不停相机(TC-DISP-12 / TC-CTL-05)
期望断言
| 项 | 期望 |
|---|---|
| 登记≠授予 | SetImage 返回即已 Share;Installed/OpenRead 在 Completion 路径(TC-DISP-01) |
| Share 不接管 | 控件不 Dispose 调用方 handle;多控件同图合法(TC-DISP-02) |
| RetainInput | 持稳定读;原地写等待退出;属性不改当前存储(TC-DISP-03) |
| Snapshot | InputReleased 后不阻原图写;Source 不置空(TC-DISP-04) |
| Detach | Released 保持来源身份;预算不足不毁当前(TC-DISP-05) |
| 四回执 | Completion ≠ InputReleased ≠ ImageRendered ≠ ReturnCompleted(TC-DISP-06) |
| 失败保留 | Failed/VersionMismatch 保留旧画面;无信息混叠(TC-DISP-07) |
| 四格式 | TryGet* 无隐式转换;Depth 无效码/米制规则正确(TC-DISP-08) |
| ROI/负步长 | 逻辑像素取样;禁止 viewPos 当缓冲偏移(TC-DISP-09) |
| 卸载/重挂 | 取消候选;重挂=新请求;context 可恢复(TC-DISP-10) |
| 预算 | 失败不丢旧图;释放本次引用;无自动重试队列(TC-DISP-11) |
| 只观察 | 卸图/Dispose 不停编排/不停相机(TC-DISP-12 / TC-CTL-05) |
| 契约映射 | TC-DISP-01…12;交叉 TC-CTL-05 |
| 边界 | 仅 C#;无 C++ UI;≠ T15 R10;不扩 T9 |
对应文档章节
image-display.md; examples.md 显示段; test-contracts.md §11( Arden · R12); host-integration.md TC-CTL-05;S05/S07 显示段薄指针(不替代本表)
使用说明(给实现 / 验收)
- 示意非可编译:骨架钉住 API 形状与断言;落地时以公开头与单测为准。树形碎片对照见 sketch/README.md(R5,非验收正文)。
- 主路径覆盖:S01–S08 为八条主路径; S09a–d(R6) 补齐嵌套/Encapsulation、SourceDisabled、InvalidBinding、IoFailure、停相机≠自动 Cancel,以及可选 §9.2 两流串联; S10(Arden · R9) 对齐 T10 输出观察可勾选; S11(Arden · R10) 对齐阶段 D 采集/会话可勾选; S12(Arden · R11) 对齐阶段 B 存储/pin/归还可勾选; S13(Arden · R12) 对齐阶段 F 显示集成可勾选(仅 C#;≠ T15 R10)——不改 v1.9 语义与 Diagnostic 码表 / T9 停止矩阵 / T10 事件闭集。
- 验收勾选:全文契约见 test-contracts.md(R4 + R6 + Arden · R9 + Arden · R10 + Arden · R11 + Arden · R12 映射,
TC-*/TC-OUT-*/TC-ACQ-*/TC-SESS-*/TC-MEM-*/TC-DISP-*);与 toolflow §10.1 / T13 生命周期对照( Arden · R13 索引已对齐 OUT/ACQ/SESS/MEM/DISP + S10–S13);场景骨架映射见契约表 §12;薄指针见 validation-and-roadmap.md。不以本文代替契约表勾选正文。 - 禁止:第二执行引擎;C++ UI;MPF/旧 DAG 链接;把 ImageDisplay 示例改成 C++;不写覆盖率 KPI;不扩观察 UI / 脚本 / IPC / ABI。
文档状态:场景规划草案(2026-10-21,Asia/Shanghai),对齐 toolflow v1.9 / R4 契约表 / R6 S09a–d / R7 S07↔host-integration / R8 导读索引 / Arden · R9 S10↔TC-OUT- / Arden · R10 S11↔TC-ACQ-·TC-SESS- / Arden · R11 S12↔TC-MEM-* / Arden · R12 S13↔TC-DISP-(显示仅 C#;≠ T15 R10)/ Arden · R13 §10.1 T13 索引对齐(本批横切收束)。实施以代码与契约表 / §10.1 勾选为准。
本仓对拍
| 场景 | 本仓 |
|---|---|
| S07 无 UI 入口 | examples/headless_run.cpp 只走到 passthrough Run + RequestCancel |
| S10–S13 | 规格与薄摘在本站;Controls.Tests / smoke 是外形,未验收 |
| S01–S06 / S08 / S09 | 无成套示例工程 |