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 本章的学习目标
读完本章,你应该能够:
- 用自己的话解释 AFSIM 的定位:它不是"一个仿真软件",而是"仿真框架 + 领域模型库 + 应用工具链"的组合。
- 看到任意一个源文件路径,大致判断它属于哪一层、会被谁调用、能否被替换。
- 画出
Application → Scenario → Simulation → Platform → Part的主对象树。 - 说出
core/wsf、core/wsf_mil、wsf_plugins、tools/util四者的职责边界。 - 根据问题类型选择正确的源码入口:输入语法问题查 parser,运行时行为查 event/part,数据输出查 observer/event_pipe,分布式问题查 xio/dis。
- 识别常见的架构误读,例如"平台就是实体""脚本只是配置""插件等于动态库"。
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。这带来两个重要约束:
- 不要在现代编译器上假设 C++14/17 特性可用。 源码中大量使用
std::unique_ptr、auto、lambda、override,但不会出现std::optional、std::string_view、结构化绑定。二次开发若引入 C++17 库,需要单独调整标准并承担兼容性风险。 - 构建系统版本较旧。
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.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(可依赖以上任何层,但只能通过注册接口接入)
具体规则:
tools/util不知道Wsf的存在。 它只提供Ut*类,如UtEntity、UtInput、UtAtmosphere、UtCalendar。这样它才能被其他项目复用。core/wsf不依赖任何领域扩展。 它定义WsfSensor抽象基类,但不知道WsfRadarSensor的存在;后者由wsf_mil注册。- 领域扩展之间尽量不互相依赖。
wsf_space不应依赖wsf_l16;如果需要共享,抽到core/wsf或tools。 - 应用可以依赖多个领域扩展。
mission默认加载内置扩展与可选扩展(RegisterBuiltinExtensions/RegisterOptionalExtensions),因此能识别所有已编译进来的模型。 - 插件通过注册宏接入,不直接改核心。 插件可以依赖核心头文件,但核心不能依赖插件。
违反这些规则的后果不是编译错误,而是循环依赖、链接膨胀、插件加载顺序问题 。例如,如果 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。这段逻辑说明:
- 一个目录只要有
wsf_module文件,就被视为可选模块; - 模块内可以有
wsf_cmake_extension.cmake,声明WSF_EXT_NAME、WSF_EXT_TYPE(lib/plugin/exe); - 构建时可选择静态链接(lib)、动态插件(plugin)或独立可执行(exe);
- 生成的
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 的"世界地图":
- 定位:平台级、离散事件、可扩展的作战仿真框架,附带领域模型与工具链。
- 版本:2.9.0(2022-02-25),C++11,CMake 3.7+,64 位。
- 分层:应用 → 插件 → 领域扩展 → 框架 → 基础设施。
- 三容器:Application(进程)→ Scenario(想定)→ Simulation(运行)。
- 对象树:Simulation → Platform → Parts(Mover/Sensor/Weapon/Comm/Processor/Fuel/Signature)。
- 事件 :以
(time, priority, sequence)排序的优先队列,所有行为的最终载体。 - 扩展:应用/场景/仿真扩展、插件、组件工厂、脚本类型、观察者。
- 数据出口:Observer、Event Output、Event Pipe、DIS、XIO。
- 代码规模:核心 31 万行,军事扩展 17 万行,可选插件 34 万行;主框架小而生态大。
- 阅读方法:沿主链、按问题、按扩展点三遍;以源码为准,不迷信文档。
下一章将从构建系统与运行入口开始,解释这棵庞大的源码树如何被 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 输入系统的关键:
- 类型定义优先。
TypesProcessInput先遍历所有类型表(平台、传感器、武器、通信、处理器、运动器、路由、区域、过滤器、天线方向图......),只要有一个认领,就返回。 - 平台实例其次。
LoadPlatformInstance处理platform与edit platform。 - 运行时参数再次。
simulation块、environment、atmosphere、message_table、脚本定义。 - 扩展最后。 场景扩展可以注册自己的命令,但优先级低于核心命令。
- 杂项命令兜底。
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,而是:
- 找到名为
baseType的原型对象; - 调用原型对象的拷贝构造函数(
WsfPlatform有 protected 拷贝构造,WsfPlatform.cpp中实现深拷贝); - 深拷贝所有组件(mover、sensor、processor、comm、weapon、fuel、signature 等);
- 重置运行时状态(平台索引、仿真指针、脚本上下文中的实例变量等);
- 返回一个新对象,名字暂时与原型相同或为空,等待
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),按顺序做九件事:
- 构建密级字符串 :把
classification、caveat、trigraph等拼成显示串; - 扩展排序 :按依赖关系对场景扩展排序(
SortExtensions); - 地形初始化 :
mTerrainInterfacePtr->Initialize(); - 延迟输入解析 :
mDeferredInputPtr->Resolve(*mInputPtr),此时所有类型都已就绪; - 消息表初始化 :
mMessageTablePtr->Initialize(); - 区域初始化 :
GetZoneTypes().InitializeZones(*this),把区域定义转换为可查询的几何对象; - 随机种子生成 :如果使用
generate_random_seeds,为每个运行号生成唯一种子; - 类型表收尾 :对每个类型表调用
CompleteLoad(),执行跨类型校验; - 输入平台收尾 :对每个输入平台调用
CompleteLoad(*this); - 仿真输入收尾 :
mSimulationInputPtr->LoadComplete(); - 扩展收尾 :依次调用场景扩展的
Complete()与Complete2(); - 置不可变标志 :
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)。典型顺序是:
WsfSimulation::AddPlatform分配索引、加入三套列表;- 调用平台的
Initialize/Initialize2; - 调用部件的
Initialize; - 触发
WsfObserver::PlatformAdded; - 调用
PlatformIntroduced,让其他平台知道新平台; - 打开部件(
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):
- 如果设置了环境变量
WSF_PLUGIN_PATH,只使用它,忽略默认路径; - 否则使用构造函数传入的
aPluginPaths; - 如果传入为空,使用
../<app>_plugins与../wsf_plugins; 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。
这些数据通常通过输入文件中的路径命令或环境变量定位。部署时最常见的错误是"程序能启动,但一加载想定就报找不到文件"。排查顺序:
- 看错误信息中的路径;
- 检查
file_path、define_path_variable; - 检查当前工作目录(相对路径的基准);
- 检查
resources/是否完整; - 检查插件是否加载(
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 自测题
- 为什么
WsfScenario在CompleteLoad后必须不可变?如果允许修改,会破坏哪些机制? WsfPlatform为什么要克隆两次(类型→输入平台→运行平台)?第二次克隆解决了什么问题?- 事件管理器为什么需要
sequence字段?只按(time, priority)排序会有什么问题? - 如果两个观察者订阅了同一个事件,它们的执行顺序由什么决定?能否依赖这个顺序?
WSF_PLUGIN_PATH设置后,默认插件目录为什么不再被搜索?这会带来什么部署风险?- 为什么模型随机数与脚本随机数要分开?如果合并,蒙特卡洛结果会受什么影响?
- 平台索引与平台指针各适合什么场景?为什么跨平台引用推荐索引?
on_platform_injection脚本在什么时机执行?它能访问哪些对象?不能访问哪些对象?- 事件步进与帧步进的核心差异是什么?实时模式必须用帧步进吗?
- 如果一条输入命令在
ProcessInputP中没有被任何分支认领,会发生什么?如何定位?
1.13.2 实践任务
- 在本地构建一个最小
mission,只启用wsf_mil,运行一个包含一个平台、一个传感器的想定;观察启动日志中的版本、插件、随机种子。 - 用
grep -rn "ProcessInput" core/wsf/source/sensor/WsfSensor.cpp找到传感器支持的命令,整理成表格。 - 写一个脚本观察者,输出平台位置随时间的变化;再用事件管道记录同一数据,比较两种方式的字段与性能。
- 故意在
platform中引用一个未定义的类型,观察CompleteLoad的报错信息;再把类型定义移到平台之后,验证延迟装载是否生效。 - 设置
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 的模块发现、目标生成、插件注册与多应用构建,并给出从零配置到运行第一个想定的完整命令序列。