1. 为什么要无界面

AFSim整个框架以wizard、warlock、mission、mystic等几个主要工具构成。wizard为集成开发框架,用于脚本编程后实时展示编辑的场景(scenario)内容,编辑完成后可使用warlock和mission进行仿真模拟,其中warlock可对仿真推演速度进行控制,而mission以尽可能快的方式完成仿真场景的推演,并支持设置仿真次数。warlock是带态势图的应用,mission不带界面只有一个控制台。

在实际项目中,一般会将仿真引擎和态势显示进行分离,即仿真引擎在服务器上跑,而界面显示在客户端上,这样可以利用服务器的高性能,同时执行多个仿真推演。而态势显示可根据需要,采用WebGis或高性能的C++版GIS客户端,并支持更高程度的定制,而不用影响仿真推演的核心部分。

2. 源码分析

对mission源码进行分析,整个仿真的创建包含主要的三个内容:1、创建Application,2、创建Scenario,3、创建Simulation,4、调用Simulation的RunEventLoop进行仿真。如下图:

上图是AFSim的mission的源码,它支持命令行参数读取和解析、启动场景文件配置、性能(profiling)分析配置、多次仿真循环等功能。

3. 最小化改造

服务器部署仿真引擎时,往往都是长时间运行,通过客户端的请求来执行不同的仿真任务,因此mission中的一些功能在服务器上不会用得上,如命令行参数、启动文件配置等。

一般的流程是:

1)客户端选择一个场景,并告知服务端仿真引擎使用此场景文件

2)客户端请求服务端初始化一个仿真

3)服务端根据场景文件创建一个仿真并返回仿真唯一标识给客户端

4)客户端通过标识对服务端仿真进行控制(启动、暂停、恢复、停止、速度控制、FPS控制等)(FPS控制AFSim本身不支持,见后续源码改造内容^_^)

5)服务端仿真引擎在运行过程中通过定义的接口(如XIO、DIS或自定义的接口)对外发送数据(见后续数据推送二次开发)

6)客户端(如态势、仿真状态监控或其他系统)接收到推送的数据进行处理或显示。

根据以上流程,需要建立一个能够支持客户端请求的服务的仿真引擎,因此第一步应该抽出一个比较简单的仿真框架,这里以mission的源码进行简化后得到一个引擎代码,如下图:

以上代码,s_app是AFSim的WsfStandardApplication,当然也可以是自己扩展的Applicatin,但应该保持全局唯一。另外,将1/2/3步放到类的初始化函数中,将第4步放到线程中即可实现多仿真推演过程。以下代码为示例:

HSimApp.h

#ifndef HSimApp_h__
#define HSimApp_h__

#include <QThread>

// AFSim
#include "WsfStandardApplication.hpp"
#include "WsfScenario.hpp"
#include "WsfFrameStepSimulation.hpp"

class HSimApp : public QThread
{
	Q_OBJECT
public:
	enum State
	{
		E_Unknown,
		E_Initialized,
		E_Running,
		E_Pausing,
	};

	enum ControlType
	{
		E_Start = 1,
		E_Pause = 2,
		E_Resume = 3,
		E_Stop = 4,
		E_SetSpeed = 5,
		E_SetFPS = 6,
	};

	HSimApp(const QString &sceneFile, QObject* parent = nullptr);
	~HSimApp();

	bool init();

	void control(ControlType controlType, double value);

	// 设置刷新率,默认为25, 1,2,4,10,25,60,90
	void setFPS(int fps);
	// 设置仿真速度默认为1,倍速,1/64, 1/32, 1/16, 1/8, 1/4, 1/2 1,2,4,8,16,32,64
	void setSpeed(double speed);

	void start();
	void pause();
	void resume();
	void stop();

private:
	static std::unique_ptr<WsfStandardApplication> s_app;
	WsfStandardApplication::Options m_options;
	std::unique_ptr<WsfScenario> m_scenario = nullptr;
	std::unique_ptr<WsfFrameStepSimulation> m_wsfSim = nullptr;

protected:
	virtual void run() override;

private:
	Property_ReadOnly(QString, Id, "");									// 仿真标识
	Property_ReadOnly(double, Speed, 1.0);								// 仿真倍速
	Property_ReadOnly(int, FPS, 1.0);									// 刷新率
	Property_ReadOnly(State, State, E_Unknown);							// 状态
Property_ReadOnly(QString, SceneFile, "");							// 场景文件
};
#endif // HSimApp_h__

HSimApp.cpp

#include "HSimApp.h"
#include <QCoreApplication>
#include <QFileInfo>
#include <QDir>
#include "Util.h"
#include "Log.h"
// afsim
#include "UtException.hpp"
#include "UtLog.hpp"
#include "WsfScenario.hpp"
#include "WsfSimulation.hpp"
#include "WsfSimulationInput.hpp"
#include "WsfStandardApplication.hpp"
// Includes all of the optional projects which can be compiled with WSF
#include "ProfilingCommon.hpp"
#include "ProfilingRegion.hpp"
#include "ProfilingSystem.hpp"
#include "wsf_extensions.hpp"
#include "wsf_version_defines.hpp"
// 全局唯一APP
std::unique_ptr<WsfStandardApplication> HSimApp::s_app = nullptr;
HSimApp::HSimApp(const QString& sceneFile, QObject* parent /*= nullptr*/)
	: QThread(parent)
	, m_Id(Util::getUid())
{
	this->moveToThread(this);
	m_SceneFile = sceneFile ;
}
HSimApp::~JCSimApp()
{
	stop();
}
bool HSimApp::init()
{
	if (s_app == nullptr)
	{
		ut::SetupApplicationLog("mission", WSF_VERSION, "mission-exception.log");
		auto appName = QCoreApplication::instance()->arguments()[0];
		char* argv[1];
		char* exeName = new char[appName.size() + 1];
		memset(exeName, 0, appName.size() + 1);
		memcpy(exeName, appName.toStdString().c_str(), appName.size());
		argv[0] = exeName;
		// 创建仿真应用对象
		s_app = std::make_unique<WsfStandardApplication>("mission", 1, argv);
		// Load built-in extensions (defined in wsf_extensions.hpp)
		// 加载内置扩展
		RegisterBuiltinExtensions(*s_app);
		// Load optional extensions (defined in wsf_extensions.hpp)
		// 加载自定义扩展
		RegisterOptionalExtensions(*s_app);
		// Register the XIO simulation interface.
		// 加载内部XIO通信接口
		WSF_REGISTER_EXTENSION(*s_app, xio_interface);
	}
	// 创建一个场景对象
	m_scenario = std::make_unique<WsfScenario>(*s_app);
	m_options.mSimType = WsfStandardApplication::cFRAME_STEPPED;
	m_options.mInputFiles.push_back(m_SceneFile.toStdString());
	try
	{
		// 读取.txt文件
		s_app->ProcessInputFiles(*m_scenario, m_options.mInputFiles);
	}
	catch (WsfApplication::Exception& e)
	{
		auto out = ut::log::fatal() << "Could not process input files.";
		out.AddNote() << e.what();
		return false;
	}
	m_wsfSim = ut::make_unique<WsfFrameStepSimulation>(*m_scenario, 1);
	m_wsfSim->SetRealtime(0, false);
	m_wsfSim->SetClockRate(m_Speed);
	m_wsfSim->setFrameTime(m_FPS);
	if (!s_app->InitializeSimulation(m_wsfSim.get())) // 初始化仿真对象
	{
		return false;
	}
	m_State = E_Initialized;
	return true;
}
void HSimApp::control(ControlType controlType, double value)
{
	switch (controlType)
	{
	case HSimApp::E_Start:
		this->start();
		break;
	case HSimApp::E_Pause:
		this->pause();
		break;
	case HSimApp::E_Resume:
		this->resume();
		break;
	case HSimApp::E_Stop:
		this->stop();
		break;
	case HSimApp::E_SetSpeed:
		this->setSpeed(value);
		break;
	case HSimApp::E_SetFPS:
		this->setFPS(value);
		break;
	default:
		break;
	}
}
void HSimApp::setFPS(int fps)
{
	Q_ASSERT(fps > 0);
	if (m_FPS != fps)
	{
		m_FPS = fps;
		// m_wsfSim->setFrameTime(1.0 / m_FPS);(这个函数需要改造源码,后续分析)
	}
}
void HSimApp::setSpeed(double speed)
{
	Q_ASSERT(speed > 0);
	if (abs(m_Speed - speed) > 0.000001)
	{
		m_Speed = speed;
		m_wsfSim->SetClockRate(m_Speed);
	}
}
void HSimApp::start()
{
	if (m_State == E_Initialized)
	{
		m_wsfSim->Start();
		m_State = E_Running;
	}
}
void HSimApp::pause()
{
	if (m_State == E_Running)
	{
		m_wsfSim->Pause();
		m_State = E_Pausing;
	}
}
void HSimApp::resume()
{
	if (m_State == E_Pausing)
	{
		m_wsfSim->Resume();
		m_State = E_Running;
	}
}
void HSimApp::stop()
{
	if (m_State == E_Initialized)
	{
		m_wsfSim->Complete(m_wsfSim->GetSimTime());
	}
	else if (m_State == E_Running)
	{
		m_wsfSim->RequestTermination();
		wait();
	}
	else if (m_State == E_Pausing)
	{
		m_wsfSim->SetEndTime(m_wsfSim->GetSimTime());
		m_wsfSim->Resume();
		wait();
	}
}
void HSimApp::run()
{
	WsfStandardApplication::SimulationResult result;
	result = s_app->RunEventLoop(m_wsfSim.get(), m_options);
	syslog->info(QStringLiteral("仿真执行完成 <%1>").arg(m_Id));
}

代码仅供参考,不具有工程价值!

往期推荐

飞腾D2000麒麟V10国防版下编译

基于wsf插件扩展内置platform

服务端仿真引擎框架

arm64版的麒麟V10服务器docker容器集成后台仿真引擎

文末二维码.png