Chapter 4 逻辑组件

逻辑组件是我们编写业务代码的地方。

internal class HeartBeatLogicet : LogicetBase
{
    public HeartBeatLogicet(IReadOnlyList<ITagChannel> channels, ITagGrp tags, int robot) 
        : base(channels, tags)
    {
        this.Robot = robot;
        this._heartbeatReq = this.Tags.SelectTag("PLC/信号区-1#/心跳请求");
        this._heartbeatAck = this.Tags.SelectTag("MST/信号区-1#/心跳响应");
    }

    public override int Order => 100;

    public int Robot { get; }

    private ITag _heartbeatReq;
    private ITag _heartbeatAck;

    public override bool MatchEntry(ITagGrp entry) => entry.Name == "信号交互";

    public override Task ProcessAsync(ITagGrp entry, ITagChannel? thisChannel)
    {
        this._heartbeatAck.Value = this._heartbeatReq.GetTagValue<bool>();
        return Task.CompletedTask;
    }
}

组件编写

逻辑组件并不一定要继承自LogicetBase,只需要实现ILogiect即可。LogicetBase本身只是一层非常薄的抽象类,避免让你写规定了的无聊属性。

  • MatchEntry(ITagGrp entry): 用来表示本逻辑组件能否在指定入口 Loop 中执行。
  • Order: (如果能被执行的话)本逻辑组件在的执行顺序。序号越小,越会被优先执行。
  • ProcessAsync(ITagGrp entry, ITagChannel? thisChannel): 具体的业务逻辑。

逻辑组件的实例一旦被创建,在整个ITagsProject的生命期内都会被重用。

注册

逻辑组件需要注册到测点项目中,才能被执行。

手动注册

proj.Logicets.Add(new HeartBeatLogicet(proj.Channels, proj.Tags, 1));

自动注册

<root>
    <!-- ... -->

    <!-- 动态指定程序集 -->
    <Logicet>Samples.Plugin1/Samples.Plugin1.dll</Logicet>
    <Logicet>Samples.Plugin2/Samples.Plugin2.dll</Logicet>
</root>

注意上面的逻辑组件,我们可以定义0~n个逻辑组件(<Logicet/>),只需指定*.dll文件路径,即可自动完成动态加载程序集、自动注册其中的ILogicet的公开(public)且具体(Concrete)的实现。

逻辑组件的动态管理

Itminus.Tags会在 ITagsProject 初始化时,动态加载相关程序集、并构造ILogicet实例;最后在Dispose()时,自动卸载不再需要的相关程序集。

开发者可以注册自己的 ILogicetCreator,来控制如何构造相关Logicet实例,如果未提供这样的自定义实现,Itminus.Tags会回退到默认实现。

组件的运行

还记得我们之前说过的运行机制吗?

针对每个入口(TagGrp),都有一个ITagGrpRunner执行 [扫描->处理 ->输出] loop:

  • 扫描: 从底层读取
  • 处理: 依次执行各LogicetProcessAsync(entry, thisChannel)
  • 输出: 向底层刷写

这里我有意模仿了PLC的工作原理,请注意这是一个重大抉择: 我们有意放弃了(也许存在的)性能提升和花活,来换取所有的逻辑组件都是串行轮询的。

注意:由于所有逻辑组件都是规定的顺序串行轮询的,所以,在任何时候,都应该让ProcessAsync(entry, thisChannel)这个方法尽早结束,否则,串行约束会让后续的逻辑组件长时间得不到执行。