第四章:Composition与连接——把模块拼起来

好,咱们进入实战环节了。前面几章我们把基础概念过了一遍,现在该动手了。这一章讲的是Composition——说白了,就是怎么把一个个小模块拼成一个大系统。

我在做第一个DaVinci项目时,最头疼的就是模块之间的连接。明明每个模块单独跑都没问题,一拼起来就各种报错。后来才发现,是Composition的层级设计出了问题。嗯,咱们今天就把它彻底讲透。

4.1 创建Composition——搭积木的第一步

Composition是什么?你可以把它想象成一个容器。这个容器里可以放各种SWC(软件组件),也可以放别的Composition。就像搭积木,大积木里套小积木。

创建Composition的步骤其实很简单:

  1. 在DaVinci Config中,右键点击你的项目
  2. 选择"New Composition"
  3. 给它起个名字(我建议用有意义的命名,比如"EngineControl_Comp")
  4. 选择类型——是Atomic还是Composition
我的习惯:顶层Composition我一般用"System_Comp"命名,下层按功能模块分。这样一看就知道谁是谁。

这里有个坑:Composition的命名不能有空格。我曾经有个同事用了"Engine Control Comp",结果编译时报了一堆莫名其妙的错误。找了半天才发现是空格的问题。

4.2 SWC实例化——让组件活起来

创建好Composition后,下一步就是往里面放SWC。这个过程叫实例化。

你想想看,SWC就像是一个类,实例化就是把它变成具体的对象。同一个SWC可以被实例化多次,每个实例都有自己的数据空间。

实例化的操作:

  • 在Composition的编辑界面,找到"SWC"选项卡
  • 拖拽你需要的SWC到Composition中
  • 给每个实例起一个唯一的名称(比如"SpeedSensor_Inst1")

重要:每个实例的名称在整个项目中必须唯一。我见过有人偷懒,两个实例用了同一个名字,结果运行时数据互相覆盖,查了三天才找到原因。

实例化时还要注意一点:SWC的端口类型。每个SWC都有输入输出端口,实例化后这些端口会暴露出来,供后续连接使用。

4.3 端口连接(Connector)——把线接上

好了,现在Composition有了,SWC实例也有了。接下来就是最关键的步骤——端口连接。

端口连接说白了就是画线。把A组件的输出端口,连到B组件的输入端口。但这里有很多讲究:

4.3.1 端口类型匹配

连接时,端口类型必须一致。比如:

端口A类型 端口B类型 能否连接
Sender-Receiver Sender-Receiver 可以
Client-Server Client-Server 可以
Sender-Receiver Client-Server 不行
注意:我曾经犯过一个错误——把Sender端口连到了Server端口。DaVinci Config不会报错,但生成的代码根本跑不起来。所以连接前一定要确认端口类型。

4.3.2 数据一致性

除了类型,还要看数据类型。比如端口A输出的是uint8,端口B输入的是uint16,虽然能连上,但会有精度损失。我建议:尽量保持数据类型一致

连接的操作步骤:

  1. 在Composition编辑界面,点击"Connector"工具
  2. 从源端口拖出一条线
  3. 连接到目标端口
  4. 系统会自动生成一个Connector对象

代码示例(生成的ARXML片段):

<CONNECTOR>
  <SHORT-NAME>Conn_SpeedToControl</SHORT-NAME>
  <CONNECTOR-PORT-REF>
    <PORT-REF>/System_Comp/SpeedSensor_Inst1/SpeedOut</PORT-REF>
  </CONNECTOR-PORT-REF>
  <CONNECTOR-PORT-REF>
    <PORT-REF>/System_Comp/Control_Inst1/SpeedIn</PORT-REF>
  </CONNECTOR-PORT-REF>
</CONNECTOR>

4.4 Composition层级设计——别把线搞乱了

这是我最想强调的部分。Composition的层级设计,直接决定了项目的可维护性。

我见过最糟糕的项目:所有SWC都放在一个Composition里,端口连接线密密麻麻,像一团蜘蛛网。别说维护了,看都看不清。

好的层级设计应该是这样的:

  • 顶层Composition:只放几个大的子Composition,比如"Engine_Comp"、"Transmission_Comp"、"Body_Comp"
  • 中间层Composition:每个子Composition里再细分,比如"Engine_Comp"里放"Sensor_Comp"、"Actuator_Comp"
  • 底层Composition:直接放具体的SWC实例

为什么要这样设计?说白了就是降低复杂度。每个层级只关注自己的事情,上层不用管下层的细节。

我的建议:每个Composition里的SWC实例不要超过10个。超过10个,就该考虑拆分了。这是我从无数次重构中总结出来的经验。

举个例子:

System_Comp (顶层)
├── Engine_Comp (中间层)
│   ├── Sensor_Comp (底层)
│   │   ├── SpeedSensor_Inst
│   │   └── TempSensor_Inst
│   └── Actuator_Comp (底层)
│       ├── Injector_Inst
│       └── Throttle_Inst
├── Transmission_Comp (中间层)
│   ├── GearControl_Inst
│   └── ClutchControl_Inst
└── Body_Comp (中间层)
    ├── LightControl_Inst
    └── DoorControl_Inst

你看,这样一分层,每个Composition的职责就清晰了。连接线也只存在于同一个层级内部,或者上下层级之间。不会出现跨三层以上的连接。

4.5 避坑指南

最后,分享几个我踩过的坑:

  • 不要用默认名称:DaVinci Config生成的默认名称像"Connector_1"、"Connector_2",根本看不出是连什么的。我习惯用"Conn_SpeedToControl"这种命名方式。
  • 注意端口方向:连接时一定要看清楚端口方向。我曾经把输出连到了输出,系统没报错,但数据就是传不过去。
  • 定期检查连接:项目大了以后,连接很容易出错。我每周都会用DaVinci Config的"Check Consistency"功能跑一遍,确保所有连接都是有效的。

嗯,这一章的内容就到这里。Composition和连接是DaVinci Config的核心,搞懂了这些,后面的内容就顺了。下一章我们讲运行时配置,到时候会用到今天学的这些知识。