AFSIM 2.9.0 深度解析:架构、原理与工程实践-第 1 章 项目全景与代码地图

AFSIM 2.9.0 深度解析:架构、原理与工程实践-第 1 章 项目全景与代码地图

1.0 导读:读懂 AFSIM 难在哪里

第一次打开 AFSIM 源码的人,通常会同时遭遇四个障碍。

第一,框架与模型混在一起。 目录里既有 WsfSimulation、WsfEvent 这样的"操作系统级"基础设施,也有 WsfRadarSensor、WsfWeapon 这样的领域模型,还有 WsfScenario、UtInput 这样的输入与配置设施。它们共享同一套命名空间、同一套对象生命周期,初看很难判断"哪些是必须理解的骨架,哪些是可以按需查阅的插件"。

第二,输入语言自成一派。 AFSIM 想定不是 JSON、YAML 或 C++ 代码,而是一种由命令(command)和块(block)构成的领域特定语言(DSL)。例如:

text 复制代码
platform frigate_01 WSF_SHIP
   side blue
   icon ship
   position 37.5n 122.3w
   heading 90 degrees
   speed 12 knots
   sensor surface_search RADAR
      mode search
         transmitter ...
      end_mode
   end_sensor
end_platform

这种语言的解析器不是通用的 parser generator 产物,而是手写的递归下降 + 命令注册表。想理解"一条命令最终落到哪个 C++ 函数",必须同时理解 WsfScenario、UtInput、类型注册表、组件工厂四条线。

第三,事件驱动被高度抽象。 AFSIM 的核心是离散事件仿真,但用户几乎看不到 WsfEvent。传感器调度、平台更新、通信传输、脚本定时器都被包装成事件。事件队列采用"时间 + 优先级 + FIFO"的稳定排序(WsfEventManager.hpp:86-100),同一时刻内多个事件的先后顺序会直接影响结果。不理解这一点,就无法解释"为什么调整一条命令的顺序会改变仿真结论"。

第四,扩展机制有多个层次。 应用扩展(Application Extension)、场景扩展(Scenario Extension)、仿真扩展(Simulation Extension)、插件(Plugin)、组件工厂、脚本类型、观察者,这些概念都能"加功能",但作用域、生命周期、注册时机完全不同。选错扩展点,代码能编译却拿不到想要的数据。

本章的目标不是立刻讲清每个细节,而是先建立一张可导航的地图:AFSIM 由哪些层组成,每层负责什么,依赖方向如何,关键抽象分别位于哪里,以及一个想定从文本到运行会穿过哪些模块。后续每一章会沿地图上的某条路线深入。

1.0.1 本章的学习目标

读完本章,你应该能够:

  1. 用自己的话解释 AFSIM 的定位:它不是"一个仿真软件",而是"仿真框架 + 领域模型库 + 应用工具链"的组合。
  2. 看到任意一个源文件路径,大致判断它属于哪一层、会被谁调用、能否被替换。
  3. 画出 Application → Scenario → Simulation → Platform → Part 的主对象树。
  4. 说出 core/wsf、core/wsf_mil、wsf_plugins、tools/util 四者的职责边界。
  5. 根据问题类型选择正确的源码入口:输入语法问题查 parser,运行时行为查 event/part,数据输出查 observer/event_pipe,分布式问题查 xio/dis。
  6. 识别常见的架构误读,例如"平台就是实体""脚本只是配置""插件等于动态库"。

1.0.2 本章不做什么

本章不会逐一讲解传感器方程、雷达探测概率、轨道递推或 DIS 报文格式------这些属于第二、三卷。本章只回答"在哪里"和"为什么这样分层",为后续的"怎么做"建立坐标系。


1.1 AFSIM 的定位与能力边界

1.1.1 名称与出身

AFSIM 全称 Advanced Framework for Simulation, Integration, and Modeling,中文通常译为"先进仿真、集成与建模框架"。它由美国空军研究实验室(AFRL)主导,波音公司等参与早期开发,源码中大量文件带有 2003--2015 年波音版权头,后续由 Infoscitex(DCS 公司)等持续维护。

从工程角度看,AFSIM 最准确的定位是:

一个以 C++ 实现的、面向平台级作战仿真的、可扩展的离散事件仿真框架,附带一整套军事领域模型、脚本语言、分布式接口和分析工具。

这句话里有四个关键词,缺一不可:

  • 平台级(platform-level):建模粒度是平台(飞机、舰船、导弹、卫星、地面站)及其部件(传感器、武器、通信设备、处理器),而不是单个士兵的生理状态,也不是纯战役级兵力池。
  • 作战仿真(mission-level engagement simulation) :关注探测、跟踪、交战、通信、任务分配、效能评估,而不是飞行器的六自由度气动细节(尽管有 wsf_p6dof、wsf_six_dof 等插件可提高保真度)。
  • 离散事件(discrete-event):状态只在事件发生的时刻变化,时间可以跳跃推进;这与固定步长的游戏循环有本质区别。
  • 可扩展(extensible):核心框架不包含"所有模型",而是提供类型系统、组件插槽、事件总线和脚本接口,让领域模型以库或插件的形式接入。

1.1.2 它提供的六类能力

把源码目录与官方架构文档(doc/user/architecture_overview.rst)对照,可以把 AFSIM 的能力归纳为六类。

(1)想定描述与装载。 用户用 AFSIM 输入语言描述平台类型、平台实例、传感器、武器、通信、处理器、路由、区域、脚本、输出等。WsfScenario 负责读入、展开、校验并持有这些"类型"数据;WsfSimulationInput 则保存运行期参数(开始时间、结束时间、随机种子、运行号等)。输入语言不是简单的键值对,它支持类型定义、继承、条件块、宏替换、延迟求值、脚本嵌入。

(2)仿真运行内核。 WsfSimulation 持有事件队列、平台列表、全局脚本上下文、随机数源、观察者集合、EM 管理器、通信网络管理器、地形接口等。它负责初始化平台、推进时间、分发事件、处理平台增删、协调部件开关。事件管理器 WsfEventManager 是一个以 (time, priority, sequence) 为键的优先队列,保证同一时刻的事件有确定顺序。

(3)平台与部件模型。 平台是容器,挂载运动器(mover)、传感器(sensor)、武器(weapon)、通信设备(comm)、处理器(processor)、燃料(fuel)、签名(signature)、视觉部件(visual part)等。每类部件都有"类型---实例"两级结构:类型在场景中定义一次,实例在平台中按名字引用并覆盖属性。这种设计让大规模想定可以复用模型定义。

(4)领域物理与战术模型。 core/wsf_mil 提供雷达、红外、光学、声学、电子战、武器、通信、IFF、激光等军事模型;core/wsf_space 提供轨道递推、星座、大气阻力、光照/阴影、发射与再入;core/wsf_cyber 提供网络攻防;core/wsf_l16 提供 Link 16 消息与终端;core/wsf_nx 提供非出口型的高保真模型(ALARM 雷达/大气、箔条、ESA 天线等);core/wsf_mtt 提供多目标跟踪;core/wsf_ripr 提供反应式任务分配与行为树决策框架。

(5)接口与数据出口。 core/wsf/source/dis 实现 IEEE 1278 DIS 接口,core/wsf/source/xio 实现 AFSIM 自有的 XIO 协议(用于 Warlock 等外部程序远程观察/控制仿真),core/wsf/source/event_pipe 把仿真事件序列化为二进制事件管道文件,WsfObserver 体系提供进程内发布-订阅,WsfEventOutput 提供人类可读的事件日志。

(6)应用与工具链。 mission 是批处理仿真主程序;warlock 是实时可视化与交互控制台;wizard 是想定编辑器;mystic 是事后分析与可视化;sensor_plot 绘制传感器覆盖;weapon_tools 分析武器包线;mover_creator 生成运动模型数据;evt_reader 读取事件日志;post_processor 做批量指标提取;engage 是交战级快速分析工具。它们共享 core 与 tools,但各自有独立的 UI 与业务流程。

1.1.3 它不做什么

理解边界比理解能力更重要。AFSIM 的常见误用包括:

  • 不是实时飞行模拟器。 它可以在实时模式下运行(WsfSimulation::SetRealtime、WsfRealTimeClockSource),但没有面向飞行员的高刷新率座舱、力反馈和 6DOF 操纵手感;高保真飞行动力学需要额外插件。
  • 不是物理第一性原理工具。 雷达探测、红外成像、通信链路都采用工程级模型与经验参数,追求"在可接受的算力下给出可信的相对趋势",而不是求解麦克斯韦方程组。
  • 不是想定数据本身。 源码仓库只带少量示例与测试想定;真实工程需要自建平台库、武器库、地形与情报数据。
  • 不是单一可执行程序。 mission 只是最常用的入口;Warlock、Wizard、Mystic 是不同应用,插件生态进一步扩展能力。
  • 不是"配置即完成"。 脚本可以表达复杂行为,但脚本不是万能胶;高性能或强类型逻辑仍应写成 C++ 部件。

工程启示:当你评估 AFSIM 是否适合某个问题时,先问三个问题:粒度是否是平台级?时间推进是否可以用事件表达?模型是否可以用工程参数描述?三者中有一个答案为"否",就需要评估插件或外部联合仿真,而不是硬套框架。


1.2 版本基线、许可与源码布局

1.2.1 版本信息从哪里来

版本定义集中在顶层 CMakeLists.txt:

cmake 复制代码
set(AFSIM_VERSION_MAJOR   "2")
set(AFSIM_VERSION_MINOR   "9")
set(AFSIM_VERSION_PATCH   "0")
set(AFSIM_VERSION_BUILD   "0")
set(AFSIM_VERSION         "${AFSIM_VERSION_MAJOR}.${AFSIM_VERSION_MINOR}.${AFSIM_VERSION_PATCH}")
set(AFSIM_RELEASE_DATE    "02-25-2022")
set(AFSIM_RELEASE_VERSION "${AFSIM_VERSION} ${AFSIM_RELEASE_DATE}")

对应源码见 src/CMakeLists.txt:70-76。CMake 会把版本写入生成的头文件 wsf_version_defines.hpp,运行时通过 wsf::version::GetString()、GetMajorVersion() 等函数访问(core/wsf/source/WsfVersion.hpp:29-55)。这意味着:

  • 版本号是编译期常量,不是运行时读配置文件;
  • 如果源码目录带 .git,CMake 还会用 git describe、分支名和提交日期生成开发版版本串(src/CMakeLists.txt:83-124);
  • 排查问题时,mission 启动日志中的版本串可以区分"正式发布版"与"开发构建版"。

1.2.2 语言与工具链基线

顶层构建配置明确要求 C++11:

cmake 复制代码
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

见 src/CMakeLists.txt:32-34。这带来两个重要约束:

  1. 不要在现代编译器上假设 C++14/17 特性可用。 源码中大量使用 std::unique_ptr、auto、lambda、override,但不会出现 std::optional、std::string_view、结构化绑定。二次开发若引入 C++17 库,需要单独调整标准并承担兼容性风险。
  2. 构建系统版本较旧。 cmake_minimum_required(VERSION 3.7...3.12),官方文档建议 CMake 3.7+,VS2019 生成器需要 CMake 3.14+。在较新的 CMake 上构建时,策略(policy)兼容性要留意。

官方支持矩阵(doc/developer/build_instructions.rst):

平台 编译器 版本 架构
Windows 10 Visual Studio 2015 / 2017 / 2019 64 位
Linux GCC 4.8.5 64 位

32 位构建从 2.6 起被官方标记为弃用,顶层 CMake 会给出警告(src/CMakeLists.txt:28-30)。这不是"不能编译",而是"部分特性不保证正确"。

1.2.3 源码树的顶层布局

src 根目录有 15 个一级目录,另有构建入口与说明文件。按职责可以分成五组:

text 复制代码
src/
├── CMakeLists.txt              # 顶层构建入口
├── CMakePresets.json           # 预设构建配置
├── cmake/                      # 自定义 CMake 模块、模板、发行配置
├── core/                       # 核心框架与领域模型库
│   ├── wsf/                    # 仿真框架主体
│   ├── wsf_util/               # 框架级小工具(打包、SHA、CSV 等)
│   ├── wsf_mil/                # 军事领域扩展
│   ├── wsf_space/              # 太空领域扩展
│   ├── wsf_cyber/              # 赛博领域扩展
│   ├── wsf_l16/                # Link 16
│   ├── wsf_nx/                 # 非出口型高保真模型
│   ├── wsf_mtt/                # 多目标跟踪
│   ├── wsf_ripr/               # 反应式任务分配(RIPR)
│   ├── wsf_parser/             # 解析器与语法基础设施
│   ├── wsf_mil_parser/         # 军事语法注册
│   ├── wsf_weapon_server/      # 武器服务端
│   ├── wsf_grammar_check/      # 语法检查工具
│   └── sensor_plot_lib/        # 传感器绘图库
├── tools/                      # 通用基础设施与应用支撑库
│   ├── util/                   # Ut* 基础类库(数学、坐标、输入、日志等)
│   ├── util_script/            # 脚本语言运行时
│   ├── dis/                    # DIS 协议库
│   ├── wkf/                    # Warlock 框架
│   ├── utilosg/                # OpenSceneGraph 封装
│   ├── utilqt/                 # Qt 封装
│   ├── vespatk/                # VESPA 回放工具包
│   ├── geodata/                # 地理数据
│   ├── genio/                  # 通用 I/O
│   ├── packetio/               # 报文 I/O
│   ├── tracking_filters/       # 跟踪滤波器
│   ├── profiling/              # 性能剖析
│   ├── artificer/              # 代码生成/构建辅助
│   └── ...
├── mission/                    # 批处理仿真应用
├── warlock/                    # 实时可视化/控制应用
├── wizard/                     # 想定编辑器
├── mystic/                     # 事后分析应用
├── sensor_plot/                # 传感器覆盖绘图应用
├── weapon_tools/               # 武器包线分析应用
├── mover_creator/              # 运动模型数据生成应用
├── engage/                     # 交战分析应用
├── post_processor/             # 后处理应用
├── evt_reader/                 # 事件日志阅读器
├── wsf_plugins/                # 可选插件集合(16 个)
└── doc/                        # 官方文档、开发规范、变更日志

这棵树最值得注意的是 core/wsf 只占全部代码的一小部分 。按 *.cpp/*.hpp 粗略统计(不含文档、资源与第三方二进制):

模块 文件数 行数(约) 角色
core/wsf 1,132 309,630 仿真框架主体
core/wsf_mil 432 173,641 军事模型
core/wsf_space 329 52,361 太空模型
core/wsf_parser 155 37,264 解析器基础设施
core/wsf_l16 105 25,028 Link 16
core/wsf_cyber 88 14,701 赛博
core/wsf_nx 72 61,644 非出口型模型
core/wsf_mtt 30 10,310 多目标跟踪
core/wsf_ripr 24 7,920 任务分配/规划
wizard 1,059 182,373 想定编辑器
warlock 616 96,554 实时控制台
mystic 291 65,390 事后分析
mover_creator 225 104,386 运动数据生成
tools 2,487 524,132 基础设施与通用库
wsf_plugins 1,353 343,540 可选插件(16 个插件,模块数量最多)

统计口径说明 :上述数字只统计 C/C++ 实现与头文件(.cpp、.hpp),行数为物理行;不含想定、文档、资源与第三方数据文件。不同模块的目录组织不同,数字用于建立量级直觉,不作为精确的交付统计。

这个表传递出一个重要事实:AFSIM 的"主框架"很小,生态很大。 真正需要精读的是 core/wsf 的百余个核心类、tools/util 的数学/坐标/输入基础设施,以及 mission、warlock、wizard 等应用的入口。其余模块可以按需查阅。

1.2.4 文档与代码的关系

doc/ 目录下有五类文档:

  • doc/user/:面向最终用户的安装、系统需求、架构总览;
  • doc/user_manual/:想定语言的参考手册(本版本中只有一个 primer 文件,更多内容以生成文档形式存在);
  • doc/developer/:构建说明、编码规范、文档规范、集成指南、性能工具;
  • doc/changelog/:版本变更记录;
  • doc/images/:架构图与示意图。

需要提醒的是:源码仓库中的文档并不完整 。很多命令的权威定义不在 .rst 里,而在解析器代码和生成语法文件中。例如某个传感器模式的可用属性,最可靠的来源是 WsfSensorMode::ProcessInput 及其基类的命令分派表,而不是手册。本系列会反复强调"以代码为准"的阅读方法。


1.3 分层架构:从应用到基础设施

1.3.1 官方分层与代码的对应关系

官方架构文档把 AFSIM 描述为"应用包含场景,场景包含仿真",并列出场景、仿真、线程管理、扩展/插件、脚本、观察者、任务、跟踪、地理空间、分布式接口、工具等核心服务(doc/user/architecture_overview.rst)。把这段描述落到代码上,可以得到如下分层:

text 复制代码
┌──────────────────────────────────────────────────────────────────────┐
│ 应用层 Applications                                                  │
│   mission  warlock  wizard  mystic  sensor_plot  weapon_tools ...    │
│   职责:命令行、UI、批处理循环、结果展示、应用专属扩展                │
├──────────────────────────────────────────────────────────────────────┤
│ 插件层 Plugins                                                       │
│   wsf_air_combat  wsf_six_dof  wsf_p6dof  wsf_iads_c2  ...            │
│   职责:以动态库形式挂载的领域能力,不修改核心代码                    │
├──────────────────────────────────────────────────────────────────────┤
│ 领域扩展层 Domain Extensions                                         │
│   wsf_mil  wsf_space  wsf_cyber  wsf_l16  wsf_nx  wsf_mtt  wsf_ripr  │
│   职责:注册新部件类型、脚本类型、观察者、输入命令                    │
├──────────────────────────────────────────────────────────────────────┤
│ 框架层 Framework                                                     │
│   core/wsf:Scenario / Simulation / Platform / Event / Component     │
│             Observer / Script / XIO / DIS / EventPipe                │
│   职责:对象生命周期、事件调度、类型系统、组件装配、数据出口          │
├──────────────────────────────────────────────────────────────────────┤
│ 基础设施层 Utilities                                                 │
│   tools/util(Ut*)、tools/util_script、core/wsf_util、tools/dis ...  │
│   职责:数学、坐标、时间、输入、日志、脚本解释器、协议编解码          │
└──────────────────────────────────────────────────────────────────────┘

这个分层不是严格的"上层只能调用下层"的洋葱模型,而是依赖倒置 + 注册表的组合:

  • 框架层定义抽象基类(WsfSensor、WsfMover、WsfProcessor、WsfComm、WsfWeapon)和工厂接口;
  • 领域扩展层在启动时把具体类型注册进场景的类型表;
  • 应用层只依赖框架层的抽象接口与扩展注册宏;
  • 插件层通过动态加载在运行时注册,不需要重新编译框架。

因此,core/wsf 不能反向依赖 wsf_mil。如果你在 core/wsf 中看到对 WsfRadarSensor 的直接引用,那通常是历史遗留或通过脚本/观察者间接耦合,而不是设计意图。

1.3.2 三个"容器":Application、Scenario、Simulation

理解 AFSIM 的第一把钥匙,是分清三个核心容器。

(1)Application(应用)

WsfApplication(core/wsf/source/WsfApplication.hpp)代表"一个可执行程序"。它持有:

  • 命令行参数与运行选项;
  • 应用扩展表(WsfApplicationExtension);
  • 插件管理器(WsfPluginManager);
  • 全局脚本类型表(UtScriptTypes);
  • 应用级配置;
  • 创建场景、创建仿真、运行事件循环的工厂方法。

mission、warlock、wizard 各自派生自己的 Application。例如 mission.cpp 中:

cpp 复制代码
WsfStandardApplication app("mission", argc, argv);
RegisterBuiltinExtensions(app);
RegisterOptionalExtensions(app);
WSF_REGISTER_EXTENSION(app, xio_interface);

见 mission/source/mission.cpp:77-86。这三行揭示了应用启动的标准动作:构造应用 → 注册内置扩展 → 注册可选扩展 → 注册 XIO 接口。

(2)Scenario(场景)

WsfScenario(core/wsf/source/WsfScenario.hpp:110)代表"一份想定定义"。它持有:

  • 输入文件列表与输入对象(UtInput);
  • 类型表集合(平台类型、传感器类型、武器类型、通信类型、处理器类型、路由类型、区域类型、脚本类型......);
  • 脚本管理器与全局脚本上下文;
  • 环境(大气、地形、IFF 管理器、消息表);
  • 延迟输入(WsfDeferredInput);
  • 组件工厂列表;
  • 场景扩展表;
  • 蒙特卡洛运行号与随机种子;
  • 输入平台实例(尚未进入仿真)。

关键点:Scenario 是"只读模板" 。CompleteLoad() 被调用后,场景进入不可变状态(WsfScenario.hpp:399-401 的 mLoadIsComplete),仿真只能读取它,不能修改类型定义。这保证了同一场景可以安全地创建多个仿真(蒙特卡洛多次运行),而不会互相污染。

(3)Simulation(仿真)

WsfSimulation(core/wsf/source/WsfSimulation.hpp:108)代表"一次运行"。它持有:

  • 当前仿真时间、结束时间、时钟源、时间步长;
  • 仿真事件队列与墙钟事件队列(mEventManager、mWallEventManager);
  • 平台列表(按索引、按名字、按插入顺序三套视图);
  • 全局脚本上下文与脚本执行器;
  • 随机数源(模型随机数与脚本随机数分离);
  • 观察者集合(平台、传感器、通信、武器、航迹、任务、区域、仿真等);
  • EM 管理器、通信网络管理器、LOS 管理器、地形接口、分组管理器;
  • 仿真扩展表;
  • 消息/交战序号、唯一 ID 分配器。

WsfSimulation 的 State 枚举(WsfSimulation.hpp:112-121)定义了严格的生命周期:

text 复制代码
cPENDING_INITIALIZE → cINITIALIZING → cPENDING_START → cSTARTING
      → cACTIVE → cPENDING_COMPLETE → cCOMPLETE

这个状态机不只是"文档好看",它决定了哪些操作合法。例如在 cACTIVE 之前调用 AddPlatform 与之后调用,走的是不同路径;在 Complete 之后继续 AdvanceTime 会触发断言或异常。

三者的关系可以概括为:

text 复制代码
Application(进程级:扩展、插件、脚本类型、命令行)
   └── Scenario(想定级:类型表、输入、环境、脚本)
          └── Simulation(运行级:事件、平台实例、观察者、接口)

1.3.3 为什么要把 Scenario 和 Simulation 分开

这是 AFSIM 架构中最重要的设计决策之一,值得单独解释。

原因一:蒙特卡洛复用。 一个想定通常要跑几十到几万次,每次使用不同的随机种子。如果类型定义和运行状态混在一起,每次运行都要重新解析输入、重新构建类型表,既慢又容易产生状态污染。分离后,Scenario 只解析一次,Simulation 可以反复创建。

mission.cpp 的主循环正是这样写的(mission/source/mission.cpp:148-184):

cpp 复制代码
for (unsigned int runNumber = scenario.GetInitalRunNumber();
     runNumber <= scenario.GetFinalRunNumber();)
{
   std::unique_ptr<WsfSimulation> simPtr = app.CreateSimulation(scenario, options, runNumber);
   app.InitializeSimulation(simPtr.get());
   WsfStandardApplication::SimulationResult result = app.RunEventLoop(simPtr.get(), options);
   if (!result.mResetRequested)
   {
      runNumber += scenario.GetRunNumberIncrement();
   }
}

注意 scenario 在循环外,simPtr 在循环内。每次运行结束,仿真对象被销毁,场景保持不变。

原因二:类型与实例分离。 场景中的 sensor_type RADAR_A ... end_sensor_type 是类型,平台中的 sensor foo RADAR_A ... end_sensor 是实例。类型只在场景中存在一份,实例在仿真中按平台克隆。这样既节省内存,又允许实例覆盖(override)类型的属性。

原因三:多仿真并行。 一个场景可以创建多个仿真,用于对比实验、参数扫描或分布式仿真中的多副本。Scenario 的不可变性让这种并行免于加锁。

原因四:输入校验前置。 解析阶段可以完成大部分语法与语义校验(类型是否存在、必填参数是否缺失、单位是否合法),把错误挡在仿真开始之前。WsfScenario::CompleteLoad() 会触发各组件的 CompleteLoad 钩子,做跨对象校验。

1.3.4 平台对象树

进入仿真后,最重要的对象树是平台:

text 复制代码
WsfSimulation
 ├── mPlatforms[]                 // 按加入顺序
 ├── mPlatformsByIndex[]          // 按稳定索引(删除后置空)
 ├── mPlatformsByName{}           // 按名字(WsfStringId)
 │
 └── WsfPlatform                 // 继承 WsfObject, WsfComponent, UtEntity...
      ├── WsfMover*              // 运动器(唯一)
      ├── WsfFuel*               // 燃料(唯一)
      ├── WsfNavigationErrors*   // 导航误差(唯一)
      ├── WsfTrackManager        // 航迹管理(平台级)
      ├── WsfScriptContext       // 平台级脚本上下文
      ├── WsfCategoryList        // 类别标签
      ├── WsfGroupList           // 分组
      ├── WsfSignatureList       // 雷达/红外/光学/声学签名
      └── WsfPlatformPart*[]     // 部件列表(多态)
           ├── WsfSensor         // 传感器
           ├── WsfWeapon         // 武器
           ├── WsfComm           // 通信
           ├── WsfProcessor      // 处理器
           ├── WsfArticulatedPart// 可转动部件(传感器/发射器常派生于此)
           └── ...

WsfPlatform 本身也是一个组件(WsfPlatformComponent),这使得平台可以作为其他平台的组件被引用,也让它能复用组件容器的能力。它同时继承 UtEntity,从而获得位置、姿态、速度等实体状态;继承 WsfUniqueId,获得全局唯一 ID。

这个多重继承结构是阅读源码时的一个难点:一个 WsfPlatform* 可以 dynamic_cast 到 WsfObject、WsfComponent、WsfPlatformComponentList、UtEntity、WsfUniqueId、WsfAuxDataEnabled。每个基类提供一组接口,调用时必须清楚"这个方法是哪一层定义的"。

1.3.5 部件模型:组合优于继承

AFSIM 没有为"战斗机""驱逐舰""卫星"分别建立庞大的继承树,而是用平台 + 部件组合来表达能力差异:

  • 飞机 = 平台 + 空气动力学运动器 + 雷达 + 导弹 + 数据链 + 任务处理器;
  • 舰船 = 平台 + 水面运动器 + 对海/对空雷达 + 防空导弹 + 通信 + 指挥处理器;
  • 卫星 = 平台 + 轨道运动器 + 光学/雷达载荷 + 星间链路 + 姿态控制器。

这种设计的直接好处是:

  1. 能力可复用:同一个雷达类型可以挂在飞机、舰船、地面站上;
  2. 想定可组合:用户通过引用类型名组装平台,不需要为每种组合写新类;
  3. 扩展点清晰:新增能力通常只需新增一个部件类型,而不是修改平台类;
  4. 性能可控:关闭某个部件只需标记不可用,不必销毁对象。

代价是部件之间的交互需要显式建模。传感器不会自动知道武器在哪,处理器也不会自动获得航迹;它们通过平台查询、消息、任务、航迹管理器等机制显式连接。这既是灵活性,也是复杂度的来源。

1.3.6 事件:框架的"时间货币"

AFSIM 的运行时行为最终都归结为事件。WsfEvent 的定义极其简单(WsfEvent.hpp:28-113):

cpp 复制代码
class WSF_EXPORT WsfEvent
{
public:
   enum EventDisposition { cDELETE, cRESCHEDULE };
   virtual EventDisposition Execute() = 0;
   double GetTime() const;
   int GetPriority() const;
   void SetTime(double aSimTime);
   void SetPriority(int aPriority);
   void SetShouldExecute(bool aExecute);
private:
   double mSimTime{0.0};
   WsfSimulation* mSimulationPtr{nullptr};
   int mPriority{0};
   bool mExecute{true};
};

一个事件只需要回答三件事:什么时候执行、执行什么、执行后是删除还是重排 。事件管理器用 std::priority_queue 按 (time, priority, sequence) 排序(WsfEventManager.hpp:86-106),其中 sequence 是自增计数器,保证同一时刻、同一优先级的事件按加入顺序执行(FIFO)。

这种极简接口带来极强的扩展性:任何逻辑都可以被包装成事件,插入时间轴。例如:

  • 平台更新事件(WsfPlatformUpdateEvent);
  • 运动器更新事件(WsfMoverUpdateEvent);
  • 传感器扫描事件(由调度器生成);
  • 通信传输完成事件(WsfCommEvent);
  • 脚本定时器(WsfScriptEvent);
  • 平台添加/删除事件(WsfSimulation::AddPlatformEvent);
  • 墙钟事件(AddWallEvent,用于实时模式下的外部交互)。

代价是调试困难 :你看到的调用栈往往是从 WsfSimulation::AdvanceTime 到 WsfEventManager::PopEvent,再到某个具体事件的 Execute,中间没有"业务层"的痕迹。要定位问题,必须知道"谁会调度这个事件"。

1.3.7 观察者:数据出口的统一点

AFSIM 不允许(也不鼓励)在模型代码里到处写 printf 或文件输出。需要外部数据时,模型触发观察者回调,订阅者决定如何处理。观察者按领域拆分:

  • WsfSimulationObserver:仿真开始/结束/时间推进;
  • WsfPlatformObserver:平台添加/删除/初始化/更新;
  • WsfSensorObserver:传感器扫描、检测、跟踪;
  • WsfCommObserver:消息发送/接收;
  • WsfWeaponObserver:发射、命中、脱靶;
  • WsfTrackObserver:航迹创建/更新/删除;
  • WsfTaskObserver:任务分配/状态变化;
  • WsfMoverObserver:运动状态更新;
  • WsfFuelObserver:燃料变化;
  • WsfZoneObserver:区域填充色/线条色变化(不提供进入/离开事件);
  • WsfExchangeObserver:交换/交互;
  • WsfDisObserver:DIS 相关事件。

每个观察者本质上是一个"回调集合",例如 WsfObserver::PlatformAdded(sim) 返回一个可绑定的回调对象(WsfSimulation.hpp:693-699 的宏展示了这种访问方式)。订阅者可以是:

  • 事件输出(WsfEventOutput);
  • 事件管道(WsfEventPipe);
  • DIS/XIO 接口;
  • 脚本观察者(WsfScriptObserver);
  • 用户自定义的 C++ 扩展。

观察者模式的价值在于解耦 :模型不知道数据会被写文件、发网络还是画图;新增一种输出不需要改模型。代价是调用开销与顺序不确定性:每次事件都遍历订阅者,订阅者过多会影响性能;多个订阅者之间的执行顺序由注册顺序决定,不应依赖。

1.3.8 扩展的五个层次

AFSIM 的扩展点可以按生命周期从长到短排列:

层次 基类/机制 注册时机 典型用途
应用扩展 WsfApplicationExtension 应用启动 新增命令行选项、全局服务、应用级初始化
场景扩展 WsfScenarioExtension 场景创建/输入处理 注册类型、命令、脚本类;读取自定义输入块
仿真扩展 WsfSimulationExtension 仿真创建 订阅观察者、维护仿真级状态、提供运行期服务
插件 WsfPlugin + 动态库 运行时加载 打包上述扩展,热插拔
组件/类型 WsfComponentFactory、类型表 场景加载 新增传感器/武器/通信/处理器等模型

此外还有两个"横切"扩展点:

  • 脚本类型注册 :通过 UtScriptTypes 注册新的脚本类,让用户脚本能操作自定义对象;
  • 观察者回调 :通过 WsfObserver 订阅数据,不必修改模型。

选择扩展点的经验法则:

  • 需要新模型(例如一种新雷达)→ 组件/类型;
  • 需要新输入命令 (例如新模型的参数块)→ 场景扩展 + 组件 ProcessInput;
  • 需要新全局服务(例如外部数据库连接)→ 应用扩展或仿真扩展;
  • 需要新数据输出(例如写入自定义格式)→ 观察者;
  • 需要打包分发且不想重编译 → 插件。

1.3.9 依赖方向与"不可逆"规则

虽然 CMake 允许多种链接方式,但架构上应遵守以下依赖方向:

text 复制代码
tools/util, tools/util_script, tools/dis, ...
        ↑
core/wsf(只能依赖 util 层)
        ↑
core/wsf_mil / wsf_space / wsf_cyber / ...
        ↑
applications(mission/warlock/wizard/...)
        ↑
wsf_plugins(可依赖以上任何层,但只能通过注册接口接入)

具体规则:

  1. tools/util 不知道 Wsf 的存在。 它只提供 Ut* 类,如 UtEntity、UtInput、UtAtmosphere、UtCalendar。这样它才能被其他项目复用。
  2. core/wsf 不依赖任何领域扩展。 它定义 WsfSensor 抽象基类,但不知道 WsfRadarSensor 的存在;后者由 wsf_mil 注册。
  3. 领域扩展之间尽量不互相依赖。 wsf_space 不应依赖 wsf_l16;如果需要共享,抽到 core/wsf 或 tools。
  4. 应用可以依赖多个领域扩展。 mission 默认加载内置扩展与可选扩展(RegisterBuiltinExtensions / RegisterOptionalExtensions),因此能识别所有已编译进来的模型。
  5. 插件通过注册宏接入,不直接改核心。 插件可以依赖核心头文件,但核心不能依赖插件。

违反这些规则的后果不是编译错误,而是循环依赖、链接膨胀、插件加载顺序问题 。例如,如果 core/wsf 直接 #include "WsfRadarSensor.hpp",那么未编译 wsf_mil 的构建会失败,核心库也不再可独立复用。

1.3.10 一次运行穿过哪些层

把上述结构串起来,一个典型想定的生命周期如下:

text 复制代码
1. 进程启动
   mission main()
     → WsfStandardApplication app("mission", argc, argv)
     → RegisterBuiltinExtensions / RegisterOptionalExtensions
     → WSF_REGISTER_EXTENSION(app, xio_interface)

2. 解析输入
   WsfScenario scenario(app)
     → app.ProcessInputFiles(scenario, options.mInputFiles)
        → WsfScenario::LoadFromFile()
           → UtInput 打开文件
           → WsfScenario::ProcessInput()
              → 处理 platform_type / sensor_type / weapon_type ...
              → 类型表填充
              → 脚本解析
     → scenario.CompleteLoad()
        → 各类型 CompleteLoad()
        → 组件工厂就绪

3. 创建仿真
   app.CreateSimulation(scenario, options, runNumber)
     → new WsfSimulation(scenario, runNumber)
     → 初始化随机种子
     → 创建时钟源

4. 初始化
   app.InitializeSimulation(sim)
     → sim.Initialize()
        → 创建应用/仿真扩展
        → 添加输入平台(AddInputPlatforms)
        → 平台 Initialize / Initialize2
        → 开启部件
        → 调度初始事件

5. 运行
   app.RunEventLoop(sim, options)
     → while (sim.IsActive()) sim.AdvanceTime()
        → 取下一个事件(时间、优先级、FIFO)
        → 推进仿真时间
        → event->Execute()
           → 平台更新 / 传感器扫描 / 通信传输 / 武器交战 / 脚本执行
           → 触发观察者
              → 事件输出 / 事件管道 / DIS / XIO / 脚本观察者

6. 结束
   sim.Complete(endTime)
     → 仿真结束事件
     → 关闭部件、释放平台、关闭接口
     → 析构仿真,保留场景

这条链上的每一环都有对应的源码入口,后续章节会逐段展开。


1.4 核心模块逐层拆解

1.4.1 core/wsf/source 根目录:框架的"中央大厅"

core/wsf/source 根目录有 381 个文件,是整个框架最密集的区域。可以按职责分成八组。

(1)对象与类型基础设施

  • WsfObject.hpp/.cpp:所有"可命名、可克隆、可输入"对象的基类,持有名字、类型名、字符串 ID。
  • WsfNamed.hpp、WsfStringId.hpp、WsfStringTable.hpp:字符串驻留(interning)机制。想定中大量重复的类型名、平台名、命令名被映射为整数 ID,避免反复哈希与比较。
  • WsfAttributeContainer.hpp、WsfAttribute.hpp:通用属性容器,用于保存输入中的键值对,支持脚本访问。
  • WsfComponent.hpp、WsfComponentList.hpp、WsfComponentFactory.hpp、WsfComponentRoles.hpp:组件模型的核心。组件通过角色(role)查询接口,平台通过组件列表管理生命周期。
  • WsfObjectTypeList.hpp、WsfObjectTypeListBase.hpp、WsfObjectTypeListBase.hpp:类型表模板,按类型种类(sensor、mover、weapon...)存储原型对象。

(2)场景与仿真容器

  • WsfScenario.hpp/.cpp、WsfScenarioExtension.hpp、WsfSimulation.hpp/.cpp、WsfSimulationExtension.hpp、WsfSimulationInput.hpp。
  • WsfApplication.hpp/.cpp、WsfStandardApplication.hpp/.cpp、WsfApplicationExtension.hpp。
  • WsfExtension.hpp、WsfExtensionList.hpp:扩展的通用注册/查找容器。

(3)事件与时间

  • WsfEvent.hpp、WsfEventManager.hpp/.cpp、WsfEventUtils.hpp/.cpp。
  • WsfClockSource.hpp/.cpp、WsfRealTimeClockSource.hpp/.cpp。
  • WsfFrameStepSimulation.hpp/.cpp、WsfEventStepSimulation.hpp/.cpp:两种时间推进策略。
  • WsfTimeDelayQueue.hpp/.cpp:延迟消息队列。

(4)平台与部件

  • WsfPlatform.hpp/.cpp、WsfPlatformPart.hpp/.cpp、WsfArticulatedPart.hpp/.cpp、WsfPlatformPartEvent.hpp。
  • WsfPlatformTypes.hpp、WsfPlatformAvailability.hpp。
  • WsfFuel.hpp、WsfFuelTypes.hpp。
  • WsfVisualPart.hpp、WsfVisualPartTypes.hpp。

(5)感知与信息

  • WsfTrack.hpp/.cpp、WsfLocalTrack.hpp、WsfTrackId.hpp、WsfTrackList.hpp、WsfTrackManager.hpp/.cpp。
  • WsfTrackMessage.hpp、WsfTrackDropMessage.hpp、WsfTrackNotifyMessage.hpp。
  • WsfCorrelationStrategy.hpp、WsfFusionStrategy.hpp、WsfTruthCorrelation.hpp、WsfNearestNeighborCorrelation.hpp。
  • WsfMeasurement.hpp、WsfCovariance.hpp、WsfKalmanFilter.hpp、WsfAlphaBetaFilter.hpp。

(6)电磁与传播

  • WsfEM_Xmtr.hpp、WsfEM_Rcvr.hpp、WsfEM_Antenna.hpp、WsfEM_Interaction.hpp、WsfEM_Propagation.hpp、WsfEM_Attenuation.hpp、WsfEM_Clutter.hpp、WsfEM_Noise.hpp、WsfEM_Manager.hpp。
  • WsfAntennaPattern.hpp、WsfRadarSignature.hpp、WsfSignature.hpp、WsfMaskingPattern.hpp。

(7)通信、网络与消息

  • WsfMessage.hpp、WsfMessageTable.hpp、WsfTrackMessage.hpp、WsfTaskAssignMessage.hpp、WsfControlMessage.hpp、WsfStatusMessage.hpp、WsfImageMessage.hpp、WsfVideoMessage.hpp。
  • WsfCommAddress.hpp、WsfNetworkInterface.hpp、WsfCommGraph.hpp。
  • WsfGroup.hpp、WsfGroupManager.hpp、WsfCommandChain.hpp。

(8)环境、区域与杂项

  • WsfEnvironment.hpp/.cpp、WsfTerrain.hpp、WsfDtedRect.hpp、WsfLandCover.hpp、WsfAtmosphere(通过 UtAtmosphere)。
  • WsfZone.hpp/.cpp、WsfZoneDefinition.hpp、WsfZoneSet.hpp、WsfZoneReference.hpp、WsfZoneAttenuation.hpp。
  • WsfDraw.hpp、WsfVisualization.hpp、WsfImage.hpp。
  • WsfRandom.hpp、WsfRandomVariable.hpp、WsfDateTime.hpp、WsfSystemLog.hpp。

这个根目录不是"杂物间",而是框架的公共词汇表。理解这些类的职责,等于掌握了 AFSIM 的"常用字"。

1.4.2 core/wsf/source/sensor:传感器骨架

sensor 子目录有 73 个文件,核心是:

  • WsfSensor:传感器抽象基类,持有平台指针、名称、模式列表、调度器、跟踪器、误差模型、信号处理器、波束列表;
  • WsfSensorMode / WsfSensorModeList:传感器模式。一个传感器可以有多种模式(搜索、跟踪、成像),每种模式有独立的参数;
  • WsfSensorScheduler:调度器,决定何时、对哪些目标、以什么顺序进行探测;
  • WsfSensorTracker:传感器级跟踪器,把单次检测串成航迹;
  • WsfSensorDetector:检测判决,输入信噪比/概率,输出检测成功/失败;
  • WsfSensorErrorModel:测量误差模型;
  • WsfSensorSignalProcessor:信号处理链;
  • WsfSensorBeam:波束;
  • WsfGeometricSensor、WsfPassiveSensor、WsfCompositeSensor、WsfNullSensor:通用传感器实现;
  • WsfRadarSensor、WsfPassiveDetector、WsfMarcumSwerling、WsfDetectionProbabilityTable:雷达与检测相关模型;
  • WsfDefaultSensorScheduler、WsfSectorScanSensorScheduler、WsfPhysicalScanSensorScheduler:调度策略。

注意:WsfRadarSensor 位于 core/wsf 而不是 wsf_mil,因为雷达是核心传感器类型;更细的军事模型(电子战、红外、光学)在 wsf_mil。这体现了"核心提供骨架与通用模型,领域扩展提供高保真模型"的分工。

1.4.3 core/wsf/source/mover:运动骨架

mover 子目录有 101 个文件,核心是:

  • WsfMover:运动器抽象基类,维护平台的位置、姿态、速度、加速度,提供 Update、GetLocationWCS、SetOrientation 等接口;
  • WsfMoverMode / WsfMoverTypes:运动模式;
  • WsfAirMover、WsfGroundMover、WsfHybridMover、WsfOffsetMover、WsfMath3D_Mover、WsfFollower:通用运动模型;
  • WsfAero、WsfAero2D、WsfAngleOfAttack、WsfFuelFlowTables:空气动力学与燃料流;
  • WsfRoute、WsfPath、WsfPathList、WsfPathComputer、WsfPathFinder、WsfPathGuidance、WsfPathConstraints、WsfNavigationMesh、WsfNavigationCell:路线与路径规划;
  • WsfFuel、WsfFuelingOperation:燃料;
  • WsfMoverUpdateEvent:运动更新事件。

运动器的核心契约是:平台不直接修改自己的位置,而是由运动器根据时间推进计算新状态。传感器、武器、通信都通过平台查询当前状态,而不是自己积分。

1.4.4 core/wsf/source/comm:通信骨架

comm 子目录有 108 个文件,是核心中较复杂的子系统:

  • WsfComm:通信设备基类,持有地址、网络、协议层、消息队列;
  • WsfCommLayer / WsfCommPhysicalLayer / WsfCommDatalinkLayer / WsfCommNetworkLayer / WsfCommApplicationLayer / WsfCommPresentationLayer:分层协议栈;
  • WsfCommMedium / WsfCommMediumGuided / WsfCommMediumUnguided:传输介质;
  • WsfCommNetwork / WsfCommNetworkGeneric / WsfCommNetworkAdHoc / WsfCommNetworkMesh / WsfCommNetworkPointToPoint / WsfCommNetworkRing / WsfCommNetworkStar:网络拓扑;
  • WsfCommNetworkManager:全局网络管理器;
  • WsfCommMessage / WsfCommEvent / WsfCommLayerEvent:消息与事件;
  • WsfCommGraph:通信图(连通性);
  • WsfCommAddress:地址;
  • WsfCommRouter / WsfCommRouterProtocol:路由。

通信子系统的设计目标是"可组合":用户可以选择物理层、数据链路层、网络层、应用层的模型,组合成不同保真度的通信链路。

1.4.5 core/wsf/source/processor:行为骨架

processor 子目录有 40 个文件:

  • WsfProcessor:处理器基类,代表平台上的计算/决策单元;
  • WsfTaskManager、WsfTask、WsfTaskData、WsfTaskResource:任务管理;
  • WsfTaskAssignMessage、WsfTaskCancelMessage、WsfTaskStatusMessage、WsfTaskControlMessage:任务消息;
  • WsfTrackProcessor、WsfSensorProcessor、WsfMessageProcessor、WsfExchangeProcessor、WsfLinkedProcessor、WsfPerfectTracker、WsfDirectionFinderProcessor、WsfTrackStateControllerProcessor:内置处理器。

处理器是脚本最常挂载的地方:用户可以用 AFSIM 脚本编写 on_update、on_message、on_track 等回调,也可以写状态机。处理器与 WsfTaskManager 一起构成"决策层"。

1.4.6 core/wsf/source/script:脚本绑定的海洋

script 子目录有 108 个文件,是核心中文件数最多的子目录之一。它把 C++ 对象暴露给 AFSIM 脚本:

  • WsfScriptContext:脚本上下文,持有全局变量、函数、类表;
  • WsfScriptManager:脚本管理器,解析、编译、缓存脚本;
  • WsfScriptClass 系列:每个 C++ 类型对应一个脚本类,定义方法、属性、构造函数;
  • WsfScriptEvent、WsfScriptCallback、WsfScriptStateMachine:事件、回调、状态机;
  • WsfScriptMessageHandler、WsfScriptMessageClass:消息处理;
  • WsfScriptTrackClass、WsfScriptPlatformClass、WsfScriptSensorClass、WsfScriptMoverClass、WsfScriptCommClass、WsfScriptWeaponClass、WsfScriptProcessorClass、WsfScriptZoneClass、WsfScriptRouteClass、WsfScriptFuelClass、WsfScriptDateTimeClass 等:领域对象的脚本包装。

脚本子系统的规模说明:AFSIM 的脚本不是"配置语法糖",而是一个完整的、带类型系统的嵌入式语言 。它有自己的解析器、字节码/解释器、类型检查、方法绑定、异常处理,甚至调试器接口(UtScriptDebugger 在 tools/util_script)。

1.4.7 core/wsf/source/observer:回调接口层

observer 子目录有 32 个文件,为每类事件定义回调集合:

  • WsfSimulationObserver:仿真初始化、开始、结束、时间推进;
  • WsfPlatformObserver:平台添加、删除、初始化、更新、损坏;
  • WsfPlatformPartObserver:部件状态;
  • WsfSensorObserver:传感器扫描、检测、跟踪;
  • WsfCommObserver:消息发送/接收;
  • WsfProcessorObserver:处理器更新、消息;
  • WsfMoverObserver:运动更新;
  • WsfFuelObserver:燃料;
  • WsfTaskObserver:任务;
  • WsfTrackObserver:航迹;
  • WsfZoneObserver:区域;
  • WsfExchangeObserver:交换;
  • WsfDisObserver:DIS;
  • WsfBehaviorObserver、WsfAdvancedBehaviorObserver:行为树;
  • WsfScriptStateMachineObserver:脚本状态机。

观察者的实现通常是"回调对象 + 订阅列表",具体回调类型定义在对应头文件中,例如 WsfObserver::PlatformAdded(sim) 返回 WsfObserver::PlatformAddedCallback&。

1.4.8 core/wsf/source/dis 与 xio、event_pipe:三个数据出口

  • dis(120 个文件):IEEE 1278 DIS 协议实现,包括实体状态、发射、爆炸、设计ator、电子战、聚合、传输、时间同步等 PDU;WsfDisInterface 负责与外部仿真互联。
  • xio(55 个文件):AFSIM 自有协议,用于 Warlock 等外部程序远程观察/控制仿真;支持订阅、查询、平台信息、传感器请求、任务请求、绘图、脚本、时间同步等。
  • event_pipe(15 个文件):高性能二进制事件记录,支持压缩、筛选、异步写盘;evt_reader、mystic、post_processor 可以消费。

三者的差异:

出口 协议 用途 是否影响仿真
DIS 标准 UDP 报文 与其他仿真互联 可以双向(接收外部实体)
XIO AFSIM 自定义 TCP/UDP 外部监视/控制 可以双向(远程控制)
Event Pipe 本地二进制文件 记录/回放/分析 单向输出,低侵入

1.4.9 core/wsf/source/ext 与 traffic:容易忽略的两个角落

  • ext(16 个文件):DIS 扩展接口(WsfExtInterface),用于非标准扩展报文。
  • traffic(12 个文件):交通流生成(航空、道路、海上),XWsfAirTraffic、XWsfRoadTraffic、XWsfSeaTraffic、XWsfRouteNetwork。这些是"想定自动生成"的工具,不是核心仿真模型。

1.4.10 core/wsf_parser 与 wsf_mil_parser:语法生成的幕后

core/wsf_parser(158 个文件,3.7 万行)提供解析器基础设施,可能包含:

  • 词法分析器(scanner);
  • 语法分析器(parser);
  • 命令/属性注册;
  • 语法检查工具;
  • 文档生成支持。

core/wsf_mil_parser(7 个文件,271 行)是 wsf_mil 的语法注册薄层。它们与 grammar/ 目录下的 .ag 文件(如果有)配合,生成命令分派代码。理解这一层需要看具体的 .wsf 语法定义与生成脚本。

说明:AFSIM 的输入解析并非全部由 wsf_parser 完成;大量 ProcessInput 函数是手写的。wsf_parser 更多是"解析器工具与生成框架",而不是唯一的输入入口。阅读时应以具体类的 ProcessInput 为准。

1.4.11 core/wsf_mil:军事模型的主仓库

wsf_mil 有 432 个文件、17 万行,按子目录组织:

子目录 头文件数 内容
sensor 18 雷达、红外、光学、声学、激光、ESM 等模型
weapon 37 导弹、炸弹、火炮、定向能、引信、杀伤模型
comm 11 军用数据链、电台、消息
ew 37 电子战:干扰、欺骗、侦察、干扰机、雷达告警
processor 23 任务处理器、威胁评估、武器分配、传感器管理
mover 9 军用运动模型(巡航导弹、弹道、飞机)
script 18 军事对象的脚本绑定
observer 3 军事专用观察者
dis 5 军事 DIS 扩展
xio 5 军事 XIO 扩展

根目录还有签名(红外、光学、声学、雷达)、激光、IFF、分类器、热图、簇相关等。

1.4.12 core/wsf_space:太空域

wsf_space 有 329 个文件、5.2 万行,核心包括:

  • 轨道递推:WsfKeplerianOrbitalPropagator、WsfJ2_PerturbationOrbitalPropagator、WsfNORAD_OrbitalPropagator、WsfIntegratingPropagator、WsfNonClassicalOrbitalPropagator;
  • 空间运动器:WsfNORAD_SpaceMover、WsfIntegratingSpaceMover;
  • 大气与摄动:WsfAtmosphere、WsfJacchiaRobertsAtmosphere、WsfAtmosphericDragTerm、WsfEarthJ2Term、WsfEarthMonopoleTerm、WsfMoonMonopoleTerm、WsfJupiterMonopoleTerm;
  • 星座:WsfConstellation、WsfConstellationMaker、WsfConstellationManager;
  • 姿态:WsfAttitudeController、WsfInstantAttitudeController;
  • 光照/阴影:WsfEclipseEventManager、WsfEclipseReport;
  • 交会/接近:WsfConjunctionInput、WsfAccessReport、WsfOrbitDeterminationFusion;
  • 目标点:WsfKinematicOffsetTargetPoint、WsfLibrationTargetPoint、WsfNTW_OffsetTargetPoint。

1.4.13 core/wsf_cyber:赛博域

wsf_cyber 有 88 个文件、1.47 万行,核心包括:

  • 攻击:WsfCyberAttack、WsfCyberAttackParameters、WsfCyberAttackTypes;
  • 防御:WsfCyberProtect、WsfCyberProtectTypes;
  • 约束:WsfCyberConstraint;
  • 效果:WsfCyberEffectTypes;
  • 交战:WsfCyberEngagement、WsfCyberEngagementManager;
  • 事件:WsfCyberEvent、WsfCyberEventManager、WsfCyberEventPipe;
  • 观察者/可视化:WsfCyberObserver、WsfCyberVisualization;
  • 扩展:WsfCyberApplicationExtension、WsfCyberScenarioExtension、WsfCyberSimulationExtension、WsfCyberScriptExtensions。

赛博模型把"网络攻击"抽象为对平台部件的效果(禁用、降级、欺骗),而不是真实的协议栈漏洞利用。这是工程级建模的典型取舍。

1.4.14 core/wsf_l16、wsf_nx、wsf_mtt、wsf_ripr

  • wsf_l16 (105 文件,2.5 万行):Link 16 战术数据链。包含 J 系列消息(JMessage.hpp、Message10_2.hpp 等)、终端(ComputerPart、ComputerProcessor)、接口(Interface_J11)、字段类型与枚举。
  • wsf_nx(72 文件,6.16 万行):非出口型高保真模型。包含 ALARM 雷达/大气/杂波/地形、箔条云与箔条弹、ESA 天线方向图、GENAP 方向图、Link 16 相关、易损性表等。其目录名 "nx" 很可能表示 "non-exportable"。
  • wsf_mtt(30 文件,1.03 万行):多目标跟踪,包含多假设跟踪(MHT)、卡尔曼滤波、航迹管理。
  • wsf_ripr (24 文件,7920 行):反应式任务分配架构(Reactive Integrated Planning aRchitecture),包含作业(WsfRIPRJob)、作业板(WsfRIPRJobBoard)、处理器(WsfRIPRProcessor)、管理器(WsfRIPRManager)与脚本接口(WsfScriptRIPRProcessorClass)。它解决"多个平台如何分配与竞标任务",不是雷达/红外现象学模型。

1.4.15 core/wsf_weapon_server 与 wsf_grammar_check

  • wsf_weapon_server(2 文件,3263 行):武器服务协议实现。mission 可作主机监听 RELEASE_STORE 请求并管理武器飞行,也可作客户端把指定武器类型外包给外部服务器;两种角色都需用 realtime 命令运行(core/wsf_weapon_server/doc/wsf_weapon_server.rst:35-65)。协议与实现见第 12.9.11 节。
  • wsf_grammar_check(2 文件,292 行):语法检查工具,读取输入文件并报告语法错误,不运行仿真。

1.4.16 tools/util:被低估的基础设施

tools/util 有 394 个 C/C++ 文件(196 个 .cpp、198 个 .hpp),是 AFSIM 的"标准库补充"。它提供:

  • 数学与几何 :UtMath、UtVector3、UtMatrix、UtQuaternion、UtDCM、UtEulerAngles、UtAngle、UtCovariance、UtEllipsoid、UtConvexHull、UtIntersectMesh、UtQuadTree;
  • 坐标与地球 :UtEntity、UtLLAPos、UtLLPos、UtCoords、UtEarth、UtEllipsoidalEarth、UtCentralBody、UtECI_Conversion、UtAtmosphere;
  • 时间 :UtCalendar、UtWallClock、UtTime;
  • 输入 :UtInput、UtInputBlock、UtInputFile、UtInputString、UtInputPreprocessor、UtInputDocument;
  • 日志 :UtLog、UtException、UtStackWalker;
  • 容器与算法 :UtCircularBuffer、UtGapBuffer、UtHash、UtBinarySearch、UtGoldenSectionSearch、UtIntegrate;
  • 脚本 :UtScript、UtScriptClass、UtScriptContext、UtScriptData、UtScriptExecutor、UtScriptParser、UtScriptTypes;
  • 其他 :UtPack、UtSHA、UtCsv、UtTarfile、UtBitmapImage、UtColor、UtRandom、UtCallback。

其中 UtEntity 是平台位置/姿态的基类,UtInput 是输入语言的读取器,UtScript 是脚本语言的核心。它们虽然位于 tools,但重要性不亚于 core/wsf。

1.4.17 tools 的其余部分

  • tools/dis(433 文件):DIS 协议库,独立于 core/wsf/source/dis,提供 PDU 编解码、网络传输、时间同步等底层能力;
  • tools/wkf(687 文件):Warlock 框架,提供实时可视化、地图、实体渲染、交互控制;
  • tools/utilosg(219 文件):OpenSceneGraph 封装,3D 渲染;
  • tools/utilqt(107 文件):Qt 封装;
  • tools/vespatk(153 文件):VESPA 回放工具包;
  • tools/geodata(97 文件):地理数据读取;
  • tools/genio(145 文件):通用 I/O;
  • tools/packetio(48 文件):报文编解码;
  • tools/tracking_filters(12 文件):跟踪滤波器;
  • tools/profiling(22 文件):性能剖析;
  • tools/artificer(40 文件):代码生成/构建辅助;
  • tools/scene_gen(21 文件):场景生成;
  • tools/misc:安装辅助,无库/可执行文件。

1.4.18 应用层:每个可执行体都是一条产品线

应用 入口 定位 关键依赖
mission mission/source/mission.cpp 批处理仿真,命令行驱动 core/wsf + 所有已编译扩展
warlock warlock/warlock_exec/source 实时可视化与交互控制台 wkf、utilosg、utilqt、XIO
wizard wizard/main/source 想定编辑器 utilqt、wkf、语法模型
mystic mystic/exec/source 事后分析与图表 utilqt、事件管道、后处理库
sensor_plot sensor_plot/source 传感器覆盖绘图 sensor_plot_lib、Qt
weapon_tools weapon_tools/source 武器包线/杀伤概率分析 core/wsf + wsf_mil
mover_creator mover_creator/source 运动模型数据生成 Qt、空气动力学数据
engage engage/source 交战级快速分析 core/wsf + wsf_mil
post_processor post_processor/exec/source 批量指标提取 事件管道、Python/C++ 脚本
evt_reader evt_reader/source 事件日志阅读器 Qt

这些应用的共同模式是:薄入口 + 厚库 。mission.cpp 只有约 200 行,真正的逻辑在 WsfStandardApplication;Warlock 的入口也很薄,核心在 warlock_core 与 wkf。这种设计便于复用与测试。

1.4.19 插件生态:16 个可选模块

wsf_plugins 下有 16 个带 wsf_module 的插件目录:

插件 类型 大致能力
wsf_air_combat plugin 空战战术、飞机行为
wsf_alternate_locations plugin 备选位置/部署方案
wsf_annotation plugin 想定标注
wsf_argo8 plugin ARGO8 相关模型
wsf_brawler plugin BRAWLER 空战模型
wsf_coverage plugin 覆盖分析
wsf_fires plugin 火力/炮兵
wsf_iads_c2_lib plugin 综合防空指挥控制(扩展类型 WSF_IADS_C2)
wsf_multiresolution plugin 多分辨率建模
wsf_oms_uci plugin OMS/UCI 接口
wsf_p6dof plugin 6 自由度飞行动力学
wsf_scenario_analyzer plugin 想定分析
wsf_scenario_analyzer_iads_c2 plugin IADS 想定分析
wsf_simdis plugin SIMDIS ASI 输出
wsf_six_dof plugin 6DOF 模型
wsf_sosm plugin SOSM 模型

16 个目录都以 wsf_module 标记自己的可选身份。插件通过 wsf_module、wsf_cmake_extension.cmake 被 CMake 发现,并生成 wsf_extensions.hpp 的注册代码(见 core/wsf/optional_projects.cmake)。

1.4.20 插件如何被"发现"

顶层 CMake 的逻辑值得记住:

cmake 复制代码
SET(MODULE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/*/wsf_module)
SET(MODULE_ROOT "${MODULE_ROOT}" "${SOURCE_PARENT_DIR}/afsim_*shared/*/wsf_module")
SET(MODULE_ROOT "${MODULE_ROOT}" "${SOURCE_PARENT_DIR}/afsim_s*t/*/wsf_module")
file(GLOB MODULE_FILES ${MODULE_ROOT})
foreach(MODULEFILE ${MODULE_FILES})
   get_filename_component(MODULE_DIR "${MODULEFILE}" PATH)
   file(GLOB_RECURSE MODULE_EXTENSION_FILES ${MODULE_DIR}/wsf_cmake_extension.cmake)
   set(WSF_EXTENSION_FILES ${WSF_EXTENSION_FILES} ${MODULE_EXTENSION_FILES})
endforeach()

见 src/CMakeLists.txt:135-160。这段逻辑说明:

  1. 一个目录只要有 wsf_module 文件,就被视为可选模块;
  2. 模块内可以有 wsf_cmake_extension.cmake,声明 WSF_EXT_NAME、WSF_EXT_TYPE(lib/plugin/exe);
  3. 构建时可选择静态链接(lib)、动态插件(plugin)或独立可执行(exe);
  4. 生成的 wsf_extensions.hpp 会被 mission.cpp 等入口包含,完成注册。

这解释了为什么 AFSIM 的构建配置如此复杂:它要在"同一个源码树"上支持多种裁剪组合。


1.5 依赖方向、数据流与关键抽象

1.5.1 依赖矩阵

把主要模块的依赖关系画成矩阵(行依赖列):

↓依赖→ util_script util wsf mil space cyber apps plugins
util_script --- 是 否 否 否 否 否 否
util 部分 --- 否 否 否 否 否 否
wsf 是 是 --- 否 否 否 否 否
mil 是 是 是 --- 否 否 否 否
space 是 是 是 否 --- 否 否 否
cyber 是 是 是 部分 否 --- 否 否
apps 是 是 是 是 是 是 --- 是
plugins 是 是 是 可能 可能 可能 否 ---

几条关键规则:

  • wsf 依赖 util 与 util_script,但不依赖任何领域扩展;
  • mil、space、cyber 平级,互相尽量不依赖;
  • 应用可以依赖所有领域扩展;
  • 插件可以依赖核心与领域扩展,但不应被核心依赖。

1.5.2 运行时数据流

以一个"雷达发现目标 → 生成航迹 → 通过数据链发送 → 指挥所接收 → 武器交战"的链路为例:

text 复制代码
[雷达传感器]
  WsfSensor::Update()
    → 调度器选择目标
    → WsfEM_Interaction 计算传播/散射
    → 检测器判决
    → 生成 WsfMeasurement
    → 传感器跟踪器更新
    → 触发 WsfSensorObserver::SensorTrackCreated
         ↓
[平台航迹管理器]
  WsfTrackManager 接收测量/航迹
    → 相关(correlation)
    → 融合(fusion)
    → 生成 WsfLocalTrack
    → 触发 WsfTrackObserver
         ↓
[通信设备]
  WsfComm::Send()
    → 协议层封装
    → 介质传输(延迟/丢包)
    → 接收方 WsfComm::Receive()
    → 触发 WsfCommObserver
         ↓
[处理器/任务管理]
  WsfProcessor::OnMessage()
    → 更新态势
    → WsfTaskManager 分配任务
    → 触发 WsfTaskObserver
         ↓
[武器]
  WsfWeapon::Fire()
    → 创建弹药平台
    → 制导/引信
    → 杀伤判定
    → 触发 WsfWeaponObserver

每一段都有对应的源码入口,本系列后续章节会逐步展开。这里先记住一个原则:AFSIM 的"业务链"不是硬编码的,而是由观察者、消息、任务、航迹管理器等机制松耦合拼接的。同一条链可以根据想定配置改变(例如雷达直接发消息、处理器转发航迹、通信设备广播)。

1.5.3 关键抽象速查

抽象 头文件 职责 生命周期
WsfApplication WsfApplication.hpp 进程级应用、扩展、插件 进程
WsfScenario WsfScenario.hpp 想定类型、输入、环境 想定
WsfSimulation WsfSimulation.hpp 运行实例、事件、平台 单次运行
WsfPlatform WsfPlatform.hpp 平台容器 仿真内
WsfPlatformPart WsfPlatformPart.hpp 部件基类 平台内
WsfComponent WsfComponent.hpp 组件接口 平台内
WsfObject WsfObject.hpp 命名/克隆/类型 场景/仿真
WsfEvent WsfEvent.hpp 事件 事件队列
WsfEventManager WsfEventManager.hpp 事件队列 仿真
WsfObserver observer/*.hpp 回调 仿真
WsfScriptContext script/WsfScriptContext.hpp 脚本上下文 场景/平台
WsfXIO xio/WsfXIO.hpp 外部接口 仿真
WsfDisInterface dis/WsfDisInterface.hpp DIS 接口 仿真
WsfEventPipe event_pipe/WsfEventPipe.hpp 事件记录 仿真

1.5.4 命名约定:读源码的"路标"

AFSIM 的命名高度一致,掌握后可以快速推断:

  • Wsf 前缀 :框架/领域类,如 WsfPlatform、WsfSensor;
  • Ut 前缀 :通用工具,如 UtInput、UtEntity;
  • X 前缀 :扩展/实验性/工具类,如 XWsfAirTraffic;
  • c 前缀的枚举值 :常量,如 cDELETE、cPENDING_START;
  • m 前缀的成员变量 :如 mSimTime、mPlatformPtr;
  • Ptr 后缀 :指针,如 mMoverPtr、mSimulationPtr;
  • Id 后缀 :字符串 ID(WsfStringId),如 mNameId、mSideId;
  • a 前缀的参数 :如 aSimTime、aPlatformPtr;
  • Get/Set/Is/Has:访问器;
  • ProcessInput:输入解析钩子;
  • CompleteLoad:加载完成钩子;
  • Initialize/Initialize2:初始化两阶段;
  • Update:时间推进钩子;
  • Clone:原型克隆;
  • QueryInterface:组件角色查询;
  • Register...:注册宏/函数。

这些约定不是强制的,但覆盖率很高。看到 ProcessInput 就知道它参与输入解析,看到 Observer 就知道它是回调接口。

1.5.5 源码阅读的"三遍法"

面对 30 万行的 core/wsf,建议分三遍:

第一遍:沿主链读。 从 mission.cpp 出发,读 WsfStandardApplication、WsfScenario、WsfSimulation、WsfPlatform、WsfPlatformPart,只求理解对象树与生命周期,不深究模型细节。

第二遍:按问题读。 选一个具体问题(例如"雷达如何检测目标"),沿 WsfSensor::Update → 调度器 → WsfEM_Interaction → 检测器 → 跟踪器 → 观察者读下去,形成一条完整的调用链。

第三遍:按扩展点读。 想清楚自己要加什么功能,找到对应的扩展点(组件、场景扩展、仿真扩展、观察者、脚本类),读一个官方实现作为模板(例如 WsfPerfectTracker、WsfDirectionFinderProcessor),再动手。

阅读工具:

  • grep -rn "class WsfXxx" 找类定义;
  • grep -rn "WsfXxx::" 找实现;
  • grep -rn "AddEvent" 找事件调度点;
  • grep -rn "GetObserver" 找观察者触发点;
  • grep -rn "ProcessInput" 找输入命令;
  • grep -rn "RegisterExtension" 找扩展注册。

在 Windows 上,Visual Studio 的"转到定义/查找所有引用"比 grep 更快;在 Linux 上,clangd + compile_commands.json(CMake 已开启 CMAKE_EXPORT_COMPILE_COMMANDS)是更好的选择。

1.5.6 常见误解澄清

误解一:"平台就是实体,实体就是平台。" 平台是仿真对象,实体(UtEntity)是它在空间中的状态表示。一个平台可以没有实体状态(例如纯逻辑节点),一个实体也可以被多个平台共享(例如编队参考点)。不要把 WsfPlatform 与 UtEntity 混为一谈。

误解二:"脚本只是配置文件。" AFSIM 脚本是图灵完备的嵌入式语言,有类型、函数、循环、状态机、事件回调。它可以修改平台状态、发送消息、创建航迹、控制传感器。把脚本当配置写,会错失大部分能力;把脚本当 C++ 写,会遭遇性能与调试困境。

误解三:"插件就是动态库。" 插件是"可加载的扩展包",动态库只是载体。一个插件可以注册多个应用/场景/仿真扩展、类型、脚本类、观察者。插件加载失败通常不是 .dll 找不到,而是注册顺序或依赖不满足。

误解四:"事件驱动就是没有固定步长。" AFSIM 同时支持事件步进(event-stepped)与帧步进(frame-stepped)。帧步进把时间切成固定步长,每个步长内调度事件;实时模式再用墙钟校准。不要把"事件驱动"等同于"无步长"。

误解五:"核心库包含所有模型。" 核心库只包含骨架与通用模型。雷达、红外、电子战、太空、赛博等大量模型在扩展库中。未编译某个扩展,相关命令就不存在,输入会报"unknown command"。

误解六:"改源码是扩展的唯一方式。" 大多数需求可以通过组件、脚本、观察者、插件完成。改核心源码会带来合并成本与升级风险,应作为最后手段。


1.6 本章小结

本章建立了 AFSIM 的"世界地图":

  1. 定位:平台级、离散事件、可扩展的作战仿真框架,附带领域模型与工具链。
  2. 版本:2.9.0(2022-02-25),C++11,CMake 3.7+,64 位。
  3. 分层:应用 → 插件 → 领域扩展 → 框架 → 基础设施。
  4. 三容器:Application(进程)→ Scenario(想定)→ Simulation(运行)。
  5. 对象树:Simulation → Platform → Parts(Mover/Sensor/Weapon/Comm/Processor/Fuel/Signature)。
  6. 事件 :以 (time, priority, sequence) 排序的优先队列,所有行为的最终载体。
  7. 扩展:应用/场景/仿真扩展、插件、组件工厂、脚本类型、观察者。
  8. 数据出口:Observer、Event Output、Event Pipe、DIS、XIO。
  9. 代码规模:核心 31 万行,军事扩展 17 万行,可选插件 34 万行;主框架小而生态大。
  10. 阅读方法:沿主链、按问题、按扩展点三遍;以源码为准,不迷信文档。

下一章将从构建系统与运行入口开始,解释这棵庞大的源码树如何被 CMake 组织成可执行体,以及 mission 从 main() 到事件循环的完整启动过程。


1.7 一条 platform 命令的完整旅程

要真正理解分层,最好的方法是跟踪一条最普通的输入命令。下面这段想定同时定义了平台类型和平台实例:

text 复制代码
platform_type FIGHTER WSF_PLATFORM
   icon fighter
   mover WSF_AIR_MOVER
      end_mover
   sensor radar RADAR_GENERIC
      end_sensor
end_platform_type

platform red_1 FIGHTER
   side red
   position 39.0n 116.0e altitude 8000 m
   heading 90 degrees
   speed 250 m/s
end_platform

从 red_1 这一行开始,AFSIM 内部会经历至少十二个步骤。

1.7.1 第一步:文件进入 UtInput

WsfStandardApplication::ProcessInputFiles 对每个输入文件调用 WsfScenario::LoadFromFile(WsfStandardApplication.cpp:279-289)。后者的实现是(WsfScenario.cpp:189-212):

cpp 复制代码
void WsfScenario::LoadFromFile(const std::string& aFileName)
{
   mInputFiles.push_back(aFileName);
   mInputPtr->PushInput(ut::make_unique<UtInputFile>(aFileName));
   mInputPtr->SetFileOpenCallback([this](const std::string& aFileName) { FileOpenCallback(aFileName); });
   FileOpenCallback(aFileName);
   LoadFromStreamP(*mInputPtr);
   ...
}

UtInput 不是简单的 ifstream 包装。它支持:

  • 多输入缓冲区栈 :include 命令会把新文件压栈,读完自动弹回;
  • 单位换算 :ReadValueOfType 接收目标类型(长度、速度、角度、频率、功率......),自动把 m、km、ft、knots、degrees、dBm 等单位转成内部 SI 值(UtInput.hpp:112-140);
  • 预处理器 :$define、$undef、$if、$include 等宏与条件编译;
  • 路径变量 :define_path_variable、file_path;
  • 精确错误定位:每个异常都带文件名、行号、当前命令。

这意味着用户在 position 行写 8000 ft 与 2438.4 m 是等价的,错误提示也能精确到文件与行。这是 AFSIM 输入语言"看起来像配置、实际有编译器级基础设施"的第一个证据。

1.7.2 第二步:逐条读取命令

WsfScenario::LoadFromStreamP(WsfScenario.cpp:225-241)是输入循环:

cpp 复制代码
void WsfScenario::LoadFromStreamP(UtInput& aInput)
{
   ConfigureInput(aInput);
   std::string command;
   while (aInput.TryReadCommand(command))
   {
      if (ProcessInputP(aInput))
      {
      }
      else
      {
         throw UtInput::UnknownCommand(aInput);
      }
   }
}

TryReadCommand 读取一个"命令词",但不读取其参数;参数由具体处理函数按需读取。这种"命令词 + 参数流"的设计让每个处理函数可以自由决定参数个数与类型,也允许同一命令在不同上下文有不同语法。

如果没有任何处理函数认领该命令,就抛出 UnknownCommand。这个异常会带上当前文件与行号,最终由 ProcessInputFiles 包装成 InputProcessingFailure 返回给 mission。

1.7.3 第三步:命令分派

ProcessInputP(WsfScenario.cpp:252-...)是一个长长的 if/else if 链,顺序非常重要:

cpp 复制代码
if (TypesProcessInput(aInput)) { }
else if (LoadPlatformInstance(aInput)) { }
else if (GetSystemLog().ProcessInput(aInput)) { }
else if (mSimulationInputPtr->ProcessInput(aInput)) { }
else if (mEnvironmentPtr->ProcessInput(aInput)) { }
else if (mAtmospherePtr->ProcessGlobalInput(aInput)) { }
else if (mMessageTablePtr->ProcessInput(aInput)) { }
else if (mScriptManagerPtr->ProcessInput(aInput)) { }
else if (ProcessExtensionInput(aInput)) { }
else if (mIFF_ManagerPtr->ProcessInput(aInput)) { }
else if (mPlatformAvailabilityPtr->ProcessInput(aInput)) { }
...

这段代码是理解 AFSIM 输入系统的关键:

  1. 类型定义优先。 TypesProcessInput 先遍历所有类型表(平台、传感器、武器、通信、处理器、运动器、路由、区域、过滤器、天线方向图......),只要有一个认领,就返回。
  2. 平台实例其次。 LoadPlatformInstance 处理 platform 与 edit platform。
  3. 运行时参数再次。 simulation 块、environment、atmosphere、message_table、脚本定义。
  4. 扩展最后。 场景扩展可以注册自己的命令,但优先级低于核心命令。
  5. 杂项命令兜底。 file_path、define_path_variable、initial_run_number 等。

这种"责任链"模式没有注册表分派那么优雅,但胜在顺序确定、调试直观 :你可以直接在 ProcessInputP 里看到所有核心命令的归属。

TypesProcessInput 的实现更简单(WsfScenario.cpp:1024-1041):

cpp 复制代码
bool WsfScenario::TypesProcessInput(UtInput& aInput)
{
   for (auto& allTypeList : mAllTypeLists)
   {
      if (allTypeList->ProcessInput(aInput))
      {
         return true;
      }
   }
   if (mTerrainInterfacePtr->ProcessInput(aInput)) { }
   else { return false; }
   return true;
}

每个类型表(WsfPlatformTypes、WsfSensorTypes......)都是一个 WsfObjectTypeListBaseI,内部持有"类型名 → 原型对象"的映射。platform_type FIGHTER WSF_PLATFORM 会被 WsfPlatformTypes 认领:它创建一个 WSF_PLATFORM 原型,命名为 FIGHTER,然后读取块内命令直到 end_platform_type。

1.7.4 第四步:平台实例的延迟装载

LoadPlatformInstance(WsfScenario.cpp:1098-1201)有一个容易被忽略的机制:延迟装载。

第一次遇到 platform red_1 FIGHTER 时,GetDeferredInput().IsDelayLoading() 为真,函数并不立即创建平台,而是:

cpp 复制代码
if (!GetDeferredInput().IsDelayLoading())
{
   std::string cmd;
   while (inputBlock.ReadCommand(cmd)) { }
   GetDeferredInput().DelayLoad("platform", instanceName, loc,
                                UtStd::Bind(&WsfScenario::LoadPlatformInstance, this));
   return true;
}

它把整个平台块"跳过",记录位置,并登记一个延迟任务。等输入文件全部读完后,CompleteLoad 再统一解析这些任务(WsfScenario.cpp:684)。

为什么要延迟?因为想定作者可能把平台实例写在类型定义之前:

text 复制代码
platform red_1 FIGHTER
   ...
end_platform

platform_type FIGHTER WSF_PLATFORM
   ...
end_platform_type

如果立即解析,此时 FIGHTER 类型还不存在,就会报"Unknown platform type"。延迟装载让输入顺序不再敏感。它还支持 Requires("platform_type", baseType) 依赖登记:如果类型始终未定义,CompleteLoad 会给出明确的缺失依赖错误,而不是模糊的"未知类型"。

这是一个典型的两阶段解析设计:第一遍收集与登记,第二遍在完整符号表上解析。编译器处理前向引用、链接器处理符号,用的是同一种思路。

1.7.5 第五步:克隆类型

真正创建实例时,LoadPlatformInstance 调用:

cpp 复制代码
std::unique_ptr<WsfPlatform> platformPtr(GetPlatformTypes().Clone(baseType));
platformPtr->SetName(instanceNameId);
platformPtr->ProcessInputBlock(inputBlock, false);
AddInputPlatform(std::move(platformPtr));

见 WsfScenario.cpp:1187-1192。Clone 不是简单的 new,而是:

  1. 找到名为 baseType 的原型对象;
  2. 调用原型对象的拷贝构造函数(WsfPlatform 有 protected 拷贝构造,WsfPlatform.cpp 中实现深拷贝);
  3. 深拷贝所有组件(mover、sensor、processor、comm、weapon、fuel、signature 等);
  4. 重置运行时状态(平台索引、仿真指针、脚本上下文中的实例变量等);
  5. 返回一个新对象,名字暂时与原型相同或为空,等待 SetName。

ProcessInputBlock(inputBlock, false) 的第二个参数表示"这不是类型定义,而是实例覆盖"。实例块中的命令只覆盖本实例的属性,不影响原型。例如:

text 复制代码
platform red_1 FIGHTER
   sensor radar RADAR_GENERIC
      mode search
         transmitter power 100 kW   # 只改 red_1 的雷达
      end_mode
   end_sensor
end_platform

这里 ProcessInputBlock 会找到 radar 组件实例(克隆自原型),再让它的 ProcessInput 处理 transmitter power。如果组件不存在,则按类型定义中的方式创建。

1.7.6 第六步:加入输入平台列表

AddInputPlatform(WsfScenario.cpp:1219)把平台放入 mInputPlatforms,并检查:

  • 指针是否为空;
  • 是否重复加入同一个指针;
  • 名字是否与已有平台冲突。

注意此时平台还没有仿真索引(mIndex == 0),也没有 WsfSimulation*。它只是"想定中的初始状态",不是"运行中的实体"。这个区分很重要:Scenario 拥有输入平台,Simulation 拥有运行平台。

1.7.7 第七步:CompleteLoad 的收尾

所有文件读完后,ProcessInputFiles 调用 aScenario.CompleteLoad()(WsfStandardApplication.cpp:288)。这个函数是场景生命周期的分水岭(WsfScenario.cpp:671-743),按顺序做九件事:

  1. 构建密级字符串 :把 classification、caveat、trigraph 等拼成显示串;
  2. 扩展排序 :按依赖关系对场景扩展排序(SortExtensions);
  3. 地形初始化 :mTerrainInterfacePtr->Initialize();
  4. 延迟输入解析 :mDeferredInputPtr->Resolve(*mInputPtr),此时所有类型都已就绪;
  5. 消息表初始化 :mMessageTablePtr->Initialize();
  6. 区域初始化 :GetZoneTypes().InitializeZones(*this),把区域定义转换为可查询的几何对象;
  7. 随机种子生成 :如果使用 generate_random_seeds,为每个运行号生成唯一种子;
  8. 类型表收尾 :对每个类型表调用 CompleteLoad(),执行跨类型校验;
  9. 输入平台收尾 :对每个输入平台调用 CompleteLoad(*this);
  10. 仿真输入收尾 :mSimulationInputPtr->LoadComplete();
  11. 扩展收尾 :依次调用场景扩展的 Complete() 与 Complete2();
  12. 置不可变标志 :mLoadIsComplete = true。

从这个时刻起,Scenario 进入只读状态。任何试图修改类型表的操作都会通过 ScenarioChange() 抛出异常(WsfScenario.hpp:139-141)。这不是"建议",而是硬约束,用来保护蒙特卡洛复用与多仿真并行。

1.7.8 第八步:创建仿真

WsfStandardApplication::CreateSimulation 根据命令行模式创建不同类型(WsfStandardApplication.cpp:323-354):

  • -es → WsfEventStepSimulation(事件步进,默认);
  • -fs → WsfFrameStepSimulation(帧步进,非实时);
  • -rt → WsfFrameStepSimulation + SetRealtime(0, true)(帧步进,实时)。

三者都派生自 WsfSimulation,共享事件队列、平台列表、观察者等基础设施,但时间推进策略不同。这再次体现了"基类定契约、派生类定策略"的设计。

1.7.9 第九步:初始化仿真

WsfSimulation::Initialize(WsfSimulation.cpp:537-648)是一次"从模板到活体"的转换:

cpp 复制代码
mState = cINITIALIZING;
WsfObserver::SimulationInitializing(this)();
CreateClock();
mSimTime = 0.0;
...
ok &= mPathFinderList.Initialize(this);
ok &= GetScriptContext().InitializeGlobalContext(*this);
ok &= mZoneAttenuation.Initialize(mScenario);
for (const auto& i : mExtensionList.GetExtensionOrder())
{
   ok &= GetExtension(i).Initialize();
   ok &= GetExtension(i).PrepareExtension();
}
ok &= WsfObserver::Initialize(this).Callback();
ok &= AddInputPlatforms();
ok = SimulationInitialized();
mState = cPENDING_START;
...
AddEvent(ut::make_unique<WsfOneShotEvent>(GetEndTime() + 0.001, []() {}));

见 WsfSimulation.cpp:586-647。几个关键点:

  • 先扩展,后平台。 仿真扩展先初始化,因为它们可能提供平台初始化所需的服务(例如通信网络管理器、LOS 管理器)。
  • 观察者先于平台。 WsfObserver::Initialize 让事件输出、DIS/XIO 等订阅者准备好,才能捕获后续的平台添加事件。
  • AddInputPlatforms 才真正克隆平台。 注意是第二次克隆:第一次是 Scenario 从类型克隆出输入平台,第二次是 Simulation 从输入平台克隆出运行平台。这样每个仿真都有独立的平台副本,互不影响。
  • 创建时间可以是未来。 如果平台的 creation_time 大于 0,仿真不会立即添加,而是调度一个 AddPlatformEvent(WsfSimulation.cpp:1204-1208)。
  • 结束事件是一个"哑事件"。 在 end_time + 0.001 调度一个空事件,确保仿真至少推进到结束时间之后,事件循环能自然退出。

1.7.10 第十步:平台注入脚本

AddInputPlatforms 支持一个全局脚本钩子 on_platform_injection(WsfSimulation.cpp:1163-1196):

cpp 复制代码
UtScript* scriptPtr = globalContext.FindScript("on_platform_injection");
if (scriptPtr != nullptr && globalContext.ValidateScript(scriptPtr, "bool", "WsfPlatform"))
{
   ...
   globalContext.ExecuteScript(0.0, scriptPtr, ret, args);
   okToAdd = ret.GetBool();
}

它在平台被克隆之后、加入仿真之前执行,参数是新平台指针,返回值决定是否加入。这个钩子非常适合做:

  • 按外部数据动态启用/禁用平台;
  • 在平台加入前修改其属性;
  • 实现蒙特卡洛中的平台抽样;
  • 与外部系统联动(例如从数据库读取初始状态)。

注意脚本在 t=0 执行,且只能访问全局上下文与平台指针,不能假设其他平台已经加入。

1.7.11 第十一步:平台初始化与引入

平台加入仿真后,会经历 PlatformInitialized 与 PlatformIntroduced 两个阶段(WsfSimulation.hpp:515-517)。典型顺序是:

  1. WsfSimulation::AddPlatform 分配索引、加入三套列表;
  2. 调用平台的 Initialize / Initialize2;
  3. 调用部件的 Initialize;
  4. 触发 WsfObserver::PlatformAdded;
  5. 调用 PlatformIntroduced,让其他平台知道新平台;
  6. 打开部件(TurnOnSystems),调度传感器扫描、通信心跳等初始事件。

WsfPlatform::Update 只更新位置与燃料,部件更新由仿真执行器按特定顺序调度(WsfPlatform.hpp:155-181 的注释明确指出这一点)。这个顺序不是随意的:传感器需要最新的平台位置,武器需要最新的航迹,通信需要最新的网络拓扑。把顺序交给仿真执行器,才能保证一致性。

1.7.12 第十二步:进入事件循环

WsfStandardApplication::RunEventLoop(WsfStandardApplication.cpp:404)先调用 sim->Start(),然后:

cpp 复制代码
while (aSimPtr->IsActive())
{
   mLogServer.ProcessMessages();
   aSimPtr->WaitForAdvanceTime();
   simTime = aSimPtr->AdvanceTime();
   ...
}

AdvanceTime 是仿真的心跳。它从事件队列取出下一个事件,把仿真时间推进到该事件的时间,执行事件,处理事件返回值(删除或重排),然后返回新的仿真时间。循环直到:

  • 到达结束时间(cEND_TIME_REACHED);
  • 外部请求终止(cTERMINATE);
  • 外部请求重置(cRESET);
  • 发生错误。

至此,一条 platform 命令完成了从文本到运行实体的旅程。回顾这十二步,可以看到 AFSIM 的核心设计模式:

  • 两阶段解析:收集 + 解析,解决前向引用;
  • 类型/实例分离:原型克隆,支持复用与覆盖;
  • 场景/仿真分离:模板与运行分离,支持蒙特卡洛;
  • 钩子链 :ProcessInput → CompleteLoad → Initialize → Initialize2 → Update;
  • 观察者:在每个关键节点发出通知,供外部订阅。

1.7.13 这条链上的常见错误

现象 可能原因 定位位置
Unknown command: foo 命令拼写错误;对应扩展未编译/未加载 ProcessInputP 的分派链;wsf_extensions.hpp
Unknown platform type: FIGHTER 类型未定义;类型定义在延迟解析后仍缺失 WsfScenario::CompleteLoad 的 Resolve;Requires 依赖
Platform is already defined 同名平台重复定义;应使用 edit platform LoadPlatformInstance
平台未出现在仿真中 creation_time 在未来;on_platform_injection 返回 false;可用性过滤 AddInputPlatforms、AddPlatformEvent
平台位置不对 单位错误;坐标系错误;mover 初始化覆盖 UtInput 单位、WsfMover::Initialize
传感器不探测 传感器未开启;模式未定义;调度器未调度;EM 参数缺失 WsfSensor::Initialize、TurnOnSystems
脚本不执行 脚本名拼错;签名不匹配;上下文错误 WsfScriptManager、ValidateScript

1.8 模块职责矩阵

上一节跟踪了"一条命令",这一节从"一个模块"的视角看职责。下表是后续章节的导航索引。

1.8.1 core/wsf:框架层

维度 内容
输入 想定文本(通过 WsfScenario)、命令行参数(通过 WsfApplication)、外部接口(DIS/XIO)
输出 观察者回调、事件日志、事件管道、DIS/XIO 报文、系统日志
关键类 WsfApplication、WsfScenario、WsfSimulation、WsfPlatform、WsfPlatformPart、WsfEvent、WsfEventManager、WsfObserver、WsfComponent、WsfObject、WsfTrackManager、WsfScriptContext
扩展点 应用/场景/仿真扩展、组件工厂、脚本类、观察者、插件
典型问题 生命周期错误、事件顺序、平台指针失效、脚本上下文污染、观察者性能
不应包含 具体雷达/武器/轨道模型(应在领域扩展中)

1.8.2 core/wsf_mil:军事领域层

维度 内容
输入 军事模型类型定义(雷达、红外、武器、电子战、通信、IFF、激光)
输出 检测、航迹、交战、干扰、杀伤、通信效果
关键类 WsfRadarSensor、WsfInfraredSensor、WsfOpticalSensor、WsfESM_Sensor、WsfWeapon、WsfExplicitWeapon、WsfImplicitWeapon、WsfGuidedWeapon、WsfRF_Jammer、WsfIFF_Manager、WsfLaserDesignations
扩展点 新的传感器/武器/电子战模型、杀伤模型、签名模型、任务处理器
典型问题 电磁参数不一致、检测概率表缺失、制导律发散、杀伤判定不符合预期
依赖 core/wsf(传感器/武器/通信骨架)、tools/util(数学/坐标)

1.8.3 core/wsf_space:太空领域层

维度 内容
输入 轨道根数、历元、摄动模型、星座定义、姿态控制、地面站
输出 卫星位置/速度、可见性、光照/阴影、交会/接近、链路访问
关键类 WsfNORAD_SpaceMover、WsfKeplerianOrbitalPropagator、WsfJ2_PerturbationOrbitalPropagator、WsfIntegratingSpaceMover、WsfConstellation、WsfEclipseEventManager、WsfAccessReport、WsfAttitudeController
扩展点 新的轨道递推器、摄动项、姿态控制器、目标点、访问计算
典型问题 历元/坐标系混淆、单位错误(km vs m)、摄动模型选择不当、数值积分步长
依赖 core/wsf、tools/util(地球模型、坐标转换)

1.8.4 core/wsf_cyber:赛博领域层

维度 内容
输入 网络拓扑、攻击/防御类型、约束、效果定义
输出 部件禁用/降级、数据欺骗、事件记录、可视化
关键类 WsfCyberAttack、WsfCyberProtect、WsfCyberConstraint、WsfCyberEngagement、WsfCyberEngagementManager、WsfCyberEventManager
扩展点 新的攻击/防御/效果类型、约束、事件输出
典型问题 攻击效果未映射到部件、网络连通性未建立、脚本接口权限
依赖 core/wsf(部件、事件、观察者)

1.8.5 core/wsf_l16:战术数据链

维度 内容
输入 J 系列消息、终端配置、网络参与组、时隙
输出 消息收发、终端状态、航迹共享
关键类 JMessage、Message10_2、Message12_*、Message13_*、ComputerPart、ComputerProcessor、Interface_J11、HeaderTDL
扩展点 新消息类型、字段类型、终端行为
典型问题 消息字段编码/解码错误、网络参与组不匹配、时隙冲突
依赖 core/wsf(通信/处理器)、wsf_mil(部分平台模型)

1.8.6 core/wsf_nx:非出口型高保真模型

维度 内容
输入 ALARM 参数、箔条云、ESA 天线、GENAP 方向图、易损性表
输出 高保真雷达/大气/杂波、箔条效果、天线增益
关键类 WsfEM_ALARM_*、WsfChaffCloud、WsfChaffParcel、WsfESA_NX_AntennaPattern、WsfGENAP_AntennaPattern、WsfEngageLaunchPkTableLethality
扩展点 受出口限制,通常不作为二次开发入口
典型问题 参数文件缺失、Fortran 依赖、许可证限制
依赖 core/wsf、wsf_mil、ALARM 库

1.8.7 core/wsf_mtt:多目标跟踪

维度 内容
输入 传感器测量、跟踪器配置
输出 多目标航迹、协方差、关联假设
关键类 多假设跟踪器、卡尔曼滤波器、航迹管理
扩展点 新的跟踪算法、关联策略
典型问题 航迹起始/终止门限、协方差一致性、计算量爆炸
依赖 core/wsf(WsfTrack、WsfMeasurement)

1.8.8 core/wsf_ripr:反应式任务分配架构

维度 内容
输入 任务定义、作业板、行为树脚本、平台/传感器/武器状态
输出 任务分配、竞价结果、作业状态、指挥链上的任务流转
关键类 WsfRIPRProcessor、WsfRIPRJob、WsfRIPRJobBoard、WsfRIPRManager、WSF_RIPR_PROCESSOR
扩展点 行为树节点、脚本决策、任务类型与评分
典型问题 作业板/指挥链配置、脚本错误、任务无人投标
依赖 core/wsf、wsf_mil(core/wsf_ripr/CMakeLists.txt)

1.8.9 core/wsf_parser、wsf_mil_parser、wsf_grammar_check

维度 内容
输入 语法定义、命令注册
输出 解析器、语法检查结果、生成代码
关键类 解析器生成器、语法节点、命令表
扩展点 新命令的语法定义
典型问题 语法冲突、生成代码与手写代码不一致、文档不同步
依赖 tools/util

1.8.10 core/wsf_util、tools/util、tools/util_script

维度 内容
输入 原始数据、文件、数学参数
输出 通用数据结构、坐标、时间、输入、日志、脚本执行
关键类 UtInput、UtEntity、UtAtmosphere、UtCalendar、UtScript、UtScriptContext、UtMath、UtRandom、UtPack
扩展点 新脚本类型、新输入格式、新数学工具
典型问题 单位/坐标系、浮点精度、随机数可复现性、脚本性能
依赖 无(或仅系统库)

1.8.11 tools/dis、core/wsf/source/dis

维度 内容
输入 DIS PDU、网络报文、实体状态
输出 DIS PDU、实体状态、事件
关键类 PDU 编解码、WsfDisInterface、WsfDisEntityState、WsfDisEmission、WsfDisFire、WsfDisDetonation
扩展点 自定义 PDU、过滤器、映射规则
典型问题 版本兼容、坐标转换、实体 ID 映射、时间同步
依赖 tools/util、tools/packetio、core/wsf

1.8.12 core/wsf/source/xio

维度 内容
输入 外部请求(平台列表、传感器、任务、绘图、脚本)
输出 订阅数据、查询响应、事件流
关键类 WsfXIO、WsfXIO_Connection、WsfXIO_Service、WsfXIO_Publisher、WsfXIO_Subscription
扩展点 新服务、新报文、新订阅类型
典型问题 连接时序、序列化版本、线程安全、带宽
依赖 core/wsf

1.8.13 core/wsf/source/event_pipe

维度 内容
输入 观察者事件、筛选条件
输出 二进制事件文件
关键类 WsfEventPipe、WsfEventPipeInterface、WsfEventPipeFileWriteWorker、WsfEventPipeOptions
扩展点 新事件类型、新筛选条件、新压缩/写入策略
典型问题 文件膨胀、写入阻塞、版本兼容、事件丢失
依赖 core/wsf(观察者)、tools/util

1.8.14 应用层

应用 输入 输出 关键依赖 典型问题
mission 输入文件、命令行 日志、事件文件、DIS/XIO 全部核心扩展 想定错误、性能、随机种子
warlock XIO/DIS、想定 实时画面、控制命令 wkf、utilosg、utilqt 连接、渲染性能、交互延迟
wizard 想定文件 编辑后的想定 utilqt、语法模型 语法高亮、类型补全、保存一致性
mystic 事件管道、CSV 图表、报告 utilqt、后处理 数据量、字段映射、时间对齐
sensor_plot 传感器定义、地形 覆盖图 sensor_plot_lib、Qt 计算量、坐标、地形数据
weapon_tools 武器定义、目标 包线、Pk 表 core/wsf、wsf_mil 参数范围、收敛、计算时间
mover_creator 气动数据 mover 数据文件 Qt、气动模型 数据格式、插值、单位
engage 交战想定 交战结果 core/wsf、wsf_mil 初始化、杀伤判定
post_processor 事件管道、脚本 指标文件 后处理库 脚本 API、批量性能
evt_reader .evt 文件 可视化事件列表 Qt 大文件、过滤

1.8.15 插件层

插件 主要能力 依赖 典型用途
wsf_air_combat 空战战术、编队、交战决策 wsf_mil 空战想定
wsf_brawler BRAWLER 空战模型 wsf_mil 高保真空战
wsf_six_dof / wsf_p6dof 6 自由度飞行力学 wsf_mil、气动数据 飞行性能分析
wsf_iads_c2 综合防空指挥控制 wsf_mil、通信 IADS 想定
wsf_fires 火力/炮兵 wsf_mil 火力打击
wsf_coverage 覆盖分析 core/wsf 传感器/通信覆盖
wsf_multiresolution 多分辨率建模 core/wsf 大规模仿真
wsf_oms_uci OMS/UCI 接口 外部标准 联合互操作
wsf_simdis SIMDIS ASI 输出 core/wsf 可视化回放
wsf_scenario_analyzer 想定分析 core/wsf 想定质量检查
wsf_annotation 标注 core/wsf 想定说明
wsf_alternate_locations 备选位置 core/wsf 部署分析
wsf_argo8 ARGO8 模型 外部模型 特定领域
wsf_sosm SOSM 模型 外部模型 特定领域

使用建议:不要把插件当成"可选装饰"。插件往往引入新的输入命令、类型和脚本 API,未加载时相关想定会直接报错。部署时必须保证"想定所用的插件"与"运行环境加载的插件"一致。


1.9 架构决策记录:十个关键取舍

架构不是一堆类的集合,而是一系列取舍的结果。下面用"决策记录"(ADR)的形式,解释 AFSIM 为什么长成今天这样。每条都给出背景、决策、后果、证据。

1.9.1 ADR-1:事件驱动,而不是固定步长

背景:作战仿真的活动是稀疏的。雷达每秒扫描几次,导弹每隔几十毫秒更新,通信事件偶尔发生。如果用固定步长(例如 1 ms)推进,绝大多数步长内没有状态变化,算力被浪费。

决策 :以离散事件为核心,事件按时间排序,时间可以跳跃。固定步长作为可选的帧步进模式(WsfFrameStepSimulation)。

后果:

  • 优点:稀疏活动下极快;事件顺序精确;易于表达"在 t+Δ 发生"的逻辑。
  • 缺点:事件风暴时性能下降;调试需要理解事件链;同一时刻的事件顺序影响结果,必须用优先级与 FIFO 保证确定性。
  • 证据:WsfEventManager 的 (time, priority, sequence) 排序(WsfEventManager.hpp:86-100);WsfEventStepSimulation 与 WsfFrameStepSimulation 并存。

1.9.2 ADR-2:Scenario 与 Simulation 分离

背景:蒙特卡洛需要反复运行同一想定;多仿真需要共享类型定义;并行实验需要只读模板。

决策 :把"想定定义"(类型、输入、环境、脚本)与"运行实例"(事件、平台、观察者、接口)分成两个对象,Scenario 在 CompleteLoad 后不可变。

后果:

  • 优点:解析一次、运行多次;并行安全;输入校验前置。
  • 缺点:两次克隆(类型→输入平台→运行平台)带来内存与初始化开销;场景扩展与仿真扩展的职责容易混淆。
  • 证据:WsfScenario::CompleteLoad 的 mLoadIsComplete(WsfScenario.cpp:737);WsfSimulation::AddInputPlatforms 的第二次克隆(WsfSimulation.cpp:1179)。

1.9.3 ADR-3:类型与实例分离

背景:大规模想定中,同一型雷达可能挂在几十个平台上;如果每个实例都重复定义参数,维护成本极高。

决策 :类型定义(sensor_type、platform_type)在场景中唯一存在;实例通过名字引用类型,并可覆盖属性。克隆发生在装载与仿真初始化两个阶段。

后果:

  • 优点:复用、可维护、可批量修改;支持继承(类型派生类型)。
  • 缺点:克隆语义复杂(深拷贝哪些、重置哪些);实例覆盖容易产生"以为改了类型其实只改了实例"的误解。
  • 证据:WsfObjectTypeList 的 Clone;WsfPlatform 的 protected 拷贝构造;WsfPlatform::ProcessInputBlock(inputBlock, false)。

1.9.4 ADR-4:组合式平台,而不是继承式平台

背景:平台种类繁多,如果为每种平台建一个类,继承树会爆炸,且能力无法复用。

决策:平台是容器,能力由部件组合。部件通过角色(role)查询接口,平台通过组件列表管理生命周期。

后果:

  • 优点:能力复用、想定可组合、扩展点清晰。
  • 缺点:部件间交互需要显式建模;组件查询有运行时开销;调试时需要理解"谁拥有谁"。
  • 证据:WsfPlatform 继承 WsfPlatformComponentList;WsfComponent::QueryInterface;WsfPlatform::GetMover/GetFuel 等持久指针。

1.9.5 ADR-5:脚本是一等公民

背景:作战行为(规则、条令、决策)变化快,如果都写成 C++,每次修改都要重新编译、重新部署。

决策:内置一套带类型系统的脚本语言,支持全局脚本、平台脚本、处理器脚本、状态机、回调、事件。脚本可以访问平台、传感器、航迹、消息、任务等对象。

后果:

  • 优点:快速迭代;领域专家可参与;无需重编译。
  • 缺点:性能低于 C++;调试工具有限;脚本错误可能在运行时才暴露;脚本上下文容易污染。
  • 证据:tools/util_script 的 UtScript、UtScriptClass、UtScriptExecutor;core/wsf/source/script 的 108 个绑定文件。

1.9.6 ADR-6:观察者而非直接输出

背景:同一份仿真数据可能被写日志、写事件管道、发 DIS、发 XIO、画图、做脚本分析。如果模型直接写文件,输出格式一变就要改模型。

决策:模型只触发观察者回调,订阅者决定数据处理方式。观察者按领域拆分,支持多订阅者。

后果:

  • 优点:解耦、可扩展、可组合;同一模型支持多种输出。
  • 缺点:回调开销;订阅顺序不确定;观察者生命周期必须与仿真匹配。
  • 证据:core/wsf/source/observer/ 的 32 个文件;WsfSimulation 中的 16 个观察者成员(WsfSimulation.hpp:650-665)。

1.9.7 ADR-7:插件与静态扩展并存

背景:有些能力需要随核心一起发布(稳定、常用),有些需要独立分发(可选、受控、实验性)。

决策 :同时支持静态链接扩展(WSF_EXT_TYPE lib)、动态插件(plugin)、独立可执行(exe),通过 wsf_module 与 wsf_cmake_extension.cmake 统一发现。

后果:

  • 优点:灵活裁剪;插件可热插拔;发布粒度可控。
  • 缺点:构建配置复杂;插件 ABI/版本兼容性风险;加载顺序与依赖管理困难。
  • 证据:src/CMakeLists.txt:135-160;core/wsf/optional_projects.cmake;WsfPluginManager;WSF_PLUGIN_PATH 环境变量(WsfApplication.cpp:97-118)。

1.9.8 ADR-8:字符串驻留

背景:想定中有大量重复字符串(类型名、平台名、命令名、单位名)。反复哈希与比较代价高。

决策 :使用 WsfStringId 与全局字符串表,把字符串映射为整数 ID;比较 ID 而不是字符串。

后果:

  • 优点:比较 O(1);内存占用低;适合作为 map 键。
  • 缺点:字符串表生命周期长;跨场景/跨库的 ID 一致性需要保证;调试时 ID 不直观。
  • 证据:WsfStringId.hpp、WsfStringTable.hpp;WsfScenario::Strings();WsfPlatform::GetNameId()。

1.9.9 ADR-9:双随机数源

背景:模型随机数(检测、误差、天气)与脚本随机数(用户逻辑)如果共用一个源,脚本的任何改动都会影响模型结果的可复现性。

决策 :WsfSimulation 维护两个 ut::Random:mRandom(模型)与 mScriptRandom(脚本),各有独立锁(WsfSimulation.hpp:620-629)。

后果:

  • 优点:脚本改动不破坏模型随机序列;可分别复现;多线程下锁粒度更细。
  • 缺点:用户需要知道"哪个随机源";脚本随机数不适合做敏感抽样;种子管理更复杂。
  • 证据:WsfSimulation::GetRandom、GetScriptRandom;WsfScenario::GetRandomSeed 的按运行号种子生成(WsfScenario.cpp:1056-1082)。

1.9.10 ADR-10:平台索引而非裸指针

背景:平台可以在仿真中被删除。如果传感器/处理器持有平台裸指针,删除后会悬空。

决策 :跨平台持久引用使用平台索引(GetIndex),通过 GetPlatformByIndex 安全查询;平台内部部件可以使用持久指针,因为它们与平台同生命周期。

后果:

  • 优点:避免悬空指针;支持平台删除/重建;索引稳定不复用。
  • 缺点:每次查询有间接寻址;索引为 0 表示未加入仿真;删除后 GetPlatformByIndex 返回 nullptr,调用者必须检查。
  • 证据:WsfPlatform.hpp:87-105 的注释;WsfSimulation::GetPlatformByIndex;WsfPlatform::GetIndex。

1.9.11 这些决策之间的关系

这十个决策不是孤立的,它们互相支撑:

text 复制代码
事件驱动(ADR-1)
  ├── 需要稳定排序 → 事件管理器
  ├── 需要时间源 → 时钟源
  └── 需要数据出口 → 观察者(ADR-6)

Scenario/Simulation 分离(ADR-2)
  ├── 需要类型/实例分离(ADR-3)
  ├── 需要不可变场景 → CompleteLoad
  └── 需要随机种子管理(ADR-9)

组合式平台(ADR-4)
  ├── 需要组件/角色系统
  ├── 需要平台索引(ADR-10)
  └── 需要脚本访问(ADR-5)

插件(ADR-7)
  ├── 需要类型注册
  ├── 需要扩展点
  └── 需要字符串驻留(ADR-8)保证跨库一致

理解这些关系,就能理解为什么 AFSIM 的代码"看起来复杂但有内在秩序":每一个看似冗余的间接层,通常对应一个真实的工程约束。


1.10 构建产物与运行目录

1.10.1 源码目录 vs 安装目录

源码树是"开发视图",安装目录是"运行视图"。官方发行包的典型结构(doc/developer/build_instructions.rst):

text 复制代码
afsim-x.y.z-win64/
├── bin/                 # 可执行文件与动态库
├── demos/               # 示例想定
├── documentation/       # 文档
├── resources/           # 地图、模型、图标等资源
├── swdev/src/           # 源码
├── tools/               # 辅助工具
└── training/            # 培训材料

mission.exe、warlock.exe、wizard.exe 等位于 bin。插件通常位于 bin/../wsf_plugins 或 bin/../<app>_plugins,这正是 WsfApplication 构造函数默认搜索的路径(WsfApplication.cpp:107-110):

cpp 复制代码
pluginPaths.push_back("../" + GetApplicationName() + "_plugins");
pluginPaths.emplace_back("../wsf_plugins");

路径是相对于可执行文件 的,不是相对于当前工作目录。这意味着你可以从任意目录启动 mission,只要 bin 与插件目录的相对关系不变。

1.10.2 插件路径的优先级

插件搜索路径的解析顺序(WsfApplication.cpp:97-118):

  1. 如果设置了环境变量 WSF_PLUGIN_PATH,只使用它,忽略默认路径;
  2. 否则使用构造函数传入的 aPluginPaths;
  3. 如果传入为空,使用 ../<app>_plugins 与 ../wsf_plugins;
  4. AddPluginSearchPath(path, recursive=true) 支持递归搜索子目录。

WSF_PLUGIN_PATH 是排查插件问题的重要工具。例如,当你想让 mission 加载一个开发中的插件,可以:

bash 复制代码
# Linux
export WSF_PLUGIN_PATH=/path/to/my_plugins
./bin/mission scenario.txt

# Windows (PowerShell)
$env:WSF_PLUGIN_PATH = "work\my_plugins"
.\bin\mission.exe scenario.txt

注意:一旦设置该变量,默认的 wsf_plugins 目录不再被搜索 。如果插件依赖其他插件,必须把它们放在同一路径下,或使用分号分隔多个路径(具体分隔符取决于实现,Windows 通常为 ;,Linux 为 :)。

1.10.3 构建目录

顶层 CMake 把可执行文件、动态库、静态库分别输出到:

cmake 复制代码
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)

见 src/CMakeLists.txt:197-198。在 Windows 上,DLL 与 EXE 放在同一目录,避免运行时找不到依赖;在 Linux 上,共享库也在同一目录。安装时通过 INSTALL_EXE_PATH、INSTALL_DLL_PATH、INSTALL_PLUGIN_PATH 重新组织。

CMakePresets.json 提供了 default-vs2019、ninja 等预设,输出到 ${sourceDir}/BUILDS/${presetName}。实际构建命令通常类似:

bash 复制代码
cmake --preset default-vs2019
cmake --build --preset default-vs2019 --config Release

具体预设名称取决于 CMakePresets.json 的完整内容与本地工具链。

1.10.4 数据与资源

AFSIM 运行需要的不只是可执行文件:

  • 地形数据 :DTED、GeoTIFF 等,通过 terrain 命令指定;
  • 地图/模型 :Warlock 的 3D 模型、图标、纹理,位于 resources/;
  • 天线方向图 :.ant、.azel 等表格;
  • 雷达/红外签名:表格或解析模型;
  • 通信/武器数据:各模型专用数据文件;
  • 插件 :.dll/.so。

这些数据通常通过输入文件中的路径命令或环境变量定位。部署时最常见的错误是"程序能启动,但一加载想定就报找不到文件"。排查顺序:

  1. 看错误信息中的路径;
  2. 检查 file_path、define_path_variable;
  3. 检查当前工作目录(相对路径的基准);
  4. 检查 resources/ 是否完整;
  5. 检查插件是否加载(PrintLoadedPlugins 或启动日志)。

1.10.5 构建裁剪

AFSIM 的构建选项非常多,常用的有:

选项 默认 作用
WSF_PLUGIN_BUILD TRUE 是否构建插件
WSF_INSTALL_BASE_GRAMMAR TRUE 是否安装基础语法文件
WSF_INSTALL_SOURCE FALSE 是否安装源码
WSF_INSTALL_DEMOS FALSE 是否安装示例
WSF_INSTALL_SCENARIOS FALSE 是否安装想定
WSF_INSTALL_TOOLS FALSE 是否安装工具
WSF_INSTALL_TRAINING FALSE 是否安装培训材料
WSF_INSTALL_DOCUMENTATION FALSE 是否安装文档
WSF_INSTALL_DEPENDENCIES FALSE 是否安装依赖(如 MSVC 运行时)
WSF_BUILD_API FALSE 是否构建 API
WSF_ADD_EXTENSION_PATH 空 额外的扩展/插件搜索路径

每个可选模块还有 BUILD_WITH_<name> 开关,例如 BUILD_WITH_wsf_mil、BUILD_WITH_wsf_space。裁剪构建时,务必保证应用所需的扩展被打开,否则会在链接或运行时缺失类型。


1.11 常用任务索引:从问题到源码

这一节是一张"问题 → 文件"的速查表,供日常开发使用。

1.11.1 想定编写

任务 入口
查某条命令的语法 对应类的 ProcessInput;grep -rn "\"command_name\""
查平台类型可用属性 WsfPlatform::ProcessInput、WsfPlatformPart::ProcessInput
查传感器模式属性 WsfSensor::ProcessInput、WsfSensorMode::ProcessInput
查武器属性 core/wsf_mil/source/weapon/WsfWeapon.cpp 的 ProcessInput
查通信属性 core/wsf/source/comm/WsfComm.cpp
查脚本 API core/wsf/source/script/WsfScript*Class.cpp
查观察者脚本名 WsfScriptObserver、WsfMilScriptObserver
查单位 tools/util/source/UtUnits.cpp
查预处理器 tools/util/source/UtInputPreprocessor.cpp
查区域语法 WsfZoneDefinition.cpp、WsfZone.cpp

1.11.2 运行时行为

任务 入口
平台为何没更新 WsfPlatform::Update、WsfMover::Update、WsfPlatformPart::Update
事件何时执行 WsfEventManager::AddEvent/PopEvent、WsfSimulation::AdvanceTime
传感器为何不探测 WsfSensor::Update、WsfSensorScheduler、WsfEM_Interaction
航迹为何不生成 WsfSensorTracker、WsfTrackManager、WsfTrack
消息为何没收到 WsfComm::Send/Receive、WsfCommNetwork、WsfCommMedium
武器为何没命中 WsfWeapon::Fire、WsfGuidedWeapon、引信/杀伤模型
任务为何没分配 WsfTaskManager、WsfTaskProcessor
脚本为何没执行 WsfScriptManager、WsfScriptContext、处理器回调
平台为何消失 WsfSimulation::DeletePlatform、OnBrokenEvent

1.11.3 数据输出

任务 入口
事件日志格式 WsfEventOutput、WsfCSV_EventOutput
事件管道格式 WsfEventPipe、WsfEventPipeInterface、WsfEventPipeOptions
自定义输出 订阅 WsfObserver 回调
脚本输出 writeln、WsfScriptOutput
DIS 输出 WsfDisInterface、WsfDisEntityState
XIO 输出 WsfXIO、WsfXIO_Publisher
绘图 WsfDraw、WsfVisualization

1.11.4 扩展开发

任务 入口
新增传感器类型 派生 WsfSensor + WsfSensorMode,注册类型
新增武器类型 派生 WsfWeapon,注册类型
新增通信类型 派生 WsfComm,注册类型
新增运动器 派生 WsfMover,注册类型
新增处理器 派生 WsfProcessor,注册类型
新增脚本类 派生 UtScriptClass,注册到 UtScriptTypes
新增观察者事件 在对应 Observer 类中加回调,触发点调用
新增输入命令 WsfScenarioExtension::ProcessInput 或组件的 ProcessInput
新增全局服务 WsfSimulationExtension
新增应用选项 WsfApplicationExtension::ProcessCommandLine
打包插件 wsf_module + wsf_cmake_extension.cmake + WSF_PLUGIN_DEFINE_VERSION

1.11.5 调试与性能

任务 入口
打开输入调试 stream_debug_on(WsfScenario.cpp:323-327)
查看随机种子 启动日志;WsfScenario::GetRandomSeed
查看插件加载 WsfPluginManager::PrintLoadedPlugins
查看事件队列 WsfEventManager::PeekEvent;自定义观察者
性能剖析 tools/profiling;ProfilingRegion
多线程调试 WsfMultiThreadManager、WsfSimulation::MultiThreadingActive
内存问题 平台索引/指针生命周期;WsfPlatform 删除逻辑
脚本调试 UtScriptDebugger、脚本异常信息

1.12 术语表

以下术语按字母顺序排列,首次阅读时可作为参考。

术语 英文 含义
应用 Application 进程级容器,持有扩展、插件、脚本类型,负责创建场景与仿真
场景 Scenario 想定级容器,持有类型表、输入、环境、脚本;CompleteLoad 后不可变
仿真 Simulation 运行级容器,持有事件、平台、观察者、接口;一次运行一个实例
平台 Platform 仿真中的实体容器,挂载运动器、传感器、武器、通信、处理器等
部件 Part 平台上的功能组件,如传感器、武器、通信、处理器
组件 Component 更广义的可查询接口对象,平台本身也是组件
类型 Type 场景中的原型定义,如 platform_type、sensor_type
实例 Instance 从类型克隆出的对象,可覆盖属性
运动器 Mover 维护平台位置、姿态、速度的部件
传感器 Sensor 探测目标并生成测量/航迹的部件
武器 Weapon 执行杀伤或干扰的部件
通信 Comm 发送/接收消息的部件
处理器 Processor 执行决策/计算的部件
航迹 Track 对目标的估计状态,含位置、速度、协方差、身份
本地航迹 Local Track 平台自己维护的航迹
融合 Fusion 合并多个来源的航迹/测量
相关 Correlation 判断测量/航迹是否属于同一目标
任务 Task 平台间的任务分配,关联航迹与状态机
事件 Event 在指定仿真时间执行的逻辑单元
事件队列 Event Queue 按时间/优先级/顺序排序的优先队列
观察者 Observer 发布-订阅回调接口
扩展 Extension 在应用/场景/仿真生命周期中注册的能力
插件 Plugin 可动态加载的扩展包
脚本 Script AFSIM 内置的嵌入式语言
脚本上下文 Script Context 脚本变量、函数、类的作用域
电磁交互 EM Interaction 发射机、接收机、天线、传播、目标的综合计算
签名 Signature 目标的雷达/红外/光学/声学特征
天线方向图 Antenna Pattern 天线增益随角度变化的函数
传播 Propagation 电磁波在空间中的衰减/折射
杂波 Clutter 地海面等背景回波
检测概率 Pd Probability of Detection
虚警率 Pfa Probability of False Alarm
信噪比 SNR Signal-to-Noise Ratio
杀伤概率 Pk Probability of Kill
防区内 AOR Area of Responsibility
任务链 Kill Chain 探测-识别-决策-交战-评估的链条
数据链 Datalink 平台间传输战术数据的通信系统
网络参与组 NPG Network Participation Group(Link 16)
时隙 Slot TDMA 时间片
分布式仿真 Distributed Simulation 多个仿真/模拟器互联
高层体系结构 HLA High Level Architecture(IEEE 1516)
分布式交互仿真 DIS Distributed Interactive Simulation(IEEE 1278)
外部输入输出 XIO AFSIM 自有外部接口
事件管道 Event Pipe 二进制事件记录格式
蒙特卡洛 Monte Carlo 多次随机运行统计
运行号 Run Number 蒙特卡洛的迭代编号
随机种子 Seed 随机数发生器的初始值
墙钟 Wall Clock 真实世界时间
仿真时间 Sim Time 仿真内部时间
实时 Real Time 仿真时间与墙钟同步
帧步进 Frame Stepped 固定时间步长推进
事件步进 Event Stepped 按事件时间跳跃推进
确定性 Determinism 相同输入/种子产生相同结果
字符串驻留 String Interning 字符串到整数 ID 的映射
组件角色 Component Role 组件的能力标识,用于 QueryInterface
延迟输入 Deferred Input 先登记、后解析的两阶段输入机制
代码生成 Code Generation 由语法/模板生成 C++ 代码

1.13 练习与检查清单

1.13.1 自测题

  1. 为什么 WsfScenario 在 CompleteLoad 后必须不可变?如果允许修改,会破坏哪些机制?
  2. WsfPlatform 为什么要克隆两次(类型→输入平台→运行平台)?第二次克隆解决了什么问题?
  3. 事件管理器为什么需要 sequence 字段?只按 (time, priority) 排序会有什么问题?
  4. 如果两个观察者订阅了同一个事件,它们的执行顺序由什么决定?能否依赖这个顺序?
  5. WSF_PLUGIN_PATH 设置后,默认插件目录为什么不再被搜索?这会带来什么部署风险?
  6. 为什么模型随机数与脚本随机数要分开?如果合并,蒙特卡洛结果会受什么影响?
  7. 平台索引与平台指针各适合什么场景?为什么跨平台引用推荐索引?
  8. on_platform_injection 脚本在什么时机执行?它能访问哪些对象?不能访问哪些对象?
  9. 事件步进与帧步进的核心差异是什么?实时模式必须用帧步进吗?
  10. 如果一条输入命令在 ProcessInputP 中没有被任何分支认领,会发生什么?如何定位?

1.13.2 实践任务

  1. 在本地构建一个最小 mission,只启用 wsf_mil,运行一个包含一个平台、一个传感器的想定;观察启动日志中的版本、插件、随机种子。
  2. 用 grep -rn "ProcessInput" core/wsf/source/sensor/WsfSensor.cpp 找到传感器支持的命令,整理成表格。
  3. 写一个脚本观察者,输出平台位置随时间的变化;再用事件管道记录同一数据,比较两种方式的字段与性能。
  4. 故意在 platform 中引用一个未定义的类型,观察 CompleteLoad 的报错信息;再把类型定义移到平台之后,验证延迟装载是否生效。
  5. 设置 WSF_PLUGIN_PATH 指向一个空目录,观察插件加载行为;再恢复默认,比较启动日志。

1.13.3 阅读检查清单

读完本章后,回到源码,确认你能回答:

  • WsfApplication、WsfScenario、WsfSimulation 各持有什么?
  • WsfSimulation 的 State 有哪些?转换条件是什么?
  • WsfPlatform 的多重继承分别带来什么能力?
  • 事件管理器的排序键是什么?为什么需要三个字段?
  • 观察者有哪些领域?各自的触发点在哪里?
  • 扩展的五个层次分别是什么?注册时机在哪里?
  • 插件如何被发现?WSF_PLUGIN_PATH 的优先级如何?
  • core/wsf 与 wsf_mil 的依赖方向是什么?为什么不能反向?
  • 一条 platform 命令从文本到运行经历哪些阶段?
  • 常见错误分别应该查哪个文件?

1.14 关键类索引:六十个必须知道的类型

下面的索引按"先框架、后领域、再工具"的顺序排列。每一项给出头文件与一句话职责。它不是完整的 API 文档,而是帮助你在一万七千个文件中快速定位"那个类到底在哪"。

1.14.1 框架核心(core/wsf)

类/接口 头文件 职责
WsfApplication WsfApplication.hpp 进程级应用,管理扩展、插件、脚本类型、命令行
WsfStandardApplication WsfStandardApplication.hpp 标准应用实现,提供输入装载、仿真创建、事件循环
WsfApplicationExtension WsfApplicationExtension.hpp 应用级扩展基类
WsfScenario WsfScenario.hpp 想定容器,类型表、输入、环境、脚本
WsfScenarioExtension WsfScenarioExtension.hpp 场景级扩展基类
WsfSimulation WsfSimulation.hpp 仿真容器,事件、平台、观察者、服务
WsfSimulationExtension WsfSimulationExtension.hpp 仿真级扩展基类
WsfSimulationInput WsfSimulationInput.hpp 运行期参数(时间、线程、随机、优先级)
WsfObject WsfObject.hpp 命名、类型、克隆的基类
WsfNamed WsfNamed.hpp 命名对象的轻量基类
WsfComponent WsfComponent.hpp 组件接口,支持角色查询与克隆
WsfComponentList WsfComponentList.hpp 组件容器,管理添加/删除/查询
WsfComponentFactory WsfComponentFactory.hpp 组件工厂,按名字创建组件
WsfPlatform WsfPlatform.hpp 平台容器,挂载部件与状态
WsfPlatformPart WsfPlatformPart.hpp 部件基类,提供开关、更新、损伤等
WsfArticulatedPart WsfArticulatedPart.hpp 可转动部件(传感器/发射器)
WsfEvent WsfEvent.hpp 事件接口,Execute 返回删除或重排
WsfEventManager WsfEventManager.hpp 事件优先队列
WsfEventStepSimulation WsfEventStepSimulation.hpp 事件步进仿真
WsfFrameStepSimulation WsfFrameStepSimulation.hpp 帧步进仿真
WsfClockSource WsfClockSource.hpp 时钟源抽象
WsfRealTimeClockSource WsfRealTimeClockSource.hpp 墙钟同步时钟
WsfExtension WsfExtension.hpp 扩展通用基类
WsfExtensionList WsfExtensionList.hpp 扩展注册、排序、查找
WsfScriptContext script/WsfScriptContext.hpp 脚本上下文
WsfScriptManager script/WsfScriptManager.hpp 脚本解析、缓存、执行
WsfTrack WsfTrack.hpp 航迹状态与元数据
WsfTrackManager WsfTrackManager.hpp 平台航迹管理
WsfTrackId WsfTrackId.hpp 航迹标识
WsfLocalTrack WsfLocalTrack.hpp 本地航迹
WsfMeasurement WsfMeasurement.hpp 传感器测量
WsfMessage WsfMessage.hpp 消息基类
WsfMessageTable WsfMessageTable.hpp 消息类型表
WsfZone WsfZone.hpp 区域几何与查询
WsfZoneDefinition WsfZoneDefinition.hpp 区域定义解析
WsfRoute WsfRoute.hpp 路线
WsfStringId WsfStringId.hpp 字符串 ID
WsfStringTable WsfStringTable.hpp 字符串驻留表
WsfEM_Interaction WsfEM_Interaction.hpp 电磁交互计算
WsfEM_Xmtr / WsfEM_Rcvr WsfEM_Xmtr.hpp / WsfEM_Rcvr.hpp 发射机/接收机
WsfEM_Antenna WsfEM_Antenna.hpp 天线
WsfAntennaPattern WsfAntennaPattern.hpp 天线方向图基类
WsfSignature WsfSignature.hpp 签名基类
WsfRadarSignature WsfRadarSignature.hpp 雷达签名
WsfEnvironment WsfEnvironment.hpp 环境(大气、地形、时间)
WsfTerrain WsfTerrain.hpp 地形接口
WsfVisualization WsfVisualization.hpp 可视化接口
WsfDraw WsfDraw.hpp 绘图请求
WsfXIO xio/WsfXIO.hpp 外部接口
WsfEventPipe event_pipe/WsfEventPipe.hpp 事件管道
WsfDisInterface dis/WsfDisInterface.hpp DIS 接口

1.14.2 领域模型(core/wsf_mil、wsf_space 等)

类/接口 头文件 领域
WsfSensor sensor/WsfSensor.hpp 传感器骨架
WsfSensorMode sensor/WsfSensorMode.hpp 传感器模式
WsfSensorScheduler sensor/WsfSensorScheduler.hpp 扫描调度
WsfSensorDetector sensor/WsfSensorDetector.hpp 检测判决
WsfSensorTracker sensor/WsfSensorTracker.hpp 传感器跟踪
WsfRadarSensor sensor/WsfRadarSensor.hpp 雷达
WsfPassiveSensor sensor/WsfPassiveSensor.hpp 被动传感器
WsfGeometricSensor sensor/WsfGeometricSensor.hpp 几何传感器
WsfMover mover/WsfMover.hpp 运动器骨架
WsfAirMover mover/WsfAirMover.hpp 空中运动
WsfGroundMover mover/WsfGroundMover.hpp 地面运动
WsfFuel mover/WsfFuel.hpp 燃料
WsfPathFinder mover/WsfPathFinder.hpp 路径规划
WsfComm comm/WsfComm.hpp 通信骨架
WsfCommNetwork comm/WsfCommNetwork.hpp 通信网络
WsfCommMedium comm/WsfCommMedium.hpp 传输介质
WsfCommLayer comm/WsfCommLayer.hpp 协议层
WsfProcessor processor/WsfProcessor.hpp 处理器骨架
WsfTaskManager processor/WsfTaskManager.hpp 任务管理
WsfTask processor/WsfTask.hpp 任务
WsfWeapon weapon/WsfWeapon.hpp 武器骨架
WsfExplicitWeapon weapon/WsfExplicitWeapon.hpp 显式武器(弹药平台)
WsfImplicitWeapon weapon/WsfImplicitWeapon.hpp 隐式武器(干扰/激光)
WsfGuidedWeapon weapon/WsfGuidedWeapon.hpp 制导武器
WsfRF_Jammer ew/WsfRF_Jammer.hpp 射频干扰机
WsfIFF_Manager WsfIFF_Manager.hpp 敌我识别
WsfLaserDesignations WsfLaserDesignations.hpp 激光指示
WsfInfraredSignature WsfInfraredSignature.hpp 红外签名
WsfOpticalSignature WsfOpticalSignature.hpp 光学签名
WsfAcousticSignature WsfAcousticSignature.hpp 声学签名
WsfNORAD_SpaceMover wsf_space/.../WsfNORAD_SpaceMover.hpp 轨道运动
WsfKeplerianOrbitalPropagator wsf_space/.../WsfKeplerianOrbitalPropagator.hpp 开普勒递推
WsfConstellation wsf_space/.../WsfConstellation.hpp 星座
WsfEclipseEventManager wsf_space/.../WsfEclipseEventManager.hpp 光照/阴影
WsfCyberAttack wsf_cyber/.../WsfCyberAttack.hpp 赛博攻击
WsfCyberProtect wsf_cyber/.../WsfCyberProtect.hpp 赛博防御
JMessage wsf_l16/.../JMessage.hpp Link 16 消息
ComputerProcessor wsf_l16/.../ComputerProcessor.hpp Link 16 终端处理器

1.14.3 工具与运行时(tools)

类/接口 头文件 职责
UtInput tools/util/source/UtInput.hpp 输入读取、单位换算、include
UtInputBlock tools/util/source/UtInputBlock.hpp 块读取
UtInputPreprocessor tools/util/source/UtInputPreprocessor.hpp 宏与条件编译
UtEntity tools/util/source/UtEntity.hpp 空间实体状态
UtAtmosphere tools/util/source/UtAtmosphere.hpp 大气模型
UtCalendar tools/util/source/UtCalendar.hpp 日历/时间
UtWallClock tools/util/source/UtWallClock.hpp 墙钟
UtRandom tools/util/source/UtRandom.hpp 随机数
UtScript tools/util_script/source/UtScript.hpp 脚本函数/程序
UtScriptClass tools/util_script/source/UtScriptClass.hpp 脚本类绑定
UtScriptContext tools/util_script/source/UtScriptContext.hpp 脚本作用域
UtScriptExecutor tools/util_script/source/UtScriptExecutor.hpp 脚本执行器
UtScriptTypes tools/util_script/source/UtScriptTypes.hpp 脚本类型表
UtDynamicLibrary tools/util/source/UtDynamicLibrary.hpp 动态库加载
UtPluginManager tools/util/source/UtPluginManager.hpp 插件管理基类
UtPack core/wsf_util/source/UtPack.hpp 序列化
UtCsv core/wsf_util/source/UtCsv.hpp CSV 读写
UtQuadTree tools/util_script/source/UtQuadTreeGeneric.hpp 空间索引
UtCovariance tools/util/source/UtCovariance.hpp 协方差
UtDCM tools/util/source/UtDCM.hpp 方向余弦矩阵
UtQuaternion tools/util/source/UtQuaternion.hpp 四元数
UtECI_Conversion tools/util/source/UtECI_Conversion.hpp 地心惯性坐标转换

1.14.4 文件命名规律

除了类名,文件命名也有规律可循:

  • WsfXxx.hpp/.cpp:类的声明与实现,通常一一对应;
  • WsfXxxTypes.hpp/.cpp:类型表与工厂注册;
  • WsfXxxEvent.hpp/.cpp:事件类;
  • WsfXxxMessage.hpp/.cpp:消息类;
  • WsfXxxObserver.hpp/.cpp:观察者回调集合;
  • WsfScriptXxxClass.hpp/.cpp:脚本绑定;
  • WsfDefaultXxx.hpp/.cpp:默认实现;
  • WsfStandardXxx.hpp/.cpp:标准实现;
  • WsfXxxMode.hpp/.cpp:模式;
  • WsfXxxScheduler.hpp/.cpp:调度器;
  • WsfXxxStrategy.hpp/.cpp:策略;
  • XWsfXxx.hpp/.cpp:扩展/工具类(X 前缀);
  • UtXxx.hpp/.cpp:通用工具类。

知道这些规律后,看到一个类名就能猜到头文件名;反之,看到一个文件名也能猜出它的职责。


1.15 本章结语

AFSIM 的复杂度不是"代码写得多",而是"抽象层次多且互相支撑"。本章给出的地图可以归纳为一句话:

Application 管进程,Scenario 管模板,Simulation 管运行;Platform 是容器,Part 是能力,Event 是时间,Observer 是出口,Script 是胶水,Plugin 是分发。

后续章节会沿着这张地图逐层深入。第 2 章先讲构建系统与运行入口,因为只有先把工程跑起来、把调试符号配好,后续的源码阅读才有意义。


1.16 附录:本章使用的源码索引

主题 源码位置
版本定义 src/CMakeLists.txt:70-76
C++ 标准 src/CMakeLists.txt:32-34
可选模块发现 src/CMakeLists.txt:135-160;core/wsf/optional_projects.cmake
版本 API core/wsf/source/WsfVersion.hpp:29-55
应用基类 core/wsf/source/WsfApplication.hpp
标准应用 core/wsf/source/WsfStandardApplication.hpp
场景 core/wsf/source/WsfScenario.hpp:110-513
仿真 core/wsf/source/WsfSimulation.hpp:108-691
事件 core/wsf/source/WsfEvent.hpp:28-172
事件管理器 core/wsf/source/WsfEventManager.hpp:40-110
平台 core/wsf/source/WsfPlatform.hpp:108-695
组件 core/wsf/source/WsfComponent.hpp
传感器 core/wsf/source/sensor/WsfSensor.hpp
运动器 core/wsf/source/mover/WsfMover.hpp
通信 core/wsf/source/comm/WsfComm.hpp
处理器 core/wsf/source/processor/WsfProcessor.hpp
任务管理 core/wsf/source/processor/WsfTaskManager.hpp
航迹管理 core/wsf/source/WsfTrackManager.hpp
脚本上下文 core/wsf/source/script/WsfScriptContext.hpp
观察者 core/wsf/source/observer/
区域 core/wsf/source/WsfZone.hpp、WsfZoneDefinition.hpp、WsfZoneSet.hpp
DIS core/wsf/source/dis/
XIO core/wsf/source/xio/
事件管道 core/wsf/source/event_pipe/
mission 入口 mission/source/mission.cpp
官方架构说明 doc/user/architecture_overview.rst

下一章预告:第 2 章将拆解 CMake 的模块发现、目标生成、插件注册与多应用构建,并给出从零配置到运行第一个想定的完整命令序列。

相关推荐
充值内卷2 小时前
C++单文件迷宫游戏:TauriCPP 开发全记录
开发语言·c++·游戏
Antruly2 小时前
C++ Web 开发选型漫谈:从 Drogon 到 libuvcpp + uvp,八个项目的定位、兼容性与务实排名
开发语言·前端·c++
another heaven2 小时前
【C/C++ C++ 新特性中的那些内存优化的特性】
c语言·开发语言·c++
ysu_03143 小时前
【C++ 进阶之路】(二)从内联函数到类与对象:深挖内存对齐、this 指针与三大默认成员函数
开发语言·数据结构·c++·笔记·面向对象·this
布莱克6053 小时前
C++ 中 set 与 map 的区别详解
开发语言·c++·算法·哈希
我叫洋洋3 小时前
Allegro 画布“垃圾显示“清理指南:把板子调成干净工作台(实战总结)
c语言·c++·单片机·嵌入式硬件·eda·嘉立创
jimy13 小时前
构造函数表达式的值类别是:prvalue
开发语言·c++
wuminyu3 小时前
JVM规范学习总结-Class文件结构
java·linux·c语言·jvm·c++
多弗朗皮卡丘4 小时前
C++特殊类设计
开发语言·c++·设计模式