LadderLogic框架
概述 / Overview
LadderLogic 是 Medulla 提供的 周期循环宿主。CartDefinition 插件用嵌套类 `: LadderLogic<MyCart>` + `[UseLadderLogic(IntervalMs = N)]` 声明一个定时执行的控制循环;框架按 N 毫秒间隔调用 `Operation(int iteration)`。多个循环可在同一 Cart 上共存,各自在独立线程。
LadderLogic is Medulla's cyclic-loop host. A CartDefinition plugin declares a timed loop via a nested class `: LadderLogic<MyCart>` plus `[UseLadderLogic(IntervalMs = N)]`; the framework calls `Operation(int iteration)` every N ms. Multiple loops can coexist on one cart, each on its own thread.
实现:`D:\src\M2\OfficialPlugins\CartActivator\LadderLogic.cs:7-272`,属性定义 `D:\src\M2\OfficialPlugins\CartActivator\Attrs.cs:26-36`。 Implementation: `D:\src\M2\OfficialPlugins\CartActivator\LadderLogic.cs:7-272`; attribute at `Attrs.cs:26-36`.
最小骨架 / Minimal skeleton
public class MyCart : CartDefinition
{
[AsUpperIO] public float vCmd;
[AsLowerIO] public float vEst;
public override void Init()
{
// connect to hardware here
}
[UseLadderLogic(IntervalMs = 50)]
public class Loop : LadderLogic<MyCart>
{
public override void Operation(int iteration)
{
// iteration == 0 on first tick after Init/restart
self.WriteToHw(self.vCmd);
self.vEst = self.ReadFromHw();
}
}
}
属性参数 / Attribute parameters
| 字段 / Field | 默认 / Default | 含义 / Meaning |
|---|---|---|
| `logic: Type` | (自动) | LadderLogic 子类(嵌套类自动绑定) |
| `scanInterval: int` (or `IntervalMs`) | 50 | tick 周期(ms) |
| `realTime: bool` | false | 保留,暂未使用 / reserved |
| `resume: bool` | true | 异常后自动重启循环 / auto-restart on exception |
| `resumeRetryDelay: int` | 1000 | 重启前等多久(ms) |
| `timeoutInterval: double` | 10000 | 保留 / reserved |
生命周期 / Lifecycle
CartDefinition.Init() ← 你的初始化
│
▼
CartDefinition.createLadders() ← 反射扫 [UseLadderLogic]
│
▼ 对每个属性 / for each attribute
实例化 LadderLogic<T>, 注入 self = this
│
▼
创建 MultimediaTimer + 工作线程
│
▼ 周期触发 / every scanInterval ms
Operation(iteration: 0) ← 第一次 / first tick
Operation(iteration: 1)
Operation(iteration: 2)
...
│
▼ 异常时 / on exception
Hedingben.ToastText(...)
如果 resume=true 等 resumeRetryDelay 后重启 / resume after delay
Tick 精度 / Tick precision
基于 MultimediaTimer(OS 决定,Windows 约 1–15 ms 分辨率)。 Based on MultimediaTimer; OS-dependent (~1–15 ms on Windows).
超时行为 / Overrun: 若 `Operation()` 耗时 > scanInterval,下一 tick 排队执行(不丢 tick)。长期超时会累积延迟。 If `Operation()` exceeds scanInterval, the next tick queues behind (no tick skipping). Sustained overrun accrues latency.
【注意】 不要在 `Operation()` 里阻塞 / sleep / 等 IO 超过 scanInterval。
Don't block / sleep / wait IO longer than scanInterval inside `Operation()`.
多 LadderLogic 共存 / Multiple loops
一个 CartDefinition 可以有多个 `[UseLadderLogic]` 属性,每个一对独立的线程 + Timer:
A CartDefinition can declare multiple loops, each on its own thread:
[UseLadderLogic(IntervalMs = 50)]
public class CtrlLoop : LadderLogic<MyCart> { ... } // motion control 50 Hz
[UseLadderLogic(IntervalMs = 200)]
public class BatteryLoop : LadderLogic<MyCart> { ... } // battery / telemetry 5 Hz
[UseLadderLogic(IntervalMs = 1000)]
public class DiagLoop : LadderLogic<MyCart> { ... } // health 1 Hz
线程之间共享 cart 字段;如果同一字段同时被多个 loop 读写,自己处理同步(lock / Interlocked)。 Threads share cart fields; if multiple loops touch the same field, synchronise (lock / Interlocked).
内置工具方法 / Built-in helpers
`LadderLogic<T>` 基类提供以下状态机助手(`LadderLogic.cs:50-272`): The `LadderLogic<T>` base class offers these state-machine helpers (`LadderLogic.cs:50-272`):
| 方法 / Method | 用途 / Use |
|---|---|
| `TriggerOnce(bool active, int millis, Action a)` | 信号持续 `millis` ms 后触发 `a()` 一次 / fire once after signal held `millis` ms |
| `WaitSignal(bool sig, int timeout)` | 等信号上升或超时 / wait for signal rising or timeout |
| `TriggerIfChanged<T>(Func<T> g, Action<T> h)` | 值变化触发 / fire when value changes |
| `FlipFlop<T>(ref T field, int millis, T[] vals)` | 周期切换值 / cycle values periodically (e.g. blinking LED) |
| `SwitcherTrigger(bool, Action, bool)` | 边沿触发一次 / edge-trigger one-shot |
| `ModSignal(Func<bool>, int delay, int span)` | 时间窗内信号检测 / time-window detection |
| `IsochronousFork(Action)` | "同步分叉",一次只跑一个 / one-at-a-time async fork |
| `PriorityActions` | 高优先级动作执行器 / priority action runner |
| `DaemonVal(float send, float read, float r, int t, Action<bool>)` | 多变量一致性看门狗 / multi-var consistency watcher |
用这些替代裸 `if (Math.Abs(now - lastEvent) > T) ...` 的散乱时间状态机。
Use these helpers to replace ad-hoc `if (Math.Abs(now - lastEvent) > T) ...` time state-machines scattered through the loop.
错误传播 / Error propagation
- `Operation()` 抛异常 → 框架捕获 → log 到 DLog + Hedingben toast `"Ladder broken: <msg>"` 在 UI 显示。
- 若 `resume = true`:等 `resumeRetryDelay` 后重新调用 `Operation(iteration: 0)`(iteration 重置)。
- 若 `resume = false`:循环退出,cart 进入"半工作"状态 —— 其它 loop 还在跑。
【发现】 `resume = true` 是默认值。一个 buggy 插件不会让整个进程崩,但会 无限重试 —— 务必在开发期密切看 Hedingben。
`resume = true` is the default. A buggy plugin won't crash the process but will silently retry forever — watch Hedingben during development.
iteration 参数 / iteration parameter
- `iteration == 0`:首次 tick 或异常重启后的首次 tick;框架自动清空内部跟踪状态(如 `TriggerOnce` 的计时器)。
- `iteration > 0`:递增;可用来做 "每 N tick 做一次"。
调试 / Debugging
- `Hedingben` toast 是最直接的报错通道 —— 总打开。
- DLog 有详细 stack trace 用 `$ladder` 主题。
- 用 `[IOObjectMonitor]` 暴露 `iteration` 或自定义计数器到 Medulla dashboard 看跳速。