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

0 阅读54分钟

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

1.0 导读:读懂 AFSIM 难在哪里

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

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

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

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:

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:

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 10Visual Studio2015 / 2017 / 201964 位
LinuxGCC4.8.564 位

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

1.2.3 源码树的顶层布局

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

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/wsf1,132309,630仿真框架主体
core/wsf_mil432173,641军事模型
core/wsf_space32952,361太空模型
core/wsf_parser15537,264解析器基础设施
core/wsf_l1610525,028Link 16
core/wsf_cyber8814,701赛博
core/wsf_nx7261,644非出口型模型
core/wsf_mtt3010,310多目标跟踪
core/wsf_ripr247,920任务分配/规划
wizard1,059182,373想定编辑器
warlock61696,554实时控制台
mystic29165,390事后分析
mover_creator225104,386运动数据生成
tools2,487524,132基础设施与通用库
wsf_plugins1,353343,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)。把这段描述落到代码上,可以得到如下分层:

┌──────────────────────────────────────────────────────────────────────┐
│ 应用层 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 中:

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)定义了严格的生命周期:

cPENDING_INITIALIZE → cINITIALIZING → cPENDING_START → cSTARTING
      → cACTIVE → cPENDING_COMPLETE → cCOMPLETE

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

三者的关系可以概括为:

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

1.3.3 为什么要把 Scenario 和 Simulation 分开

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

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

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

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 平台对象树

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

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):

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 允许多种链接方式,但架构上应遵守以下依赖方向:

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 一次运行穿过哪些层

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

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 报文与其他仿真互联可以双向(接收外部实体)
XIOAFSIM 自定义 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 万行,按子目录组织:

子目录头文件数内容
sensor18雷达、红外、光学、声学、激光、ESM 等模型
weapon37导弹、炸弹、火炮、定向能、引信、杀伤模型
comm11军用数据链、电台、消息
ew37电子战:干扰、欺骗、侦察、干扰机、雷达告警
processor23任务处理器、威胁评估、武器分配、传感器管理
mover9军用运动模型(巡航导弹、弹道、飞机)
script18军事对象的脚本绑定
observer3军事专用观察者
dis5军事 DIS 扩展
xio5军事 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 应用层:每个可执行体都是一条产品线

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

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

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

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

插件类型大致能力
wsf_air_combatplugin空战战术、飞机行为
wsf_alternate_locationsplugin备选位置/部署方案
wsf_annotationplugin想定标注
wsf_argo8pluginARGO8 相关模型
wsf_brawlerpluginBRAWLER 空战模型
wsf_coverageplugin覆盖分析
wsf_firesplugin火力/炮兵
wsf_iads_c2_libplugin综合防空指挥控制(扩展类型 WSF_IADS_C2)
wsf_multiresolutionplugin多分辨率建模
wsf_oms_ucipluginOMS/UCI 接口
wsf_p6dofplugin6 自由度飞行动力学
wsf_scenario_analyzerplugin想定分析
wsf_scenario_analyzer_iads_c2pluginIADS 想定分析
wsf_simdispluginSIMDIS ASI 输出
wsf_six_dofplugin6DOF 模型
wsf_sosmpluginSOSM 模型

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

1.4.20 插件如何被"发现"

顶层 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_scriptutilwsfmilspacecyberappsplugins
util_script—是否否否否否否
util部分—否否否否否否
wsf是是—否否否否否
mil是是是—否否否否
space是是是否—否否否
cyber是是是部分否—否否
apps是是是是是是—是
plugins是是是可能可能可能否—

几条关键规则:

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

1.5.2 运行时数据流

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

[雷达传感器]
  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 关键抽象速查

抽象头文件职责生命周期
WsfApplicationWsfApplication.hpp进程级应用、扩展、插件进程
WsfScenarioWsfScenario.hpp想定类型、输入、环境想定
WsfSimulationWsfSimulation.hpp运行实例、事件、平台单次运行
WsfPlatformWsfPlatform.hpp平台容器仿真内
WsfPlatformPartWsfPlatformPart.hpp部件基类平台内
WsfComponentWsfComponent.hpp组件接口平台内
WsfObjectWsfObject.hpp命名/克隆/类型场景/仿真
WsfEventWsfEvent.hpp事件事件队列
WsfEventManagerWsfEventManager.hpp事件队列仿真
WsfObserverobserver/*.hpp回调仿真
WsfScriptContextscript/WsfScriptContext.hpp脚本上下文场景/平台
WsfXIOxio/WsfXIO.hpp外部接口仿真
WsfDisInterfacedis/WsfDisInterface.hppDIS 接口仿真
WsfEventPipeevent_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 命令的完整旅程

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

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):

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)是输入循环:

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 链,顺序非常重要:

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):

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() 为真,函数并不立即创建平台,而是:

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)。

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

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 调用:

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) 的第二个参数表示"这不是类型定义,而是实例覆盖"。实例块中的命令只覆盖本实例的属性,不影响原型。例如:

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)是一次"从模板到活体"的转换:

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):

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(),然后:

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 platformLoadPlatformInstance
平台未出现在仿真中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全部核心扩展想定错误、性能、随机种子
warlockXIO/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_brawlerBRAWLER 空战模型wsf_mil高保真空战
wsf_six_dof / wsf_p6dof6 自由度飞行力学wsf_mil、气动数据飞行性能分析
wsf_iads_c2综合防空指挥控制wsf_mil、通信IADS 想定
wsf_fires火力/炮兵wsf_mil火力打击
wsf_coverage覆盖分析core/wsf传感器/通信覆盖
wsf_multiresolution多分辨率建模core/wsf大规模仿真
wsf_oms_uciOMS/UCI 接口外部标准联合互操作
wsf_simdisSIMDIS ASI 输出core/wsf可视化回放
wsf_scenario_analyzer想定分析core/wsf想定质量检查
wsf_annotation标注core/wsf想定说明
wsf_alternate_locations备选位置core/wsf部署分析
wsf_argo8ARGO8 模型外部模型特定领域
wsf_sosmSOSM 模型外部模型特定领域

使用建议:不要把插件当成"可选装饰"。插件往往引入新的输入命令、类型和脚本 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 这些决策之间的关系

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

事件驱动(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):

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):

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 加载一个开发中的插件,可以:

# 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 把可执行文件、动态库、静态库分别输出到:

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}。实际构建命令通常类似:

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_BUILDTRUE是否构建插件
WSF_INSTALL_BASE_GRAMMARTRUE是否安装基础语法文件
WSF_INSTALL_SOURCEFALSE是否安装源码
WSF_INSTALL_DEMOSFALSE是否安装示例
WSF_INSTALL_SCENARIOSFALSE是否安装想定
WSF_INSTALL_TOOLSFALSE是否安装工具
WSF_INSTALL_TRAININGFALSE是否安装培训材料
WSF_INSTALL_DOCUMENTATIONFALSE是否安装文档
WSF_INSTALL_DEPENDENCIESFALSE是否安装依赖(如 MSVC 运行时)
WSF_BUILD_APIFALSE是否构建 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
查脚本 APIcore/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可动态加载的扩展包
脚本ScriptAFSIM 内置的嵌入式语言
脚本上下文Script Context脚本变量、函数、类的作用域
电磁交互EM Interaction发射机、接收机、天线、传播、目标的综合计算
签名Signature目标的雷达/红外/光学/声学特征
天线方向图Antenna Pattern天线增益随角度变化的函数
传播Propagation电磁波在空间中的衰减/折射
杂波Clutter地海面等背景回波
检测概率PdProbability of Detection
虚警率PfaProbability of False Alarm
信噪比SNRSignal-to-Noise Ratio
杀伤概率PkProbability of Kill
防区内AORArea of Responsibility
任务链Kill Chain探测-识别-决策-交战-评估的链条
数据链Datalink平台间传输战术数据的通信系统
网络参与组NPGNetwork Participation Group(Link 16)
时隙SlotTDMA 时间片
分布式仿真Distributed Simulation多个仿真/模拟器互联
高层体系结构HLAHigh Level Architecture(IEEE 1516)
分布式交互仿真DISDistributed Interactive Simulation(IEEE 1278)
外部输入输出XIOAFSIM 自有外部接口
事件管道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)

类/接口头文件职责
WsfApplicationWsfApplication.hpp进程级应用,管理扩展、插件、脚本类型、命令行
WsfStandardApplicationWsfStandardApplication.hpp标准应用实现,提供输入装载、仿真创建、事件循环
WsfApplicationExtensionWsfApplicationExtension.hpp应用级扩展基类
WsfScenarioWsfScenario.hpp想定容器,类型表、输入、环境、脚本
WsfScenarioExtensionWsfScenarioExtension.hpp场景级扩展基类
WsfSimulationWsfSimulation.hpp仿真容器,事件、平台、观察者、服务
WsfSimulationExtensionWsfSimulationExtension.hpp仿真级扩展基类
WsfSimulationInputWsfSimulationInput.hpp运行期参数(时间、线程、随机、优先级)
WsfObjectWsfObject.hpp命名、类型、克隆的基类
WsfNamedWsfNamed.hpp命名对象的轻量基类
WsfComponentWsfComponent.hpp组件接口,支持角色查询与克隆
WsfComponentListWsfComponentList.hpp组件容器,管理添加/删除/查询
WsfComponentFactoryWsfComponentFactory.hpp组件工厂,按名字创建组件
WsfPlatformWsfPlatform.hpp平台容器,挂载部件与状态
WsfPlatformPartWsfPlatformPart.hpp部件基类,提供开关、更新、损伤等
WsfArticulatedPartWsfArticulatedPart.hpp可转动部件(传感器/发射器)
WsfEventWsfEvent.hpp事件接口,Execute 返回删除或重排
WsfEventManagerWsfEventManager.hpp事件优先队列
WsfEventStepSimulationWsfEventStepSimulation.hpp事件步进仿真
WsfFrameStepSimulationWsfFrameStepSimulation.hpp帧步进仿真
WsfClockSourceWsfClockSource.hpp时钟源抽象
WsfRealTimeClockSourceWsfRealTimeClockSource.hpp墙钟同步时钟
WsfExtensionWsfExtension.hpp扩展通用基类
WsfExtensionListWsfExtensionList.hpp扩展注册、排序、查找
WsfScriptContextscript/WsfScriptContext.hpp脚本上下文
WsfScriptManagerscript/WsfScriptManager.hpp脚本解析、缓存、执行
WsfTrackWsfTrack.hpp航迹状态与元数据
WsfTrackManagerWsfTrackManager.hpp平台航迹管理
WsfTrackIdWsfTrackId.hpp航迹标识
WsfLocalTrackWsfLocalTrack.hpp本地航迹
WsfMeasurementWsfMeasurement.hpp传感器测量
WsfMessageWsfMessage.hpp消息基类
WsfMessageTableWsfMessageTable.hpp消息类型表
WsfZoneWsfZone.hpp区域几何与查询
WsfZoneDefinitionWsfZoneDefinition.hpp区域定义解析
WsfRouteWsfRoute.hpp路线
WsfStringIdWsfStringId.hpp字符串 ID
WsfStringTableWsfStringTable.hpp字符串驻留表
WsfEM_InteractionWsfEM_Interaction.hpp电磁交互计算
WsfEM_Xmtr / WsfEM_RcvrWsfEM_Xmtr.hpp / WsfEM_Rcvr.hpp发射机/接收机
WsfEM_AntennaWsfEM_Antenna.hpp天线
WsfAntennaPatternWsfAntennaPattern.hpp天线方向图基类
WsfSignatureWsfSignature.hpp签名基类
WsfRadarSignatureWsfRadarSignature.hpp雷达签名
WsfEnvironmentWsfEnvironment.hpp环境(大气、地形、时间)
WsfTerrainWsfTerrain.hpp地形接口
WsfVisualizationWsfVisualization.hpp可视化接口
WsfDrawWsfDraw.hpp绘图请求
WsfXIOxio/WsfXIO.hpp外部接口
WsfEventPipeevent_pipe/WsfEventPipe.hpp事件管道
WsfDisInterfacedis/WsfDisInterface.hppDIS 接口

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

类/接口头文件领域
WsfSensorsensor/WsfSensor.hpp传感器骨架
WsfSensorModesensor/WsfSensorMode.hpp传感器模式
WsfSensorSchedulersensor/WsfSensorScheduler.hpp扫描调度
WsfSensorDetectorsensor/WsfSensorDetector.hpp检测判决
WsfSensorTrackersensor/WsfSensorTracker.hpp传感器跟踪
WsfRadarSensorsensor/WsfRadarSensor.hpp雷达
WsfPassiveSensorsensor/WsfPassiveSensor.hpp被动传感器
WsfGeometricSensorsensor/WsfGeometricSensor.hpp几何传感器
WsfMovermover/WsfMover.hpp运动器骨架
WsfAirMovermover/WsfAirMover.hpp空中运动
WsfGroundMovermover/WsfGroundMover.hpp地面运动
WsfFuelmover/WsfFuel.hpp燃料
WsfPathFindermover/WsfPathFinder.hpp路径规划
WsfCommcomm/WsfComm.hpp通信骨架
WsfCommNetworkcomm/WsfCommNetwork.hpp通信网络
WsfCommMediumcomm/WsfCommMedium.hpp传输介质
WsfCommLayercomm/WsfCommLayer.hpp协议层
WsfProcessorprocessor/WsfProcessor.hpp处理器骨架
WsfTaskManagerprocessor/WsfTaskManager.hpp任务管理
WsfTaskprocessor/WsfTask.hpp任务
WsfWeaponweapon/WsfWeapon.hpp武器骨架
WsfExplicitWeaponweapon/WsfExplicitWeapon.hpp显式武器(弹药平台)
WsfImplicitWeaponweapon/WsfImplicitWeapon.hpp隐式武器(干扰/激光)
WsfGuidedWeaponweapon/WsfGuidedWeapon.hpp制导武器
WsfRF_Jammerew/WsfRF_Jammer.hpp射频干扰机
WsfIFF_ManagerWsfIFF_Manager.hpp敌我识别
WsfLaserDesignationsWsfLaserDesignations.hpp激光指示
WsfInfraredSignatureWsfInfraredSignature.hpp红外签名
WsfOpticalSignatureWsfOpticalSignature.hpp光学签名
WsfAcousticSignatureWsfAcousticSignature.hpp声学签名
WsfNORAD_SpaceMoverwsf_space/.../WsfNORAD_SpaceMover.hpp轨道运动
WsfKeplerianOrbitalPropagatorwsf_space/.../WsfKeplerianOrbitalPropagator.hpp开普勒递推
WsfConstellationwsf_space/.../WsfConstellation.hpp星座
WsfEclipseEventManagerwsf_space/.../WsfEclipseEventManager.hpp光照/阴影
WsfCyberAttackwsf_cyber/.../WsfCyberAttack.hpp赛博攻击
WsfCyberProtectwsf_cyber/.../WsfCyberProtect.hpp赛博防御
JMessagewsf_l16/.../JMessage.hppLink 16 消息
ComputerProcessorwsf_l16/.../ComputerProcessor.hppLink 16 终端处理器

1.14.3 工具与运行时(tools)

类/接口头文件职责
UtInputtools/util/source/UtInput.hpp输入读取、单位换算、include
UtInputBlocktools/util/source/UtInputBlock.hpp块读取
UtInputPreprocessortools/util/source/UtInputPreprocessor.hpp宏与条件编译
UtEntitytools/util/source/UtEntity.hpp空间实体状态
UtAtmospheretools/util/source/UtAtmosphere.hpp大气模型
UtCalendartools/util/source/UtCalendar.hpp日历/时间
UtWallClocktools/util/source/UtWallClock.hpp墙钟
UtRandomtools/util/source/UtRandom.hpp随机数
UtScripttools/util_script/source/UtScript.hpp脚本函数/程序
UtScriptClasstools/util_script/source/UtScriptClass.hpp脚本类绑定
UtScriptContexttools/util_script/source/UtScriptContext.hpp脚本作用域
UtScriptExecutortools/util_script/source/UtScriptExecutor.hpp脚本执行器
UtScriptTypestools/util_script/source/UtScriptTypes.hpp脚本类型表
UtDynamicLibrarytools/util/source/UtDynamicLibrary.hpp动态库加载
UtPluginManagertools/util/source/UtPluginManager.hpp插件管理基类
UtPackcore/wsf_util/source/UtPack.hpp序列化
UtCsvcore/wsf_util/source/UtCsv.hppCSV 读写
UtQuadTreetools/util_script/source/UtQuadTreeGeneric.hpp空间索引
UtCovariancetools/util/source/UtCovariance.hpp协方差
UtDCMtools/util/source/UtDCM.hpp方向余弦矩阵
UtQuaterniontools/util/source/UtQuaternion.hpp四元数
UtECI_Conversiontools/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
版本 APIcore/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
DIScore/wsf/source/dis/
XIOcore/wsf/source/xio/
事件管道core/wsf/source/event_pipe/
mission 入口mission/source/mission.cpp
官方架构说明doc/user/architecture_overview.rst

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