修改XNEngine支持数据库读取,还未完成

This commit is contained in:
jinchao 2025-05-26 17:01:23 +08:00
parent 88d2ffb032
commit 7da7a8a5ee
8 changed files with 374 additions and 249 deletions

Binary file not shown.

View File

@ -15,6 +15,7 @@ endif()
find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)
find_package(SQLite3 REQUIRED)
add_executable(XNEngine
main.cpp
@ -30,6 +31,7 @@ target_link_libraries(XNEngine PRIVATE
pthread
OpenSSL::SSL
OpenSSL::Crypto
sqlite3
dl
)

View File

@ -16,6 +16,7 @@
#include "../XNCore/XNServiceManager.h"
#include "../XNCore/XNModelManager.h"
#include "../XNCore/XNScenarioManager.h"
#include <sqlite3.h>
// 引擎类构造函数
XNEngine::XNEngine()
@ -55,35 +56,42 @@ void XNEngine::SimControlListener(const XNSim::XNSimControl::XNRuntimeControl &c
}
}
// 设置日志级别
bool XNEngine::SetLogLevel(const std::string &XmlPath)
// 解析配置文件
bool XNEngine::ParseConfig(const std::string &XmlPath)
{
size_t index = XmlPath.find_last_of('.');
if (index != std::string::npos) {
std::string suffix = XmlPath.substr(index);
if (suffix != ".xml" && suffix != ".sce") {
std::cerr << "0x1005 配置文件不是 .xml 或 .sce 文件, 引擎将退出!" << std::endl;
return false;
}
} else {
std::cerr << "0x1006 配置文件没有 .xml 或 .sce 的后缀名, 引擎将退出!" << std::endl;
return false;
}
// 打开配置文件
std::ifstream file(XmlPath);
if (!file.is_open()) {
std::cerr << "0x1003 Failed to open the runtime environment configuration file, "
"the engine will exit!"
<< std::endl;
std::cerr << "0x1007 打开配置文件失败, 引擎将退出!" << std::endl;
return false;
}
// 解析配置文件
tinyxml2::XMLDocument doc;
if (doc.LoadFile(XmlPath.c_str()) != tinyxml2::XML_SUCCESS) {
std::cerr << "0x1004 Failed to parse the runtime environment configuration file, "
"the engine will exit!"
<< std::endl;
std::cerr << "0x1008 解析配置文件失败, 引擎将退出!" << std::endl;
return false;
}
// 获取根元素
tinyxml2::XMLElement *root = doc.FirstChildElement("Scenario");
if (!root) {
std::cerr << "0x1005 Failed to find Scenario element in configuration file!" << std::endl;
std::cerr << "0x1009 配置文件中未找到 Scenario 根元素, 引擎将退出!" << std::endl;
return false;
}
// 顺便读取一下CPU亲和性
int cpus = sysconf(_SC_NPROCESSORS_ONLN);
std::cout << "Current number of CPU cores-> " << cpus << std::endl;
std::cout << "当前CPU核心数-> " << cpus << std::endl;
// 设置CPU亲和性
cpu_set_t mask;
@ -106,30 +114,34 @@ bool XNEngine::SetLogLevel(const std::string &XmlPath)
CPU_SET(index, &mask);
}
} catch (const std::exception &e) {
std::cerr << "Invalid CPU affinity value: " << cpuIndex << std::endl;
std::cerr << "0x1010 无效的CPU亲和性值: " << cpuIndex << std::endl;
}
}
}
}
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
std::cerr << "0x1020 Failed to set engine CPU affinity-> " << strerror(errno) << std::endl;
std::cerr << "0x1011 设置引擎CPU亲和性失败-> " << strerror(errno) << std::endl;
return false;
}
std::cout << "Successfully set engine CPU affinity-> " << CPUAffinity << std::endl;
std::cout << "成功设置引擎CPU亲和性-> " << CPUAffinity << std::endl;
// 锁定内存
if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
std::cerr << "0x1021 Failed to lock engine memory-> " << strerror(errno) << std::endl;
std::cerr << "0x1012 锁定引擎内存失败-> " << strerror(errno) << std::endl;
return false;
}
std::cout << "Successfully locked engine memory!" << std::endl;
std::cout << "成功锁定引擎内存!" << std::endl;
// 获取配置文件中的日志元素
bool isDebug = false;
bool isInfo = false;
bool isWarn = false;
bool isError = false;
// 获取配置文件中的控制台输出元素
tinyxml2::XMLElement *consoleOutputNode = root->FirstChildElement("ConsoleOutput");
if (!consoleOutputNode) {
std::cout << "The runtime environment configuration file does not contain "
"the ConsoleOutput element, the Console will Output all Log!"
std::cout << "0x1013 配置文件中未找到 ConsoleOutput 元素, 控制台将输出所有日志!"
<< std::endl;
} else {
// 获取配置文件中的调试日志输出元素
@ -138,9 +150,9 @@ bool XNEngine::SetLogLevel(const std::string &XmlPath)
&& (strcmp(debugConsoleOutput, "true") == 0 || strcmp(debugConsoleOutput, "1") == 0
|| strcmp(debugConsoleOutput, "True") == 0
|| strcmp(debugConsoleOutput, "TRUE") == 0)) {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Debug, true);
isDebug = true;
} else {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Debug, false);
isDebug = false;
}
// 获取配置文件中的信息日志输出元素
@ -149,9 +161,9 @@ bool XNEngine::SetLogLevel(const std::string &XmlPath)
&& (strcmp(infoConsoleOutput, "true") == 0 || strcmp(infoConsoleOutput, "1") == 0
|| strcmp(infoConsoleOutput, "True") == 0
|| strcmp(infoConsoleOutput, "TRUE") == 0)) {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Info, true);
isInfo = true;
} else {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Info, false);
isInfo = false;
}
// 获取配置文件中的警告日志输出元素
@ -160,9 +172,9 @@ bool XNEngine::SetLogLevel(const std::string &XmlPath)
&& (strcmp(warningConsoleOutput, "true") == 0 || strcmp(warningConsoleOutput, "1") == 0
|| strcmp(warningConsoleOutput, "True") == 0
|| strcmp(warningConsoleOutput, "TRUE") == 0)) {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Warning, true);
isWarn = true;
} else {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Warning, false);
isWarn = false;
}
// 获取配置文件中的错误日志输出元素
@ -171,27 +183,26 @@ bool XNEngine::SetLogLevel(const std::string &XmlPath)
&& (strcmp(errorConsoleOutput, "true") == 0 || strcmp(errorConsoleOutput, "1") == 0
|| strcmp(errorConsoleOutput, "True") == 0
|| strcmp(errorConsoleOutput, "TRUE") == 0)) {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Error, true);
isError = true;
} else {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Error, false);
isError = false;
}
}
// 设置控制台输出
SetConsoleOutput(isDebug, isInfo, isWarn, isError);
// 获取配置文件中的日志元素
tinyxml2::XMLElement *logNode = root->FirstChildElement("Log");
if (!logNode) {
std::cout << "The runtime environment configuration file does not contain "
"the Log element, the Log File will record all Log!"
<< std::endl;
std::cout << "0x1014 配置文件中未找到 Log 元素, 日志文件将记录所有日志!" << std::endl;
} else {
// 获取配置文件中的调试日志输出元素
const char *debugLog = logNode->Attribute("Debug");
if (debugLog
&& (strcmp(debugLog, "true") == 0 || strcmp(debugLog, "1") == 0
|| strcmp(debugLog, "True") == 0 || strcmp(debugLog, "TRUE") == 0)) {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Debug, true);
isDebug = true;
} else {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Debug, false);
isDebug = false;
}
// 获取配置文件中的信息日志输出元素
@ -199,9 +210,9 @@ bool XNEngine::SetLogLevel(const std::string &XmlPath)
if (infoLog
&& (strcmp(infoLog, "true") == 0 || strcmp(infoLog, "1") == 0
|| strcmp(infoLog, "True") == 0 || strcmp(infoLog, "TRUE") == 0)) {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Info, true);
isInfo = true;
} else {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Info, false);
isInfo = false;
}
// 获取配置文件中的警告日志输出元素
@ -209,9 +220,9 @@ bool XNEngine::SetLogLevel(const std::string &XmlPath)
if (warningLog
&& (strcmp(warningLog, "true") == 0 || strcmp(warningLog, "1") == 0
|| strcmp(warningLog, "True") == 0 || strcmp(warningLog, "TRUE") == 0)) {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Warning, true);
isWarn = true;
} else {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Warning, false);
isWarn = false;
}
// 获取配置文件中的错误日志输出元素
@ -219,18 +230,73 @@ bool XNEngine::SetLogLevel(const std::string &XmlPath)
if (errorLog
&& (strcmp(errorLog, "true") == 0 || strcmp(errorLog, "1") == 0
|| strcmp(errorLog, "True") == 0 || strcmp(errorLog, "TRUE") == 0)) {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Error, true);
isError = true;
} else {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Error, false);
isError = false;
}
}
// 设置日志级别
SetLogLevel(isDebug, isInfo, isWarn, isError);
// 关闭配置文件
file.close();
// 返回成功
return true;
}
// 设置控制台输出
bool XNEngine::SetConsoleOutput(bool isDebug, bool isInfo, bool isWarn, bool isError)
{
if (isDebug) {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Debug, true);
} else {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Debug, false);
}
if (isInfo) {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Info, true);
} else {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Info, false);
}
if (isWarn) {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Warning, true);
} else {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Warning, false);
}
if (isError) {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Error, true);
} else {
XNLogger::instance().enableConsoleOutput(XNLogger::LogLevel::Error, false);
}
return true;
}
// 设置日志级别
bool XNEngine::SetLogLevel(bool isDebug, bool isInfo, bool isWarn, bool isError)
{
if (isDebug) {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Debug, true);
} else {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Debug, false);
}
if (isInfo) {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Info, true);
} else {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Info, false);
}
if (isWarn) {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Warning, true);
} else {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Warning, false);
}
if (isError) {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Error, true);
} else {
XNLogger::instance().enableFileOutput(XNLogger::LogLevel::Error, false);
}
return true;
}
// 发布引擎状态
void XNEngine::PublishEngineStatus()
{
@ -332,7 +398,7 @@ void XNEngine::PublishEngineStatus()
// 写入引擎状态
engineStatusWriter->write(&engineStatus);
// 记录调试日志
LOG_DEBUG("XNEngine Write DDS!");
LOG_DEBUG("引擎运行状态 DDS 写入成功!");
}
}
@ -342,9 +408,9 @@ bool XNEngine::Run(const std::string &XmlPath)
if (!framework) {
return false;
}
// 设置日志级别
bool isReady = SetLogLevel(XmlPath);
// 如果设置日志级别失败
// 解析配置文件
bool isReady = ParseConfig(XmlPath);
// 如果解析配置文件失败
if (!isReady) {
// 返回失败
return false;
@ -357,6 +423,7 @@ bool XNEngine::Run(const std::string &XmlPath)
bool ret = framework->Initialize(0);
// 如果初始化失败
if (!ret) {
LOG_ERROR("0x1012 初始化失败, 引擎将退出!");
// 返回失败
return false;
}
@ -364,17 +431,18 @@ bool XNEngine::Run(const std::string &XmlPath)
// 设置框架状态
frameworkStatus = XNFrameObjectStatus::Initialized;
// 记录信息日志
LOG_INFO("XNEngine Initialize Success!");
LOG_INFO("引擎初始化成功!");
// 如果测试模式
if (isTestMode) {
// 记录信息日志
LOG_INFO("Verification passed!");
LOG_INFO("引擎测试通过!");
// 返回成功
return true;
}
ret = framework->PrepareForExecute();
// 如果准备执行失败
if (!ret) {
LOG_ERROR("0x1013 准备执行失败, 引擎将退出!");
// 返回失败
return false;
}
@ -412,15 +480,197 @@ bool XNEngine::Run(const std::string &XmlPath)
}
} else {
// 记录错误日志
LOG_ERROR("0x1007 Failed to prepare for execution, the engine will exit!");
LOG_ERROR("0x1014 无法发送引擎运行状态, 引擎将退出!");
}
// 返回成功
return true;
}
// 运行引擎
bool XNEngine::ParseDataBase(const uint32_t &ConfigId)
{
// 获取数据库路径
std::string dbPath = std::getenv("XNCore");
if (dbPath.empty()) {
LOG_ERROR("0x1015 未设置XNCore环境变量, 引擎将退出!");
return false;
}
dbPath += "/database/XNSim.db";
// 打开数据库
sqlite3 *db;
if (sqlite3_open(dbPath.c_str(), &db) != SQLITE_OK) {
LOG_ERROR("0x1016 打开数据库失败: {}", sqlite3_errmsg(db));
return false;
}
// 准备SQL语句
std::string sql = "SELECT * FROM Configuration WHERE ConfID = ?";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
LOG_ERROR("0x1017 准备SQL语句失败: {}", sqlite3_errmsg(db));
sqlite3_close(db);
return false;
}
// 绑定参数
if (sqlite3_bind_int(stmt, 1, ConfigId) != SQLITE_OK) {
LOG_ERROR("0x1018 绑定参数失败: {}", sqlite3_errmsg(db));
sqlite3_finalize(stmt);
sqlite3_close(db);
return false;
}
// 执行查询
if (sqlite3_step(stmt) != SQLITE_ROW) {
LOG_ERROR("0x1019 未找到配置ID为{}的记录", ConfigId);
sqlite3_finalize(stmt);
sqlite3_close(db);
return false;
}
int cpus = sysconf(_SC_NPROCESSORS_ONLN);
std::cout << "当前CPU核心数-> " << cpus << std::endl;
// 设置CPU亲和性
cpu_set_t mask;
CPU_ZERO(&mask);
CPUAffinity = 0;
// 读取配置信息
std::string CPUAff = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 6));
std::istringstream iss(CPUAff);
std::string cpuIndex;
while (std::getline(iss, cpuIndex, ',')) {
try {
int index = std::stoi(cpuIndex);
if (index >= 0 && index < cpus) {
CPUAffinity |= (1 << index);
CPU_SET(index, &mask);
}
} catch (const std::exception &e) {
std::cerr << "0x1010 无效的CPU亲和性值: " << cpuIndex << std::endl;
}
}
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
std::cerr << "0x1011 设置引擎CPU亲和性失败-> " << strerror(errno) << std::endl;
return false;
}
std::cout << "成功设置引擎CPU亲和性-> " << CPUAffinity << std::endl;
// 锁定内存
if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
std::cerr << "0x1012 锁定引擎内存失败-> " << strerror(errno) << std::endl;
return false;
}
std::cout << "成功锁定引擎内存!" << std::endl;
auto readBoolean = [](sqlite3_stmt *stmt, int column) -> bool {
return sqlite3_column_int(stmt, column) != 0;
};
bool isDebug = readBoolean(stmt, 11);
bool isInfo = readBoolean(stmt, 12);
bool isWarn = readBoolean(stmt, 13);
bool isError = readBoolean(stmt, 14);
SetConsoleOutput(isDebug, isInfo, isWarn, isError);
isDebug = readBoolean(stmt, 15);
isInfo = readBoolean(stmt, 16);
isWarn = readBoolean(stmt, 17);
isError = readBoolean(stmt, 18);
SetLogLevel(isDebug, isInfo, isWarn, isError);
// 清理资源
sqlite3_finalize(stmt);
sqlite3_close(db);
return true;
}
bool XNEngine::Run(const uint32_t &ConfigId)
{
if (!framework) {
return false;
}
// 解析数据库
bool isReady = ParseDataBase(ConfigId);
// 如果解析数据库失败
if (!isReady) {
// 返回失败
return false;
}
// 设置构型ID
//framework->SetScenarioId(ConfigId);
// 设置CPU亲和性
framework->SetCpuAffinity(CPUAffinity);
// 单次触发初始化信号
bool ret = framework->Initialize(1);
// 如果初始化失败
if (!ret) {
LOG_ERROR("0x1012 初始化失败, 引擎将退出!");
// 返回失败
return false;
}
// 如果初始化成功
// 设置框架状态
frameworkStatus = XNFrameObjectStatus::Initialized;
// 记录信息日志
LOG_INFO("引擎初始化成功!");
// 如果测试模式
if (isTestMode) {
// 记录信息日志
LOG_INFO("引擎测试通过!");
// 返回成功
return true;
}
ret = framework->PrepareForExecute();
// 如果准备执行失败
if (!ret) {
LOG_ERROR("0x1013 准备执行失败, 引擎将退出!");
// 返回失败
return false;
}
// 设置框架状态
frameworkStatus = XNFrameObjectStatus::Ready;
// 获取DDS管理器
XNDDSManagerPtr ddsManager = framework->GetDDSManager();
// 如果DDS管理器存在
if (ddsManager) {
// 注册仿真控制订阅者
auto func = std::bind(&XNEngine::SimControlListener, this, std::placeholders::_1);
ddsManager->RegisterSubscriber<XNSim::XNSimControl::XNRuntimeControlPubSubType>(
"XNSim::XNSimControl::XNRuntimeControl", 0, func);
// 触发仿真控制信号
framework->SimControl(0, SimControlCmd::Start);
// 注册引擎状态发布者
engineStatusWriter =
ddsManager->RegisterPublisher<XNSim::XNSimStatus::XNEngineStatusPubSubType>(
"XNSim::XNSimStatus::XNEngineStatus", 0);
// 如果引擎状态写入器存在
if (engineStatusWriter) {
// 设置引擎运行标志
engineRunning = true;
while (engineRunning) {
// 发布一次初始状态
PublishEngineStatus();
// 等待1秒
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (!engineRunning) {
// 取消注册引擎状态发布者
//ddsManager->UnregisterPublisher("XNSim::XNSimStatus::XNEngineStatus");
//ddsManager->UnregisterSubscriber("XNSim::XNSimControl::XNRuntimeControl");
}
}
} else {
// 记录错误日志
LOG_ERROR("0x1014 无法发送引擎运行状态, 引擎将退出!");
}
return true;
}
// 设置测试模式

View File

@ -27,13 +27,14 @@ public:
* @brief
*/
~XNEngine();
public:
/**
* @brief 仿
* @param cmd 仿
*/
void SimControlListener(const XNSim::XNSimControl::XNRuntimeControl &cmd);
public:
/**
* @brief
* @param XmlPath XML路径
@ -54,11 +55,37 @@ public:
private:
/**
* @brief
* @param XmlPath XML路
* @brief
* @param XmlPath
* @return
*/
bool SetLogLevel(const std::string &XmlPath);
bool ParseConfig(const std::string &XmlPath);
/**
* @brief
* @param ConfigId ID
* @return
*/
bool ParseDataBase(const uint32_t &ConfigId);
/**
* @brief
* @param isDebug
* @param isInfo
* @param isWarn
* @param isError
* @return
*/
bool SetConsoleOutput(bool isDebug, bool isInfo, bool isWarn, bool isError);
/**
* @brief
* @param isDebug
* @param isInfo
* @param isWarn
* @param isError
* @return
*/
bool SetLogLevel(bool isDebug, bool isInfo, bool isWarn, bool isError);
/**
* @brief
*/

View File

@ -24,7 +24,7 @@ int main(int argc, char *argv[])
XNEngine engine;
//检测输入参数个数
if (argc <= 2) {
std::cerr << "0x1000 The input parameters is too less!" << std::endl;
std::cerr << "0x1000 输入参数太少!" << std::endl;
return -1;
}
@ -42,10 +42,7 @@ int main(int argc, char *argv[])
hasConfigPath = true;
i += 2;
} else {
std::cerr << "0x1004 After the -f parameter, the configuration file path is not "
"specified, "
"the engine will exit!"
<< std::endl;
std::cerr << "0x1001 在-f参数后未指定配置文件路径引擎将退出!" << std::endl;
return -1;
}
} else if ("-id" == arg) {
@ -54,52 +51,30 @@ int main(int argc, char *argv[])
hasConfigId = true;
i += 2;
} else {
std::cerr
<< "0x1005 After the -id parameter, the configuration ID is not specified, "
"the engine will exit!"
<< std::endl;
std::cerr << "0x1002 在-id参数后未指定配置ID引擎将退出!" << std::endl;
return -1;
}
} else if ("-test" == arg) {
engine.SetTestMode(true);
i++;
} else {
std::cerr << "0x1007 The parameter " << arg << " is not valid, the engine will exit!"
<< std::endl;
std::cerr << "0x1003 无法识别的参数 " << arg << " ,引擎将退出!" << std::endl;
return -1;
}
}
//检查是否同时包含-f和-id参数
if (hasConfigPath && hasConfigId) {
std::cerr
<< "0x1006 Please specify either -f <config_file> or -id <config_id>, but not both. "
"The engine will exit!"
<< std::endl;
std::cerr << "0x1004 请指定 -f <config_file> 或 -id <config_id> ,但不要同时指定!"
<< std::endl;
return -1;
}
//检测配置文件格式
if (hasConfigPath) {
size_t index = configPath.find_last_of('.');
if (index != std::string::npos) {
std::string suffix = configPath.substr(index);
if (suffix != ".xml" && suffix != ".sce") {
std::cerr << "0x1001 The configuration file is not a .xml or .sce "
"file, the engine will exit!"
<< std::endl;
return -1;
}
} else {
std::cerr << "0x1002 The configuration file is not a .xml or .sce "
"file, the engine will exit!"
<< std::endl;
return -1;
}
return engine.Run(configPath);
} else if (hasConfigId) {
return engine.Run(configId);
}
// 引擎运行
return engine.Run(configId);
return -1;
}

View File

@ -410,7 +410,7 @@ class RunSimulation extends HTMLElement {
this.currentSimulationId = Date.now().toString();
// 准备启动参数
const simulationArgs = [selectedScenario];
const simulationArgs = ['-f', selectedScenario];
// 调用后端API执行仿真
const response = await fetch('/api/run-simulation', {

View File

@ -6,7 +6,6 @@ class RunTest extends HTMLElement {
this.currentScenario = null;
this.modelGroups = [];
this.services = [];
this.currentSimulationId = null;
this.eventSource = null;
}
@ -173,12 +172,6 @@ class RunTest extends HTMLElement {
runButton.disabled = true;
runButton.textContent = '运行中...';
// 显示终止按钮
const stopButton = this.shadowRoot.querySelector('#stop-button');
if (stopButton) {
stopButton.style.display = 'block';
}
// 清空并初始化输出框
const outputContent = this.shadowRoot.querySelector('#output-content');
outputContent.innerHTML = '开始执行测试...\n';
@ -189,20 +182,16 @@ class RunTest extends HTMLElement {
this.eventSource = null;
}
// 保存当前仿真信息
this.currentSimulationId = Date.now().toString();
// 准备启动参数
const simulationArgs = [selectedScenario, '-test'];
const simulationArgs = ['-f', selectedScenario, '-test'];
// 调用后端API执行仿真
// 调用后端API执行测试
const response = await fetch('/api/run-simulation', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: this.currentSimulationId,
args: simulationArgs,
timeout: 120000 // 2分钟超时
})
@ -218,14 +207,15 @@ class RunTest extends HTMLElement {
const responseData = await response.json();
// 获取仿真ID
const simulationId = responseData.simulationId || this.currentSimulationId;
this.currentSimulationId = simulationId;
// 连接到SSE获取实时输出
this.connectToEventSource(responseData.simulationId);
// 设置SSE连接获取实时输出
this.connectToEventSource(simulationId);
this.showSuccess(`测试已启动`);
// 根据测试结果更新UI
if (responseData.success) {
this.showSuccess('测试已启动');
} else {
this.showError(`测试启动失败: ${responseData.message || '未知错误'}`);
}
} catch (error) {
console.error('执行测试失败:', error);
@ -234,16 +224,20 @@ class RunTest extends HTMLElement {
// 显示错误详情
const outputContent = this.shadowRoot.querySelector('#output-content');
outputContent.innerHTML += `\n\n执行错误: ${error.message}`;
} finally {
// 重置UI
this.resetUIAfterCompletion();
const runButton = this.shadowRoot.querySelector('#run-button');
runButton.disabled = false;
runButton.textContent = '运行测试';
}
}
// 连接到SSE事件源获取实时输出
connectToEventSource(simulationId) {
// 关闭之前的连接
this.closeEventSource();
if (this.eventSource) {
this.eventSource.close();
}
// 创建新的SSE连接
const url = `/api/simulation-output/${simulationId}`;
@ -274,11 +268,9 @@ class RunTest extends HTMLElement {
const data = JSON.parse(event.data);
if (data.running === false) {
// 仿真已经不存在或已结束
this.showMessage(data.message || '测试不存在或已结束');
// 重置UI
this.resetUIAfterCompletion();
// 测试已经结束
this.showMessage(data.message || '测试已结束');
this.closeEventSource();
}
} catch (error) {
console.error('处理状态事件失败:', error);
@ -296,8 +288,7 @@ class RunTest extends HTMLElement {
this.showError(`测试执行失败: ${data.message}`);
}
// 重置UI
this.resetUIAfterCompletion();
this.closeEventSource();
} catch (error) {
console.error('处理完成事件失败:', error);
}
@ -308,59 +299,17 @@ class RunTest extends HTMLElement {
try {
const data = JSON.parse(event.data);
this.showError(`测试错误: ${data.message}`);
// 重置UI
this.resetUIAfterCompletion();
this.closeEventSource();
} catch (error) {
console.error('处理错误事件失败:', error);
}
});
// 仿真终止
this.eventSource.addEventListener('terminated', (event) => {
try {
const data = JSON.parse(event.data);
this.showMessage(`测试已终止: ${data.message}`);
// 在输出框中添加终止信息
const outputContent = this.shadowRoot.querySelector('#output-content');
outputContent.innerHTML += `\n\n测试已终止:${data.message}`;
// 重置UI
this.resetUIAfterCompletion();
} catch (error) {
console.error('处理终止事件失败:', error);
}
});
// 仿真超时
this.eventSource.addEventListener('timeout', (event) => {
try {
const data = JSON.parse(event.data);
this.showError(`测试超时: ${data.message}`);
// 在输出框中添加超时信息
const outputContent = this.shadowRoot.querySelector('#output-content');
outputContent.innerHTML += `\n\n测试超时:${data.message}`;
// 重置UI
this.resetUIAfterCompletion();
} catch (error) {
console.error('处理超时事件失败:', error);
}
});
// 连接错误
// 连接错误处理
this.eventSource.onerror = (error) => {
console.error('SSE连接错误:', error);
// 检查是否已经清理了资源
if (this.eventSource && this.eventSource.readyState === 2) { // CLOSED
this.showError('实时输出连接已断开');
// 重置UI
this.resetUIAfterCompletion();
}
this.showError('实时输出连接已断开');
this.closeEventSource();
};
}
@ -371,69 +320,6 @@ class RunTest extends HTMLElement {
this.eventSource = null;
}
}
// 在仿真完成后重置UI
resetUIAfterCompletion() {
// 隐藏终止按钮
const stopButton = this.shadowRoot.querySelector('#stop-button');
if (stopButton) {
stopButton.style.display = 'none';
}
// 重置运行按钮
const runButton = this.shadowRoot.querySelector('#run-button');
if (runButton) {
runButton.disabled = false;
runButton.textContent = '运行测试';
}
// 关闭SSE连接
this.closeEventSource();
// 清除当前仿真ID
this.currentSimulationId = null;
}
async stopSimulation() {
if (!this.currentSimulationId) {
this.showMessage('没有正在运行的测试');
return;
}
try {
this.showMessage('正在终止测试...');
const response = await fetch('/api/stop-simulation', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: this.currentSimulationId
})
});
if (response.ok) {
const result = await response.json();
this.showSuccess(`${result.message || '测试已终止'}`);
// 终止信息会通过SSE推送到UI
// 此处不需要额外操作
} else {
const errorData = await response.json();
this.showError(`终止测试失败: ${errorData.message || '未知错误'}`);
// 可能是SSE已断开手动重置UI
this.resetUIAfterCompletion();
}
} catch (error) {
console.error('终止测试失败:', error);
this.showError('终止测试失败: ' + error.message);
// 手动重置UI
this.resetUIAfterCompletion();
}
}
showError(message) {
const messageElement = this.shadowRoot.querySelector('#message');
@ -514,16 +400,6 @@ class RunTest extends HTMLElement {
background-color: #cccccc;
cursor: not-allowed;
}
#stop-button {
background-color: #d32f2f;
display: none;
margin-left: 10px;
}
#stop-button:hover {
background-color: #b71c1c;
}
#message {
margin: 10px 0;
@ -645,10 +521,6 @@ class RunTest extends HTMLElement {
.output-content .ansi-bold { font-weight: bold; }
.output-content .ansi-italic { font-style: italic; }
.output-content .ansi-underline { text-decoration: underline; }
.buttons-container {
display: flex;
}
</style>
<div class="test-container">
<div class="config-selector">
@ -656,10 +528,7 @@ class RunTest extends HTMLElement {
<select id="scenario-select">
<option value="" disabled selected>-- 加载配置文件中... --</option>
</select>
<div class="buttons-container">
<button id="run-button">运行测试</button>
<button id="stop-button">终止测试</button>
</div>
<button id="run-button">运行测试</button>
</div>
<div id="message"></div>
<div class="content-container">
@ -685,9 +554,6 @@ class RunTest extends HTMLElement {
const runButton = this.shadowRoot.querySelector('#run-button');
runButton.addEventListener('click', () => this.runTest());
const stopButton = this.shadowRoot.querySelector('#stop-button');
stopButton.addEventListener('click', () => this.stopSimulation());
const scenarioSelect = this.shadowRoot.querySelector('#scenario-select');
scenarioSelect.addEventListener('change', (event) => this.onScenarioSelected(event));
}

View File

@ -241,7 +241,7 @@ router.post('/run-simulation', async (req, res) => {
const logFile = path.join(logDir, `xnengine_${mainProcess.pid}.log`);
// 从命令行参数中提取配置文件路径
const scenarioFile = args[0];
const scenarioFile = args[1];
// 将进程信息写入数据库
await saveXNEngineProcess({
@ -261,7 +261,7 @@ router.post('/run-simulation', async (req, res) => {
isExisting: true,
startTime: mainProcess.startTime,
totalProcesses: sortedProcesses.length,
scenarioFile: (existingProcess && existingProcess.scenario_file) || args[0]
scenarioFile: (existingProcess && existingProcess.scenario_file) || args[1]
});
return;
} else {
@ -313,14 +313,17 @@ router.post('/run-simulation', async (req, res) => {
const isRunning = await isProcessRunning(processId);
const isXNEngine = await isXNEngineProcess(processId);
if (isRunning && isXNEngine) {
// 检查是否是测试模式
const isTestMode = args.includes('-test');
if ((isRunning && isXNEngine) || isTestMode) {
// 将进程信息写入数据库
await saveXNEngineProcess({
pid: processId,
log_file: logFile,
start_time: new Date().toISOString(),
cmd: `${enginePath} ${args.join(' ')}`,
scenario_file: args[0]
scenario_file: args[1]
});
// 保存到运行中的仿真Map但不启动tail进程
@ -335,15 +338,17 @@ router.post('/run-simulation', async (req, res) => {
// 立即响应返回仿真ID
res.json({
success: true,
message: '仿真已启动',
message: isTestMode ? '测试已启动' : '仿真已启动',
simulationId: processId.toString(),
scenarioFile: args[0]
scenarioFile: args[1],
isTestMode: isTestMode,
logFile: logFile
});
} else {
// 进程启动失败或不是XNEngine进程
await deleteXNEngineProcess(processId);
res.status(500).json({
error: '启动仿真失败',
error: '启动失败',
message: '进程启动后立即退出或不是XNEngine进程'
});
}